1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-28 04:46:51 +01:00

Add heart rate average to Activity and Sleep → Sleep

This commit is contained in:
vanous 2020-10-31 23:44:06 +01:00
parent e8534e01f8
commit 329c47fff2
7 changed files with 236 additions and 54 deletions

View File

@ -146,6 +146,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int AK_NOT_WORN_COLOR; protected int AK_NOT_WORN_COLOR;
protected String HEARTRATE_LABEL; protected String HEARTRATE_LABEL;
protected String HEARTRATE_AVERAGE_LABEL;
protected AbstractChartFragment(String... intentFilterActions) { protected AbstractChartFragment(String... intentFilterActions) {
mIntentFilterActions = new HashSet<>(); mIntentFilterActions = new HashSet<>();
@ -197,6 +198,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
AK_NOT_WORN_COLOR = runningColor.data; AK_NOT_WORN_COLOR = runningColor.data;
HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate); HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate);
HEARTRATE_AVERAGE_LABEL = getContext().getString(R.string.charts_legend_heartrate_average);
akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR); akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR); akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);

View File

@ -19,10 +19,13 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.animation.Easing;
@ -30,6 +33,7 @@ import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LegendEntry; import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineData;
@ -38,9 +42,11 @@ import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry; import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.formatter.ValueFormatter; import com.github.mikephil.charting.formatter.ValueFormatter;
import org.apache.commons.lang3.tuple.Triple;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -66,17 +72,22 @@ public class SleepChartFragment extends AbstractChartFragment {
private LineChart mActivityChart; private LineChart mActivityChart;
private PieChart mSleepAmountChart; private PieChart mSleepAmountChart;
private TextView mSleepchartInfo; private TextView mSleepchartInfo;
private TextView heartRateAverageLabel;
private ImageView heartRateWidget;
private int mSmartAlarmFrom = -1; private int mSmartAlarmFrom = -1;
private int mSmartAlarmTo = -1; private int mSmartAlarmTo = -1;
private int mTimestampFrom = -1; private int mTimestampFrom = -1;
private int mSmartAlarmGoneOff = -1; private int mSmartAlarmGoneOff = -1;
Prefs prefs = GBApplication.getPrefs();
private boolean CHARTS_SLEEP_RANGE_24H = prefs.getBoolean("chart_sleep_range_24h", false);
private boolean SHOW_CHARTS_AVERAGE = GBApplication.getPrefs().getBoolean("charts_show_average", true);
@Override @Override
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Prefs prefs = GBApplication.getPrefs();
List<? extends ActivitySample> samples; List<? extends ActivitySample> samples;
if (prefs.getBoolean("chart_sleep_range_24h", false)) { if (CHARTS_SLEEP_RANGE_24H) {
samples = getSamples(db, device); samples = getSamples(db, device);
} else { } else {
samples = getSamplesofSleep(db, device); samples = getSamplesofSleep(db, device);
@ -84,7 +95,7 @@ public class SleepChartFragment extends AbstractChartFragment {
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples); MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
if (!prefs.getBoolean("chart_sleep_range_24h", false)) { if (!CHARTS_SLEEP_RANGE_24H) {
if (mySleepChartsData.sleepSessions.size() > 0) { if (mySleepChartsData.sleepSessions.size() > 0) {
long tstart = mySleepChartsData.sleepSessions.get(0).getSleepStart().getTime() / 1000; long tstart = mySleepChartsData.sleepSessions.get(0).getSleepStart().getTime() / 1000;
long tend = mySleepChartsData.sleepSessions.get(mySleepChartsData.sleepSessions.size() - 1).getSleepEnd().getTime() / 1000; long tend = mySleepChartsData.sleepSessions.get(mySleepChartsData.sleepSessions.size() - 1).getSleepEnd().getTime() / 1000;
@ -98,10 +109,14 @@ public class SleepChartFragment extends AbstractChartFragment {
} }
} }
DefaultChartsData chartsData = refresh(device, samples); DefaultChartsData chartsData = refresh(device, samples);
Triple<Float, Integer, Integer> hrData = calculateHrData(samples);
return new MyChartsData(mySleepChartsData, chartsData); //Pair<Float, Float> intensityMinMax = calculateIntensityMinMax(samples); //so far unused
Pair<Float, Float> intensityMinMax = Pair.create(0f, 0f);
return new MyChartsData(mySleepChartsData, chartsData, hrData.getLeft(), hrData.getMiddle(), hrData.getRight(), intensityMinMax.first, intensityMinMax.second);
} }
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) { private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
SleepAnalysis sleepAnalysis = new SleepAnalysis(); SleepAnalysis sleepAnalysis = new SleepAnalysis();
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples); List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
@ -173,13 +188,76 @@ public class SleepChartFragment extends AbstractChartFragment {
MySleepChartsData pieData = mcd.getPieData(); MySleepChartsData pieData = mcd.getPieData();
mSleepAmountChart.setCenterText(pieData.getTotalSleep()); mSleepAmountChart.setCenterText(pieData.getTotalSleep());
mSleepAmountChart.setData(pieData.getPieData()); mSleepAmountChart.setData(pieData.getPieData());
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter()); mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
mActivityChart.setData(mcd.getChartsData().getData()); mActivityChart.setData(mcd.getChartsData().getData());
mSleepchartInfo.setText(buildYouSleptText(pieData)); mSleepchartInfo.setText(buildYouSleptText(pieData));
if (!CHARTS_SLEEP_RANGE_24H
&& supportsHeartrate(getChartsHost().getDevice())
&& SHOW_CHARTS_AVERAGE) {
if (mcd.getHeartRateAxisMax() != 0 || mcd.getHeartRateAxisMin() != 0) {
mActivityChart.getAxisRight().setAxisMaximum(mcd.getHeartRateAxisMax());
mActivityChart.getAxisRight().setAxisMinimum(mcd.getHeartRateAxisMin());
}
LimitLine hrAverage_line = new LimitLine(mcd.getHeartRateAverage());
hrAverage_line.setLineColor(Color.RED);
hrAverage_line.setLineWidth(0.1f);
mActivityChart.getAxisRight().removeAllLimitLines();
mActivityChart.getAxisRight().addLimitLine(hrAverage_line);
DecimalFormat df = new DecimalFormat("##.#");
heartRateAverageLabel.setText(df.format(mcd.getHeartRateAverage()));
}
}
private Triple<Float, Integer, Integer> calculateHrData(List<? extends ActivitySample> samples) {
if (samples.toArray().length < 1) {
return Triple.of(0f, 0, 0);
}
List<Integer> heartRateValues = new ArrayList<>();
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
for (ActivitySample sample : samples) {
if (sample.getKind() == ActivityKind.TYPE_LIGHT_SLEEP || sample.getKind() == ActivityKind.TYPE_DEEP_SLEEP) {
int heartRate = sample.getHeartRate();
if (heartRateUtilsInstance.isValidHeartRateValue(heartRate)) {
heartRateValues.add(heartRate);
}
}
}
if (heartRateValues.toArray().length < 1) {
return Triple.of(0f, 0, 0);
}
int min = Collections.min(heartRateValues);
int max = Collections.max(heartRateValues);
int count = heartRateValues.toArray().length;
float sum = calculateSum(heartRateValues);
float average = sum / count;
return Triple.of(average, min, max);
}
private float calculateSum(List<Integer> samples) {
float result = 0;
for (Integer sample : samples) {
result += sample;
}
return result;
}
private Pair<Float, Float> calculateIntensityMinMax(List<? extends ActivitySample> samples) {
List<Float> allIntensities = new ArrayList<>();
for (ActivitySample s : samples) {
if (s.getKind() == ActivityKind.TYPE_LIGHT_SLEEP || s.getKind() == ActivityKind.TYPE_DEEP_SLEEP) {
float HR = s.getIntensity();
allIntensities.add(HR);
}
}
Float min = Collections.min(allIntensities);
Float max = Collections.max(allIntensities);
return Pair.create(min, max);
} }
private String buildYouSleptText(MySleepChartsData pieData) { private String buildYouSleptText(MySleepChartsData pieData) {
@ -211,6 +289,8 @@ public class SleepChartFragment extends AbstractChartFragment {
mActivityChart = rootView.findViewById(R.id.sleepchart); mActivityChart = rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep); mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep);
mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info); mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
heartRateWidget = rootView.findViewById(R.id.heartrate_widget_icon);
heartRateAverageLabel = rootView.findViewById(R.id.heartrate_widget_label);
setupActivityChart(); setupActivityChart();
setupSleepAmountChart(); setupSleepAmountChart();
@ -276,8 +356,8 @@ public class SleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true); yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.getInstance().getMaxHeartRate()); yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
yAxisRight.setAxisMinValue(HeartRateUtils.getInstance().getMinHeartRate()); yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
} }
@Override @Override
@ -292,12 +372,20 @@ public class SleepChartFragment extends AbstractChartFragment {
deepSleepEntry.label = akDeepSleep.label; deepSleepEntry.label = akDeepSleep.label;
deepSleepEntry.formColor = akDeepSleep.color; deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry); legendEntries.add(deepSleepEntry);
heartRateWidget.setVisibility(View.GONE); //hide heart icon
if (supportsHeartrate(getChartsHost().getDevice())) { if (supportsHeartrate(getChartsHost().getDevice())) {
LegendEntry hrEntry = new LegendEntry(); LegendEntry hrEntry = new LegendEntry();
hrEntry.label = HEARTRATE_LABEL; hrEntry.label = HEARTRATE_LABEL;
hrEntry.formColor = HEARTRATE_COLOR; hrEntry.formColor = HEARTRATE_COLOR;
legendEntries.add(hrEntry); legendEntries.add(hrEntry);
if (!CHARTS_SLEEP_RANGE_24H && SHOW_CHARTS_AVERAGE) {
LegendEntry hrAverageEntry = new LegendEntry();
hrAverageEntry.label = HEARTRATE_AVERAGE_LABEL;
hrAverageEntry.formColor = Color.RED;
legendEntries.add(hrAverageEntry);
heartRateWidget.setVisibility(View.VISIBLE);
}
} }
chart.getLegend().setCustom(legendEntries); chart.getLegend().setCustom(legendEntries);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
@ -343,10 +431,18 @@ public class SleepChartFragment extends AbstractChartFragment {
private static class MyChartsData extends ChartsData { private static class MyChartsData extends ChartsData {
private final DefaultChartsData<LineData> chartsData; private final DefaultChartsData<LineData> chartsData;
private final MySleepChartsData pieData; private final MySleepChartsData pieData;
private final float heartRateAverage;
private float heartRateAxisMax;
private float heartRateAxisMin;
private float intensityAxisMax;
private float intensityAxisMin;
public MyChartsData(MySleepChartsData pieData, DefaultChartsData<LineData> chartsData) { public MyChartsData(MySleepChartsData pieData, DefaultChartsData<LineData> chartsData, float heartRateAverage, float heartRateAxisMin, float heartRateAxisMax, float intensityAxisMin, float intensityAxisMax) {
this.pieData = pieData; this.pieData = pieData;
this.chartsData = chartsData; this.chartsData = chartsData;
this.heartRateAverage = heartRateAverage;
this.heartRateAxisMax = heartRateAxisMax;
this.heartRateAxisMin = heartRateAxisMin;
} }
public MySleepChartsData getPieData() { public MySleepChartsData getPieData() {
@ -356,5 +452,25 @@ public class SleepChartFragment extends AbstractChartFragment {
public DefaultChartsData<LineData> getChartsData() { public DefaultChartsData<LineData> getChartsData() {
return chartsData; return chartsData;
} }
public float getHeartRateAverage() {
return heartRateAverage;
}
public float getHeartRateAxisMax() {
return heartRateAxisMax;
}
public float getHeartRateAxisMin() {
return heartRateAxisMin;
}
public float getIntensityAxisMax() {
return intensityAxisMax;
}
public float getIntensityAxisMin() {
return intensityAxisMin;
}
} }
} }

View File

@ -24,6 +24,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
@ -64,12 +65,12 @@ public class StepAnalysis {
float activeIntensity = 0; float activeIntensity = 0;
float intensityBetweenActivePeriods = 0; float intensityBetweenActivePeriods = 0;
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
for (ActivitySample sample : samples) { for (ActivitySample sample : samples) {
if (sample.getKind() != ActivityKind.TYPE_SLEEP //anything but sleep counts if (sample.getKind() != ActivityKind.TYPE_SLEEP //anything but sleep counts
&& !(sample instanceof TrailingActivitySample)) { //trailing samples have wrong date and make trailing activity have 0 duration && !(sample instanceof TrailingActivitySample)) { //trailing samples have wrong date and make trailing activity have 0 duration
if (sample.getHeartRate() != 255 && sample.getHeartRate() != -1) { if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
heartRateToAdd = sample.getHeartRate(); heartRateToAdd = sample.getHeartRate();
activeHrSamplesToAdd = 1; activeHrSamplesToAdd = 1;
} else { } else {

View File

@ -1,8 +1,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment" android:orientation="vertical"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
<include
layout="@layout/heartrate_average_widget"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_gravity="top|end"
android:layout_marginTop="-5dp"
android:layout_marginEnd="-2dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -38,3 +53,4 @@
<!--android:layout_weight="20" />--> <!--android:layout_weight="20" />-->
</LinearLayout> </LinearLayout>
</RelativeLayout>

View File

@ -1,8 +1,21 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment" android:orientation="vertical"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
<include
layout="@layout/heartrate_average_widget"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_gravity="top|end" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -24,3 +37,5 @@
android:layout_weight="2" /> android:layout_weight="2" />
</LinearLayout> </LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/heartrate_widget_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/heartrate_widget_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="@string/heart_rate"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_heart" />
<TextView
android:id="@+id/heartrate_widget_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="6dp"
android:text=""
android:textAlignment="center"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -633,6 +633,7 @@
<string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string> <string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
<string name="charts_legend_heartrate">Heart rate</string> <string name="charts_legend_heartrate">Heart rate</string>
<string name="live_activity_heart_rate">Heart rate</string> <string name="live_activity_heart_rate">Heart rate</string>
<string name="charts_legend_heartrate_average">Heart rate average</string>
<string name="activity_prefs_calories_burnt">Daily target: calories burnt</string> <string name="activity_prefs_calories_burnt">Daily target: calories burnt</string>
<string name="activity_prefs_distance_meters">Daily target: distance in meters</string> <string name="activity_prefs_distance_meters">Daily target: distance in meters</string>
<string name="activity_prefs_activetime_minutes">Daily target: active time in minutes</string> <string name="activity_prefs_activetime_minutes">Daily target: active time in minutes</string>