1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-24 02:46:50 +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 String HEARTRATE_LABEL;
protected String HEARTRATE_AVERAGE_LABEL;
protected AbstractChartFragment(String... intentFilterActions) {
mIntentFilterActions = new HashSet<>();
@ -197,6 +198,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
AK_NOT_WORN_COLOR = runningColor.data;
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);
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.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
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.PieChart;
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.YAxis;
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.formatter.ValueFormatter;
import org.apache.commons.lang3.tuple.Triple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -66,17 +72,22 @@ public class SleepChartFragment extends AbstractChartFragment {
private LineChart mActivityChart;
private PieChart mSleepAmountChart;
private TextView mSleepchartInfo;
private TextView heartRateAverageLabel;
private ImageView heartRateWidget;
private int mSmartAlarmFrom = -1;
private int mSmartAlarmTo = -1;
private int mTimestampFrom = -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
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Prefs prefs = GBApplication.getPrefs();
List<? extends ActivitySample> samples;
if (prefs.getBoolean("chart_sleep_range_24h", false)) {
if (CHARTS_SLEEP_RANGE_24H) {
samples = getSamples(db, device);
} else {
samples = getSamplesofSleep(db, device);
@ -84,7 +95,7 @@ public class SleepChartFragment extends AbstractChartFragment {
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
if (!prefs.getBoolean("chart_sleep_range_24h", false)) {
if (!CHARTS_SLEEP_RANGE_24H) {
if (mySleepChartsData.sleepSessions.size() > 0) {
long tstart = mySleepChartsData.sleepSessions.get(0).getSleepStart().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);
return new MyChartsData(mySleepChartsData, chartsData);
Triple<Float, Integer, Integer> hrData = calculateHrData(samples);
//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) {
SleepAnalysis sleepAnalysis = new SleepAnalysis();
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
@ -173,13 +188,76 @@ public class SleepChartFragment extends AbstractChartFragment {
MySleepChartsData pieData = mcd.getPieData();
mSleepAmountChart.setCenterText(pieData.getTotalSleep());
mSleepAmountChart.setData(pieData.getPieData());
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
mActivityChart.setData(mcd.getChartsData().getData());
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) {
@ -211,6 +289,8 @@ public class SleepChartFragment extends AbstractChartFragment {
mActivityChart = rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep);
mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
heartRateWidget = rootView.findViewById(R.id.heartrate_widget_icon);
heartRateAverageLabel = rootView.findViewById(R.id.heartrate_widget_label);
setupActivityChart();
setupSleepAmountChart();
@ -276,8 +356,8 @@ public class SleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.getInstance().getMaxHeartRate());
yAxisRight.setAxisMinValue(HeartRateUtils.getInstance().getMinHeartRate());
yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
}
@Override
@ -292,12 +372,20 @@ public class SleepChartFragment extends AbstractChartFragment {
deepSleepEntry.label = akDeepSleep.label;
deepSleepEntry.formColor = akDeepSleep.color;
legendEntries.add(deepSleepEntry);
heartRateWidget.setVisibility(View.GONE); //hide heart icon
if (supportsHeartrate(getChartsHost().getDevice())) {
LegendEntry hrEntry = new LegendEntry();
hrEntry.label = HEARTRATE_LABEL;
hrEntry.formColor = HEARTRATE_COLOR;
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().setTextColor(LEGEND_TEXT_COLOR);
@ -343,10 +431,18 @@ public class SleepChartFragment extends AbstractChartFragment {
private static class MyChartsData extends ChartsData {
private final DefaultChartsData<LineData> chartsData;
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.chartsData = chartsData;
this.heartRateAverage = heartRateAverage;
this.heartRateAxisMax = heartRateAxisMax;
this.heartRateAxisMin = heartRateAxisMin;
}
public MySleepChartsData getPieData() {
@ -356,5 +452,25 @@ public class SleepChartFragment extends AbstractChartFragment {
public DefaultChartsData<LineData> getChartsData() {
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 nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
@ -64,12 +65,12 @@ public class StepAnalysis {
float activeIntensity = 0;
float intensityBetweenActivePeriods = 0;
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
for (ActivitySample sample : samples) {
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
if (sample.getHeartRate() != 255 && sample.getHeartRate() != -1) {
if (heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
heartRateToAdd = sample.getHeartRate();
activeHrSamplesToAdd = 1;
} 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"
android:layout_width="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">
<TextView
@ -38,3 +53,4 @@
<!--android:layout_weight="20" />-->
</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"
android:layout_width="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">
<TextView
@ -24,3 +37,5 @@
android:layout_weight="2" />
</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="charts_legend_heartrate">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_distance_meters">Daily target: distance in meters</string>
<string name="activity_prefs_activetime_minutes">Daily target: active time in minutes</string>