diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt index eb56d8067e..065a2f1fc3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/activities/QuickWizardListActivity.kt @@ -1,12 +1,18 @@ package info.nightscout.androidaps.plugins.general.overview.activities +import android.annotation.SuppressLint import android.os.Bundle +import android.util.Log import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.ImageView import android.widget.TextView import androidx.fragment.app.FragmentManager +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.ItemTouchHelper.* import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import info.nightscout.androidaps.R @@ -20,6 +26,8 @@ import info.nightscout.androidaps.utils.FabricPrivacy import io.reactivex.rxkotlin.plusAssign import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.wizard.QuickWizard +import info.nightscout.androidaps.utils.wizard.QuickWizardEntry +import info.nightscout.shared.sharedPreferences.SP import io.reactivex.disposables.CompositeDisposable import javax.inject.Inject @@ -30,20 +38,95 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() { @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var quickWizard: QuickWizard @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var sp: SP private var disposable: CompositeDisposable = CompositeDisposable() private lateinit var binding: OverviewQuickwizardlistActivityBinding + private val itemTouchHelper by lazy { + val simpleItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN or START or END, 0) { + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val adapter = recyclerView.adapter as RecyclerViewAdapter + val from = viewHolder.layoutPosition + val to = target.layoutPosition + adapter.moveItem(from, to) + adapter.notifyItemMoved(from, to) + + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + } + + override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + super.onSelectedChanged(viewHolder, actionState) + + if (actionState == ACTION_STATE_DRAG) { + viewHolder?.itemView?.alpha = 0.5f + } + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + super.clearView(recyclerView, viewHolder) + + viewHolder.itemView.alpha = 1.0f + + val adapter = recyclerView.adapter as RecyclerViewAdapter + adapter.onDrop() + } + } + + ItemTouchHelper(simpleItemTouchCallback) + } + + fun startDragging(viewHolder: RecyclerView.ViewHolder) { + itemTouchHelper.startDrag(viewHolder) + } + private inner class RecyclerViewAdapter(var fragmentManager: FragmentManager) : RecyclerView.Adapter() { + @SuppressLint("ClickableViewAccessibility") override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuickWizardEntryViewHolder { - return QuickWizardEntryViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.overview_quickwizardlist_item, parent, false), fragmentManager) + val itemView = LayoutInflater.from(parent.context).inflate(R.layout.overview_quickwizardlist_item, parent, false) + val viewHolder = QuickWizardEntryViewHolder(itemView, fragmentManager) + + viewHolder.handleView.setOnTouchListener { _, event -> + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + startDragging(viewHolder) + } + return@setOnTouchListener true + } + + return viewHolder } override fun onBindViewHolder(holder: QuickWizardEntryViewHolder, position: Int) { holder.from.text = dateUtil.timeString(quickWizard[position].validFromDate()) holder.to.text = dateUtil.timeString(quickWizard[position].validToDate()) + val wearControl = sp.getBoolean(R.string.key_wear_control, false) + + if (wearControl) { + holder.handleView.visibility = View.VISIBLE + } else { + holder.handleView.visibility = View.GONE + } + if (quickWizard[position].device() == QuickWizardEntry.DEVICE_ALL) { + holder.device.visibility = View.GONE + } else { + holder.device.visibility = View.VISIBLE + holder.device.setImageResource( + when (quickWizard[position].device()) { + QuickWizardEntry.DEVICE_WATCH -> R.drawable.ic_watch + else -> R.drawable.ic_smartphone + } + ) + } holder.buttonText.text = quickWizard[position].buttonText() holder.carbs.text = rh.gs(R.string.format_carbs, quickWizard[position].carbs()) } @@ -55,6 +138,8 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() { val buttonText: TextView = itemView.findViewById(R.id.overview_quickwizard_item_buttonText) val carbs: TextView = itemView.findViewById(R.id.overview_quickwizard_item_carbs) val from: TextView = itemView.findViewById(R.id.overview_quickwizard_item_from) + val handleView: ImageView = itemView.findViewById(R.id.handleView) + val device: ImageView = itemView.findViewById(R.id.overview_quickwizard_item_device) val to: TextView = itemView.findViewById(R.id.overview_quickwizard_item_to) private val editButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_edit_button) private val removeButton: Button = itemView.findViewById(R.id.overview_quickwizard_item_remove_button) @@ -74,6 +159,16 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() { } } } + + fun moveItem(from: Int, to: Int) { + Log.i("QuickWizard", "moveItem") + quickWizard.move(from, to) + } + + fun onDrop() { + Log.i("QuickWizard", "onDrop") + rxBus.send(EventQuickWizardChange()) + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -84,6 +179,7 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() { binding.recyclerview.setHasFixedSize(true) binding.recyclerview.layoutManager = LinearLayoutManager(this) binding.recyclerview.adapter = RecyclerViewAdapter(supportFragmentManager) + itemTouchHelper.attachToRecyclerView(binding.recyclerview) binding.addButton.setOnClickListener { val manager = supportFragmentManager @@ -98,13 +194,13 @@ class QuickWizardListActivity : NoSplashAppCompatActivity() { .toObservable(EventQuickWizardChange::class.java) .observeOn(aapsSchedulers.main) .subscribe({ - val adapter = RecyclerViewAdapter(supportFragmentManager) - binding.recyclerview.swapAdapter(adapter, false) - }, fabricPrivacy::logException) + val adapter = RecyclerViewAdapter(supportFragmentManager) + binding.recyclerview.swapAdapter(adapter, false) + }, fabricPrivacy::logException) } override fun onPause() { disposable.clear() super.onPause() } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt index bfe47b2bdf..cb4c3ee652 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/dialogs/EditQuickWizardDialog.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.view.Window import android.view.WindowManager import dagger.android.support.DaggerDialogFragment +import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.OverviewEditquickwizardDialogBinding import info.nightscout.shared.logging.AAPSLogger import info.nightscout.androidaps.plugins.bus.RxBus @@ -21,6 +22,7 @@ import info.nightscout.androidaps.utils.extensions.setEnableForChildren import info.nightscout.androidaps.utils.extensions.setSelection import info.nightscout.androidaps.utils.wizard.QuickWizard import info.nightscout.androidaps.utils.wizard.QuickWizardEntry +import info.nightscout.shared.sharedPreferences.SP import org.json.JSONException import javax.inject.Inject @@ -30,6 +32,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var quickWizard: QuickWizard @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var sp: SP var position = -1 @@ -57,6 +60,14 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener { position = bundle.getInt("position", -1) } val entry = if (position == -1) quickWizard.newEmptyItem() else quickWizard[position] + if (sp.getBoolean(R.string.key_wear_control, false)) { + binding.deviceLabel.visibility = View.VISIBLE + binding.device.visibility = View.VISIBLE + } else { + binding.deviceLabel.visibility = View.GONE + binding.device.visibility = View.GONE + } + binding.okcancel.ok.setOnClickListener { try { entry.storage.put("buttonText", binding.buttonEdit.text.toString()) @@ -66,6 +77,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener { entry.storage.put("useBG", binding.useBg.selectedItemPosition) entry.storage.put("useCOB", binding.useCob.selectedItemPosition) entry.storage.put("useBolusIOB", binding.useBolusIob.selectedItemPosition) + entry.storage.put("device", binding.device.selectedItemPosition) entry.storage.put("useBasalIOB", binding.useBasalIob.selectedItemPosition) entry.storage.put("useTrend", binding.useTrend.selectedItemPosition) entry.storage.put("useSuperBolus", binding.useSuperBolus.selectedItemPosition) @@ -122,6 +134,7 @@ class EditQuickWizardDialog : DaggerDialogFragment(), View.OnClickListener { binding.useCob.setSelection(entry.useCOB()) binding.useBolusIob.setSelection(entry.useBolusIOB()) binding.useBasalIob.setSelection(entry.useBasalIOB()) + binding.device.setSelection(entry.device()) binding.useTrend.setSelection(entry.useTrend()) binding.useSuperBolus.setSelection(entry.useSuperBolus()) binding.useTempTarget.setSelection(entry.useTempTarget()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt index db33722e31..f64eb9430a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/ActionStringHandler.kt @@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.general.wear import android.app.NotificationManager import android.content.Context +import android.util.Log import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Constants import info.nightscout.androidaps.R @@ -39,6 +40,7 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.shared.sharedPreferences.SP import info.nightscout.androidaps.utils.wizard.BolusWizard +import info.nightscout.androidaps.utils.wizard.QuickWizard import info.nightscout.shared.SafeParse import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign @@ -71,6 +73,7 @@ class ActionStringHandler @Inject constructor( private val activePlugin: ActivePlugin, private val iobCobCalculator: IobCobCalculator, private val localInsightPlugin: LocalInsightPlugin, + private val quickWizard: QuickWizard, private val danaRPlugin: DanaRPlugin, private val danaRKoreanPlugin: DanaRKoreanPlugin, private val danaRv2Plugin: DanaRv2Plugin, @@ -144,34 +147,39 @@ class ActionStringHandler @Inject constructor( } rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints" } else if ("temptarget" == act[0]) { ///////////////////////////////////////////////////////// TEMPTARGET - aapsLogger.info(LTag.WEAR, "temptarget received:" + act) + aapsLogger.info(LTag.WEAR, "temptarget received: $act") if ("cancel" == act[1]) { rMessage += rh.gs(R.string.wear_action_tempt_cancel_message) rAction = "temptarget true 0 0 0" } else if ("preset" == act[1]) { val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL val preset = act[2] - if ("activity" == preset) { - val activityTTDuration = defaultValueHelper.determineActivityTTDuration() - val activityTT = defaultValueHelper.determineActivityTT() - val reason = rh.gs(R.string.activity) - rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration) - rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT" - } else if ("hypo" == preset) { - val hypoTTDuration = defaultValueHelper.determineHypoTTDuration() - val hypoTT = defaultValueHelper.determineHypoTT() - val reason = rh.gs(R.string.hypo) - rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration) - rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT" - } else if ("eating" == preset) { - val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration() - val eatingSoonTT = defaultValueHelper.determineEatingSoonTT() - val reason = rh.gs(R.string.eatingsoon) - rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration) - rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT" - } else { - sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset)) - return + when (preset) { + "activity" -> { + val activityTTDuration = defaultValueHelper.determineActivityTTDuration() + val activityTT = defaultValueHelper.determineActivityTT() + val reason = rh.gs(R.string.activity) + rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration) + rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT" + } + "hypo" -> { + val hypoTTDuration = defaultValueHelper.determineHypoTTDuration() + val hypoTT = defaultValueHelper.determineHypoTT() + val reason = rh.gs(R.string.hypo) + rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration) + rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT" + } + "eating" -> { + val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration() + val eatingSoonTT = defaultValueHelper.determineEatingSoonTT() + val reason = rh.gs(R.string.eatingsoon) + rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration) + rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT" + } + else -> { + sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset)) + return + } } } else { 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" } 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]) { val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet() if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values @@ -364,7 +400,10 @@ class ActionStringHandler @Inject constructor( rAction = "cancelChangeRequest" wearPlugin.requestNotificationCancel(rAction) return - } else return + } else { + sendError("Unknown action command: " + act[0] ) + return + } // send result wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction) lastSentTimestamp = System.currentTimeMillis() @@ -698,4 +737,4 @@ class ActionStringHandler @Inject constructor( lastConfirmActionString = null lastBolusWizard = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java index d6c840625f..9e1e6dd9cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/wear/wearintegration/WatchUpdaterService.java @@ -46,6 +46,7 @@ import info.nightscout.androidaps.interfaces.Loop; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.Profile; import info.nightscout.androidaps.interfaces.ProfileFunction; +import info.nightscout.androidaps.utils.wizard.QuickWizardEntry; import info.nightscout.shared.logging.AAPSLogger; import info.nightscout.shared.logging.LTag; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; @@ -62,6 +63,7 @@ import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.DefaultValueHelper; import info.nightscout.androidaps.utils.TrendCalculator; import info.nightscout.androidaps.utils.resources.ResourceHelper; +import info.nightscout.androidaps.utils.wizard.QuickWizard; import info.nightscout.shared.sharedPreferences.SP; public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { @@ -81,6 +83,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog @Inject ReceiverStatusStore receiverStatusStore; @Inject Config config; @Inject public TrendCalculator trendCalculator; + @Inject public QuickWizard quickWizard; public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend"); public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings"); @@ -101,12 +104,14 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private static final String OPEN_SETTINGS_PATH = "/openwearsettings"; private static final String NEW_STATUS_PATH = "/sendstatustowear"; private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear"; + private static final String QUICK_WIZARD_PATH = "/send_quick_wizard"; public static final String BASAL_DATA_PATH = "/nightscout_watch_basal"; public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress"; public static final String ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest"; public static final String ACTION_CHANGECONFIRMATION_REQUEST_PATH = "/nightscout_watch_changeconfirmationrequest"; public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest"; + String TAG = "WatchUpdateService"; private static boolean lastLoopStatus; @@ -156,7 +161,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog public int onStartCommand(Intent intent, int flags, int startId) { String action = intent != null ? intent.getAction() : null; - // Log.d(TAG, logPrefix + "onStartCommand: " + action); + // Log.d(TAG, "onStartCommand: " + action); if (wearIntegration()) { handler.post(() -> { @@ -235,7 +240,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog super.onPeerConnected(peer); String id = peer.getId(); String name = peer.getDisplayName(); - // Log.d(TAG, logPrefix + "onPeerConnected peer name & ID: " + name + "|" + id); + Log.d(TAG, "onPeerConnected peer name & ID: " + name + "|" + id); } @@ -244,14 +249,14 @@ public class WatchUpdaterService extends WearableListenerService implements Goog super.onPeerDisconnected(peer); String id = peer.getId(); String name = peer.getDisplayName(); - // Log.d(TAG, logPrefix + "onPeerDisconnected peer name & ID: " + name + "|" + id); + Log.d(TAG, "onPeerDisconnected peer name & ID: " + name + "|" + id); } @Override public void onMessageReceived(MessageEvent event) { - // Log.d(TAG, logPrefix + "onMessageRecieved: " + event); + // Log.d(TAG, "onMessageRecieved: " + event); if (wearIntegration()) { if (event != null && event.getPath().equals(WEARABLE_RESEND_PATH)) { @@ -283,7 +288,7 @@ public class WatchUpdaterService extends WearableListenerService implements Goog private void sendData() { GlucoseValue lastBG = iobCobCalculator.getAds().lastBg(); - // Log.d(TAG, logPrefix + "LastBg=" + lastBG); + // Log.d(TAG, "LastBg=" + lastBG); if (lastBG != null) { GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData(); @@ -364,6 +369,10 @@ public class WatchUpdaterService extends WearableListenerService implements Goog if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) { googleApiConnect(); } + + sendPreferences(); + sendQuickWizard(); + long startTime = System.currentTimeMillis() - (long) (60000 * 60 * 5.5); GlucoseValue last_bg = iobCobCalculator.getAds().lastBg(); @@ -382,7 +391,6 @@ public class WatchUpdaterService extends WearableListenerService implements Goog entries.putDataMapArrayList("entries", dataMaps); (new SendToDataLayerThread(WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries); } - sendPreferences(); sendBasals(); sendStatus(); } @@ -738,6 +746,41 @@ public class WatchUpdaterService extends WearableListenerService implements Goog } } + private void sendQuickWizard() { + if (googleApiClient != null && googleApiClient.isConnected()) { + int size = quickWizard.size(); + ArrayList entities = new ArrayList<>(); + for(int i=0; i < size; i++) { + QuickWizardEntry q = quickWizard.get(i); + if (q.forDevice(QuickWizardEntry.DEVICE_WATCH)) { + entities.add(quickMap(q)); + } + } + + PutDataMapRequest dataMapRequest = PutDataMapRequest.create(QUICK_WIZARD_PATH); + + DataMap dm = dataMapRequest.getDataMap(); + dm.putLong("timestamp", System.currentTimeMillis()); + dm.putDataMapArrayList("quick_wizard", entities); + + PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest(); + Log.i(TAG, "sendQuickWizard: " + putDataRequest); + Wearable.DataApi.putDataItem(googleApiClient, putDataRequest); + } else { + Log.e("sendQuickWizard", "No connection to wearable available!"); + } + } + + private DataMap quickMap(QuickWizardEntry q) { + DataMap dm = new DataMap(); + dm.putString("guid", q.guid()); + dm.putString("button_text", q.buttonText()); + dm.putInt("carbs", q.carbs()); + dm.putInt("from", q.validFrom()); + dm.putInt("to", q.validTo()); + return dm; + } + @NonNull private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt index a254831690..b48d44c9cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizard.kt @@ -1,10 +1,12 @@ package info.nightscout.androidaps.utils.wizard +import android.util.Log import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.shared.sharedPreferences.SP import org.json.JSONArray import org.json.JSONObject +import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -18,6 +20,18 @@ class QuickWizard @Inject constructor( init { setData(JSONArray(sp.getString(R.string.key_quickwizard, "[]"))) + setGuidsForOldEntries() + } + + private fun setGuidsForOldEntries() { + // for migration purposes; guid is a new required property + for (i in 0 until storage.length()) { + val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i) + if (entry.guid() == "") { + val guid = UUID.randomUUID().toString() + entry.storage.put("guid", guid) + } + } } fun getActive(): QuickWizardEntry? { @@ -41,6 +55,38 @@ class QuickWizard @Inject constructor( operator fun get(position: Int): QuickWizardEntry = QuickWizardEntry(injector).from(storage.get(position) as JSONObject, position) + fun get(guid: String): QuickWizardEntry? { + for (i in 0 until storage.length()) { + val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i) + if (entry.guid() == guid) { + return entry + } + } + return null + } + + fun move(from: Int, to: Int) { + Log.i("QuickWizard", "moveItem: $from $to") + val fromEntry = storage[from] as JSONObject + storage.remove(from) + addToPos(to, fromEntry, storage) + save() + } + + fun removePos(pos: Int, jsonObj: JSONObject?, jsonArr: JSONArray) { + for (i in jsonArr.length() downTo pos + 1) { + jsonArr.put(i, jsonArr[i - 1]) + } + jsonArr.put(pos, jsonObj) + } + + private fun addToPos(pos: Int, jsonObj: JSONObject?, jsonArr: JSONArray) { + for (i in jsonArr.length() downTo pos + 1) { + jsonArr.put(i, jsonArr[i - 1]) + } + jsonArr.put(pos, jsonObj) + } + fun newEmptyItem(): QuickWizardEntry { return QuickWizardEntry(injector) } @@ -57,4 +103,5 @@ class QuickWizard @Inject constructor( storage.remove(position) save() } + } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt index 79ff43bf66..1f3d48fc7a 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/wizard/QuickWizardEntry.kt @@ -19,6 +19,7 @@ import info.nightscout.androidaps.utils.JsonHelper.safeGetString import info.nightscout.shared.sharedPreferences.SP import org.json.JSONException import org.json.JSONObject +import java.util.* import javax.inject.Inject class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjector) { @@ -41,11 +42,15 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec const val NO = 1 private const val POSITIVE_ONLY = 2 private const val NEGATIVE_ONLY = 3 + const val DEVICE_ALL = 0 + const val DEVICE_PHONE = 1 + const val DEVICE_WATCH = 2 } init { injector.androidInjector().inject(this) - val emptyData = "{\"buttonText\":\"\",\"carbs\":0,\"validFrom\":0,\"validTo\":86340}" + val guid = UUID.randomUUID().toString() + val emptyData = "{\"guid\": \"$guid\",\"buttonText\":\"\",\"carbs\":0,\"validFrom\":0,\"validTo\":86340, \"device\": \"all\"}" try { storage = JSONObject(emptyData) } catch (e: JSONException) { @@ -55,6 +60,8 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec /* { + guid: string, + device: string, // (phone, watch, all) buttonText: "Meal", carbs: 36, validFrom: 8 * 60 * 60, // seconds from midnight @@ -69,12 +76,13 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec } */ fun from(entry: JSONObject, position: Int): QuickWizardEntry { + // TODO set guid if missing for migration storage = entry this.position = position return this } - fun isActive(): Boolean = profileFunction.secondsFromMidnight() >= validFrom() && profileFunction.secondsFromMidnight() <= validTo() + fun isActive(): Boolean = profileFunction.secondsFromMidnight() >= validFrom() && profileFunction.secondsFromMidnight() <= validTo() && forDevice(DEVICE_PHONE) fun doCalc(profile: Profile, profileName: String, lastBG: GlucoseValue, _synchronized: Boolean): BolusWizard { val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() @@ -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 } + fun guid(): String = safeGetString(storage, "guid", "") + + fun device(): Int = safeGetInt(storage, "device", DEVICE_ALL) + + fun forDevice(device: Int) = device() == device || device() == DEVICE_ALL + fun buttonText(): String = safeGetString(storage, "buttonText", "") fun carbs(): Int = safeGetInt(storage, "carbs") @@ -148,4 +162,4 @@ class QuickWizardEntry @Inject constructor(private val injector: HasAndroidInjec fun useSuperBolus(): Int = safeGetInt(storage, "useSuperBolus", NO) fun useTempTarget(): Int = safeGetInt(storage, "useTempTarget", NO) -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/overview_editquickwizard_dialog.xml b/app/src/main/res/layout/overview_editquickwizard_dialog.xml index 422b787269..af1eee4f84 100644 --- a/app/src/main/res/layout/overview_editquickwizard_dialog.xml +++ b/app/src/main/res/layout/overview_editquickwizard_dialog.xml @@ -108,6 +108,35 @@ + + + + + + + + + + + + android:layout_height="match_parent" + android:orientation="horizontal"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + - + + + + + + + android:adjustViewBounds="false" + android:cropToPadding="false" + android:paddingRight="10dp" + android:scaleType="fitStart" + card_view:srcCompat="@drawable/ic_smartphone" /> - - + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:adjustViewBounds="false" + android:cropToPadding="false" + android:scaleType="fitStart" + card_view:srcCompat="@drawable/ic_reorder_gray_24dp" /> @@ -88,9 +115,9 @@ android:textStyle="normal|bold" /> + android:layout_height="wrap_content" + android:text="-" /> Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s Temptarget:\nTarget: %1$s\nDuration: %2$s Temptarget:\Reason: %1$s\nTarget: %2$s\nDuration: %3$s + No insulin needed nor are carbs added. + Can not calculate wizard, requires actual blood glucose and active profile. + Quick Wizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg + Show entry on device: diff --git a/core/src/main/res/drawable/ic_smartphone.xml b/core/src/main/res/drawable/ic_smartphone.xml new file mode 100644 index 0000000000..3d7f349a29 --- /dev/null +++ b/core/src/main/res/drawable/ic_smartphone.xml @@ -0,0 +1,5 @@ + + + diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 1f5fc5ae2a..224b6e59f6 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -258,6 +258,10 @@ android:host="*" android:pathPrefix="/openwearsettings" android:scheme="wear" /> + + + + + + + + + + + diff --git a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java index bd64caacc4..42f87f5b4c 100644 --- a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java +++ b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java @@ -13,6 +13,7 @@ import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.preference.PreferenceManager; +import android.util.Base64; import android.util.Log; import androidx.core.app.NotificationCompat; @@ -22,10 +23,6 @@ import androidx.wear.tiles.TileService; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.PendingResult; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.wearable.CapabilityApi; -import com.google.android.gms.wearable.CapabilityInfo; import com.google.android.gms.wearable.ChannelApi; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; @@ -36,7 +33,8 @@ import com.google.android.gms.wearable.NodeApi; import com.google.android.gms.wearable.Wearable; import com.google.android.gms.wearable.WearableListenerService; -import java.util.Set; +import org.jetbrains.annotations.NotNull; + import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -47,11 +45,11 @@ import info.nightscout.androidaps.interaction.AAPSPreferences; import info.nightscout.androidaps.interaction.actions.AcceptActivity; import info.nightscout.androidaps.interaction.actions.CPPActivity; import info.nightscout.androidaps.interaction.utils.Persistence; +import info.nightscout.androidaps.interaction.utils.WearUtil; import info.nightscout.androidaps.tile.ActionsTileService; +import info.nightscout.androidaps.tile.QuickWizardTileService; import info.nightscout.androidaps.tile.TempTargetTileService; import info.nightscout.shared.SafeParse; -import info.nightscout.androidaps.interaction.utils.WearUtil; - /** * Created by emmablack on 12/26/14. @@ -62,7 +60,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp @Inject WearUtil wearUtil; @Inject Persistence persistence; - private static final String WEARABLE_DATA_PATH = "/nightscout_watch_data"; private static final String WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend"; private static final String WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus"; public static final String WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring"; @@ -71,13 +68,13 @@ public class ListenerService extends WearableListenerService implements GoogleAp private static final String OPEN_SETTINGS = "/openwearsettings"; private static final String NEW_STATUS_PATH = "/sendstatustowear"; private static final String NEW_PREFERENCES_PATH = "/sendpreferencestowear"; + private static final String QUICK_WIZARD_PATH = "/send_quick_wizard"; public static final String BASAL_DATA_PATH = "/nightscout_watch_basal"; public static final String BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress"; public static final String ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest"; public static final String NEW_CHANGECONFIRMATIONREQUEST_PATH = "/nightscout_watch_changeconfirmationrequest"; public static final String ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest"; - public static final int BOLUS_PROGRESS_NOTIF_ID = 1; public static final int CONFIRM_NOTIF_ID = 2; public static final int CHANGE_NOTIF_ID = 556677; @@ -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_INITIATE_ACTION = "com.dexdrip.stephenblack.nightwatch.INITIATE_ACTION"; - - private static final String ACTION_RESEND_BULK = "com.dexdrip.stephenblack.nightwatch.RESEND_BULK_DATA"; private static final String AAPS_NOTIFY_CHANNEL_ID_OPENLOOP = "AndroidAPS-OpenLoop"; private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS = "bolus progress vibration"; private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT = "bolus progress silent"; - GoogleApiClient googleApiClient; - private long lastRequest = 0; + private DismissThread bolusprogressThread; private static final String TAG = "ListenerService"; - private DataRequester mDataRequester = null; - private static final int GET_CAPABILITIES_TIMEOUT_MS = 5000; - - // Phone - private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs"; - private static final String MESSAGE_PATH_PHONE = "/phone_message_path"; - // Wear - private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs"; - private static final String MESSAGE_PATH_WEAR = "/wear_message_path"; - private final String mPhoneNodeId = null; - private String localnode = null; private final String logPrefix = ""; // "WR: " // Not derived from DaggerService, do injection here @@ -120,143 +103,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp super.onCreate(); } - public class DataRequester extends AsyncTask { - Context mContext; - String path; - byte[] payload; - - - DataRequester(Context context, String thispath, byte[] thispayload) { - path = thispath; - payload = thispayload; - // Log.d(TAG, logPrefix + "DataRequester DataRequester: " + thispath + " lastRequest:" + lastRequest); - } - - - @Override - protected Void doInBackground(Void... params) { - // Log.d(TAG, logPrefix + "DataRequester: doInBack: " + params); - - try { - - forceGoogleApiConnect(); - DataMap datamap; - - if (isCancelled()) { - Log.d(TAG, "doInBackground CANCELLED programmatically"); - return null; - } - - if (googleApiClient != null) { - if (!googleApiClient.isConnected()) - googleApiClient.blockingConnect(15, TimeUnit.SECONDS); - } - - // this code might not be needed in this way, but we need to see that later - if ((googleApiClient != null) && (googleApiClient.isConnected())) { - if ((System.currentTimeMillis() - lastRequest > 20 * 1000)) { - - // enforce 20-second debounce period - lastRequest = System.currentTimeMillis(); - - // NodeApi.GetConnectedNodesResult nodes = - // Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); - if (localnode == null || (localnode != null && localnode.isEmpty())) - setLocalNodeName(); - - CapabilityInfo capabilityInfo = getCapabilities(); - - int count = 0; - Node phoneNode = null; - - if (capabilityInfo != null) { - phoneNode = updatePhoneSyncBgsCapability(capabilityInfo); - count = capabilityInfo.getNodes().size(); - } - - Log.d(TAG, "doInBackground connected. CapabilityApi.GetCapabilityResult mPhoneNodeID=" - + (phoneNode != null ? phoneNode.getId() : "") + " count=" + count + " localnode=" - + localnode);// KS - - if (count > 0) { - - for (Node node : capabilityInfo.getNodes()) { - - // Log.d(TAG, "doInBackground path: " + path); - - switch (path) { - // simple send as is payloads - - case WEARABLE_RESEND_PATH: - Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), - WEARABLE_RESEND_PATH, null); - break; - case WEARABLE_DATA_PATH: - case WEARABLE_CANCELBOLUS_PATH: - case WEARABLE_CONFIRM_ACTIONSTRING_PATH: - case WEARABLE_INITIATE_ACTIONSTRING_PATH: - case OPEN_SETTINGS: - case NEW_STATUS_PATH: - case NEW_PREFERENCES_PATH: - case BASAL_DATA_PATH: - case BOLUS_PROGRESS_PATH: - case ACTION_CONFIRMATION_REQUEST_PATH: - case NEW_CHANGECONFIRMATIONREQUEST_PATH: - case ACTION_CANCELNOTIFICATION_REQUEST_PATH: { - Log.w(TAG, logPrefix + "Unhandled path"); - // sendMessagePayload(node, path, path, payload); - } - - default:// SYNC_ALL_DATA - // this fall through is messy and non-deterministic for new paths - - } - } - } else { - - Log.d(TAG, logPrefix + "doInBackground connected but getConnectedNodes returns 0."); - - } - } else { - // no resend - Log.d(TAG, logPrefix + "Inside the timeout, will not be executed"); - - } - } else { - Log.d(TAG, logPrefix + "Not connected for sending: api " - + ((googleApiClient == null) ? "is NULL!" : "not null")); - if (googleApiClient != null) { - googleApiClient.connect(); - } else { - googleApiConnect(); - } - } - - } catch (Exception ex) { - Log.e(TAG, logPrefix + "Error executing DataRequester in background. Exception: " + ex.getMessage()); - } - - return null; - } - } - - - public CapabilityInfo getCapabilities() { - - CapabilityApi.GetCapabilityResult capabilityResult = Wearable.CapabilityApi.getCapability(googleApiClient, - CAPABILITY_PHONE_APP, CapabilityApi.FILTER_REACHABLE).await(GET_CAPABILITIES_TIMEOUT_MS, - TimeUnit.MILLISECONDS); - - if (!capabilityResult.getStatus().isSuccess()) { - Log.e(TAG, logPrefix + "doInBackground Failed to get capabilities, status: " - + capabilityResult.getStatus().getStatusMessage()); - return null; - } - - return capabilityResult.getCapability(); - - } - public class BolusCancelTask extends AsyncTask { Context mContext; @@ -266,8 +112,11 @@ public class ListenerService extends WearableListenerService implements GoogleAp @Override protected Void doInBackground(Void... params) { - // Log.d(TAG, logPrefix + "BolusCancelTask: doInBack: " + params); - + Log.d(TAG, logPrefix + "BolusCancelTask.doInBackground: " + params); + if (!googleApiClient.isConnected()) { + Log.i(TAG, "BolusCancelTask.doInBackground: not connected"); + googleApiClient.blockingConnect(15, TimeUnit.SECONDS); + } if (googleApiClient.isConnected()) { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); @@ -275,16 +124,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_CANCELBOLUS_PATH, null); } - } else { - googleApiClient.blockingConnect(15, TimeUnit.SECONDS); - if (googleApiClient.isConnected()) { - NodeApi.GetConnectedNodesResult nodes = - Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); - for (Node node : nodes.getNodes()) { - Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_CANCELBOLUS_PATH, null); - } - - } } return null; } @@ -303,9 +142,12 @@ public class ListenerService extends WearableListenerService implements GoogleAp @Override protected Void doInBackground(Void... params) { + Log.i(TAG, "MessageActionTask.doInBackground: "); - forceGoogleApiConnect(); - + if (!googleApiClient.isConnected()) { + Log.i(TAG, "MessageActionTask.doInBackground: not connected"); + googleApiClient.blockingConnect(15, TimeUnit.SECONDS); + } if (googleApiClient.isConnected()) { NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); @@ -313,22 +155,42 @@ public class ListenerService extends WearableListenerService implements GoogleAp Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), mMessagePath, mActionstring.getBytes()); } - } else { - googleApiClient.blockingConnect(15, TimeUnit.SECONDS); - if (googleApiClient.isConnected()) { - NodeApi.GetConnectedNodesResult nodes = - Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); - for (Node node : nodes.getNodes()) { - Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), mMessagePath, mActionstring.getBytes()); - } - } } return null; } } + public class ResendDataTask extends AsyncTask { + Context mContext; + + ResendDataTask(Context context) { + mContext = context; + } + + @Override + protected Void doInBackground(Void... params) { + Log.d(TAG, logPrefix + "ResendDataTask.doInBackground: " + params); + + if (!googleApiClient.isConnected()) { + Log.i(TAG, "ResendDataTask.doInBackground: not connected"); + googleApiClient.blockingConnect(15, TimeUnit.SECONDS); + } + if (googleApiClient.isConnected()) { + Log.i(TAG, "ResendDataTask.doInBackground: connected"); + NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(); + for (Node node : nodes.getNodes()) { + Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WEARABLE_RESEND_PATH, null); + } + } else { + Log.i(TAG, "ResendDataTask.doInBackground: could not connect"); + } + return null; + + } + } + public void requestData() { - sendData(WEARABLE_RESEND_PATH, null); + new ResendDataTask(this).execute(); } public void cancelBolus() { @@ -343,59 +205,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp new MessageActionTask(this, WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute(); } - - private Node updatePhoneSyncBgsCapability(CapabilityInfo capabilityInfo) { - // Log.d(TAG, "CapabilityInfo: " + capabilityInfo); - - Set connectedNodes = capabilityInfo.getNodes(); - return pickBestNode(connectedNodes); - // mPhoneNodeId = pickBestNodeId(connectedNodes); - } - - - private Node pickBestNode(Set nodes) { - Node bestNode = null; - // Find a nearby node or pick one arbitrarily - for (Node node : nodes) { - if (node.isNearby()) { - return node; - } - bestNode = node; - } - return bestNode; - } - - - private synchronized void sendData(String path, byte[] payload) { - // Log.d(TAG, "WR: sendData: path: " + path + ", payload=" + payload); - - if (path == null) - return; - if (mDataRequester != null) { - // Log.d(TAG, logPrefix + "sendData DataRequester != null lastRequest:" + - // WearUtil.dateTimeText(lastRequest)); - if (mDataRequester.getStatus() != AsyncTask.Status.FINISHED) { - // Log.d(TAG, logPrefix + "sendData Should be canceled? Let run 'til finished."); - // mDataRequester.cancel(true); - } - } - - Log.d(TAG, - logPrefix + "sendData: execute lastRequest:" + wearUtil.dateTimeText(lastRequest)); - mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute(); - // executeTask(mDataRequester); - - // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Log.d(TAG, "sendData SDK < M call execute lastRequest:" + WearUtil.dateTimeText(lastRequest)); - // mDataRequester = (DataRequester) new DataRequester(this, path, payload).execute(); - // } else { - // Log.d(TAG, "sendData SDK >= M call executeOnExecutor lastRequest:" + WearUtil.dateTimeText(lastRequest)); - // // TODO xdrip executor - // mDataRequester = (DataRequester) new DataRequester(this, path, payload).executeOnExecutor(xdrip.executor); - // } - } - - private void googleApiConnect() { if (googleApiClient != null) { // Remove old listener(s) @@ -419,20 +228,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp Wearable.MessageApi.addListener(googleApiClient, this); } - - private void forceGoogleApiConnect() { - if (googleApiClient == null || (!googleApiClient.isConnected() && !googleApiClient.isConnecting())) { - try { - Log.d(TAG, "forceGoogleApiConnect: forcing google api reconnection"); - googleApiConnect(); - Thread.sleep(2000); - } catch (InterruptedException e) { - // - } - } - } - - @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -486,7 +281,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp return START_STICKY; } - @Override public void onDataChanged(DataEventBuffer dataEvents) { @@ -548,10 +342,13 @@ public class ListenerService extends WearableListenerService implements GoogleAp SharedPreferences.Editor editor = sharedPreferences.edit(); String keyControl = getString(R.string.key_wear_control); if (dataMap.containsKey(keyControl)) { + boolean previousWearControl = sharedPreferences.getBoolean(keyControl, false); boolean wearControl = dataMap.getBoolean(keyControl, false); editor.putBoolean(keyControl, wearControl); editor.apply(); - updateTiles(); + if (wearControl != previousWearControl) { + updateTiles(); + } } String keyPercentage = getString(R.string.key_boluswizard_percentage); if (dataMap.containsKey(keyPercentage)) { @@ -565,6 +362,26 @@ public class ListenerService extends WearableListenerService implements GoogleAp editor.putBoolean(keyUnits, mgdl); editor.apply(); } + } else if (path.equals(QUICK_WIZARD_PATH)) { + dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap(); + Log.i(TAG, "onDataChanged: QUICK_WIZARD_PATH" + dataMap); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + dataMap.remove("timestamp"); + String key = getString(R.string.key_quick_wizard_data_map); + String dataString = Base64.encodeToString(dataMap.toByteArray(), Base64.DEFAULT); + if (!dataString.equals(sharedPreferences.getString(key, ""))) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(key, dataString); + editor.apply(); + // Todo maybe add debounce function, due to 20 seconds update limit? + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + TileService.getUpdater(this) + .requestUpdate(QuickWizardTileService.class); + } + Log.i(TAG, "onDataChanged: updated QUICK_WIZARD"); + } else { + Log.i(TAG, "onDataChanged: ignore update"); + } } else if (path.equals(NEW_CHANGECONFIRMATIONREQUEST_PATH)) { String title = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("title"); String message = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("message"); @@ -592,6 +409,9 @@ public class ListenerService extends WearableListenerService implements GoogleAp TileService.getUpdater(this) .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); NotificationCompat.Builder notificationBuilder = - new NotificationCompat.Builder(this, vibrate ? AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS: AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT) + new NotificationCompat.Builder(this, vibrate ? AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS : AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT) .setSmallIcon(R.drawable.ic_icon) .setContentTitle(getString(R.string.bolus_progress)) .setContentText(progresspercent + "% - " + progresstatus) @@ -723,7 +543,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp bolusprogressThread.start(); } - private class DismissThread extends Thread { private final int notificationID; private final int seconds; @@ -757,7 +576,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp context.startService(intent); } - public static void initiateAction(Context context, String actionstring) { + public static void initiateAction(Context context, @NotNull String actionstring) { Intent intent = new Intent(context, ListenerService.class); intent.putExtra("actionstring", actionstring); intent.setAction(ACTION_INITIATE_ACTION); @@ -780,18 +599,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp public void onConnected(Bundle bundle) { // Log.d(TAG, logPrefix + "onConnected call requestData"); - CapabilityApi.CapabilityListener capabilityListener = new CapabilityApi.CapabilityListener() { - - @Override - public void onCapabilityChanged(CapabilityInfo capabilityInfo) { - updatePhoneSyncBgsCapability(capabilityInfo); - Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mPhoneNodeID:" + mPhoneNodeId - + ", Capability: " + capabilityInfo); - } - }; - - Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_PHONE_APP); - Wearable.ChannelApi.addListener(googleApiClient, this); requestData(); } @@ -806,28 +613,6 @@ public class ListenerService extends WearableListenerService implements GoogleAp } - - private void setLocalNodeName() { - forceGoogleApiConnect(); - PendingResult result = Wearable.NodeApi.getLocalNode(googleApiClient); - result.setResultCallback(new ResultCallback() { - - @Override - public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) { - if (!getLocalNodeResult.getStatus().isSuccess()) { - Log.e(TAG, "ERROR: failed to getLocalNode Status=" - + getLocalNodeResult.getStatus().getStatusMessage()); - } else { - Log.d(TAG, "getLocalNode Status=: " + getLocalNodeResult.getStatus().getStatusMessage()); - Node getnode = getLocalNodeResult.getNode(); - localnode = getnode != null ? getnode.getDisplayName() + "|" + getnode.getId() : ""; - Log.d(TAG, "setLocalNodeName. localnode=" + localnode); - } - } - }); - } - - @Override public void onDestroy() { super.onDestroy(); diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BackgroundActionActivity.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BackgroundActionActivity.kt new file mode 100644 index 0000000000..a7db92eea9 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BackgroundActionActivity.kt @@ -0,0 +1,29 @@ +package info.nightscout.androidaps.interaction.actions + +import android.app.Activity +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import info.nightscout.androidaps.data.ListenerService + +const val TAG = "QuickWizard" + +class BackgroundActionActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val actionString = intent.extras?.getString("actionString") + Log.i(TAG, "QuickWizardActivity.onCreate: actionString=$actionString") + if (actionString != null) { + ListenerService.initiateAction(this, actionString) + val message = intent.extras?.getString("message") + if (message != null) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() + } + } else { + Log.e(TAG, "BackgroundActionActivity.onCreate extras 'actionString' required") + } + finishAffinity() + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java index 8c761f898b..df2db8fafa 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/TempTargetActivity.java @@ -31,9 +31,7 @@ public class TempTargetActivity extends ViewSelectorActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (executeInBackground()){ - return; - } + setAdapter(new MyGridViewPagerAdapter()); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); @@ -41,21 +39,6 @@ public class TempTargetActivity extends ViewSelectorActivity { 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 protected void onPause() { super.onPause(); diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt index 9c2ccfdf56..a495d3ddf8 100644 --- a/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt +++ b/wear/src/main/java/info/nightscout/androidaps/tile/ActionSource.kt @@ -1,5 +1,6 @@ package info.nightscout.androidaps.tile +import android.content.res.Resources import info.nightscout.androidaps.R import info.nightscout.androidaps.interaction.actions.BolusActivity import info.nightscout.androidaps.interaction.actions.TreatmentActivity @@ -7,48 +8,49 @@ import info.nightscout.androidaps.interaction.actions.ECarbActivity import info.nightscout.androidaps.interaction.actions.TempTargetActivity import info.nightscout.androidaps.interaction.actions.WizardActivity -object ActionSource : TileSource { +object ActionSource : StaticTileSource(), TileSource { - override fun getActions(): List { + override val preferencePrefix = "tile_action_" + + override fun getActions(resources: Resources): List { return listOf( - Action( - id = 0, + StaticAction( settingName = "wizard", - nameRes = R.string.menu_wizard_short, + buttonText = resources.getString(R.string.menu_wizard_short), iconRes = R.drawable.ic_calculator_green, activityClass = WizardActivity::class.java.name, ), - Action( - id = 1, + StaticAction( settingName = "treatment", - nameRes = R.string.menu_treatment_short, + buttonText = resources.getString(R.string.menu_treatment_short), iconRes = R.drawable.ic_bolus_carbs, activityClass = TreatmentActivity::class.java.name, ), - Action( - id = 2, + StaticAction( settingName = "bolus", - nameRes = R.string.action_insulin, + buttonText = resources.getString(R.string.action_insulin), iconRes = R.drawable.ic_bolus, activityClass = BolusActivity::class.java.name, ), - Action( - id = 3, + StaticAction( settingName = "carbs", - nameRes = R.string.action_carbs, + buttonText = resources.getString(R.string.action_carbs), iconRes = R.drawable.ic_carbs_orange, activityClass = ECarbActivity::class.java.name, ), - Action( - id = 4, + StaticAction( settingName = "temp_target", - nameRes = R.string.menu_tempt, + buttonText = resources.getString(R.string.menu_tempt), iconRes = R.drawable.ic_temptarget_flat, activityClass = TempTargetActivity::class.java.name, ) ) } + override fun getResourceReferences(resources: Resources): List { + return getActions(resources).map { it.iconRes } + } + override fun getDefaultConfig(): Map { return mapOf( "tile_action_1" to "wizard", diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt b/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt index da0dac33cb..b30ec2a87a 100644 --- a/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt +++ b/wear/src/main/java/info/nightscout/androidaps/tile/ActionsTileService.kt @@ -1,10 +1,6 @@ package info.nightscout.androidaps.tile class ActionsTileService : TileBase() { - - override val preferencePrefix = "tile_action_" - override val resourceVersion = "1" - override val idIconActionPrefix = "ic_action_" + override val resourceVersion = "ActionsTileService" override val source = ActionSource - } diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardSource.kt new file mode 100644 index 0000000000..d241ac0b9a --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardSource.kt @@ -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 { + val quickList = mutableListOf() + val quickMap = getDataMap(context) + val sfm = secondsFromMidnight() + + for (quick in quickMap) { + val validFrom = quick.getInt("from", 0) + val validTo = quick.getInt("to", 0) + val isActive = sfm in validFrom..validTo + // 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 { + val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context) + val key = context.resources.getString(R.string.key_quick_wizard_data_map) + if (sharedPrefs.contains(key)) { + val rawB64Data: String? = sharedPrefs.getString(key, null) + val rawData: ByteArray = Base64.decode(rawB64Data, Base64.DEFAULT) + try { + val map = DataMap.fromByteArray(rawData) + return map.getDataMapArrayList("quick_wizard") + + } catch (ex: IllegalArgumentException) { + Log.e(TAG, "getSelectedActions: IllegalArgumentException ", ex) + } + } + return arrayListOf() + } + + private fun secondsFromMidnight(): Int { + val c = Calendar.getInstance() + c.set(Calendar.HOUR_OF_DAY, 0) + c.set(Calendar.MINUTE, 0) + c.set(Calendar.SECOND, 0) + c.set(Calendar.MILLISECOND, 0) + val passed: Long = System.currentTimeMillis() - c.timeInMillis + + return (passed / 1000).toInt() + } + + override fun getResourceReferences(resources: Resources): List { + return listOf(R.drawable.ic_quick_wizard) + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardTileService.kt b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardTileService.kt new file mode 100644 index 0000000000..ef85e789a7 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/QuickWizardTileService.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.tile + +const val TAG = "QuickWizard" + +class QuickWizardTileService : TileBase() { + override val resourceVersion = "QuickWizardTileService" + override val source = QuickWizardSource +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/StaticTileSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/StaticTileSource.kt new file mode 100644 index 0000000000..bbd7be1a91 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/tile/StaticTileSource.kt @@ -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 + + abstract val preferencePrefix: String + abstract fun getDefaultConfig(): Map + + open fun getSelectedActions(context: Context): List { + val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context) + setDefaultSettings(sharedPrefs) + + val actionList: MutableList = mutableListOf() + for (i in 1..4) { + val action = getActionFromPreference(context.resources, sharedPrefs, i) + if (action != null) { + actionList.add(action) + } + } + if (actionList.isEmpty()) { + return getActions(context.resources).take(4) + } + return actionList + } + + 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() + } + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt index 234d35bcdf..37d4f55e1c 100644 --- a/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt +++ b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetSource.kt @@ -1,61 +1,64 @@ package info.nightscout.androidaps.tile +import android.content.res.Resources import info.nightscout.androidaps.R +import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity import info.nightscout.androidaps.interaction.actions.TempTargetActivity -object TempTargetSource : TileSource { +object TempTargetSource : StaticTileSource(), TileSource { + override val preferencePrefix= "tile_tempt_" - override fun getActions(): List { + override fun getActions(resources: Resources): List { + val message = resources.getString(R.string.action_tempt_confirmation) return listOf( - Action( - id = 0, + StaticAction( settingName = "activity", - nameRes = R.string.temp_target_activity, + buttonText = resources.getString(R.string.temp_target_activity), iconRes = R.drawable.ic_target_activity, - activityClass = TempTargetActivity::class.java.name, - background = true, + activityClass = BackgroundActionActivity::class.java.name, + message = message, // actionString = "temptarget false 90 8.0 8.0", actionString = "temptarget preset activity", ), - Action( - id = 1, + StaticAction( 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, - activityClass = TempTargetActivity::class.java.name, - background = true, + activityClass = BackgroundActionActivity::class.java.name, + message = message, // actionString = "temptarget false 45 4.5 4.5", actionString = "temptarget preset eating", ), - Action( - id = 2, + StaticAction( settingName = "hypo", - nameRes = R.string.temp_target_hypo, + buttonText = resources.getString(R.string.temp_target_hypo), iconRes = R.drawable.ic_target_hypo, - activityClass = TempTargetActivity::class.java.name, - background = true, + activityClass = BackgroundActionActivity::class.java.name, + message = message, // actionString = "temptarget false 45 7.0 7.0", actionString = "temptarget preset hypo", ), - Action( - id = 3, + StaticAction( settingName = "manual", - nameRes = R.string.temp_target_manual, + buttonText = resources.getString(R.string.temp_target_manual), iconRes = R.drawable.ic_target_manual, activityClass = TempTargetActivity::class.java.name, ), - Action( - id = 4, + StaticAction( settingName = "cancel", - nameRes = R.string.generic_cancel, + buttonText = resources.getString(R.string.generic_cancel), iconRes = R.drawable.ic_target_cancel, - activityClass = TempTargetActivity::class.java.name, + activityClass = BackgroundActionActivity::class.java.name, + message = message, actionString = "temptarget cancel", - background = true, ) ) } + override fun getResourceReferences(resources: Resources): List { + return getActions(resources).map { it.iconRes } + } + override fun getDefaultConfig(): Map { return mapOf( "tile_tempt_1" to "activity", diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt index 1c7c6ebe39..6e0ff0e30f 100644 --- a/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt +++ b/wear/src/main/java/info/nightscout/androidaps/tile/TempTargetTileService.kt @@ -2,9 +2,7 @@ package info.nightscout.androidaps.tile class TempTargetTileService : TileBase() { - override val preferencePrefix = "tile_tempt_" - override val resourceVersion = "1" - override val idIconActionPrefix = "ic_tempt_" + override val resourceVersion = "TempTargetTileService" override val source = TempTargetSource; } diff --git a/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt b/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt index 415e727920..5c86a89f84 100644 --- a/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt +++ b/wear/src/main/java/info/nightscout/androidaps/tile/TileBase.kt @@ -1,8 +1,9 @@ package info.nightscout.androidaps.tile -import android.content.SharedPreferences +import android.content.Context +import android.os.Build import androidx.annotation.DrawableRes -import androidx.annotation.StringRes +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.wear.tiles.ActionBuilders @@ -42,18 +43,17 @@ private const val LARGE_SCREEN_WIDTH_DP = 210 interface TileSource { - fun getActions(): List - fun getDefaultConfig(): Map + fun getResourceReferences(resources: android.content.res.Resources): List + fun getSelectedActions(context: Context): List } -data class Action( - val id: Int, - val settingName: String, - @StringRes val nameRes: Int, +open class Action( + val buttonText: String, + val buttonTextSub: String? = null, val activityClass: String, @DrawableRes val iconRes: Int, - val background: Boolean = false, val actionString: String? = null, + val message: String? = null, ) enum class WearControl { @@ -62,10 +62,7 @@ enum class WearControl { abstract class TileBase : TileService() { - open val resourceVersion = "1" - open val idIconActionPrefix = "ic_action_" - - abstract val preferencePrefix: String + abstract val resourceVersion: String abstract val source: TileSource private val serviceJob = Job() @@ -89,19 +86,25 @@ abstract class TileBase : TileService() { .build() } + private fun getSelectedActions(): List { + // 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( requestParams: ResourcesRequest ): ListenableFuture = serviceScope.future { Resources.Builder() .setVersion(resourceVersion) .apply { - source.getActions().mapNotNull { action -> + source.getResourceReferences(resources).forEach { resourceId -> addIdToImageMapping( - idIconActionPrefix + action.id, + resourceId.toString(), ImageResource.Builder() .setAndroidResourceByResId( AndroidImageResourceByResId.Builder() - .setResourceId(action.iconRes) + .setResourceId(resourceId) .build() ) .build() @@ -157,15 +160,17 @@ abstract class TileBase : TileService() { .build() private fun doAction(action: Action): ActionBuilders.Action { - val inBackground = ActionBuilders.AndroidBooleanExtra.Builder().setValue(action.background).build() val builder = ActionBuilders.AndroidActivity.Builder() .setClassName(action.activityClass) .setPackageName(this.packageName) - .addKeyToExtraMapping("inBackground", inBackground) if (action.actionString != null) { val actionString = ActionBuilders.AndroidStringExtra.Builder().setValue(action.actionString).build() builder.addKeyToExtraMapping("actionString", actionString) } + if (action.message != null) { + val message = ActionBuilders.AndroidStringExtra.Builder().setValue(action.message).build() + builder.addKeyToExtraMapping("message", message) + } return ActionBuilders.LaunchAction.Builder() .setAndroidActivity(builder.build()) @@ -174,8 +179,8 @@ abstract class TileBase : TileService() { private fun action(action: Action, deviceParameters: DeviceParameters): LayoutElement { val circleDiameter = circleDiameter(deviceParameters) - val iconSize = dp(circleDiameter * ICON_SIZE_FRACTION) - val text = resources.getString(action.nameRes) + val text = action.buttonText + val textSub = action.buttonTextSub return Box.Builder() .setWidth(dp(circleDiameter)) .setHeight(dp(circleDiameter)) @@ -193,7 +198,7 @@ abstract class TileBase : TileService() { ) .setSemantics( Semantics.Builder() - .setContentDescription(text) + .setContentDescription("$text $textSub") .build() ) .setClickable( @@ -203,32 +208,55 @@ abstract class TileBase : TileService() { ) .build() ) - .addContent( - 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() - ) + .addContent(addTextContent(action, deviceParameters)) .build() } + private fun addTextContent(action: Action, deviceParameters: DeviceParameters): LayoutElement { + val circleDiameter = circleDiameter(deviceParameters) + val iconSize = dp(circleDiameter * ICON_SIZE_FRACTION) + val text = action.buttonText + val textSub = action.buttonTextSub + val col = Column.Builder() + .addContent( + Image.Builder() + .setWidth(iconSize) + .setHeight(iconSize) + .setResourceId(action.iconRes.toString()) + .build() + ).addContent( + Text.Builder() + .setText(text) + .setFontStyle( + FontStyle.Builder() + .setWeight(FONT_WEIGHT_BOLD) + .setColor( + argb(ContextCompat.getColor(baseContext, R.color.white)) + ) + .setSize(buttonTextSize(deviceParameters, text)) + .build() + ) + .build() + ) + if (textSub != null) { + col.addContent( + Text.Builder() + .setText(textSub) + .setFontStyle( + FontStyle.Builder() + .setColor( + argb(ContextCompat.getColor(baseContext, R.color.white)) + ) + .setSize(buttonTextSize(deviceParameters, textSub)) + .build() + ) + .build() + ) + } + + return col.build() + } + private fun circleDiameter(deviceParameters: DeviceParameters) = when (deviceParameters.screenShape) { SCREEN_SHAPE_ROUND -> ((sqrt(2f) - 1) * deviceParameters.screenHeightDp) - (2 * SPACING_ACTIONS) else -> 0.5f * deviceParameters.screenHeightDp - SPACING_ACTIONS @@ -257,37 +285,4 @@ abstract class TileBase : TileService() { return WearControl.DISABLED } - private fun getSelectedActions(): List { - val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this) - setDefaultSettings(sharedPrefs) - - val actionList: MutableList = 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() - } - } } diff --git a/wear/src/main/res/drawable-notround-nodpi/quick_wizard_tile_preview.png b/wear/src/main/res/drawable-notround-nodpi/quick_wizard_tile_preview.png new file mode 100644 index 0000000000..850ff52de7 Binary files /dev/null and b/wear/src/main/res/drawable-notround-nodpi/quick_wizard_tile_preview.png differ diff --git a/wear/src/main/res/drawable-round-nodpi/quick_wizard_tile_preview.png b/wear/src/main/res/drawable-round-nodpi/quick_wizard_tile_preview.png new file mode 100644 index 0000000000..850ff52de7 Binary files /dev/null and b/wear/src/main/res/drawable-round-nodpi/quick_wizard_tile_preview.png differ diff --git a/wear/src/main/res/drawable/ic_carbs_orange.xml b/wear/src/main/res/drawable/ic_carbs_orange.xml index 53bb025c54..6a41ff68ac 100644 --- a/wear/src/main/res/drawable/ic_carbs_orange.xml +++ b/wear/src/main/res/drawable/ic_carbs_orange.xml @@ -1,7 +1,6 @@ + + + + + + diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index 77481f8360..f6865795b1 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ AAPS(Actions) AAPS(Temp Target) + AAPS(Quick Wizard) No data! Old data! @@ -86,9 +87,11 @@ None Default Menu + XL Duration Temp Target Requested + Quick Wizard Requested Treatment Requested Bolus Requested Calculation Requested @@ -165,7 +168,7 @@ AAPS Bolus Progress Silent Bolus progress and cancel Bolus progress and cancel with less vibrations - + QuickWizard wearcontrol units_mgdl boluswizard_percentage @@ -182,5 +185,6 @@ No config available Wear controls disabled No data available + quick_wizard_data_map