From 21de228204dea11d4a3a68b325814e2d25cd90c6 Mon Sep 17 00:00:00 2001 From: a0z Date: Sun, 25 Aug 2024 23:12:00 +0200 Subject: [PATCH] Garmin: Awake time --- .../charts/AbstractActivityChartFragment.java | 26 +++ .../charts/AbstractWeekChartFragment.java | 7 + .../BarChartStackedTimeValueFormatter.java | 53 +++++ .../activities/charts/SleepChartFragment.java | 27 ++- .../charts/WeekSleepChartFragment.java | 49 ++++- .../dashboard/DashboardTodayWidget.java | 2 +- .../devices/AbstractDeviceCoordinator.java | 5 + .../devices/DeviceCoordinator.java | 5 + .../devices/garmin/GarminCoordinator.java | 5 + .../main/res/layout/fragment_sleepchart.xml | 208 +++++++++--------- app/src/main/res/values/strings.xml | 1 + 11 files changed, 272 insertions(+), 116 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java index fbf626569..a9a6723d7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java @@ -62,6 +62,11 @@ public abstract class AbstractActivityChartFragment extend return coordinator != null && coordinator.supportsRemSleep(); } + public boolean supportsAwakeSleep(GBDevice device) { + DeviceCoordinator coordinator = device.getDeviceCoordinator(); + return coordinator != null && coordinator.supportsAwakeSleep(); + } + protected static final class ActivityConfig { public final ActivityKind type; public final String label; @@ -196,6 +201,7 @@ public abstract class AbstractActivityChartFragment extend List deepSleepEntries = new ArrayList<>(numEntries); List lightSleepEntries = new ArrayList<>(numEntries); List remSleepEntries = new ArrayList<>(numEntries); + List awakeSleepEntries = new ArrayList<>(numEntries); List notWornEntries = new ArrayList<>(numEntries); boolean hr = supportsHeartrate(gbDevice); List heartrateEntries = hr ? new ArrayList(numEntries) : null; @@ -233,6 +239,7 @@ public abstract class AbstractActivityChartFragment extend remSleepEntries.add(createLineEntry(0, ts)); notWornEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts)); + awakeSleepEntries.add(createLineEntry(0, ts)); } deepSleepEntries.add(createLineEntry(value + Y_VALUE_DEEP_SLEEP, ts)); break; @@ -244,6 +251,7 @@ public abstract class AbstractActivityChartFragment extend remSleepEntries.add(createLineEntry(0, ts)); notWornEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts)); + awakeSleepEntries.add(createLineEntry(0, ts)); } lightSleepEntries.add(createLineEntry(value, ts)); break; @@ -255,9 +263,22 @@ public abstract class AbstractActivityChartFragment extend deepSleepEntries.add(createLineEntry(0, ts)); notWornEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts)); + awakeSleepEntries.add(createLineEntry(0, ts)); } remSleepEntries.add(createLineEntry(value, ts)); break; + case AWAKE_SLEEP: + if (last_type != type) { + awakeSleepEntries.add(createLineEntry(0, ts - 1)); + + lightSleepEntries.add(createLineEntry(0, ts)); + deepSleepEntries.add(createLineEntry(0, ts)); + notWornEntries.add(createLineEntry(0, ts)); + activityEntries.add(createLineEntry(0, ts)); + remSleepEntries.add(createLineEntry(0, ts)); + } + awakeSleepEntries.add(createLineEntry(value, ts)); + break; case NOT_WORN: if (last_type != type) { notWornEntries.add(createLineEntry(0, ts - 1)); @@ -266,6 +287,7 @@ public abstract class AbstractActivityChartFragment extend deepSleepEntries.add(createLineEntry(0, ts)); remSleepEntries.add(createLineEntry(0, ts)); activityEntries.add(createLineEntry(0, ts)); + awakeSleepEntries.add(createLineEntry(0, ts)); } notWornEntries.add(createLineEntry(Y_VALUE_DEEP_SLEEP, ts)); //a small value, just to show something on the graphs break; @@ -335,6 +357,10 @@ public abstract class AbstractActivityChartFragment extend LineDataSet remSleepSet = createDataSet(remSleepEntries, akRemSleep.color, "REM Sleep"); lineDataSets.add(remSleepSet); } + if (supportsAwakeSleep(gbDevice)) { + LineDataSet awakeSleepSet = createDataSet(awakeSleepEntries, akAwakeSleep.color, "Awake Sleep"); + lineDataSets.add(awakeSleepSet); + } LineDataSet notWornSet = createDataSet(notWornEntries, akNotWorn.color, "Not worn"); lineDataSets.add(notWornSet); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java index 6b4158851..182026133 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java @@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.app.Activity; import android.graphics.Color; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -222,6 +223,12 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra View rootView = inflater.inflate(R.layout.fragment_weeksteps_chart, container, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + getChartsHost().enableSwipeRefresh(scrollY == 0); + }); + } + final int goal = getGoal(); if (goal >= 0) { mTargetValue = goal; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java new file mode 100644 index 000000000..a2736b928 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/BarChartStackedTimeValueFormatter.java @@ -0,0 +1,53 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.formatter.StackedValueFormatter; + +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; + +public class BarChartStackedTimeValueFormatter extends StackedValueFormatter { + private float[] processedValues; + private BarEntry lastEntry; + private int lastNonZeroIndex; + private int index = 0; + + public BarChartStackedTimeValueFormatter(boolean drawWholeStack, String suffix, int decimals) { + super(drawWholeStack, suffix, decimals); + } + + private int getLastNonZeroIndex(float[] array) { + int last = 0; + int i = 0; + for(float v: array) { + last = v == 0 ? last : i; + i++; + } + return last; + } + + @Override + public String getBarStackedLabel(float value, BarEntry entry) { + if (lastEntry != entry) { + processedValues = entry.getYVals(); + lastEntry = entry; + lastNonZeroIndex = getLastNonZeroIndex(processedValues); + index = 0; + } + + if (index == lastNonZeroIndex) { + return getFormattedValue(processedValues); + } + + index++; + return ""; + } + + String getFormattedValue(float[] values) { + float sum = 0; + for (int i = 0; i < values.length - 1; i++) { + sum += values[i]; + } + return DateTimeUtils.minutesToHHMM((int) sum); + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java index 42677ad25..09b39f030 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java @@ -135,7 +135,7 @@ public class SleepChartFragment extends AbstractActivityChartFragment entries = new ArrayList<>(); final List colors = new ArrayList<>(); @@ -151,9 +151,10 @@ public class SleepChartFragment extends AbstractActivityChartFragment chart) { - List legendEntries = new ArrayList<>(3); + List legendEntries = new ArrayList<>(4); LegendEntry lightSleepEntry = new LegendEntry(); - lightSleepEntry.label = akLightSleep.label; + lightSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_light); lightSleepEntry.formColor = akLightSleep.color; legendEntries.add(lightSleepEntry); LegendEntry deepSleepEntry = new LegendEntry(); - deepSleepEntry.label = akDeepSleep.label; + deepSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_deep); deepSleepEntry.formColor = akDeepSleep.color; legendEntries.add(deepSleepEntry); if (supportsRemSleep(getChartsHost().getDevice())) { LegendEntry remSleepEntry = new LegendEntry(); - remSleepEntry.label = akRemSleep.label; + remSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_rem); remSleepEntry.formColor = akRemSleep.color; legendEntries.add(remSleepEntry); } + if (supportsAwakeSleep(getChartsHost().getDevice())) { + LegendEntry awakeSleepEntry = new LegendEntry(); + awakeSleepEntry.label = getActivity().getString(R.string.abstract_chart_fragment_kind_awake_sleep); + awakeSleepEntry.formColor = akAwakeSleep.color; + legendEntries.add(awakeSleepEntry); + } + if (supportsHeartrate(getChartsHost().getDevice())) { LegendEntry hrEntry = new LegendEntry(); hrEntry.label = HEARTRATE_LABEL; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java index 008b4c639..7d8b88bca 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java @@ -94,7 +94,11 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { deepWeeklyTotal += (long) totalAmounts[0]; lightWeeklyTotal += (long) totalAmounts[1]; remWeeklyTotal += (long) totalAmounts[2]; - awakeWeeklyTotal += (long) totalAmounts[3]; + + if (supportsAwakeSleep(getChartsHost().getDevice())) { + awakeWeeklyTotal += (long) totalAmounts[3]; + } + day.add(Calendar.DATE, 1); } @@ -128,21 +132,25 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { return rootView; } - @Override - protected void updateChartsnUIThread(MyChartsData mcd) { - setupLegend(mWeekChart); - + protected void setupWeekChart() { + super.setupWeekChart(); if (TOTAL_DAYS > 7) { mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler())); } else { mWeekChart.setScaleEnabled(false); mWeekChart.setTouchEnabled(false); } + } + + @Override + protected void updateChartsnUIThread(MyChartsData mcd) { + setupLegend(mWeekChart); mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 mWeekChart.setData(mcd.getWeekBeforeData().getData()); mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter()); mWeekChart.getBarData().setValueTextSize(10f); + mWeekChart.getBarData().setValueFormatter(new BarChartStackedTimeValueFormatter(false, "", 0)); if (TOTAL_DAYS_FOR_AVERAGE > 0) { float avgDeep = Math.abs(this.mySleepWeeklyData.getTotalDeep() / TOTAL_DAYS_FOR_AVERAGE); @@ -164,6 +172,10 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { remSleepTimeTextWrapper.setVisibility(View.GONE); } + if (!supportsAwakeSleep(getChartsHost().getDevice())) { + awakeSleepTimeTextWrapper.setVisibility(View.GONE); + } + Date to = new Date((long) this.getTSEnd() * 1000); Date from = DateUtils.addDays(to,-(TOTAL_DAYS - 1)); String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to); @@ -260,7 +272,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { int totalMinutesLightSleep = (int) (totalSecondsLightSleep / 60); int totalMinutesRemSleep = (int) (totalSecondsRemSleep / 60); int totalMinutesAwakeSleep = (int) (totalSecondsAwakeSleep / 60); - return new float[]{totalMinutesDeepSleep, totalMinutesLightSleep, totalMinutesRemSleep, totalMinutesAwakeSleep}; + + float[] activityAmountsTotals = {totalMinutesDeepSleep, totalMinutesLightSleep, totalMinutesRemSleep}; + if (supportsAwakeSleep(getChartsHost().getDevice())) { + activityAmountsTotals = ArrayUtils.add(activityAmountsTotals, totalMinutesAwakeSleep); + } + + return activityAmountsTotals; } @Override @@ -277,6 +295,9 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { if (supportsRemSleep(getChartsHost().getDevice())) { labels = ArrayUtils.add(labels, getString(R.string.abstract_chart_fragment_kind_rem_sleep)); } + if (supportsAwakeSleep(getChartsHost().getDevice())) { + labels = ArrayUtils.add(labels, getString(R.string.abstract_chart_fragment_kind_awake_sleep)); + } return labels; } @@ -316,6 +337,9 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { if (supportsRemSleep(getChartsHost().getDevice())) { colors = ArrayUtils.add(colors, akRemSleep.color); } + if (supportsAwakeSleep(getChartsHost().getDevice())) { + colors = ArrayUtils.add(colors, akAwakeSleep.color); + } return colors; } @@ -324,22 +348,29 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { List legendEntries = new ArrayList<>(2); LegendEntry lightSleepEntry = new LegendEntry(); - lightSleepEntry.label = akLightSleep.label; + lightSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_light); lightSleepEntry.formColor = akLightSleep.color; legendEntries.add(lightSleepEntry); LegendEntry deepSleepEntry = new LegendEntry(); - deepSleepEntry.label = akDeepSleep.label; + deepSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_deep); deepSleepEntry.formColor = akDeepSleep.color; legendEntries.add(deepSleepEntry); if (supportsRemSleep(getChartsHost().getDevice())) { LegendEntry remSleepEntry = new LegendEntry(); - remSleepEntry.label = akRemSleep.label; + remSleepEntry.label = getActivity().getString(R.string.sleep_colored_stats_rem); remSleepEntry.formColor = akRemSleep.color; legendEntries.add(remSleepEntry); } + if (supportsAwakeSleep(getChartsHost().getDevice())) { + LegendEntry awakeSleepEntry = new LegendEntry(); + awakeSleepEntry.label = getActivity().getString(R.string.abstract_chart_fragment_kind_awake_sleep); + awakeSleepEntry.formColor = akAwakeSleep.color; + legendEntries.add(awakeSleepEntry); + } + chart.getLegend().setCustom(legendEntries); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); chart.getLegend().setWordWrapEnabled(true); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java index 101a30187..cbb1f235a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardTodayWidget.java @@ -115,7 +115,7 @@ public class DashboardTodayWidget extends AbstractDashboardWidget { l_deep_sleep.setSpan(new ForegroundColorSpan(color_deep_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); SpannableString l_light_sleep = new SpannableString("■ " + getString(R.string.activity_type_light_sleep)); l_light_sleep.setSpan(new ForegroundColorSpan(color_light_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - SpannableString l_rem_sleep = new SpannableString("■ " + getString(R.string.abstract_chart_fragment_kind_rem_sleep)); + SpannableString l_rem_sleep = new SpannableString("■ " + getString(R.string.activity_type_rem_sleep)); l_rem_sleep.setSpan(new ForegroundColorSpan(color_rem_sleep), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); SpannableStringBuilder legendBuilder = new SpannableStringBuilder(); legend.setText(legendBuilder.append(l_not_worn).append(" ").append(l_worn).append("\n").append(l_activity).append(" ").append(l_exercise).append("\n").append(l_light_sleep).append(" ").append(l_deep_sleep).append(" ").append(l_rem_sleep)); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index 2ee20557d..7687dcc00 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -628,6 +628,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { return false; } + @Override + public boolean supportsAwakeSleep() { + return false; + } + @Override public boolean supportsWeather() { return false; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java index 4517f5b7a..8e632d8a3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -550,6 +550,11 @@ public interface DeviceCoordinator { */ boolean supportsRemSleep(); + /** + * Indicates whether the device supports Awake sleep tracking. + */ + boolean supportsAwakeSleep(); + /** * Indicates whether the device supports current weather and/or weather * forecast display. diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java index 383b323fc..8c0f76847 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/garmin/GarminCoordinator.java @@ -230,6 +230,11 @@ public abstract class GarminCoordinator extends AbstractBLEDeviceCoordinator { return true; } + @Override + public boolean supportsAwakeSleep() { + return true; + } + @Override public boolean supportsFindDevice() { return true; diff --git a/app/src/main/res/layout/fragment_sleepchart.xml b/app/src/main/res/layout/fragment_sleepchart.xml index 84c27e06a..f162eca3e 100644 --- a/app/src/main/res/layout/fragment_sleepchart.xml +++ b/app/src/main/res/layout/fragment_sleepchart.xml @@ -193,104 +193,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a49c470a..1cc2f6a87 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1310,6 +1310,7 @@ Not measured Activity Light sleep + REM sleep Deep sleep Device not worn Running