From 11665a1d89c46f4cdc478a55150d48091a4f5569 Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Thu, 7 Nov 2019 22:39:29 +0100 Subject: [PATCH] [#728] Complication support - Watch OS selection of Short, Long and Image complications with current AAPS status (SGV, trend, delta, BR, IoB, CoB, phone/uploader battery) --- .../battery-charging-wireless-10-burnin.svg | 4 + .../battery-charging-wireless-20-burnin.svg | 4 + .../battery-charging-wireless-30-burnin.svg | 4 + .../battery-charging-wireless-40-burnin.svg | 4 + .../battery-charging-wireless-50-burnin.svg | 4 + .../battery-charging-wireless-60-burnin.svg | 4 + .../battery-charging-wireless-70-burnin.svg | 4 + .../battery-charging-wireless-80-burnin.svg | 4 + .../battery-charging-wireless-90-burnin.svg | 4 + .../battery-charging-wireless-burnin.svg | 4 + .../battery-burnin/battery-unknown-burnin.svg | 4 + .../mask-burnin-battery-raw.svg | 55 +++ icons/battery-source/mask-burnin-battery.svg | 223 ++++++++++ .../battery/battery-charging-wireless-10.svg | 1 + .../battery/battery-charging-wireless-20.svg | 1 + .../battery/battery-charging-wireless-30.svg | 1 + .../battery/battery-charging-wireless-40.svg | 1 + .../battery/battery-charging-wireless-50.svg | 1 + .../battery/battery-charging-wireless-60.svg | 1 + .../battery/battery-charging-wireless-70.svg | 1 + .../battery/battery-charging-wireless-80.svg | 1 + .../battery/battery-charging-wireless-90.svg | 1 + icons/battery/battery-charging-wireless.svg | 1 + icons/battery/battery-outline.svg | 1 + icons/battery/battery-unknown.svg | 1 + .../ic_br_cob_iob_orig.svg | 93 ++++ .../ic_cob_detailed_orig.svg | 83 ++++ .../complications-source/ic_cob_iob_orig.svg | 83 ++++ .../ic_ins_burnin_orig.svg | 57 +++ icons/complications-source/ic_ins_orig.svg | 56 +++ .../ic_iob_detailed_orig.svg | 88 ++++ icons/complications/ic_aaps_full.svg | 36 ++ icons/complications/ic_basal.svg | 35 ++ icons/complications/ic_br_cob_iob.svg | 40 ++ icons/complications/ic_carbs.svg | 35 ++ icons/complications/ic_cob_detailed.svg | 36 ++ icons/complications/ic_cob_iob.svg | 36 ++ icons/complications/ic_ins.svg | 31 ++ icons/complications/ic_ins_burnin.svg | 31 ++ icons/complications/ic_iob_detailed.svg | 36 ++ icons/complications/ic_sgv.svg | 35 ++ wear/build.gradle | 4 + wear/src/main/AndroidManifest.xml | 224 ++++++++++ wear/src/main/assets/watch_dark.jpg | Bin 0 -> 21801 bytes wear/src/main/assets/watch_gray.jpg | Bin 0 -> 19759 bytes wear/src/main/assets/watch_light.jpg | Bin 0 -> 19708 bytes .../java/info/nightscout/androidaps/aaps.java | 75 ++++ .../BaseComplicationProviderService.java | 415 ++++++++++++++++++ .../complications/BrCobIobComplication.java | 49 +++ .../CobDetailedComplication.java | 52 +++ .../complications/CobIconComplication.java | 51 +++ .../complications/CobIobComplication.java | 46 ++ .../complications/ComplicationAction.java | 12 + .../ComplicationTapBroadcastReceiver.java | 167 +++++++ .../IobDetailedComplication.java | 52 +++ .../complications/IobIconComplication.java | 56 +++ .../complications/LongStatusComplication.java | 52 +++ .../LongStatusFlippedComplication.java | 53 +++ .../complications/SgvComplication.java | 48 ++ .../complications/UploaderBattery.java | 114 +++++ .../complications/WallpaperComplication.java | 62 +++ .../WallpaperDarkComplication.java | 22 + .../WallpaperGrayComplication.java | 22 + .../WallpaperLightComplication.java | 22 + .../androidaps/data/DisplayRawData.java | 269 ++++++++++++ .../androidaps/data/ListenerService.java | 4 + .../interaction/utils/Constants.java | 14 + .../interaction/utils/DisplayFormat.java | 129 ++++++ .../interaction/utils/Inevitable.java | 117 +++++ .../interaction/utils/Persistence.java | 94 ++++ .../utils/SmallestDoubleString.java | 130 ++++++ .../interaction/utils/WearUtil.java | 110 ++++- .../androidaps/watchfaces/BaseWatchFace.java | 226 ++-------- .../androidaps/watchfaces/BgGraphBuilder.java | 37 ++ .../androidaps/watchfaces/Cockpit.java | 8 +- .../androidaps/watchfaces/Home.java | 16 +- .../androidaps/watchfaces/Home2.java | 18 +- .../androidaps/watchfaces/LargeHome.java | 22 +- .../androidaps/watchfaces/Steampunk.java | 34 +- wear/src/main/res/drawable/ic_aaps_dark.xml | 12 + wear/src/main/res/drawable/ic_aaps_full.xml | 12 + wear/src/main/res/drawable/ic_aaps_gray.xml | 12 + wear/src/main/res/drawable/ic_aaps_light.xml | 12 + wear/src/main/res/drawable/ic_alert.xml | 9 + .../src/main/res/drawable/ic_alert_burnin.xml | 9 + .../ic_battery_alert_variant_outline.xml | 9 + .../drawable/ic_battery_charging_wireless.xml | 9 + .../ic_battery_charging_wireless_10.xml | 9 + ...ic_battery_charging_wireless_10_burnin.xml | 10 + .../ic_battery_charging_wireless_20.xml | 9 + ...ic_battery_charging_wireless_20_burnin.xml | 10 + .../ic_battery_charging_wireless_30.xml | 9 + ...ic_battery_charging_wireless_30_burnin.xml | 10 + .../ic_battery_charging_wireless_40.xml | 9 + ...ic_battery_charging_wireless_40_burnin.xml | 10 + .../ic_battery_charging_wireless_50.xml | 9 + ...ic_battery_charging_wireless_50_burnin.xml | 10 + .../ic_battery_charging_wireless_60.xml | 9 + ...ic_battery_charging_wireless_60_burnin.xml | 10 + .../ic_battery_charging_wireless_70.xml | 9 + ...ic_battery_charging_wireless_70_burnin.xml | 10 + .../ic_battery_charging_wireless_80.xml | 9 + ...ic_battery_charging_wireless_80_burnin.xml | 10 + .../ic_battery_charging_wireless_90.xml | 9 + ...ic_battery_charging_wireless_90_burnin.xml | 10 + .../ic_battery_charging_wireless_burnin.xml | 10 + .../ic_battery_charging_wireless_outline.xml | 9 + .../main/res/drawable/ic_battery_outline.xml | 9 + .../main/res/drawable/ic_battery_unknown.xml | 9 + .../drawable/ic_battery_unknown_burnin.xml | 10 + wear/src/main/res/drawable/ic_br_cob_iob.xml | 11 + wear/src/main/res/drawable/ic_carbs.xml | 11 + .../src/main/res/drawable/ic_cob_detailed.xml | 11 + wear/src/main/res/drawable/ic_cob_iob.xml | 11 + wear/src/main/res/drawable/ic_ins.xml | 11 + wear/src/main/res/drawable/ic_ins_burnin.xml | 11 + .../src/main/res/drawable/ic_iob_detailed.xml | 11 + wear/src/main/res/drawable/ic_sgv.xml | 13 + wear/src/main/res/drawable/ic_sync_alert.xml | 9 + wear/src/main/res/values/strings.xml | 28 ++ wear/src/main/res/xml/preferences.xml | 14 + 121 files changed, 4181 insertions(+), 233 deletions(-) create mode 100644 icons/battery-burnin/battery-charging-wireless-10-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-20-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-30-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-40-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-50-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-60-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-70-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-80-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-90-burnin.svg create mode 100644 icons/battery-burnin/battery-charging-wireless-burnin.svg create mode 100644 icons/battery-burnin/battery-unknown-burnin.svg create mode 100644 icons/battery-source/mask-burnin-battery-raw.svg create mode 100644 icons/battery-source/mask-burnin-battery.svg create mode 100644 icons/battery/battery-charging-wireless-10.svg create mode 100644 icons/battery/battery-charging-wireless-20.svg create mode 100644 icons/battery/battery-charging-wireless-30.svg create mode 100644 icons/battery/battery-charging-wireless-40.svg create mode 100644 icons/battery/battery-charging-wireless-50.svg create mode 100644 icons/battery/battery-charging-wireless-60.svg create mode 100644 icons/battery/battery-charging-wireless-70.svg create mode 100644 icons/battery/battery-charging-wireless-80.svg create mode 100644 icons/battery/battery-charging-wireless-90.svg create mode 100644 icons/battery/battery-charging-wireless.svg create mode 100644 icons/battery/battery-outline.svg create mode 100644 icons/battery/battery-unknown.svg create mode 100644 icons/complications-source/ic_br_cob_iob_orig.svg create mode 100644 icons/complications-source/ic_cob_detailed_orig.svg create mode 100644 icons/complications-source/ic_cob_iob_orig.svg create mode 100644 icons/complications-source/ic_ins_burnin_orig.svg create mode 100644 icons/complications-source/ic_ins_orig.svg create mode 100644 icons/complications-source/ic_iob_detailed_orig.svg create mode 100644 icons/complications/ic_aaps_full.svg create mode 100644 icons/complications/ic_basal.svg create mode 100644 icons/complications/ic_br_cob_iob.svg create mode 100644 icons/complications/ic_carbs.svg create mode 100644 icons/complications/ic_cob_detailed.svg create mode 100644 icons/complications/ic_cob_iob.svg create mode 100644 icons/complications/ic_ins.svg create mode 100644 icons/complications/ic_ins_burnin.svg create mode 100644 icons/complications/ic_iob_detailed.svg create mode 100644 icons/complications/ic_sgv.svg create mode 100644 wear/src/main/assets/watch_dark.jpg create mode 100644 wear/src/main/assets/watch_gray.jpg create mode 100644 wear/src/main/assets/watch_light.jpg create mode 100644 wear/src/main/java/info/nightscout/androidaps/aaps.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/data/DisplayRawData.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java create mode 100644 wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java create mode 100644 wear/src/main/res/drawable/ic_aaps_dark.xml create mode 100644 wear/src/main/res/drawable/ic_aaps_full.xml create mode 100644 wear/src/main/res/drawable/ic_aaps_gray.xml create mode 100644 wear/src/main/res/drawable/ic_aaps_light.xml create mode 100644 wear/src/main/res/drawable/ic_alert.xml create mode 100644 wear/src/main/res/drawable/ic_alert_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml create mode 100644 wear/src/main/res/drawable/ic_battery_outline.xml create mode 100644 wear/src/main/res/drawable/ic_battery_unknown.xml create mode 100644 wear/src/main/res/drawable/ic_battery_unknown_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_br_cob_iob.xml create mode 100644 wear/src/main/res/drawable/ic_carbs.xml create mode 100644 wear/src/main/res/drawable/ic_cob_detailed.xml create mode 100644 wear/src/main/res/drawable/ic_cob_iob.xml create mode 100644 wear/src/main/res/drawable/ic_ins.xml create mode 100644 wear/src/main/res/drawable/ic_ins_burnin.xml create mode 100644 wear/src/main/res/drawable/ic_iob_detailed.xml create mode 100644 wear/src/main/res/drawable/ic_sgv.xml create mode 100644 wear/src/main/res/drawable/ic_sync_alert.xml diff --git a/icons/battery-burnin/battery-charging-wireless-10-burnin.svg b/icons/battery-burnin/battery-charging-wireless-10-burnin.svg new file mode 100644 index 0000000000..2888abf209 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-10-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-20-burnin.svg b/icons/battery-burnin/battery-charging-wireless-20-burnin.svg new file mode 100644 index 0000000000..77d52751d7 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-20-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-30-burnin.svg b/icons/battery-burnin/battery-charging-wireless-30-burnin.svg new file mode 100644 index 0000000000..9ffa356436 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-30-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-40-burnin.svg b/icons/battery-burnin/battery-charging-wireless-40-burnin.svg new file mode 100644 index 0000000000..d9ccc46027 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-40-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-50-burnin.svg b/icons/battery-burnin/battery-charging-wireless-50-burnin.svg new file mode 100644 index 0000000000..4d4905046c --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-50-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-60-burnin.svg b/icons/battery-burnin/battery-charging-wireless-60-burnin.svg new file mode 100644 index 0000000000..e3c4ba5c44 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-60-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-70-burnin.svg b/icons/battery-burnin/battery-charging-wireless-70-burnin.svg new file mode 100644 index 0000000000..fe26b5e93d --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-70-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-80-burnin.svg b/icons/battery-burnin/battery-charging-wireless-80-burnin.svg new file mode 100644 index 0000000000..c949afb1c8 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-80-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-90-burnin.svg b/icons/battery-burnin/battery-charging-wireless-90-burnin.svg new file mode 100644 index 0000000000..13677dc328 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-90-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-charging-wireless-burnin.svg b/icons/battery-burnin/battery-charging-wireless-burnin.svg new file mode 100644 index 0000000000..f074b89d34 --- /dev/null +++ b/icons/battery-burnin/battery-charging-wireless-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-burnin/battery-unknown-burnin.svg b/icons/battery-burnin/battery-unknown-burnin.svg new file mode 100644 index 0000000000..b0cc3bba40 --- /dev/null +++ b/icons/battery-burnin/battery-unknown-burnin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/battery-source/mask-burnin-battery-raw.svg b/icons/battery-source/mask-burnin-battery-raw.svg new file mode 100644 index 0000000000..e2ab79f2a1 --- /dev/null +++ b/icons/battery-source/mask-burnin-battery-raw.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/battery-source/mask-burnin-battery.svg b/icons/battery-source/mask-burnin-battery.svg new file mode 100644 index 0000000000..09be5da8a9 --- /dev/null +++ b/icons/battery-source/mask-burnin-battery.svg @@ -0,0 +1,223 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/battery/battery-charging-wireless-10.svg b/icons/battery/battery-charging-wireless-10.svg new file mode 100644 index 0000000000..d6dbd7febc --- /dev/null +++ b/icons/battery/battery-charging-wireless-10.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-20.svg b/icons/battery/battery-charging-wireless-20.svg new file mode 100644 index 0000000000..9e4badedc9 --- /dev/null +++ b/icons/battery/battery-charging-wireless-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-30.svg b/icons/battery/battery-charging-wireless-30.svg new file mode 100644 index 0000000000..7da87ce966 --- /dev/null +++ b/icons/battery/battery-charging-wireless-30.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-40.svg b/icons/battery/battery-charging-wireless-40.svg new file mode 100644 index 0000000000..b9aaad2b0f --- /dev/null +++ b/icons/battery/battery-charging-wireless-40.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-50.svg b/icons/battery/battery-charging-wireless-50.svg new file mode 100644 index 0000000000..705a61c55b --- /dev/null +++ b/icons/battery/battery-charging-wireless-50.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-60.svg b/icons/battery/battery-charging-wireless-60.svg new file mode 100644 index 0000000000..b2cd9f7734 --- /dev/null +++ b/icons/battery/battery-charging-wireless-60.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-70.svg b/icons/battery/battery-charging-wireless-70.svg new file mode 100644 index 0000000000..608a404882 --- /dev/null +++ b/icons/battery/battery-charging-wireless-70.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-80.svg b/icons/battery/battery-charging-wireless-80.svg new file mode 100644 index 0000000000..c604743cc3 --- /dev/null +++ b/icons/battery/battery-charging-wireless-80.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless-90.svg b/icons/battery/battery-charging-wireless-90.svg new file mode 100644 index 0000000000..246886ad08 --- /dev/null +++ b/icons/battery/battery-charging-wireless-90.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-charging-wireless.svg b/icons/battery/battery-charging-wireless.svg new file mode 100644 index 0000000000..b36143c4c2 --- /dev/null +++ b/icons/battery/battery-charging-wireless.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-outline.svg b/icons/battery/battery-outline.svg new file mode 100644 index 0000000000..e05e71b288 --- /dev/null +++ b/icons/battery/battery-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/battery/battery-unknown.svg b/icons/battery/battery-unknown.svg new file mode 100644 index 0000000000..8e117be5cb --- /dev/null +++ b/icons/battery/battery-unknown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/complications-source/ic_br_cob_iob_orig.svg b/icons/complications-source/ic_br_cob_iob_orig.svg new file mode 100644 index 0000000000..f650e12d99 --- /dev/null +++ b/icons/complications-source/ic_br_cob_iob_orig.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/icons/complications-source/ic_cob_detailed_orig.svg b/icons/complications-source/ic_cob_detailed_orig.svg new file mode 100644 index 0000000000..e599774c5d --- /dev/null +++ b/icons/complications-source/ic_cob_detailed_orig.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons/complications-source/ic_cob_iob_orig.svg b/icons/complications-source/ic_cob_iob_orig.svg new file mode 100644 index 0000000000..98c1764554 --- /dev/null +++ b/icons/complications-source/ic_cob_iob_orig.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons/complications-source/ic_ins_burnin_orig.svg b/icons/complications-source/ic_ins_burnin_orig.svg new file mode 100644 index 0000000000..c5bcdfe0ff --- /dev/null +++ b/icons/complications-source/ic_ins_burnin_orig.svg @@ -0,0 +1,57 @@ + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons/complications-source/ic_ins_orig.svg b/icons/complications-source/ic_ins_orig.svg new file mode 100644 index 0000000000..7601867f58 --- /dev/null +++ b/icons/complications-source/ic_ins_orig.svg @@ -0,0 +1,56 @@ + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons/complications-source/ic_iob_detailed_orig.svg b/icons/complications-source/ic_iob_detailed_orig.svg new file mode 100644 index 0000000000..3479a78565 --- /dev/null +++ b/icons/complications-source/ic_iob_detailed_orig.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons/complications/ic_aaps_full.svg b/icons/complications/ic_aaps_full.svg new file mode 100644 index 0000000000..4e005a8228 --- /dev/null +++ b/icons/complications/ic_aaps_full.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/complications/ic_basal.svg b/icons/complications/ic_basal.svg new file mode 100644 index 0000000000..0491265177 --- /dev/null +++ b/icons/complications/ic_basal.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/complications/ic_br_cob_iob.svg b/icons/complications/ic_br_cob_iob.svg new file mode 100644 index 0000000000..b340c42187 --- /dev/null +++ b/icons/complications/ic_br_cob_iob.svg @@ -0,0 +1,40 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons/complications/ic_carbs.svg b/icons/complications/ic_carbs.svg new file mode 100644 index 0000000000..0785741d47 --- /dev/null +++ b/icons/complications/ic_carbs.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/complications/ic_cob_detailed.svg b/icons/complications/ic_cob_detailed.svg new file mode 100644 index 0000000000..5132260c3c --- /dev/null +++ b/icons/complications/ic_cob_detailed.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/complications/ic_cob_iob.svg b/icons/complications/ic_cob_iob.svg new file mode 100644 index 0000000000..81a2e9250f --- /dev/null +++ b/icons/complications/ic_cob_iob.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/complications/ic_ins.svg b/icons/complications/ic_ins.svg new file mode 100644 index 0000000000..e5d1b3e147 --- /dev/null +++ b/icons/complications/ic_ins.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons/complications/ic_ins_burnin.svg b/icons/complications/ic_ins_burnin.svg new file mode 100644 index 0000000000..623db41e5b --- /dev/null +++ b/icons/complications/ic_ins_burnin.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons/complications/ic_iob_detailed.svg b/icons/complications/ic_iob_detailed.svg new file mode 100644 index 0000000000..8bd9aabc0e --- /dev/null +++ b/icons/complications/ic_iob_detailed.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/complications/ic_sgv.svg b/icons/complications/ic_sgv.svg new file mode 100644 index 0000000000..1dc46d403b --- /dev/null +++ b/icons/complications/ic_sgv.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/wear/build.gradle b/wear/build.gradle index 7e6500fe56..8d4dd71af3 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -92,6 +92,10 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //implementation files("libs/hellocharts-library-1.5.5.jar") //compile "com.ustwo.android:clockwise-wearable:1.0.2" + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + compileOnly "com.google.android.wearable:wearable:${wearableVersion}" implementation "com.google.android.support:wearable:${wearableVersion}" implementation "com.google.android.gms:play-services-wearable:${playServicesWearable}" diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml index 874a9c8763..2f89781293 100644 --- a/wear/src/main/AndroidManifest.xml +++ b/wear/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wear/src/main/assets/watch_dark.jpg b/wear/src/main/assets/watch_dark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54cd4dc1dc79f34c55857323696c048e33613414 GIT binary patch literal 21801 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

v;t;JEa-hkx&vXO2_SjwYqYCpDLvKj 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 literal 0 HcmV?d00001 diff --git a/wear/src/main/assets/watch_light.jpg b/wear/src/main/assets/watch_light.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a8183304d62c37b7c04ac51111c2005a756694da GIT binary patch literal 19708 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 23; + } + + public static String gs(@StringRes final int id) { + return getAppContext().getString(id); + } + + public static String gs(@StringRes final int id, String... args) { + return getAppContext().getString(id, (Object[]) args); + } + + public static Boolean areComplicationsUnicode() { + return unicodeComplications; + } + + public static String getComplicationTapAction() { + return complicationTapAction; + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + updatePrefs(sharedPrefs); + + // we trigger update on Complications + Intent messageIntent = new Intent(); + messageIntent.setAction(Intent.ACTION_SEND); + LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java b/wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java new file mode 100644 index 0000000000..c1f33e41c5 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/BaseComplicationProviderService.java @@ -0,0 +1,415 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.drawable.Icon; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationManager; +import android.support.wearable.complications.ComplicationProviderService; +import android.support.wearable.complications.ComplicationText; +import android.support.wearable.complications.ProviderUpdateRequester; +import android.util.Log; + +import java.util.HashSet; +import java.util.Set; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.aaps; +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.data.ListenerService; +import info.nightscout.androidaps.interaction.utils.Constants; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; +import info.nightscout.androidaps.interaction.utils.Inevitable; +import info.nightscout.androidaps.interaction.utils.Persistence; +import info.nightscout.androidaps.interaction.utils.WearUtil; + +/** + * Base class for all complications + * + * Created by dlvoy on 2019-11-12 + */ +public abstract class BaseComplicationProviderService extends ComplicationProviderService { + + private static final String TAG = BaseComplicationProviderService.class.getSimpleName(); + + private static final String KEY_COMPLICATIONS = "complications"; + private static final String KEY_LAST_SINCE = "lastSince"; + private static final String KEY_STALE_REPORTED = "staleReported"; + private static final String TASK_ID_REFRESH_COMPLICATION = "refresh-complication"; + + + private LocalBroadcastManager localBroadcastManager; + private MessageReceiver messageReceiver; + + public static void turnOff() { + Log.d(TAG, "TURNING OFF all active complications"); + final Persistence persistence = new Persistence(); + persistence.putString(KEY_COMPLICATIONS, ""); + } + + //============================================================================================== + // ABSTRACT COMPLICATION INTERFACE + //============================================================================================== + + public abstract ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent); + public abstract String getProviderCanonicalName(); + + public ComplicationAction getComplicationAction() { return ComplicationAction.MENU; }; + + //---------------------------------------------------------------------------------------------- + // DEFAULT BEHAVIOURS + //---------------------------------------------------------------------------------------------- + + public ComplicationData buildNoSyncComplicationData(int dataType, + DisplayRawData raw, + PendingIntent complicationPendingIntent, + PendingIntent exceptionalPendingIntent, + long since) { + ComplicationData complicationData = null; + + final ComplicationData.Builder builder = new ComplicationData.Builder(dataType); + if (dataType != ComplicationData.TYPE_LARGE_IMAGE) { + builder.setIcon(Icon.createWithResource(this, R.drawable.ic_sync_alert)); + } + + if (dataType == ComplicationData.TYPE_RANGED_VALUE) { + builder.setMinValue(0); + builder.setMaxValue(100); + builder.setValue(0); + } + + switch (dataType) { + case ComplicationData.TYPE_ICON: + case ComplicationData.TYPE_SHORT_TEXT: + case ComplicationData.TYPE_RANGED_VALUE: + if (since > 0) { + builder.setShortText(ComplicationText.plainText(DisplayFormat.shortTimeSince(since) + " old")); + } else { + builder.setShortText(ComplicationText.plainText("!err!")); + } + break; + case ComplicationData.TYPE_LONG_TEXT: + builder.setLongTitle(ComplicationText.plainText(aaps.gs(R.string.label_warning_sync))); + if (since > 0) { + builder.setLongText(ComplicationText.plainText(String.format(aaps.gs(R.string.label_warning_since), DisplayFormat.shortTimeSince(since)))); + } else { + builder.setLongText(ComplicationText.plainText(aaps.gs(R.string.label_warning_sync_aaps))); + } + break; + case ComplicationData.TYPE_LARGE_IMAGE: + return buildComplicationData(dataType, raw, complicationPendingIntent); + default: + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + break; + } + + builder.setTapAction(exceptionalPendingIntent); + complicationData = builder.build(); + return complicationData; + } + + public ComplicationData buildOutdatedComplicationData(int dataType, + DisplayRawData raw, + PendingIntent complicationPendingIntent, + PendingIntent exceptionalPendingIntent, + long since) { + ComplicationData complicationData = null; + + final ComplicationData.Builder builder = new ComplicationData.Builder(dataType); + if (dataType != ComplicationData.TYPE_LARGE_IMAGE) { + builder.setIcon(Icon.createWithResource(this, R.drawable.ic_alert)); + builder.setBurnInProtectionIcon(Icon.createWithResource(this, R.drawable.ic_alert_burnin)); + } + + if (dataType == ComplicationData.TYPE_RANGED_VALUE) { + builder.setMinValue(0); + builder.setMaxValue(100); + builder.setValue(0); + } + + switch (dataType) { + case ComplicationData.TYPE_ICON: + case ComplicationData.TYPE_SHORT_TEXT: + case ComplicationData.TYPE_RANGED_VALUE: + if (since > 0) { + builder.setShortText(ComplicationText.plainText(DisplayFormat.shortTimeSince(since) + " old")); + } else { + builder.setShortText(ComplicationText.plainText("!old!")); + } + break; + case ComplicationData.TYPE_LONG_TEXT: + builder.setLongTitle(ComplicationText.plainText(aaps.gs(R.string.label_warning_old))); + if (since > 0) { + builder.setLongText(ComplicationText.plainText(String.format(aaps.gs(R.string.label_warning_since), DisplayFormat.shortTimeSince(since)))); + } else { + builder.setLongText(ComplicationText.plainText(aaps.gs(R.string.label_warning_sync_aaps))); + } + break; + case ComplicationData.TYPE_LARGE_IMAGE: + return buildComplicationData(dataType, raw, complicationPendingIntent); + default: + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + break; + } + + builder.setTapAction(exceptionalPendingIntent); + complicationData = builder.build(); + return complicationData; + } + + /** + * If Complication depend on "since" field and need to be updated every minute or not + * and need only update when new DisplayRawData arrive + */ + protected boolean usesSinceField() { + return false; + } + + //============================================================================================== + // COMPLICATION LIFECYCLE + //============================================================================================== + + /* + * Called when a complication has been activated. The method is for any one-time + * (per complication) set-up. + * + * You can continue sending data for the active complicationId until onComplicationDeactivated() + * is called. + */ + @Override + public void onComplicationActivated( + int complicationId, int dataType, ComplicationManager complicationManager) { + Log.d(TAG, "onComplicationActivated(): " + complicationId + " of kind: "+getProviderCanonicalName()); + + Persistence persistence = new Persistence(); + persistence.putString("complication_"+complicationId, getProviderCanonicalName()); + persistence.putBoolean("complication_"+complicationId+"_since", usesSinceField()); + persistence.addToSet(KEY_COMPLICATIONS, "complication_"+complicationId); + + IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND); + + messageReceiver = new BaseComplicationProviderService.MessageReceiver(); + localBroadcastManager = LocalBroadcastManager.getInstance(this); + localBroadcastManager.registerReceiver(messageReceiver, messageFilter); + + ListenerService.requestData(this); + checkIfUpdateNeeded(); + } + + /* + * Called when the complication needs updated data from your provider. There are four scenarios + * when this will happen: + * + * 1. An active watch face complication is changed to use this provider + * 2. A complication using this provider becomes active + * 3. The period of time you specified in the manifest has elapsed (UPDATE_PERIOD_SECONDS) + * 4. You triggered an update from your own class via the + * ProviderUpdateRequester.requestUpdate() method. + */ + @Override + public void onComplicationUpdate( + int complicationId, int dataType, ComplicationManager complicationManager) { + Log.d(TAG, "onComplicationUpdate() id: " + complicationId + " of class: "+getProviderCanonicalName()); + + // Create Tap Action so that the user can checkIfUpdateNeeded an update by tapping the complication. + final ComponentName thisProvider = new ComponentName(this, getProviderCanonicalName()); + + // We pass the complication id, so we can only update the specific complication tapped. + final PendingIntent complicationPendingIntent = + ComplicationTapBroadcastReceiver.getTapActionIntent( + aaps.getAppContext(), thisProvider, complicationId, getComplicationAction()); + + final Persistence persistence = new Persistence(); + + final DisplayRawData raw = new DisplayRawData(); + raw.partialUpdateFromPersistence(persistence); + Log.d(TAG, "Complication data: " + raw.toDebugString()); + + // store what is currently rendered since field, to detect it need update + persistence.putString(KEY_LAST_SINCE, DisplayFormat.shortTimeSince(raw.datetime)); + + // by each render we clear stale flag to ensure it is re-rendered at next refresh detection round + persistence.putBoolean(KEY_STALE_REPORTED, false); + + ComplicationData complicationData; + + if (WearUtil.msSince(persistence.whenDataUpdated()) > Constants.STALE_MS) { + // no new data arrived - probably configuration or connection error + final PendingIntent infoToast = ComplicationTapBroadcastReceiver.getTapWarningSinceIntent( + aaps.getAppContext(), thisProvider, complicationId, ComplicationAction.WARNING_SYNC, persistence.whenDataUpdated()); + complicationData = buildNoSyncComplicationData(dataType, raw, complicationPendingIntent, infoToast, persistence.whenDataUpdated()); + } else if (WearUtil.msSince(raw.datetime) > Constants.STALE_MS) { + // data arriving from phone AAPS, but it is outdated (uploader/NS/xDrip/Sensor error) + final PendingIntent infoToast = ComplicationTapBroadcastReceiver.getTapWarningSinceIntent( + aaps.getAppContext(), thisProvider, complicationId, ComplicationAction.WARNING_OLD, raw.datetime); + complicationData = buildOutdatedComplicationData(dataType, raw, complicationPendingIntent, infoToast, raw.datetime); + } else { + // data is up-to-date, we can render standard complication + complicationData = buildComplicationData(dataType, raw, complicationPendingIntent); + } + + if (complicationData != null) { + complicationManager.updateComplicationData(complicationId, complicationData); + + } else { + // If no data is sent, we still need to inform the ComplicationManager, so the update + // job can finish and the wake lock isn't held any longer than necessary. + complicationManager.noUpdateRequired(complicationId); + } + } + + /* + * Called when the complication has been deactivated. + */ + @Override + public void onComplicationDeactivated(int complicationId) { + Log.d(TAG, "onComplicationDeactivated(): " + complicationId); + + Persistence persistence = new Persistence(); + persistence.removeFromSet(KEY_COMPLICATIONS, "complication_"+complicationId); + + if (localBroadcastManager != null && messageReceiver != null) { + localBroadcastManager.unregisterReceiver(messageReceiver); + } + Inevitable.kill(TASK_ID_REFRESH_COMPLICATION); + } + + //============================================================================================== + // UPDATE AND REFRESH LOGIC + //============================================================================================== + + /* + * Schedule check for field update + */ + public static void checkIfUpdateNeeded() { + + Persistence p = new Persistence(); + + Log.d(TAG, "Pending check if update needed - "+p.getString(KEY_COMPLICATIONS, "")); + + Inevitable.task(TASK_ID_REFRESH_COMPLICATION, 15 * Constants.SECOND_IN_MS, () -> { + if (WearUtil.rateLimit("complication-checkIfUpdateNeeded", 5)) { + Log.d(TAG, "Checking if update needed"); + requestUpdateIfSinceChanged(); + // We reschedule need for check - to make sure next check will Inevitable go in next 15s + checkIfUpdateNeeded(); + } + }); + + } + + /* + * Check if displayed since field (field that shows how old, in minutes, is reading) + * is up-to-date or need to be changed (a minute or more elapsed) + */ + private static void requestUpdateIfSinceChanged() { + final Persistence persistence = new Persistence(); + + final DisplayRawData raw = new DisplayRawData(); + raw.partialUpdateFromPersistence(persistence); + + final String lastSince = persistence.getString(KEY_LAST_SINCE, "-"); + final String calcSince = DisplayFormat.shortTimeSince(raw.datetime); + final boolean isStale = (WearUtil.msSince(persistence.whenDataUpdated()) > Constants.STALE_MS) + ||(WearUtil.msSince(raw.datetime) > Constants.STALE_MS); + + final boolean staleWasRefreshed = persistence.getBoolean(KEY_STALE_REPORTED, false); + final boolean sinceWasChanged = !lastSince.equals(calcSince); + + if (sinceWasChanged|| (isStale && !staleWasRefreshed)) { + persistence.putString(KEY_LAST_SINCE, calcSince); + persistence.putBoolean(KEY_STALE_REPORTED, isStale); + + Log.d(TAG, "Detected refresh of time needed! Reason: " + + (isStale ? "- stale detected": "") + + (sinceWasChanged ? "- since changed from: "+lastSince+" to: "+calcSince : "")); + + if (isStale) { + // all complications should update to show offline/old warning + requestUpdate(getActiveProviderClasses()); + } else { + // ... but only some require update due to 'since' field change + requestUpdate(getSinceDependingProviderClasses()); + } + } + } + + /* + * Request update for specified list of providers + */ + private static void requestUpdate(Set providers) { + for (String provider: providers) { + Log.d(TAG, "Pending update of "+provider); + // We wait with updating allowing all request, from various sources, to arrive + Inevitable.task("update-req-"+provider, 700, () -> { + if (WearUtil.rateLimit("update-req-"+provider, 2)) { + Log.d(TAG, "Requesting update of "+provider); + final ComponentName componentName = new ComponentName(aaps.getAppContext(), provider); + final ProviderUpdateRequester providerUpdateRequester = new ProviderUpdateRequester(aaps.getAppContext(), componentName); + providerUpdateRequester.requestUpdateAll(); + } + }); + } + } + + /* + * List all Complication providing classes that have active (registered) providers + */ + private static Set getActiveProviderClasses() { + Persistence persistence = new Persistence(); + Set providers = new HashSet<>(); + Set complications = persistence.getSetOf(KEY_COMPLICATIONS); + for (String complication: complications) { + final String providerClass = persistence.getString(complication, ""); + if (providerClass.length() > 0) { + providers.add(providerClass); + } + } + return providers; + } + + /* + * List all Complication providing classes that have active (registered) providers + * and additionally they depend on "since" field + * == they need to be updated not only on data broadcasts, but every minute or so + */ + private static Set getSinceDependingProviderClasses() { + Persistence persistence = new Persistence(); + Set providers = new HashSet<>(); + Set complications = persistence.getSetOf(KEY_COMPLICATIONS); + for (String complication: complications) { + final String providerClass = persistence.getString(complication, ""); + final boolean dependOnSince = persistence.getBoolean(complication+"_since", false); + if ((providerClass.length() > 0)&&(dependOnSince)) { + providers.add(providerClass); + } + } + return providers; + } + + /* + * Listen to broadcast --> new data was stored by ListenerService to Persistence + */ + public class MessageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Set complications = Persistence.setOf(KEY_COMPLICATIONS); + if (complications.size() > 0) { + checkIfUpdateNeeded(); + // We request all active providers + requestUpdate(getActiveProviderClasses()); + } + } + } + + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java new file mode 100644 index 0000000000..e9f05c5f79 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/BrCobIobComplication.java @@ -0,0 +1,49 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; +import info.nightscout.androidaps.interaction.utils.SmallestDoubleString; + +import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MAX_SHORT_FIELD; +import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MIN_COB_FIELD; +import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MIN_IOB_FIELD; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class BrCobIobComplication extends BaseComplicationProviderService { + + private static final String TAG = BrCobIobComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + final String cob = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE).minimise(MIN_COB_FIELD); + final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_IOB_FIELD, (MAX_SHORT_FIELD-1) - cob.length())); + + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(DisplayFormat.basalRateSymbol()+raw.sBasalRate)) + .setShortTitle(ComplicationText.plainText(cob + " " + iob)) + .setTapAction(complicationPendingIntent); + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return BrCobIobComplication.class.getCanonicalName(); + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java new file mode 100644 index 0000000000..48341b53ef --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/CobDetailedComplication.java @@ -0,0 +1,52 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; +import android.util.Pair; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class CobDetailedComplication extends BaseComplicationProviderService { + + private static final String TAG = CobDetailedComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + + Pair cob = DisplayFormat.detailedCob(raw); + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(cob.first)) + .setTapAction(complicationPendingIntent); + + if (cob.second.length() > 0) { + builder.setShortTitle(ComplicationText.plainText(cob.second)); + } + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return CobDetailedComplication.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.WIZARD; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java new file mode 100644 index 0000000000..40824fd313 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/CobIconComplication.java @@ -0,0 +1,51 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.graphics.drawable.Icon; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DisplayRawData; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class CobIconComplication extends BaseComplicationProviderService { + + private static final String TAG = CobIconComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(raw.sCOB2)) + .setIcon( + Icon.createWithResource( + this, R.drawable.ic_carbs)) + .setBurnInProtectionIcon( + Icon.createWithResource( + this, R.drawable.ic_carbs)) + .setTapAction(complicationPendingIntent); + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return CobIconComplication.class.getCanonicalName(); + } + + public ComplicationAction getComplicationAction() { + return ComplicationAction.WIZARD; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java new file mode 100644 index 0000000000..1f7dff7ceb --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/CobIobComplication.java @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.SmallestDoubleString; + +import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MAX_SHORT_FIELD; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class CobIobComplication extends BaseComplicationProviderService { + + private static final String TAG = CobIobComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + final String cob = raw.sCOB2; + final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_SHORT_FIELD); + + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(cob)) + .setShortTitle(ComplicationText.plainText(iob)) + .setTapAction(complicationPendingIntent); + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return CobIobComplication.class.getCanonicalName(); + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java new file mode 100644 index 0000000000..9ca1e9c456 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationAction.java @@ -0,0 +1,12 @@ +package info.nightscout.androidaps.complications; + +public enum ComplicationAction { + NONE, + MENU, + WIZARD, + BOLUS, + ECARB, + STATUS, + WARNING_SYNC, + WARNING_OLD +} \ No newline at end of file diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java new file mode 100644 index 0000000000..61dc8d4dde --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/ComplicationTapBroadcastReceiver.java @@ -0,0 +1,167 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.wearable.complications.ProviderUpdateRequester; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.StringRes; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.aaps; +import info.nightscout.androidaps.interaction.actions.BolusActivity; +import info.nightscout.androidaps.interaction.actions.ECarbActivity; +import info.nightscout.androidaps.interaction.actions.WizardActivity; +import info.nightscout.androidaps.interaction.menus.MainMenuActivity; +import info.nightscout.androidaps.interaction.menus.StatusMenuActivity; +import info.nightscout.androidaps.interaction.utils.Constants; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; +import info.nightscout.androidaps.interaction.utils.WearUtil; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class ComplicationTapBroadcastReceiver extends BroadcastReceiver { + + private static final String TAG = ComplicationTapBroadcastReceiver.class.getSimpleName(); + + private static final String EXTRA_PROVIDER_COMPONENT = + "info.nightscout.androidaps.complications.action.PROVIDER_COMPONENT"; + private static final String EXTRA_COMPLICATION_ID = + "info.nightscout.androidaps.complications.action.COMPLICATION_ID"; + private static final String EXTRA_COMPLICATION_ACTION = + "info.nightscout.androidaps.complications.action.COMPLICATION_ACTION"; + private static final String EXTRA_COMPLICATION_SINCE = + "info.nightscout.androidaps.complications.action.COMPLICATION_SINCE"; + + @Override + public void onReceive(Context context, Intent intent) { + Bundle extras = intent.getExtras(); + ComponentName provider = extras.getParcelable(EXTRA_PROVIDER_COMPONENT); + int complicationId = extras.getInt(EXTRA_COMPLICATION_ID); + String complicationAction = extras.getString(EXTRA_COMPLICATION_ACTION, ComplicationAction.MENU.toString()); + + ComplicationAction action = ComplicationAction.MENU; + try { + action = ComplicationAction.valueOf(ComplicationAction.class, complicationAction); + } catch (IllegalArgumentException | NullPointerException ex) { + // but how? + Log.e(TAG, "Cannot interpret complication action: "+complicationAction); + } + + action = remapActionWithUserPreferences(action); + + // Request an update for the complication that has just been tapped. + ProviderUpdateRequester requester = new ProviderUpdateRequester(context, provider); + requester.requestUpdate(complicationId); + + Intent intentOpen = null; + + switch (action) { + case NONE: + // do nothing + return; + case WIZARD: + intentOpen = new Intent(aaps.getAppContext(), WizardActivity.class); + break; + case BOLUS: + intentOpen = new Intent(aaps.getAppContext(), BolusActivity.class); + break; + case ECARB: + intentOpen = new Intent(aaps.getAppContext(), ECarbActivity.class); + break; + case STATUS: + intentOpen = new Intent(aaps.getAppContext(), StatusMenuActivity.class); + break; + case WARNING_OLD: + case WARNING_SYNC: + long oneAndHalfMinuteAgo = WearUtil.timestamp() - (Constants.MINUTE_IN_MS+Constants.SECOND_IN_MS * 30); + long since = extras.getLong(EXTRA_COMPLICATION_SINCE, oneAndHalfMinuteAgo); + @StringRes int labelId = (action == ComplicationAction.WARNING_SYNC) ? + R.string.msg_warning_sync : R.string.msg_warning_old; + String msg = String.format(aaps.gs(labelId), DisplayFormat.shortTimeSince(since)); + Toast.makeText(aaps.getAppContext(), msg, Toast.LENGTH_LONG).show(); + break; + case MENU: + default: + intentOpen = new Intent(aaps.getAppContext(), MainMenuActivity.class); + } + + if (intentOpen != null) { + // Perform intent - open dialog + intentOpen.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + aaps.getAppContext().startActivity(intentOpen); + } + } + + private ComplicationAction remapActionWithUserPreferences(ComplicationAction originalAction) { + final String userPrefAction = aaps.getComplicationTapAction(); + switch (originalAction) { + case WARNING_OLD: + case WARNING_SYNC: + // warnings cannot be reconfigured by user + return originalAction; + default: + switch (userPrefAction) { + case "menu": + return ComplicationAction.MENU; + case "wizard": + return ComplicationAction.WIZARD; + case "bolus": + return ComplicationAction.BOLUS; + case "ecarb": + return ComplicationAction.ECARB; + case "status": + return ComplicationAction.STATUS; + case "none": + return ComplicationAction.NONE; + case "default": + default: + return originalAction; + } + } + } + + /** + * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be + * toggled and updated. + */ + static PendingIntent getTapActionIntent( + Context context, ComponentName provider, int complicationId, ComplicationAction action) { + Intent intent = new Intent(context, ComplicationTapBroadcastReceiver.class); + intent.putExtra(EXTRA_PROVIDER_COMPONENT, provider); + intent.putExtra(EXTRA_COMPLICATION_ID, complicationId); + intent.putExtra(EXTRA_COMPLICATION_ACTION, action.toString()); + + + // Pass complicationId as the requestCode to ensure that different complications get + // different intents. + return PendingIntent.getBroadcast( + context, complicationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + /** + * Returns a pending intent, suitable for use as a tap intent, that causes a complication to be + * toggled and updated. + */ + static PendingIntent getTapWarningSinceIntent( + Context context, ComponentName provider, int complicationId, ComplicationAction action, long since) { + Intent intent = new Intent(context, ComplicationTapBroadcastReceiver.class); + intent.putExtra(EXTRA_PROVIDER_COMPONENT, provider); + intent.putExtra(EXTRA_COMPLICATION_ID, complicationId); + intent.putExtra(EXTRA_COMPLICATION_ACTION, action.toString()); + intent.putExtra(EXTRA_COMPLICATION_SINCE, since); + + + // Pass complicationId as the requestCode to ensure that different complications get + // different intents. + return PendingIntent.getBroadcast( + context, complicationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java new file mode 100644 index 0000000000..d9ee729874 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/IobDetailedComplication.java @@ -0,0 +1,52 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; +import android.util.Pair; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class IobDetailedComplication extends BaseComplicationProviderService { + + private static final String TAG = IobDetailedComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + + Pair iob = DisplayFormat.detailedIob(raw); + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(iob.first)) + .setTapAction(complicationPendingIntent); + + if (iob.second.length() > 0) { + builder.setShortTitle(ComplicationText.plainText(iob.second)); + } + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return IobDetailedComplication.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.BOLUS; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java new file mode 100644 index 0000000000..ba253a048d --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/IobIconComplication.java @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.graphics.drawable.Icon; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.SmallestDoubleString; + +import static info.nightscout.androidaps.interaction.utils.DisplayFormat.MAX_SHORT_FIELD; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class IobIconComplication extends BaseComplicationProviderService { + + private static final String TAG = IobIconComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_SHORT_FIELD); + + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(iob)) + .setIcon(Icon.createWithResource( + this, R.drawable.ic_ins)) + .setBurnInProtectionIcon( + Icon.createWithResource( + this, R.drawable.ic_ins_burnin)) + .setTapAction(complicationPendingIntent); + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return IobIconComplication.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.BOLUS; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java new file mode 100644 index 0000000000..834f40b0a7 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusComplication.java @@ -0,0 +1,52 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class LongStatusComplication extends BaseComplicationProviderService { + + private static final String TAG = LongStatusComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + switch (dataType) { + case ComplicationData.TYPE_LONG_TEXT: + + final String glucoseLine = DisplayFormat.longGlucoseLine(raw); + final String detailsLine = DisplayFormat.longDetailsLine(raw); + + final ComplicationData.Builder builderLong = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT) + .setLongTitle(ComplicationText.plainText(glucoseLine)) + .setLongText(ComplicationText.plainText(detailsLine)) + .setTapAction(complicationPendingIntent); + complicationData = builderLong.build(); + + break; + default: + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return LongStatusComplication.class.getCanonicalName(); + } + + @Override + protected boolean usesSinceField() { + return true; + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java new file mode 100644 index 0000000000..da3384d358 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/LongStatusFlippedComplication.java @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; +import info.nightscout.androidaps.interaction.utils.SmallestDoubleString; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class LongStatusFlippedComplication extends BaseComplicationProviderService { + + private static final String TAG = LongStatusFlippedComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + switch (dataType) { + case ComplicationData.TYPE_LONG_TEXT: + + final String glucoseLine = DisplayFormat.longGlucoseLine(raw); + final String detailsLine = DisplayFormat.longDetailsLine(raw); + + final ComplicationData.Builder builderLong = new ComplicationData.Builder(ComplicationData.TYPE_LONG_TEXT) + .setLongTitle(ComplicationText.plainText(detailsLine)) + .setLongText(ComplicationText.plainText(glucoseLine)) + .setTapAction(complicationPendingIntent); + complicationData = builderLong.build(); + + break; + default: + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return LongStatusFlippedComplication.class.getCanonicalName(); + } + + @Override + protected boolean usesSinceField() { + return true; + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java new file mode 100644 index 0000000000..b6f9e94946 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/SgvComplication.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import info.nightscout.androidaps.data.DisplayRawData; +import info.nightscout.androidaps.interaction.utils.DisplayFormat; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class SgvComplication extends BaseComplicationProviderService { + + private static final String TAG = SgvComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + switch (dataType) { + case ComplicationData.TYPE_SHORT_TEXT: + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(raw.sSgv + raw.sDirection)) + .setShortTitle(ComplicationText.plainText(DisplayFormat.shortTrend(raw))) + .setTapAction(complicationPendingIntent); + + complicationData = builder.build(); + break; + default: + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return SgvComplication.class.getCanonicalName(); + } + + @Override + protected boolean usesSinceField() { + return true; + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java b/wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java new file mode 100644 index 0000000000..4b268ef9c7 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/UploaderBattery.java @@ -0,0 +1,114 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.graphics.drawable.Icon; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.complications.ComplicationText; +import android.util.Log; + +import androidx.annotation.DrawableRes; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DisplayRawData; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class UploaderBattery extends BaseComplicationProviderService { + + private static final String TAG = UploaderBattery.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + @DrawableRes int batteryIcon = R.drawable.ic_battery_unknown; + @DrawableRes int burnInBatteryIcon = R.drawable.ic_battery_unknown_burnin; + int level = 0; + String levelStr = "???"; + + if (raw.sUploaderBattery.matches("^[0-9]+$")) { + try { + level = Integer.parseInt(raw.sUploaderBattery); + level = Math.max(Math.min(level, 100), 0); + levelStr = level + "%"; + int iconNo = (int)Math.floor(level / 10.0); + if (level > 95) { + iconNo = 10; + } + switch (iconNo) { + case 10: batteryIcon = R.drawable.ic_battery_charging_wireless; break; + case 9: batteryIcon = R.drawable.ic_battery_charging_wireless_90; break; + case 8: batteryIcon = R.drawable.ic_battery_charging_wireless_80; break; + case 7: batteryIcon = R.drawable.ic_battery_charging_wireless_70; break; + case 6: batteryIcon = R.drawable.ic_battery_charging_wireless_60; break; + case 5: batteryIcon = R.drawable.ic_battery_charging_wireless_50; break; + case 4: batteryIcon = R.drawable.ic_battery_charging_wireless_40; break; + case 3: batteryIcon = R.drawable.ic_battery_charging_wireless_30; break; + case 2: batteryIcon = R.drawable.ic_battery_charging_wireless_20; break; + case 1: batteryIcon = R.drawable.ic_battery_charging_wireless_10; break; + case 0: batteryIcon = R.drawable.ic_battery_alert_variant_outline; break; + default: batteryIcon = R.drawable.ic_battery_charging_wireless_outline; + } + + switch (iconNo) { + case 10: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_burnin; break; + case 9: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_90_burnin; break; + case 8: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_80_burnin; break; + case 7: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_70_burnin; break; + case 6: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_60_burnin; break; + case 5: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_50_burnin; break; + case 4: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_40_burnin; break; + case 3: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_30_burnin; break; + case 2: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_20_burnin; break; + case 1: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_10_burnin; break; + case 0: burnInBatteryIcon = R.drawable.ic_battery_alert_variant_outline; break; + default: burnInBatteryIcon = R.drawable.ic_battery_charging_wireless_outline; + } + + + } catch (NumberFormatException ex){ + Log.e(TAG, "Cannot parse battery level of: " + raw.sUploaderBattery); + } + } + + if (dataType == ComplicationData.TYPE_RANGED_VALUE) { + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE) + .setMinValue(0) + .setMaxValue(100) + .setValue(level) + .setShortText(ComplicationText.plainText(levelStr)) + .setIcon(Icon.createWithResource(this, batteryIcon)) + .setBurnInProtectionIcon(Icon.createWithResource(this, burnInBatteryIcon)) + .setTapAction(complicationPendingIntent); + complicationData = builder.build(); + } else if (dataType == ComplicationData.TYPE_SHORT_TEXT) { + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT) + .setShortText(ComplicationText.plainText(levelStr)) + .setIcon(Icon.createWithResource(this, batteryIcon)) + .setBurnInProtectionIcon(Icon.createWithResource(this, burnInBatteryIcon)) + .setTapAction(complicationPendingIntent); + complicationData = builder.build(); + } else if (dataType == ComplicationData.TYPE_ICON) { + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_ICON) + .setIcon(Icon.createWithResource(this, batteryIcon)) + .setBurnInProtectionIcon(Icon.createWithResource(this, burnInBatteryIcon)) + .setTapAction(complicationPendingIntent); + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } + + @Override + public String getProviderCanonicalName() { + return UploaderBattery.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.STATUS; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java new file mode 100644 index 0000000000..2448bebee0 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperComplication.java @@ -0,0 +1,62 @@ +package info.nightscout.androidaps.complications; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; +import android.support.wearable.complications.ComplicationData; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; + +import java.io.IOException; +import java.io.InputStream; + +import info.nightscout.androidaps.aaps; +import info.nightscout.androidaps.data.DisplayRawData; + +/* + * Created by dlvoy on 2019-11-12 + */ +public abstract class WallpaperComplication extends BaseComplicationProviderService { + + public abstract String getWallpaperAssetsFileName(); + + private static final String TAG = WallpaperComplication.class.getSimpleName(); + + public ComplicationData buildComplicationData(int dataType, DisplayRawData raw, PendingIntent complicationPendingIntent) { + + ComplicationData complicationData = null; + + if (dataType == ComplicationData.TYPE_LARGE_IMAGE) { + + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager windowManager = (WindowManager) aaps.getAppContext() + .getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getMetrics(metrics); + int width = metrics.widthPixels; + int height = metrics.heightPixels; + + final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_LARGE_IMAGE); + + AssetManager assetManager = getAssets(); + try (InputStream istr = assetManager.open(getWallpaperAssetsFileName())) { + Bitmap bitmap = BitmapFactory.decodeStream(istr); + Bitmap scaled = Bitmap.createScaledBitmap(bitmap, width, height, true); + builder.setLargeImage(Icon.createWithBitmap(scaled)); + } catch (IOException e) { + Log.e(TAG, "Cannot read wallpaper asset: "+e.getMessage(), e); + e.printStackTrace(); + } + + complicationData = builder.build(); + } else { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "Unexpected complication type " + dataType); + } + } + return complicationData; + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java new file mode 100644 index 0000000000..8c84e1d8c3 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperDarkComplication.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.complications; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class WallpaperDarkComplication extends WallpaperComplication { + + @Override + public String getWallpaperAssetsFileName() { + return "watch_dark.jpg"; + } + + @Override + public String getProviderCanonicalName() { + return WallpaperDarkComplication.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.NONE; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java new file mode 100644 index 0000000000..bec047f323 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperGrayComplication.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.complications; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class WallpaperGrayComplication extends WallpaperComplication { + + @Override + public String getWallpaperAssetsFileName() { + return "watch_gray.jpg"; + } + + @Override + public String getProviderCanonicalName() { + return WallpaperGrayComplication.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.NONE; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java new file mode 100644 index 0000000000..2d2bbf6f14 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/complications/WallpaperLightComplication.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.complications; + +/* + * Created by dlvoy on 2019-11-12 + */ +public class WallpaperLightComplication extends WallpaperComplication { + + @Override + public String getWallpaperAssetsFileName() { + return "watch_light.jpg"; + } + + @Override + public String getProviderCanonicalName() { + return WallpaperLightComplication.class.getCanonicalName(); + } + + @Override + public ComplicationAction getComplicationAction() { + return ComplicationAction.NONE; + }; +} diff --git a/wear/src/main/java/info/nightscout/androidaps/data/DisplayRawData.java b/wear/src/main/java/info/nightscout/androidaps/data/DisplayRawData.java new file mode 100644 index 0000000000..e9cf997db8 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/data/DisplayRawData.java @@ -0,0 +1,269 @@ +package info.nightscout.androidaps.data; + +import android.content.Intent; +import android.os.Bundle; +import android.os.PowerManager; + +import com.google.android.gms.wearable.DataMap; + +import java.util.ArrayList; + +import info.nightscout.androidaps.interaction.utils.Constants; +import info.nightscout.androidaps.interaction.utils.Persistence; +import info.nightscout.androidaps.interaction.utils.WearUtil; + +/** + * Holds bunch of data model variables and lists that arrive from phone app and are due to be + * displayed on watchface and complications. Keeping them together makes code cleaner and allows + * passing it to complications via persistence layer. + * + * Created by dlvoy on 2019-11-12 + */ +public class DisplayRawData { + + static final String DATA_PERSISTENCE_KEY = "raw_data"; + static final String BASALS_PERSISTENCE_KEY = "raw_basals"; + static final String STATUS_PERSISTENCE_KEY = "raw_status"; + + // data bundle + public long sgvLevel = 0; + public long datetime; + public String sSgv = "---"; + public String sDirection = "--"; + public String sDelta = "--"; + public String sAvgDelta = "--"; + public String sUnits = "-"; + + // status bundle + public String sBasalRate = "-.--U/h"; + public String sUploaderBattery = "--"; + public String sRigBattery = "--"; + public boolean detailedIOB = false; + public String sIOB1 = "IOB"; + public String sIOB2 = "-.--"; + public String sCOB1 = "Carb"; + public String sCOB2= "--g"; + public String sBgi = "--"; + public boolean showBGI = false; + public String externalStatusString = "no status"; + public int batteryLevel = 1; + public long openApsStatus = -1; + + // basals bundle + public ArrayList bgDataList = new ArrayList<>(); + public ArrayList tempWatchDataList = new ArrayList<>(); + public ArrayList basalWatchDataList = new ArrayList<>(); + public ArrayList bolusWatchDataList = new ArrayList<>(); + public ArrayList predictionList = new ArrayList<>(); + + public String toDebugString() { + return "DisplayRawData{" + + "sgvLevel=" + sgvLevel + + ", datetime=" + datetime + + ", sSgv='" + sSgv + '\'' + + ", sDirection='" + sDirection + '\'' + + ", sDelta='" + sDelta + '\'' + + ", sAvgDelta='" + sAvgDelta + '\'' + + ", sUnits='" + sUnits + '\'' + + ", sBasalRate='" + sBasalRate + '\'' + + ", sUploaderBattery='" + sUploaderBattery + '\'' + + ", sRigBattery='" + sRigBattery + '\'' + + ", detailedIOB=" + detailedIOB + + ", sIOB1='" + sIOB1 + '\'' + + ", sIOB2='" + sIOB2 + '\'' + + ", sCOB1='" + sCOB1 + '\'' + + ", sCOB2='" + sCOB2 + '\'' + + ", sBgi='" + sBgi + '\'' + + ", showBGI=" + showBGI + + ", externalStatusString='" + externalStatusString + '\'' + + ", batteryLevel=" + batteryLevel + + ", openApsStatus=" + openApsStatus + + ", bgDataList size=" + bgDataList.size() + + ", tempWatchDataList size=" + tempWatchDataList.size() + + ", basalWatchDataList size=" + basalWatchDataList.size() + + ", bolusWatchDataLis size=" + bolusWatchDataList.size() + + ", predictionList size=" + predictionList.size() + + '}'; + } + + public void updateFromPersistence(Persistence persistence) { + + DataMap dataMapData = persistence.getDataMap(DATA_PERSISTENCE_KEY); + if (dataMapData != null) { + updateData(dataMapData); + } + DataMap dataMapStatus = persistence.getDataMap(STATUS_PERSISTENCE_KEY); + if (dataMapStatus != null) { + updateStatus(dataMapStatus); + } + DataMap dataMapBasals = persistence.getDataMap(BASALS_PERSISTENCE_KEY); + if (dataMapBasals != null) { + updateBasals(dataMapBasals); + } + } + + /* + * Since complications do not need Basals, we skip them for performance + */ + public void partialUpdateFromPersistence(Persistence persistence) { + + DataMap dataMapData = persistence.getDataMap(DATA_PERSISTENCE_KEY); + if (dataMapData != null) { + updateData(dataMapData); + } + DataMap dataMapStatus = persistence.getDataMap(STATUS_PERSISTENCE_KEY); + if (dataMapStatus != null) { + updateStatus(dataMapStatus); + } + } + + public DataMap updateDataFromMessage(Intent intent, PowerManager.WakeLock wakeLock) { + Bundle bundle = intent.getBundleExtra("data"); + if (bundle != null) { + DataMap dataMap = DataMap.fromBundle(bundle); + updateData(dataMap); + return dataMap; + } + return null; + } + + private void updateData(DataMap dataMap) { + WearUtil.getWakeLock("readingPrefs", 50); + sgvLevel = dataMap.getLong("sgvLevel"); + datetime = dataMap.getLong("timestamp"); + sSgv = dataMap.getString("sgvString"); + sDirection = dataMap.getString("slopeArrow"); + sDelta = dataMap.getString("delta"); + sAvgDelta = dataMap.getString("avgDelta"); + sUnits = dataMap.getString("glucoseUnits"); + } + + public DataMap updateStatusFromMessage(Intent intent, PowerManager.WakeLock wakeLock) { + Bundle bundle = intent.getBundleExtra("status"); + if (bundle != null) { + DataMap dataMap = DataMap.fromBundle(bundle); + updateStatus(dataMap); + return dataMap; + } + return null; + } + + private void updateStatus(DataMap dataMap) { + WearUtil.getWakeLock("readingPrefs", 50); + sBasalRate = dataMap.getString("currentBasal"); + sUploaderBattery = dataMap.getString("battery"); + sRigBattery = dataMap.getString("rigBattery"); + detailedIOB = dataMap.getBoolean("detailedIob"); + sIOB1 = dataMap.getString("iobSum") + "U"; + sIOB2 = dataMap.getString("iobDetail"); + sCOB1 = "Carb"; + sCOB2 = dataMap.getString("cob"); + sBgi = dataMap.getString("bgi"); + showBGI = dataMap.getBoolean("showBgi"); + externalStatusString = dataMap.getString("externalStatusString"); + batteryLevel = dataMap.getInt("batteryLevel"); + openApsStatus = dataMap.getLong("openApsStatus"); + } + + public DataMap updateBasalsFromMessage(Intent intent, PowerManager.WakeLock wakeLock) { + Bundle bundle = intent.getBundleExtra("basals"); + if (bundle != null) { + DataMap dataMap = DataMap.fromBundle(bundle); + updateBasals(dataMap); + return dataMap; + } + return null; + } + + private void updateBasals(DataMap dataMap) { + WearUtil.getWakeLock("readingPrefs", 500); + loadBasalsAndTemps(dataMap); + } + + private void loadBasalsAndTemps(DataMap dataMap) { + ArrayList temps = dataMap.getDataMapArrayList("temps"); + if (temps != null) { + tempWatchDataList = new ArrayList<>(); + for (DataMap temp : temps) { + TempWatchData twd = new TempWatchData(); + twd.startTime = temp.getLong("starttime"); + twd.startBasal = temp.getDouble("startBasal"); + twd.endTime = temp.getLong("endtime"); + twd.endBasal = temp.getDouble("endbasal"); + twd.amount = temp.getDouble("amount"); + tempWatchDataList.add(twd); + } + } + ArrayList basals = dataMap.getDataMapArrayList("basals"); + if (basals != null) { + basalWatchDataList = new ArrayList<>(); + for (DataMap basal : basals) { + BasalWatchData bwd = new BasalWatchData(); + bwd.startTime = basal.getLong("starttime"); + bwd.endTime = basal.getLong("endtime"); + bwd.amount = basal.getDouble("amount"); + basalWatchDataList.add(bwd); + } + } + ArrayList boluses = dataMap.getDataMapArrayList("boluses"); + if (boluses != null) { + bolusWatchDataList = new ArrayList<>(); + for (DataMap bolus : boluses) { + BolusWatchData bwd = new BolusWatchData(); + bwd.date = bolus.getLong("date"); + bwd.bolus = bolus.getDouble("bolus"); + bwd.carbs = bolus.getDouble("carbs"); + bwd.isSMB = bolus.getBoolean("isSMB"); + bwd.isValid = bolus.getBoolean("isValid"); + bolusWatchDataList.add(bwd); + } + } + ArrayList predictions = dataMap.getDataMapArrayList("predictions"); + if (boluses != null) { + predictionList = new ArrayList<>(); + for (DataMap prediction : predictions) { + BgWatchData bwd = new BgWatchData(); + bwd.timestamp = prediction.getLong("timestamp"); + bwd.sgv = prediction.getDouble("sgv"); + bwd.color = prediction.getInt("color"); + predictionList.add(bwd); + } + } + } + + public void addToWatchSet(DataMap dataMap) { + ArrayList entries = dataMap.getDataMapArrayList("entries"); + if (entries != null) { + bgDataList = new ArrayList<>(); + for (DataMap entry : entries) { + double sgv = entry.getDouble("sgvDouble"); + double high = entry.getDouble("high"); + double low = entry.getDouble("low"); + long timestamp = entry.getLong("timestamp"); + int color = entry.getInt("color", 0); + bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color)); + } + } else { + double sgv = dataMap.getDouble("sgvDouble"); + double high = dataMap.getDouble("high"); + double low = dataMap.getDouble("low"); + long timestamp = dataMap.getLong("timestamp"); + int color = dataMap.getInt("color", 0); + + final int size = bgDataList.size(); + if (size > 0) { + if (bgDataList.get(size - 1).timestamp == timestamp) + return; // Ignore duplicates. + } + + bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color)); + } + + for (int i = 0; i < bgDataList.size(); i++) { + if (bgDataList.get(i).timestamp < (System.currentTimeMillis() - (Constants.HOUR_IN_MS * 5))) { + bgDataList.remove(i); //Get rid of anything more than 5 hours old + break; + } + } + } +} 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 83c9ad58b3..7bd43a79e1 100644 --- a/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java +++ b/wear/src/main/java/info/nightscout/androidaps/data/ListenerService.java @@ -40,6 +40,7 @@ import info.nightscout.androidaps.interaction.AAPSPreferences; import info.nightscout.androidaps.R; 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.SafeParse; import info.nightscout.androidaps.interaction.utils.WearUtil; @@ -512,12 +513,14 @@ public class ListenerService extends WearableListenerService implements GoogleAp Intent messageIntent = new Intent(); messageIntent.setAction(Intent.ACTION_SEND); messageIntent.putExtra("status", dataMap.toBundle()); + Persistence.storeDataMap(DisplayRawData.STATUS_PERSISTENCE_KEY, dataMap); LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); } else if (path.equals(BASAL_DATA_PATH)){ dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap(); Intent messageIntent = new Intent(); messageIntent.setAction(Intent.ACTION_SEND); messageIntent.putExtra("basals", dataMap.toBundle()); + Persistence.storeDataMap(DisplayRawData.BASALS_PERSISTENCE_KEY, dataMap); LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); } else if (path.equals(NEW_PREFERENCES_PATH)){ dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap(); @@ -541,6 +544,7 @@ public class ListenerService extends WearableListenerService implements GoogleAp Intent messageIntent = new Intent(); messageIntent.setAction(Intent.ACTION_SEND); messageIntent.putExtra("data", dataMap.toBundle()); + Persistence.storeDataMap(DisplayRawData.DATA_PERSISTENCE_KEY, dataMap); LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent); } } diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java new file mode 100644 index 0000000000..2af3ef34b6 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.interaction.utils; + +public class Constants { + + public static final long SECOND_IN_MS = 1000; + public static final long MINUTE_IN_MS = 60000; + public static final long HOUR_IN_MS = 3600000; + public static final long DAY_IN_MS = 86400000; + public static final long WEEK_IN_MS = DAY_IN_MS * 7; + public static final long MONTH_IN_MS = DAY_IN_MS * 30; + + public static final long STALE_MS = Constants.MINUTE_IN_MS * 12; + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java new file mode 100644 index 0000000000..366ca28eed --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java @@ -0,0 +1,129 @@ +package info.nightscout.androidaps.interaction.utils; + +import android.util.Pair; + +import info.nightscout.androidaps.aaps; +import info.nightscout.androidaps.data.DisplayRawData; + +public class DisplayFormat { + + /** + * Maximal lengths of fields/labels shown in complications + */ + public static final int MAX_LONG_FIELD = 22; // this is empirical, above that many watch faces start to ellipsize + public static final int MAX_SHORT_FIELD = 7; // according to Wear OS docs for TYPE_SHORT_TEXT + public static final int MIN_COB_FIELD = 3; // since carbs are 0..99g + public static final int MIN_IOB_FIELD = 3; // IoB can range from like .1U to 99U + + public static String deltaSymbol() { + return aaps.areComplicationsUnicode() ? "\u0394" : ""; + } + + public static String verticalSeparatorSymbol() { + return aaps.areComplicationsUnicode() ? "\u205E" : "|"; + } + + public static String basalRateSymbol() { + return aaps.areComplicationsUnicode() ? "\u238D\u2006" : ""; + } + + public static String shortTimeSince(final long refTime) { + + long deltaTimeMs = WearUtil.msSince(refTime); + + if (deltaTimeMs < Constants.MINUTE_IN_MS) { + return "0'"; + } else if (deltaTimeMs < Constants.HOUR_IN_MS) { + int minutes = (int) (deltaTimeMs / Constants.MINUTE_IN_MS); + return minutes + "'"; + } else if (deltaTimeMs < Constants.DAY_IN_MS) { + int hours = (int) (deltaTimeMs / Constants.HOUR_IN_MS); + return hours + "h"; + } else { + int days = (int) (deltaTimeMs / Constants.DAY_IN_MS); + if (days < 7) { + return days + "d"; + } else { + int weeks = days / 7; + return weeks + "d"; + } + } + } + + public static String shortTrend(final DisplayRawData raw) { + String minutes = shortTimeSince(raw.datetime); + String delta = (new SmallestDoubleString(raw.sDelta)).minimise(MAX_SHORT_FIELD-1); + + if (minutes.length() + delta.length() + 1 < MAX_SHORT_FIELD) { + delta = deltaSymbol() + delta; + } + + return minutes + " " + delta; + } + + public static String longGlucoseLine(final DisplayRawData raw) { + return raw.sSgv + raw.sDirection + " " + deltaSymbol() + (new SmallestDoubleString(raw.sDelta)).minimise(8) + " (" + shortTimeSince(raw.datetime) + ")"; + } + + public static String longDetailsLine(final DisplayRawData raw) { + + final String SEP_LONG = " " + verticalSeparatorSymbol() + " "; + final String SEP_SHORT = " " + verticalSeparatorSymbol() + " "; + final int SEP_SHORT_LEN = SEP_SHORT.length(); + final String SEP_MIN = " "; + + String line = raw.sCOB2 + SEP_LONG + raw.sIOB1 + SEP_LONG + basalRateSymbol()+raw.sBasalRate; + if (line.length() <= MAX_LONG_FIELD) { + return line; + } + line = raw.sCOB2 + SEP_SHORT + raw.sIOB1 + SEP_SHORT + raw.sBasalRate; + if (line.length() <= MAX_LONG_FIELD) { + return line; + } + + int remainingMax = MAX_LONG_FIELD - (raw.sCOB2.length() + raw.sBasalRate.length() + SEP_SHORT_LEN*2); + final String smallestIoB = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_IOB_FIELD, remainingMax)); + line = raw.sCOB2 + SEP_SHORT + smallestIoB + SEP_SHORT + raw.sBasalRate; + if (line.length() <= MAX_LONG_FIELD) { + return line; + } + + remainingMax = MAX_LONG_FIELD - (smallestIoB.length() + raw.sBasalRate.length() + SEP_SHORT_LEN*2); + final String simplifiedCob = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_COB_FIELD, remainingMax)); + + line = simplifiedCob + SEP_SHORT + smallestIoB + SEP_SHORT + raw.sBasalRate; + if (line.length() <= MAX_LONG_FIELD) { + return line; + } + + line = simplifiedCob + SEP_MIN + smallestIoB + SEP_MIN + raw.sBasalRate; + + return line; + } + + public static Pair detailedIob(DisplayRawData raw) { + final String iob1 = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_SHORT_FIELD); + String iob2 = ""; + if (raw.sIOB2.contains("|")) { + + String[] iobs = raw.sIOB2.replace("(", "").replace(")", "").split("\\|"); + if (iobs.length == 2) { + final String iobBolus = new SmallestDoubleString(iobs[0]).minimise(MIN_IOB_FIELD); + final String iobBasal = new SmallestDoubleString(iobs[1]).minimise((MAX_SHORT_FIELD-1) - Math.max(MIN_IOB_FIELD, iobBolus.length())); + iob2 = iobBolus+" "+iobBasal; + } + } + return Pair.create(iob1, iob2); + } + + public static Pair detailedCob(final DisplayRawData raw) { + SmallestDoubleString cobMini = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE); + + String cob2 = ""; + if (cobMini.getExtra().length() > 0) { + cob2 = cobMini.getExtra() + cobMini.getUnits(); + } + final String cob1 = cobMini.minimise(MAX_SHORT_FIELD); + return Pair.create(cob1, cob2); + } +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java new file mode 100644 index 0000000000..21944da9c7 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java @@ -0,0 +1,117 @@ +package info.nightscout.androidaps.interaction.utils; + +import android.os.PowerManager; +import android.util.Log; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created for xDrip by jamorham on 07/03/2018 + * Adapted for AAPS by dlvoy on 2019-11-11 + * + * Tasks which are fired from events can be scheduled here and only execute when they become idle + * and are not being rescheduled within their wait window. + * + */ + +public class Inevitable { + + private static final String TAG = Inevitable.class.getSimpleName(); + private static final int MAX_QUEUE_TIME = (int) Constants.MINUTE_IN_MS * 6; + private static final boolean d = true; + + private static final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); + + public static synchronized void task(final String id, long idle_for, Runnable runnable) { + if (idle_for > MAX_QUEUE_TIME) { + throw new RuntimeException(id + " Requested time: " + idle_for + " beyond max queue time"); + } + final Task task = tasks.get(id); + if (task != null) { + // if it already exists then extend the time + task.extendTime(idle_for); + + if (d) + Log.d(TAG, "Extending time for: " + id + " to " + WearUtil.dateTimeText(task.when)); + } else { + // otherwise create new task + if (runnable == null) return; // extension only if already exists + tasks.put(id, new Task(id, idle_for, runnable)); + + if (d) { + Log.d(TAG, "Creating task: " + id + " due: " + WearUtil.dateTimeText(tasks.get(id).when)); + } + + // create a thread to wait and execute in background + final Thread t = new Thread(() -> { + final PowerManager.WakeLock wl = WearUtil.getWakeLock(id, MAX_QUEUE_TIME + 5000); + try { + boolean running = true; + // wait for task to be due or killed + while (running) { + WearUtil.threadSleep(500); + final Task thisTask = tasks.get(id); + running = thisTask != null && !thisTask.poll(); + } + } finally { + WearUtil.releaseWakeLock(wl); + } + }); + t.setPriority(Thread.MIN_PRIORITY); + //t.setDaemon(true); + t.start(); + } + } + + public static synchronized void stackableTask(String id, long idle_for, Runnable runnable) { + int stack = 0; + while (tasks.get(id = id + "-" + stack) != null) { + stack++; + } + if (stack > 0) { + Log.d(TAG, "Task stacked to: " + id); + } + task(id, idle_for, runnable); + } + + public static void kill(final String id) { + tasks.remove(id); + } + + public static boolean waiting(final String id) { + return tasks.containsKey(id); + } + + private static class Task { + private long when; + private final Runnable what; + private final String id; + + Task(String id, long offset, Runnable what) { + this.what = what; + this.id = id; + extendTime(offset); + } + + public void extendTime(long offset) { + this.when = WearUtil.timestamp() + offset; + } + + public boolean poll() { + final long till = WearUtil.msTill(when); + if (till < 1) { + if (d) Log.d(TAG, "Executing task! " + this.id); + tasks.remove(this.id); // early remove to allow overlapping scheduling + what.run(); + return true; + } else if (till > MAX_QUEUE_TIME) { + Log.wtf(TAG, "Task: " + this.id + " In queue too long: " + till); + tasks.remove(this.id); + return true; + } + return false; + } + + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java new file mode 100644 index 0000000000..805aa5aafa --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Persistence.java @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.interaction.utils; + +import android.content.SharedPreferences; +import android.util.Base64; + +import com.google.android.gms.wearable.DataMap; + +import java.util.Set; + +import info.nightscout.androidaps.aaps; + +/** + * Created by dlvoy on 2019-11-12 + */ +public class Persistence { + + final SharedPreferences preferences; + public static final String COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY = + "info.nightscout.androidaps.complications.COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY"; + + public Persistence() { + preferences = aaps.getAppContext().getSharedPreferences(COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY, 0); + } + + public DataMap getDataMap(String key) { + if (preferences.contains("raw_data")) { + final String rawB64Data = preferences.getString(key, null); + byte[] rawData = Base64.decode(rawB64Data, Base64.DEFAULT); + try { + DataMap dataMap = DataMap.fromByteArray(rawData); + return dataMap; + } catch (IllegalArgumentException ex) { + + } + } + return null; + } + + public void putDataMap(String key, DataMap dataMap) { + preferences.edit().putString(key, Base64.encodeToString(dataMap.toByteArray(), Base64.DEFAULT)).apply(); + } + + public String getString(String key, String defaultValue) { + return preferences.getString(key, defaultValue); + } + + public void putString(String key, String value) { + preferences.edit().putString(key, value).apply(); + } + + public boolean getBoolean(String key, boolean defaultValue) { + return preferences.getBoolean(key, defaultValue); + } + + public void putBoolean(String key, boolean value) { + preferences.edit().putBoolean(key, value).apply(); + } + + public long whenDataUpdated() { + return preferences.getLong("data_updated_at", 0); + } + + private void markDataUpdated() { + preferences.edit().putLong("data_updated_at", WearUtil.timestamp()).apply(); + } + + public Set getSetOf(String key) { + return WearUtil.explodeSet(getString(key, ""), "|"); + } + + public void addToSet(String key, String value) { + final Set set = WearUtil.explodeSet(getString(key, ""), "|"); + set.add(value); + putString(key, WearUtil.joinSet(set, "|")); + } + + public void removeFromSet(String key, String value) { + final Set set = WearUtil.explodeSet(getString(key, ""), "|"); + set.remove(value); + putString(key, WearUtil.joinSet(set, "|")); + } + + public static void storeDataMap(String key, DataMap dataMap) { + Persistence p = new Persistence(); + p.putDataMap(key, dataMap); + p.markDataUpdated(); + } + + public static Set setOf(String key) { + Persistence p = new Persistence(); + return p.getSetOf(key); + } + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java new file mode 100644 index 0000000000..5202daf8e4 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/SmallestDoubleString.java @@ -0,0 +1,130 @@ +package info.nightscout.androidaps.interaction.utils; + +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Helper to minimise various floating point values, with or without unit, to fit into specified + * and limited size, scarifying precision (rounding up) and extra characters like leading zero, + * following zero(s) in fractional part, extra plus sign etc. + * + * Created by dlvoy on 2019-11-12 + */ +public class SmallestDoubleString { + + private String sign = ""; + private String decimal = ""; + private String separator = ""; + private String fractional = ""; + private String extra = ""; + private String units = ""; + + private final Units withUnits; + + public enum Units { + SKIP, + USE + } + + private static Pattern pattern = Pattern.compile("^([+-]?)([0-9]*)([,.]?)([0-9]*)(\\([^)]*\\))?(.*?)$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE ); + + public SmallestDoubleString(String inputString) { + this(inputString, Units.SKIP); + } + + public SmallestDoubleString(String inputString, Units withUnits) { + Matcher matcher = pattern.matcher(inputString); + matcher.matches(); + + sign = matcher.group(1); + decimal = matcher.group(2); + separator = matcher.group(3); + fractional = matcher.group(4); + units = matcher.group(6); + + if (fractional == null || fractional.length() == 0) { + separator = ""; + fractional = ""; + } + if (decimal == null || decimal.length() == 0) { + decimal = ""; + } + if (separator == null || separator.length() == 0) { + separator = ""; + } + if (sign == null || sign.length() == 0) { + sign = ""; + } + + final String extraCandidate = matcher.group(5); + if (extraCandidate != null && extraCandidate.length() > 2) { + extra = extraCandidate.substring(1, extraCandidate.length()-1); + } + + if (units != null) { + units = units.trim(); + } + + this.withUnits = withUnits; + } + + public String minimise(int maxSize) { + if (Integer.parseInt("0"+fractional) == 0) { + separator = ""; + fractional = ""; + } + if (Integer.parseInt("0"+decimal) == 0 && (fractional.length() >0)) { + decimal = ""; + } + if (currentLen() <= maxSize) + return toString(); + + if (sign.equals("+")) { + sign = ""; + } + if (currentLen() <= maxSize) { + return toString(); + } + + while ((fractional.length() > 1)&&(fractional.charAt(fractional.length()-1) == '0')) { + fractional = fractional.substring(0, fractional.length()-1); + } + if (currentLen() <= maxSize) { + return toString(); + } + + if ((fractional.length() > 0)&&(decimal.length() > 0)) { + int remainingForFraction = maxSize-currentLen()+fractional.length(); + String formatCandidate = "#"; + if (remainingForFraction>=1) { + formatCandidate = "#."+("#######".substring(0, remainingForFraction)); + } + DecimalFormat df = new DecimalFormat(formatCandidate); + df.setRoundingMode(RoundingMode.HALF_UP); + + return sign + df.format(Double.parseDouble(decimal+"."+fractional)).replace(".", separator) + + ((withUnits == Units.USE) ? units : ""); + } + return toString(); + } + + private int currentLen() { + return sign.length() + decimal.length() + separator.length() + fractional.length() + + ((withUnits == Units.USE) ? units.length() : 0); + } + + public String toString() { + return sign+decimal+separator+fractional + + ((withUnits == Units.USE) ? units : ""); + } + + public String getExtra() { + return extra; + } + + public String getUnits() { return units; } + + +} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java index ca63749888..2a400b4fa0 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java @@ -1,19 +1,127 @@ package info.nightscout.androidaps.interaction.utils; -import java.time.LocalDateTime; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.util.Log; + import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import info.nightscout.androidaps.aaps; +import info.nightscout.androidaps.data.DisplayRawData; /** * Created by andy on 3/5/19. + * Adapted by dlvoy on 2019-11-06 using code from jamorham JoH class */ public class WearUtil { + private final static boolean debug_wakelocks = false; + private static final Map rateLimits = new HashMap(); + private static final String TAG = WearUtil.class.getName(); + + //============================================================================================== + // Time related util methods + //============================================================================================== public static String dateTimeText(long timeInMs) { Date d = new Date(timeInMs); return "" + d.getDay() + "." + d.getMonth() + "." + d.getYear() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); } + public static long timestamp() { + return System.currentTimeMillis(); + } + public static long msSince(long when) { + return (timestamp() - when); + } + + public static long msTill(long when) { + return (when - timestamp()); + } + + //============================================================================================== + // Thread and power management utils + //============================================================================================== + + // return true if below rate limit + public static synchronized boolean rateLimit(String name, int seconds) { + // check if over limit + if ((rateLimits.containsKey(name)) && (timestamp() - rateLimits.get(name) < (seconds * 1000))) { + Log.d(TAG, name + " rate limited: " + seconds + " seconds"); + return false; + } + // not over limit + rateLimits.put(name, timestamp()); + return true; + } + + public static PowerManager.WakeLock getWakeLock(final String name, int millis) { + final PowerManager pm = (PowerManager) aaps.getAppContext().getSystemService(Context.POWER_SERVICE); + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AAPS::"+name); + wl.acquire(millis); + if (debug_wakelocks) Log.d(TAG, "getWakeLock: " + name + " " + wl.toString()); + return wl; + } + + public static void releaseWakeLock(PowerManager.WakeLock wl) { + if (debug_wakelocks) Log.d(TAG, "releaseWakeLock: " + wl.toString()); + if (wl == null) return; + if (wl.isHeld()) wl.release(); + } + + public static void startActivity(Class c) { + aaps.getAppContext().startActivity(getStartActivityIntent(c)); + } + + + public static Intent getStartActivityIntent(Class c) { + return new Intent(aaps.getAppContext(), c).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + + + + public static void threadSleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + // + } + } + + public static String joinSet(Set set, String separator) { + StringBuilder sb = new StringBuilder(); + int i = 0; + for (String item : set) { + final String itemToAdd = item.trim(); + if (itemToAdd.length() > 0) { + if (i > 0) { + sb.append(separator); + } + i++; + sb.append(itemToAdd); + } + } + return sb.toString(); + } + + public static Set explodeSet(String joined, String separator) { + // special RegEx literal \\Q starts sequence we escape, \\E ends is + // we use it to escape separator for use in RegEx + String[] items = joined.split("\\Q"+separator+"\\E"); + Set set = new HashSet<>(); + for (String item : items) { + final String itemToAdd = item.trim(); + if (itemToAdd.length() > 0) { + set.add(itemToAdd); + } + } + return set; + } } diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java index 229eb64f1b..222f1eda6e 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BaseWatchFace.java @@ -10,7 +10,6 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; -import android.os.Bundle; import android.os.PowerManager; import android.preference.PreferenceManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -33,20 +32,18 @@ import com.ustwo.clockwise.common.WatchFaceTime; import com.ustwo.clockwise.common.WatchShape; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; -import info.nightscout.androidaps.data.BasalWatchData; -import info.nightscout.androidaps.data.BgWatchData; -import info.nightscout.androidaps.data.BolusWatchData; +import info.nightscout.androidaps.complications.BaseComplicationProviderService; +import info.nightscout.androidaps.data.DisplayRawData; import info.nightscout.androidaps.data.ListenerService; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.TempWatchData; import lecho.lib.hellocharts.view.LineChartView; /** * Created by emmablack on 12/29/14. * Updated by andrew-warrington on 02-Jan-2018. + * Refactored by dlvoy on 2019-11-2019 */ public abstract class BaseWatchFace extends WatchFace implements SharedPreferences.OnSharedPreferenceChangeListener { @@ -54,13 +51,10 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen public static final long[] vibratePattern = {0,400,300,400,300,400}; public TextView mTime, mSgv, mDirection, mTimestamp, mUploaderBattery, mRigBattery, mDelta, mAvgDelta, mStatus, mBasalRate, mIOB1, mIOB2, mCOB1, mCOB2, mBgi, mLoop, mDay, mMonth, isAAPSv2, mHighLight, mLowLight; public ImageView mGlucoseDial, mDeltaGauge, mHourHand, mMinuteHand; - public long datetime; public RelativeLayout mRelativeLayout; public LinearLayout mLinearLayout, mLinearLayout2, mDate, mChartTap, mMainMenuTap; - public long sgvLevel = 0; public int ageLevel = 1; public int loopLevel = 1; - public int batteryLevel = 1; public int highColor = Color.YELLOW; public int lowColor = Color.RED; public int midColor = Color.WHITE; @@ -74,11 +68,9 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen public int pointSize = 2; public BgGraphBuilder bgGraphBuilder; public LineChartView chart; - public ArrayList bgDataList = new ArrayList<>(); - public ArrayList tempWatchDataList = new ArrayList<>(); - public ArrayList basalWatchDataList = new ArrayList<>(); - public ArrayList bolusWatchDataList = new ArrayList<>(); - public ArrayList predictionList = new ArrayList<>(); + + + public DisplayRawData rawData = new DisplayRawData(); public PowerManager.WakeLock wakeLock; // related endTime manual layout @@ -90,26 +82,9 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen protected SharedPreferences sharedPrefs; - public boolean detailedIOB = false; - public boolean showBGI = false; public boolean forceSquareCanvas = false; //set to true by the Steampunk watch face. - public long openApsStatus; - public String externalStatusString = "no status"; - public String sSgv = "---"; - public String sDirection = "--"; - public String sUploaderBattery = "--"; - public String sRigBattery = "--"; - public String sDelta = "--"; - public String sAvgDelta = "--"; - public String sBasalRate = "-.--U/h"; - public String sIOB1 = "IOB"; - public String sIOB2 = "-.--"; - public String sCOB1 = "Carb"; - public String sCOB2 = "--g"; - public String sBgi = "--"; public String sMinute = "0"; public String sHour = "0"; - public String sUnits = "-"; @Override public void onCreate() { @@ -126,6 +101,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); sharedPrefs.registerOnSharedPreferenceChangeListener(this); + + BaseComplicationProviderService.turnOff(); } @Override @@ -197,11 +174,11 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } public double timeSince() { - return System.currentTimeMillis() - datetime; + return System.currentTimeMillis() - rawData.datetime; } public String readingAge(boolean shortString) { - if (datetime == 0) { return shortString?"--'":"-- Minute ago"; } + if (rawData.datetime == 0) { return shortString?"--'":"-- Minute ago"; } int minutesAgo = (int) Math.floor(timeSince()/(1000*60)); if (minutesAgo == 1) { return minutesAgo + (shortString?"'":" Minute ago"); @@ -266,50 +243,20 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen @Override public void onReceive(Context context, Intent intent) { - Bundle bundle = intent.getBundleExtra("data"); - if (layoutSet && bundle != null) { - DataMap dataMap = DataMap.fromBundle(bundle); - wakeLock.acquire(50); - sgvLevel = dataMap.getLong("sgvLevel"); - datetime = dataMap.getLong("timestamp"); - sSgv = dataMap.getString("sgvString"); - sDirection = dataMap.getString("slopeArrow"); - sDelta = dataMap.getString("delta"); - sAvgDelta = dataMap.getString("avgDelta"); - sUnits = dataMap.getString("glucoseUnits"); - if (chart != null) { - addToWatchSet(dataMap); + if (layoutSet) { + final DataMap dataMap = rawData.updateDataFromMessage(intent, wakeLock); + if (chart != null && dataMap != null) { + rawData.addToWatchSet(dataMap); setupCharts(); } - } - - bundle = intent.getBundleExtra("status"); - if (layoutSet && bundle != null) { - DataMap dataMap = DataMap.fromBundle(bundle); - wakeLock.acquire(50); - sBasalRate = dataMap.getString("currentBasal"); - sUploaderBattery = dataMap.getString("battery"); - sRigBattery = dataMap.getString("rigBattery"); - detailedIOB = dataMap.getBoolean("detailedIob"); - sIOB1 = dataMap.getString("iobSum") + "U"; - sIOB2 = dataMap.getString("iobDetail"); - sCOB1 = "Carb"; - sCOB2 = dataMap.getString("cob"); - sBgi = dataMap.getString("bgi"); - showBGI = dataMap.getBoolean("showBgi"); - externalStatusString = dataMap.getString("externalStatusString"); - batteryLevel = dataMap.getInt("batteryLevel"); - openApsStatus = dataMap.getLong("openApsStatus"); + rawData.updateStatusFromMessage(intent, wakeLock); } setDataFields(); setColor(); - bundle = intent.getBundleExtra("basals"); - if (layoutSet && bundle != null) { - DataMap dataMap = DataMap.fromBundle(bundle); - wakeLock.acquire(500); - loadBasalsAndTemps(dataMap); + if (layoutSet) { + rawData.updateBasalsFromMessage(intent, wakeLock); } mRelativeLayout.measure(specW, specH); @@ -328,7 +275,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mSgv != null) { if (sharedPrefs.getBoolean("showBG", true)) { - mSgv.setText(sSgv); + mSgv.setText(rawData.sSgv); mSgv.setVisibility(View.VISIBLE); } else { //leave the textview there but invisible, as a height holder for the empty space above the white line @@ -341,7 +288,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mDirection != null) { if (sharedPrefs.getBoolean("show_direction", true)) { - mDirection.setText(sDirection); + mDirection.setText(rawData.sDirection); mDirection.setVisibility(View.VISIBLE); } else { mDirection.setVisibility(View.GONE); @@ -350,7 +297,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mDelta != null) { if (sharedPrefs.getBoolean("showDelta", true)) { - mDelta.setText(sDelta); + mDelta.setText(rawData.sDelta); mDelta.setVisibility(View.VISIBLE); } else { mDelta.setVisibility(View.GONE); @@ -359,7 +306,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mAvgDelta != null) { if (sharedPrefs.getBoolean("showAvgDelta", true)) { - mAvgDelta.setText(sAvgDelta); + mAvgDelta.setText(rawData.sAvgDelta); mAvgDelta.setVisibility(View.VISIBLE); } else { mAvgDelta.setVisibility(View.GONE); @@ -367,7 +314,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } if (mCOB1 != null && mCOB2 != null) { - mCOB2.setText(sCOB2); + mCOB2.setText(rawData.sCOB2); if (sharedPrefs.getBoolean("show_cob", true)) { mCOB1.setVisibility(View.VISIBLE); mCOB2.setVisibility(View.VISIBLE); @@ -377,7 +324,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } //deal with cases where there is only the value shown for COB, and not the label } else if (mCOB2 != null) { - mCOB2.setText(sCOB2); + mCOB2.setText(rawData.sCOB2); if (sharedPrefs.getBoolean("show_cob", true)) { mCOB2.setVisibility(View.VISIBLE); } else { @@ -389,12 +336,12 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (sharedPrefs.getBoolean("show_iob", true)) { mIOB1.setVisibility(View.VISIBLE); mIOB2.setVisibility(View.VISIBLE); - if (detailedIOB) { - mIOB1.setText(sIOB1); - mIOB2.setText(sIOB2); + if (rawData.detailedIOB) { + mIOB1.setText(rawData.sIOB1); + mIOB2.setText(rawData.sIOB2); } else { mIOB1.setText("IOB"); - mIOB2.setText(sIOB1); + mIOB2.setText(rawData.sIOB1); } } else { mIOB1.setVisibility(View.GONE); @@ -404,10 +351,10 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } else if (mIOB2 != null) { if (sharedPrefs.getBoolean("show_iob", true)) { mIOB2.setVisibility(View.VISIBLE); - if (detailedIOB) { - mIOB2.setText(sIOB2); + if (rawData.detailedIOB) { + mIOB2.setText(rawData.sIOB2); } else { - mIOB2.setText(sIOB1); + mIOB2.setText(rawData.sIOB1); } } else { mIOB2.setText(""); @@ -434,13 +381,13 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mUploaderBattery != null) { if (sharedPrefs.getBoolean("show_uploader_battery", true)) { if (isAAPSv2 != null) { - mUploaderBattery.setText(sUploaderBattery + "%"); + mUploaderBattery.setText(rawData.sUploaderBattery + "%"); mUploaderBattery.setVisibility(View.VISIBLE); } else { if (sharedPrefs.getBoolean("showExternalStatus", true)) { - mUploaderBattery.setText("U: " + sUploaderBattery + "%"); + mUploaderBattery.setText("U: " + rawData.sUploaderBattery + "%"); } else { - mUploaderBattery.setText("Uploader: " + sUploaderBattery + "%"); + mUploaderBattery.setText("Uploader: " + rawData.sUploaderBattery + "%"); } } } else { @@ -450,7 +397,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mRigBattery != null) { if (sharedPrefs.getBoolean("show_rig_battery", false)) { - mRigBattery.setText(sRigBattery); + mRigBattery.setText(rawData.sRigBattery); mRigBattery.setVisibility(View.VISIBLE); } else { mRigBattery.setVisibility(View.GONE); @@ -459,7 +406,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mBasalRate != null) { if (sharedPrefs.getBoolean("show_temp_basal", true)) { - mBasalRate.setText(sBasalRate); + mBasalRate.setText(rawData.sBasalRate); mBasalRate.setVisibility(View.VISIBLE); } else { mBasalRate.setVisibility(View.GONE); @@ -467,8 +414,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } if (mBgi != null) { - if (showBGI) { - mBgi.setText(sBgi); + if (rawData.showBGI) { + mBgi.setText(rawData.sBgi); mBgi.setVisibility(View.VISIBLE); } else { mBgi.setVisibility(View.GONE); @@ -477,7 +424,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mStatus != null) { if (sharedPrefs.getBoolean("showExternalStatus", true)) { - mStatus.setText(externalStatusString); + mStatus.setText(rawData.externalStatusString); mStatus.setVisibility(View.VISIBLE); } else { mStatus.setVisibility(View.GONE); @@ -487,8 +434,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen if (mLoop != null) { if (sharedPrefs.getBoolean("showExternalStatus", true)) { mLoop.setVisibility(View.VISIBLE); - if (openApsStatus != -1) { - int mins = (int) ((System.currentTimeMillis() - openApsStatus) / 1000 / 60); + if (rawData.openApsStatus != -1) { + int mins = (int) ((System.currentTimeMillis() - rawData.openApsStatus) / 1000 / 60); mLoop.setText(mins + "'"); if (mins > 14) { loopLevel = 0; @@ -594,50 +541,13 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } } - public void addToWatchSet(DataMap dataMap) { - - ArrayList entries = dataMap.getDataMapArrayList("entries"); - if (entries != null) { - bgDataList = new ArrayList(); - for (DataMap entry : entries) { - double sgv = entry.getDouble("sgvDouble"); - double high = entry.getDouble("high"); - double low = entry.getDouble("low"); - long timestamp = entry.getLong("timestamp"); - int color = entry.getInt("color", 0); - bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color)); - } - } else { - double sgv = dataMap.getDouble("sgvDouble"); - double high = dataMap.getDouble("high"); - double low = dataMap.getDouble("low"); - long timestamp = dataMap.getLong("timestamp"); - int color = dataMap.getInt("color", 0); - - final int size = bgDataList.size(); - if (size > 0) { - if (bgDataList.get(size - 1).timestamp == timestamp) - return; // Ignore duplicates. - } - - bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color)); - } - - for (int i = 0; i < bgDataList.size(); i++) { - if (bgDataList.get(i).timestamp < (System.currentTimeMillis() - (1000 * 60 * 60 * 5))) { - bgDataList.remove(i); //Get rid of anything more than 5 hours old - break; - } - } - } - public void setupCharts() { - if(bgDataList.size() > 0) { //Dont crash things just because we dont have values, people dont like crashy things + if(rawData.bgDataList.size() > 0) { //Dont crash things just because we dont have values, people dont like crashy things int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3")); if (lowResMode) { - bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList, predictionList, tempWatchDataList, basalWatchDataList, bolusWatchDataList, pointSize, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe); + bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), rawData, pointSize, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe); } else { - bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList,predictionList, tempWatchDataList, basalWatchDataList, bolusWatchDataList, pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe); + bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), rawData, pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe); } chart.setLineChartData(bgGraphBuilder.lineData()); @@ -646,54 +556,4 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferen } } - private void loadBasalsAndTemps(DataMap dataMap) { - ArrayList temps = dataMap.getDataMapArrayList("temps"); - if (temps != null) { - tempWatchDataList = new ArrayList<>(); - for (DataMap temp : temps) { - TempWatchData twd = new TempWatchData(); - twd.startTime = temp.getLong("starttime"); - twd.startBasal = temp.getDouble("startBasal"); - twd.endTime = temp.getLong("endtime"); - twd.endBasal = temp.getDouble("endbasal"); - twd.amount = temp.getDouble("amount"); - tempWatchDataList.add(twd); - } - } - ArrayList basals = dataMap.getDataMapArrayList("basals"); - if (basals != null) { - basalWatchDataList = new ArrayList<>(); - for (DataMap basal : basals) { - BasalWatchData bwd = new BasalWatchData(); - bwd.startTime = basal.getLong("starttime"); - bwd.endTime = basal.getLong("endtime"); - bwd.amount = basal.getDouble("amount"); - basalWatchDataList.add(bwd); - } - } - ArrayList boluses = dataMap.getDataMapArrayList("boluses"); - if (boluses != null) { - bolusWatchDataList = new ArrayList<>(); - for (DataMap bolus : boluses) { - BolusWatchData bwd = new BolusWatchData(); - bwd.date = bolus.getLong("date"); - bwd.bolus = bolus.getDouble("bolus"); - bwd.carbs = bolus.getDouble("carbs"); - bwd.isSMB = bolus.getBoolean("isSMB"); - bwd.isValid = bolus.getBoolean("isValid"); - bolusWatchDataList.add(bwd); - } - } - ArrayList predictions = dataMap.getDataMapArrayList("predictions"); - if (boluses != null) { - predictionList = new ArrayList<>(); - for (DataMap prediction : predictions) { - BgWatchData bwd = new BgWatchData(); - bwd.timestamp = prediction.getLong("timestamp"); - bwd.sgv = prediction.getDouble("sgv"); - bwd.color = prediction.getInt("color"); - predictionList.add(bwd); - } - } - } } diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java index 4a401489d9..b40e15cfd4 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/BgGraphBuilder.java @@ -18,6 +18,7 @@ import java.util.TimeZone; import info.nightscout.androidaps.data.BasalWatchData; import info.nightscout.androidaps.data.BgWatchData; import info.nightscout.androidaps.data.BolusWatchData; +import info.nightscout.androidaps.data.DisplayRawData; import info.nightscout.androidaps.data.TempWatchData; import lecho.lib.hellocharts.model.Axis; import lecho.lib.hellocharts.model.AxisValue; @@ -115,6 +116,42 @@ public class BgGraphBuilder { this.end_time = (predictionEndTime>end_time)?predictionEndTime:end_time; } + public BgGraphBuilder(Context context, DisplayRawData raw, int aPointSize, int aHighColor, int aLowColor, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) { + this(context, + raw.bgDataList, + raw.predictionList, + raw.tempWatchDataList, + raw.basalWatchDataList, + raw.bolusWatchDataList, + aPointSize, + aHighColor, + aLowColor, + aMidColor, + gridColour, + basalBackgroundColor, + basalCenterColor, + bolusInvalidColor, + carbsColor, + timespan); + } + + public BgGraphBuilder(Context context, DisplayRawData raw, int aPointSize, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) { + this(context, + raw.bgDataList, + raw.predictionList, + raw.tempWatchDataList, + raw.basalWatchDataList, + raw.bolusWatchDataList, + aPointSize, + aMidColor, + gridColour, + basalBackgroundColor, + basalCenterColor, + bolusInvalidColor, + carbsColor, + timespan); + } + public LineChartData lineData() { LineChartData lineData = new LineChartData(defaultLines()); lineData.setAxisYLeft(yAxis()); diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java index b949e3832a..31d293929a 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Cockpit.java @@ -48,13 +48,13 @@ public class Cockpit extends BaseWatchFace { setTextSizes(); if (mHighLight != null && mLowLight != null) { - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mHighLight.setBackgroundResource(R.drawable.airplane_led_yellow_lit); mLowLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mHighLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit); mLowLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mHighLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit); mLowLight.setBackgroundResource(R.drawable.airplane_led_red_lit); } @@ -82,7 +82,7 @@ public class Cockpit extends BaseWatchFace { protected void setTextSizes() { if (mIOB2 != null) { - if (detailedIOB) { + if (rawData.detailedIOB) { if (bIsRound) { mIOB2.setTextSize(10); } else { diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java index 430a3feb47..209e6214e7 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home.java @@ -68,15 +68,15 @@ public class Home extends BaseWatchFace { mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_statusView)); mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime)); mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background)); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); @@ -88,7 +88,7 @@ public class Home extends BaseWatchFace { mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld)); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBattery)); } else { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty)); @@ -133,15 +133,15 @@ public class Home extends BaseWatchFace { if (getCurrentWatchMode() == WatchMode.INTERACTIVE) { mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_stripe_background)); mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background)); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); @@ -153,7 +153,7 @@ public class Home extends BaseWatchFace { mTimestamp.setTextColor(Color.RED); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(Color.WHITE); } else { mUploaderBattery.setTextColor(Color.RED); diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java index 41c9fd3029..fbc69f3c1a 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Home2.java @@ -78,13 +78,13 @@ public class Home2 extends BaseWatchFace { setTextSizes(); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); } @@ -95,7 +95,7 @@ public class Home2 extends BaseWatchFace { mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld)); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBattery)); } else { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty)); @@ -176,13 +176,13 @@ public class Home2 extends BaseWatchFace { setTextSizes(); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); } @@ -193,7 +193,7 @@ public class Home2 extends BaseWatchFace { mTimestamp.setTextColor(Color.RED); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); } else { mUploaderBattery.setTextColor(Color.RED); @@ -229,7 +229,7 @@ public class Home2 extends BaseWatchFace { if (mIOB1 != null && mIOB2 != null) { - if (detailedIOB) { + if (rawData.detailedIOB) { mIOB1.setTextSize(14); mIOB2.setTextSize(10); } else { diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java index ffb8dce3ad..75b7d995dd 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/LargeHome.java @@ -52,15 +52,15 @@ public class LargeHome extends BaseWatchFace { mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mLinearLayout)); mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime)); mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background)); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor)); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor)); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor)); @@ -72,7 +72,7 @@ public class LargeHome extends BaseWatchFace { mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld)); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBattery)); } else { mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty)); @@ -86,15 +86,15 @@ public class LargeHome extends BaseWatchFace { if (getCurrentWatchMode() == WatchMode.INTERACTIVE) { mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_stripe_background)); mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background)); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor)); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor)); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor)); @@ -106,7 +106,7 @@ public class LargeHome extends BaseWatchFace { mTimestamp.setTextColor(Color.RED); } - if (batteryLevel == 1) { + if (rawData.batteryLevel == 1) { mUploaderBattery.setTextColor(Color.WHITE); } else { mUploaderBattery.setTextColor(Color.RED); @@ -116,15 +116,15 @@ public class LargeHome extends BaseWatchFace { } else { mRelativeLayout.setBackgroundColor(Color.BLACK); mLinearLayout.setBackgroundColor(Color.LTGRAY); - if (sgvLevel == 1) { + if (rawData.sgvLevel == 1) { mSgv.setTextColor(Color.YELLOW); mDirection.setTextColor(Color.YELLOW); mDelta.setTextColor(Color.YELLOW); - } else if (sgvLevel == 0) { + } else if (rawData.sgvLevel == 0) { mSgv.setTextColor(Color.WHITE); mDirection.setTextColor(Color.WHITE); mDelta.setTextColor(Color.WHITE); - } else if (sgvLevel == -1) { + } else if (rawData.sgvLevel == -1) { mSgv.setTextColor(Color.RED); mDirection.setTextColor(Color.RED); mDelta.setTextColor(Color.RED); diff --git a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java index c42aff1518..a600290a04 100644 --- a/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java +++ b/wear/src/main/java/info/nightscout/androidaps/watchfaces/Steampunk.java @@ -83,24 +83,24 @@ public class Steampunk extends BaseWatchFace { } } - if (!sSgv.equals("---")) { + if (!rawData.sSgv.equals("---")) { float rotationAngle = 0f; //by default, show ? on the dial (? is at 0 degrees on the dial) - if (!sUnits.equals("-")) { + if (!rawData.sUnits.equals("-")) { //ensure the glucose dial is the correct units - if (sUnits.equals("mmol")) { + if (rawData.sUnits.equals("mmol")) { mGlucoseDial.setImageResource(R.drawable.steampunk_dial_mmol); } else { mGlucoseDial.setImageResource(R.drawable.steampunk_dial_mgdl); } //convert the Sgv to degrees of rotation - if (sUnits.equals("mmol")) { - rotationAngle = Float.valueOf(sSgv) * 18f; //convert to mg/dL, which is equivalent to degrees + if (rawData.sUnits.equals("mmol")) { + rotationAngle = Float.valueOf(rawData.sSgv) * 18f; //convert to mg/dL, which is equivalent to degrees } else { - rotationAngle = Float.valueOf(sSgv); //if glucose a value is received, use it to determine the amount of rotation of the dial. + rotationAngle = Float.valueOf(rawData.sSgv); //if glucose a value is received, use it to determine the amount of rotation of the dial. } } @@ -122,36 +122,36 @@ public class Steampunk extends BaseWatchFace { //set the delta gauge and rotate the delta pointer float deltaIsNegative = 1f; //by default go clockwise - if (!sAvgDelta.equals("--")) { //if a legitimate delta value is received, then... - if (sAvgDelta.substring(0,1).equals("-")) deltaIsNegative = -1f; //if the delta is negative, go counter-clockwise + if (!rawData.sAvgDelta.equals("--")) { //if a legitimate delta value is received, then... + if (rawData.sAvgDelta.substring(0,1).equals("-")) deltaIsNegative = -1f; //if the delta is negative, go counter-clockwise //ensure the delta gauge is the right units and granularity - if (!sUnits.equals("-")) { - if (sUnits.equals("mmol")) { + if (!rawData.sUnits.equals("-")) { + if (rawData.sUnits.equals("mmol")) { if (sharedPrefs.getString("delta_granularity", "2").equals("1")) { //low mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_10); - deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 30f); //get rid of the sign so it can be converted to float. + deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 30f); //get rid of the sign so it can be converted to float. } if (sharedPrefs.getString("delta_granularity", "2").equals("2")) { //medium mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_05); - deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 60f); //get rid of the sign so it can be converted to float. + deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 60f); //get rid of the sign so it can be converted to float. } if (sharedPrefs.getString("delta_granularity", "2").equals("3")) { //high mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_03); - deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 100f); //get rid of the sign so it can be converted to float. + deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 100f); //get rid of the sign so it can be converted to float. } } else { if (sharedPrefs.getString("delta_granularity", "2").equals("1")) { //low mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_20); - deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 1.5f); //get rid of the sign so it can be converted to float. + deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 1.5f); //get rid of the sign so it can be converted to float. } if (sharedPrefs.getString("delta_granularity", "2").equals("2")) { //medium mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_10); - deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 3f); //get rid of the sign so it can be converted to float. + deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 3f); //get rid of the sign so it can be converted to float. } if (sharedPrefs.getString("delta_granularity", "2").equals("3")) { //high mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_5); - deltaRotationAngle = (Float.valueOf(sAvgDelta.substring(1)) * 6f); //get rid of the sign so it can be converted to float. + deltaRotationAngle = (Float.valueOf(rawData.sAvgDelta.substring(1)) * 6f); //get rid of the sign so it can be converted to float. } } } @@ -213,7 +213,7 @@ public class Steampunk extends BaseWatchFace { //top row. large font unless text too big (i.e. detailedIOB) mCOB2.setTextSize(fontLarge); mBasalRate.setTextSize(fontLarge); - if (sIOB2.length() < 7) { + if (rawData.sIOB2.length() < 7) { mIOB2.setTextSize(fontLarge); } else { mIOB2.setTextSize(fontSmall); diff --git a/wear/src/main/res/drawable/ic_aaps_dark.xml b/wear/src/main/res/drawable/ic_aaps_dark.xml new file mode 100644 index 0000000000..4126fec9d9 --- /dev/null +++ b/wear/src/main/res/drawable/ic_aaps_dark.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wear/src/main/res/drawable/ic_aaps_full.xml b/wear/src/main/res/drawable/ic_aaps_full.xml new file mode 100644 index 0000000000..cf9716870c --- /dev/null +++ b/wear/src/main/res/drawable/ic_aaps_full.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wear/src/main/res/drawable/ic_aaps_gray.xml b/wear/src/main/res/drawable/ic_aaps_gray.xml new file mode 100644 index 0000000000..1797ca3c9a --- /dev/null +++ b/wear/src/main/res/drawable/ic_aaps_gray.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wear/src/main/res/drawable/ic_aaps_light.xml b/wear/src/main/res/drawable/ic_aaps_light.xml new file mode 100644 index 0000000000..cf9716870c --- /dev/null +++ b/wear/src/main/res/drawable/ic_aaps_light.xml @@ -0,0 +1,12 @@ + + + + diff --git a/wear/src/main/res/drawable/ic_alert.xml b/wear/src/main/res/drawable/ic_alert.xml new file mode 100644 index 0000000000..d95c322d47 --- /dev/null +++ b/wear/src/main/res/drawable/ic_alert.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_alert_burnin.xml b/wear/src/main/res/drawable/ic_alert_burnin.xml new file mode 100644 index 0000000000..6f7bd23ef0 --- /dev/null +++ b/wear/src/main/res/drawable/ic_alert_burnin.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml b/wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml new file mode 100644 index 0000000000..516ae49c17 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_alert_variant_outline.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless.xml new file mode 100644 index 0000000000..bd6bee5fb1 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml new file mode 100644 index 0000000000..932c4e5930 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_10.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml new file mode 100644 index 0000000000..a47508a1a9 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_10_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml new file mode 100644 index 0000000000..00af480fcd --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_20.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml new file mode 100644 index 0000000000..e08ff88448 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_20_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml new file mode 100644 index 0000000000..592d0f380c --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_30.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml new file mode 100644 index 0000000000..eb36861925 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_30_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml new file mode 100644 index 0000000000..f94f85a69b --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_40.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml new file mode 100644 index 0000000000..8980db2391 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_40_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml new file mode 100644 index 0000000000..f6a4148589 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_50.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml new file mode 100644 index 0000000000..9a9ee645dc --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_50_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml new file mode 100644 index 0000000000..29192af0ed --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_60.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml new file mode 100644 index 0000000000..d5191601a6 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_60_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml new file mode 100644 index 0000000000..02c0ae7f58 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_70.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml new file mode 100644 index 0000000000..9efc928672 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_70_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml new file mode 100644 index 0000000000..21e44f7105 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_80.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml new file mode 100644 index 0000000000..9f212fad87 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_80_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml new file mode 100644 index 0000000000..1004964eb2 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_90.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml new file mode 100644 index 0000000000..b6b371fa20 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_90_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml new file mode 100644 index 0000000000..fd11b20ac9 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml b/wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml new file mode 100644 index 0000000000..5fa535e117 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_charging_wireless_outline.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_outline.xml b/wear/src/main/res/drawable/ic_battery_outline.xml new file mode 100644 index 0000000000..c066289e18 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_outline.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_unknown.xml b/wear/src/main/res/drawable/ic_battery_unknown.xml new file mode 100644 index 0000000000..0ac1d8a740 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_unknown.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/drawable/ic_battery_unknown_burnin.xml b/wear/src/main/res/drawable/ic_battery_unknown_burnin.xml new file mode 100644 index 0000000000..e6271cce70 --- /dev/null +++ b/wear/src/main/res/drawable/ic_battery_unknown_burnin.xml @@ -0,0 +1,10 @@ + + + diff --git a/wear/src/main/res/drawable/ic_br_cob_iob.xml b/wear/src/main/res/drawable/ic_br_cob_iob.xml new file mode 100644 index 0000000000..39ab3af490 --- /dev/null +++ b/wear/src/main/res/drawable/ic_br_cob_iob.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_carbs.xml b/wear/src/main/res/drawable/ic_carbs.xml new file mode 100644 index 0000000000..a1ac753064 --- /dev/null +++ b/wear/src/main/res/drawable/ic_carbs.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_cob_detailed.xml b/wear/src/main/res/drawable/ic_cob_detailed.xml new file mode 100644 index 0000000000..fc92be1910 --- /dev/null +++ b/wear/src/main/res/drawable/ic_cob_detailed.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_cob_iob.xml b/wear/src/main/res/drawable/ic_cob_iob.xml new file mode 100644 index 0000000000..f029cf0d49 --- /dev/null +++ b/wear/src/main/res/drawable/ic_cob_iob.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_ins.xml b/wear/src/main/res/drawable/ic_ins.xml new file mode 100644 index 0000000000..9fbb294190 --- /dev/null +++ b/wear/src/main/res/drawable/ic_ins.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_ins_burnin.xml b/wear/src/main/res/drawable/ic_ins_burnin.xml new file mode 100644 index 0000000000..8a3a16c64b --- /dev/null +++ b/wear/src/main/res/drawable/ic_ins_burnin.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_iob_detailed.xml b/wear/src/main/res/drawable/ic_iob_detailed.xml new file mode 100644 index 0000000000..95974f2fed --- /dev/null +++ b/wear/src/main/res/drawable/ic_iob_detailed.xml @@ -0,0 +1,11 @@ + + + diff --git a/wear/src/main/res/drawable/ic_sgv.xml b/wear/src/main/res/drawable/ic_sgv.xml new file mode 100644 index 0000000000..e7aefdd542 --- /dev/null +++ b/wear/src/main/res/drawable/ic_sgv.xml @@ -0,0 +1,13 @@ + + + diff --git a/wear/src/main/res/drawable/ic_sync_alert.xml b/wear/src/main/res/drawable/ic_sync_alert.xml new file mode 100644 index 0000000000..bd4ca18d5f --- /dev/null +++ b/wear/src/main/res/drawable/ic_sync_alert.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/wear/src/main/res/values/strings.xml b/wear/src/main/res/values/strings.xml index d50cb015e6..ca24978853 100644 --- a/wear/src/main/res/values/strings.xml +++ b/wear/src/main/res/values/strings.xml @@ -46,6 +46,25 @@ 4 + + Default + Menu + Wizard + Bolus + eCarb + Status + None + + + + default + menu + wizard + bolus + ecarb + status + none + AAPS AAPS(Large) @@ -53,4 +72,13 @@ AAPS(NoChart) AAPS(Circle) + + No data! + Old data! + Since %1$s + Sync with AAPS! + + No data received since %1$s! Check if AAPS on the phone sends data to watch + AAPS data is %1$s old! Check your sensor, xDrip+, NS, AAPS config or other! + diff --git a/wear/src/main/res/xml/preferences.xml b/wear/src/main/res/xml/preferences.xml index 0befaddfd0..aa8a3f6ba2 100644 --- a/wear/src/main/res/xml/preferences.xml +++ b/wear/src/main/res/xml/preferences.xml @@ -209,6 +209,20 @@ android:title="Wizard Percentage" app:wear_iconOff="@drawable/settings_off" app:wear_iconOn="@drawable/settings_on"/> + +