Add sleep score to sleeping tabs for supported devices

This commit is contained in:
a0z 2024-12-04 17:48:28 +01:00 committed by José Rebelo
parent 51a3b5d036
commit c3433f55cb
8 changed files with 281 additions and 242 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2023-2024 Daniel Dakhno, José Rebelo
/* Copyright (C) 2023-2024 Daniel Dakhno, José Rebelo, a0z
This file is part of Gadgetbridge.
@ -31,7 +31,6 @@ import org.apache.commons.lang3.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
@ -346,17 +345,11 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
protected LineDataSet createDataSet(List<Entry> values, Integer color, String label) {
LineDataSet set1 = new LineDataSet(values, label);
set1.setColor(color);
// set1.setDrawCubic(true);
// set1.setCubicIntensity(0.2f);
set1.setDrawFilled(true);
set1.setDrawCircles(false);
// set1.setLineWidth(2f);
// set1.setCircleSize(5f);
set1.setFillColor(color);
set1.setFillAlpha(255);
set1.setDrawValues(false);
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.LEFT);
return set1;
@ -366,17 +359,9 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
LineDataSet set1 = new LineDataSet(values, label);
set1.setLineWidth(2.2f);
set1.setColor(HEARTRATE_COLOR);
// set1.setDrawCubic(true);
set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
set1.setCubicIntensity(0.1f);
set1.setDrawCircles(false);
// set1.setCircleRadius(2f);
// set1.setDrawFilled(true);
// set1.setColor(getResources().getColor(android.R.color.background_light));
// set1.setCircleColor(HEARTRATE_COLOR);
// set1.setFillColor(ColorTemplate.getHoloBlue());
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setDrawValues(true);
set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.RIGHT);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2017-2024 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, José Rebelo, Pavel Elagin, Petr Vaněk
Daniele Gobbetti, José Rebelo, Pavel Elagin, Petr Vaněk, a0z
This file is part of Gadgetbridge.
@ -27,7 +27,6 @@ import android.view.ViewGroup;
import android.widget.TextView;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
@ -35,10 +34,11 @@ import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -51,9 +51,11 @@ import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
@ -65,47 +67,11 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
protected Locale mLocale;
protected int mTargetValue = 0;
protected PieChart mTodayPieChart;
protected BarChart mWeekChart;
protected TextView mBalanceView;
private int mOffsetHours = getOffsetHours();
@Override
protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
//NB: we could have omitted the day, but this way we can move things to the past easily
DayData dayData = refreshDayPie(db, day, device);
WeekChartsData<BarData> weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device);
return new MyChartsData(dayData, weekBeforeData);
}
@Override
protected void updateChartsnUIThread(MyChartsData mcd) {
setupLegend(mWeekChart);
mTodayPieChart.setCenterText(mcd.getDayData().centerText);
mTodayPieChart.setData(mcd.getDayData().data);
//set custom renderer for 30days bar charts
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler()));
}
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mWeekChart.setData(mcd.getWeekBeforeData().getData());
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
mBalanceView.setText(mcd.getWeekBeforeData().getBalanceMessage());
}
@Override
protected void renderCharts() {
mWeekChart.invalidate();
mTodayPieChart.invalidate();
// mBalanceView.setText(getBalanceMessage(balance));
}
protected String getWeeksChartsLabel(Calendar day){
if (TOTAL_DAYS > 7) {
//month, show day date
@ -125,19 +91,39 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
long balance = 0;
long daily_balance = 0;
TOTAL_DAYS_FOR_AVERAGE=0;
List<Entry> sleepScoreEntities = new ArrayList<>();
final List<ILineDataSet> sleepScoreDataSets = new ArrayList<>();
for (int counter = 0; counter < TOTAL_DAYS; counter++) {
// Sleep stages
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
daily_balance=calculateBalance(amounts);
if (daily_balance > 0) {
TOTAL_DAYS_FOR_AVERAGE++;
}
balance += daily_balance;
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
labels.add(getWeeksChartsLabel(day));
// Sleep score
if (supportsSleepScore()) {
List<? extends SleepScoreSample> sleepScoreSamples = getSleepScoreSamples(db, device, day);
if (!sleepScoreSamples.isEmpty() && sleepScoreSamples.get(0).getSleepScore() > 0) {
sleepScoreEntities.add(new Entry(counter, sleepScoreSamples.get(0).getSleepScore()));
} else {
if (!sleepScoreEntities.isEmpty()) {
List<Entry> clone = new ArrayList<>(sleepScoreEntities.size());
clone.addAll(sleepScoreEntities);
sleepScoreDataSets.add(createSleepScoreDataSet(clone));
sleepScoreEntities.clear();
}
}
}
day.add(Calendar.DATE, 1);
}
if (!sleepScoreEntities.isEmpty()) {
sleepScoreDataSets.add(createSleepScoreDataSet(sleepScoreEntities));
}
final LineData sleepScoreLineData = new LineData(sleepScoreDataSets);
sleepScoreLineData.setHighlightEnabled(false);
BarDataSet set = new BarDataSet(entries, "");
set.setColors(getColors());
@ -179,49 +165,50 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
}
}
if (supportsSleepScore()) {
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue), sleepScoreLineData);
}
return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue));
}
protected DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) {
protected List<SleepScoreSample> getSleepScoreSamples(DBHandler db, GBDevice device, Calendar day) {
int startTs;
int endTs;
PieData data = new PieData();
List<PieEntry> entries = new ArrayList<>();
PieDataSet set = new PieDataSet(entries, "");
day = (Calendar) day.clone(); // do not modify the caller's argument
day.set(Calendar.HOUR_OF_DAY, 0);
day.set(Calendar.MINUTE, 0);
day.set(Calendar.SECOND, 0);
day.add(Calendar.HOUR, 0);
startTs = (int) (day.getTimeInMillis() / 1000);
endTs = startTs + 24 * 60 * 60 - 1;
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
float[] totalValues = getTotalsForActivityAmounts(amounts);
String[] pieLabels = getPieLabels();
float totalValue = 0;
for (int i = 0; i < totalValues.length; i++) {
float value = totalValues[i];
totalValue += value;
entries.add(new PieEntry(value, pieLabels[i]));
TimeSampleProvider<? extends SleepScoreSample> provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession());
return (List<SleepScoreSample>) provider.getAllSamples(startTs * 1000L, endTs * 1000L);
}
set.setColors(getColors());
if (totalValues.length < 2) {
if (totalValue < mTargetValue) {
entries.add(new PieEntry((mTargetValue - totalValue)));
set.addColor(Color.GRAY);
}
}
data.setDataSet(set);
if (totalValues.length < 2) {
data.setDrawValues(false);
}
else {
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
set.setValueTextColor(DESCRIPTION_COLOR);
set.setValueTextSize(13f);
set.setValueFormatter(getPieValueFormatter());
}
return new DayData(data, formatPieValue((long) totalValue));
protected LineDataSet createSleepScoreDataSet(final List<Entry> values) {
final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.sleep_score));
lineDataSet.setColor(getResources().getColor(R.color.chart_light_sleep_light));
lineDataSet.setDrawCircles(false);
lineDataSet.setLineWidth(2f);
lineDataSet.setFillAlpha(255);
lineDataSet.setCircleRadius(5f);
lineDataSet.setDrawCircles(true);
lineDataSet.setDrawCircleHole(true);
lineDataSet.setCircleColor(getResources().getColor(R.color.chart_light_sleep_light));
lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
lineDataSet.setDrawValues(true);
lineDataSet.setValueTextSize(10f);
lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
lineDataSet.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
return String.format(Locale.ROOT, "%d", (int) value);
}
});
return lineDataSet;
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -241,12 +228,10 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
mTargetValue = goal;
}
mTodayPieChart = rootView.findViewById(R.id.todaystepschart);
mWeekChart = rootView.findViewById(R.id.weekstepschart);
mBalanceView = rootView.findViewById(R.id.balance);
setupWeekChart();
setupTodayPieChart();
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@ -254,20 +239,6 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
return rootView;
}
protected void setupTodayPieChart() {
mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR);
mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR);
mTodayPieChart.setEntryLabelColor(DESCRIPTION_COLOR);
mTodayPieChart.getDescription().setText(getPieDescription(mTargetValue));
// mTodayPieChart.setNoDataTextDescription("");
mTodayPieChart.setNoDataText("");
mTodayPieChart.getLegend().setEnabled(false);
}
protected void setupWeekChart() {
mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
@ -323,29 +294,13 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
return super.getAllSamples(db, device, tsFrom, tsTo);
}
private static class DayData {
private final PieData data;
private final CharSequence centerText;
DayData(PieData data, String centerText) {
this.data = data;
this.centerText = centerText;
}
}
protected static class MyChartsData extends ChartsData {
private final WeekChartsData<BarData> weekBeforeData;
private final DayData dayData;
MyChartsData(DayData dayData, WeekChartsData<BarData> weekBeforeData) {
this.dayData = dayData;
MyChartsData(WeekChartsData<BarData> weekBeforeData) {
this.weekBeforeData = weekBeforeData;
}
DayData getDayData() {
return dayData;
}
WeekChartsData<BarData> getWeekBeforeData() {
return weekBeforeData;
}
@ -382,6 +337,11 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
}
}
public boolean supportsSleepScore() {
final GBDevice device = getChartsHost().getDevice();
return device.getDeviceCoordinator().supportsSleepScore();
}
abstract String getAverage(float value);
abstract int getGoal();
@ -410,14 +370,23 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
protected class WeekChartsData<T extends ChartData<?>> extends DefaultChartsData<T> {
private final String balanceMessage;
private LineData sleepScoresLineData;
public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) {
super(data, xIndexLabelFormatter);
this.balanceMessage = balanceMessage;
}
public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage, LineData sleepScores) {
super(data, xIndexLabelFormatter);
this.balanceMessage = balanceMessage;
this.sleepScoresLineData = sleepScores;
}
public String getBalanceMessage() {
return balanceMessage;
}
public LineData getSleepScoreData() { return sleepScoresLineData; }
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Dikay900, José Rebelo, ozkanpakdil, Pavel Elagin, Petr Vaněk, Q-er
Gobbetti, Dikay900, José Rebelo, ozkanpakdil, Pavel Elagin, Petr Vaněk, Q-er, a0z
This file is part of Gadgetbridge.
@ -23,21 +23,21 @@ import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.github.mikephil.charting.animation.Easing;
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.*;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import org.apache.commons.lang3.tuple.Triple;
import org.slf4j.Logger;
@ -52,10 +52,13 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis.SleepSession;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -64,7 +67,7 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
private LineChart mActivityChart;
private PieChart mSleepAmountChart;
private ImageView sleepStagesGauge;
private TextView mSleepchartInfo;
private TextView remSleepTimeText;
private LinearLayout remSleepTimeTextWrapper;
@ -106,8 +109,11 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
} else {
samples = getSamplesofSleep(db, device);
}
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
List<? extends SleepScoreSample> sleepScoreSamples = new ArrayList<>();
if (supportsSleepScore()) {
sleepScoreSamples = getSleepScoreSamples(db, device, getTSStart(), getTSEnd());
}
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples, sleepScoreSamples);
if (!CHARTS_SLEEP_RANGE_24H) {
if (mySleepChartsData.sleepSessions.size() > 0) {
@ -130,58 +136,22 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples, List<? extends SleepScoreSample> sleepScoreSamples) {
SleepAnalysis sleepAnalysis = new SleepAnalysis();
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
PieData data = new PieData();
final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
final long remSleepDuration = calculateRemSleepDuration(sleepSessions);
final long awakeSleepDuration = calculateAwakeSleepDuration(sleepSessions);
final long totalSeconds = lightSleepDuration + deepSleepDuration + remSleepDuration;
final List<PieEntry> entries = new ArrayList<>();
final List<Integer> colors = new ArrayList<>();
if (!sleepSessions.isEmpty()) {
entries.add(new PieEntry(lightSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_light_sleep)));
entries.add(new PieEntry(deepSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_deep_sleep)));
colors.add(getColorFor(ActivityKind.LIGHT_SLEEP));
colors.add(getColorFor(ActivityKind.DEEP_SLEEP));
if (supportsRemSleep(mGBDevice)) {
entries.add(new PieEntry(remSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_rem_sleep)));
colors.add(getColorFor(ActivityKind.REM_SLEEP));
int sleepScore = 0;
if (!sleepScoreSamples.isEmpty()) {
sleepScore = sleepScoreSamples.get(0).getSleepScore();
}
if (supportsAwakeSleep(mGBDevice)) {
entries.add(new PieEntry(awakeSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_awake_sleep)));
colors.add(getColorFor(ActivityKind.AWAKE_SLEEP));
}
} else {
entries.add(new PieEntry(1));
colors.add(getResources().getColor(R.color.gauge_line_color));
}
PieDataSet set = new PieDataSet(entries, "");
set.setSliceSpace(2f);
set.setColors(colors);
set.setValueTextColor(DESCRIPTION_COLOR);
set.setValueTextSize(13f);
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
data.setDataSet(set);
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
String totalAwake = DateTimeUtils.formatDurationHoursMinutes(awakeSleepDuration, TimeUnit.SECONDS);
String totalRem = DateTimeUtils.formatDurationHoursMinutes(remSleepDuration, TimeUnit.SECONDS);
String totalDeep = DateTimeUtils.formatDurationHoursMinutes(deepSleepDuration, TimeUnit.SECONDS);
String totalLight = DateTimeUtils.formatDurationHoursMinutes(lightSleepDuration, TimeUnit.SECONDS);
//setupLegend(pieChart);
return new MySleepChartsData(data, sleepSessions, totalSleep, totalAwake, totalRem, totalDeep, totalLight);
return new MySleepChartsData(sleepSessions, totalSeconds, awakeSleepDuration, remSleepDuration, deepSleepDuration, lightSleepDuration, sleepScore);
}
private long calculateLightSleepDuration(List<SleepSession> sleepSessions) {
@ -216,6 +186,45 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
return result;
}
protected void sleepStagesGaugeUpdate(MySleepChartsData pieData) {
int[] colors = new int[] {
ContextCompat.getColor(GBApplication.getContext(), R.color.chart_light_sleep_light),
ContextCompat.getColor(GBApplication.getContext(), R.color.chart_deep_sleep_light),
ContextCompat.getColor(GBApplication.getContext(), R.color.chart_rem_sleep_light),
ContextCompat.getColor(GBApplication.getContext(), R.color.chart_awake_sleep_light),
};
long total = pieData.getTotalSleep() + pieData.getTotalAwake();
float[] segments = new float[] {
pieData.getTotalLight() > 0 ? (float) pieData.getTotalLight() / total : 0,
pieData.getTotalDeep() > 0 ? (float) pieData.getTotalDeep() / total : 0,
pieData.getTotalRem() > 0 ? (float) pieData.getTotalRem() / total : 0,
pieData.getTotalAwake() > 0 ? (float) pieData.getTotalAwake() / total : 0,
};
final int width = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
300,
GBApplication.getContext().getResources().getDisplayMetrics()
);
String lowerText = "";
if (supportsSleepScore()) {
lowerText = GBApplication.getContext().getString(R.string.sleep_score_value, pieData.getSleepScore());
}
sleepStagesGauge.setImageBitmap(GaugeDrawer.drawCircleGaugeSegmented(
width,
width / 15,
colors,
segments,
true,
String.valueOf(timeStringFormat(pieData.getTotalSleep())),
lowerText,
getContext()
));
}
private String timeStringFormat(long seconds) {
return DateTimeUtils.formatDurationHoursMinutes(seconds, TimeUnit.SECONDS);
}
@Override
protected void updateChartsnUIThread(MyChartsData mcd) {
MySleepChartsData pieData = mcd.getPieData();
@ -224,15 +233,13 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
sleepDateText.setText(formattedDate);
pieData.pieData.setDrawValues(false);
mSleepAmountChart.setTouchEnabled(false);
mSleepAmountChart.setCenterTextColor(GBApplication.getTextColor(getContext()));
mSleepAmountChart.setCenterText(pieData.getTotalSleep());
sleepStagesGaugeUpdate(pieData);
if (!pieData.sleepSessions.isEmpty()) {
awakeSleepTimeText.setText(pieData.getTotalAwake());
remSleepTimeText.setText(pieData.getTotalRem());
deepSleepTimeText.setText(pieData.getTotalDeep());
lightSleepTimeText.setText(pieData.getTotalLight());
awakeSleepTimeText.setText(timeStringFormat(pieData.getTotalAwake()));
remSleepTimeText.setText(timeStringFormat(pieData.getTotalRem()));
deepSleepTimeText.setText(timeStringFormat(pieData.getTotalDeep()));
lightSleepTimeText.setText(timeStringFormat(pieData.getTotalLight()));
} else {
awakeSleepTimeText.setText("-");
remSleepTimeText.setText("-");
@ -245,9 +252,6 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
if (!supportsAwakeSleep(getChartsHost().getDevice())) {
awakeSleepTimeTextWrapper.setVisibility(View.GONE);
}
mSleepAmountChart.setCenterTextSize(18f);
mSleepAmountChart.setHoleColor(getContext().getResources().getColor(R.color.transparent));
mSleepAmountChart.setData(pieData.getPieData());
mSleepchartInfo.setText(buildYouSleptText(pieData));
mSleepchartInfo.setMovementMethod(new ScrollingMovementMethod());
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
@ -264,10 +268,6 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
movementIntensityTextWrapper.setVisibility(intensityTotal > 0 ? View.VISIBLE : View.GONE);
dummyTile.setVisibility(intensityTotal > 0 ? View.VISIBLE : View.GONE);
mSleepAmountChart.setHoleRadius(85);
mSleepAmountChart.setDrawEntryLabels(false);
mSleepAmountChart.getLegend().setEnabled(false);
if (!CHARTS_SLEEP_RANGE_24H
&& supportsHeartrate(getChartsHost().getDevice())
&& SHOW_CHARTS_AVERAGE) {
@ -366,6 +366,11 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
return result.toString();
}
public boolean supportsSleepScore() {
final GBDevice device = getChartsHost().getDevice();
return device.getDeviceCoordinator().supportsSleepScore();
}
@Override
public String getTitle() {
return getString(R.string.sleepchart_your_sleep);
@ -383,7 +388,7 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
}
mActivityChart = rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep);
sleepStagesGauge = rootView.findViewById(R.id.sleep_stages_gauge);
mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
@ -401,7 +406,6 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
mSleepchartInfo.setMaxLines(sleepLinesLimit);
setupActivityChart();
setupSleepAmountChart();
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@ -424,16 +428,6 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
}
}
private void setupSleepAmountChart() {
mSleepAmountChart.setBackgroundColor(BACKGROUND_COLOR);
mSleepAmountChart.getDescription().setTextColor(DESCRIPTION_COLOR);
mSleepAmountChart.setEntryLabelColor(DESCRIPTION_COLOR);
mSleepAmountChart.getDescription().setText("");
// mSleepAmountChart.getDescription().setNoDataTextDescription("");
mSleepAmountChart.setNoDataText("");
mSleepAmountChart.getLegend().setEnabled(false);
}
private void setupActivityChart() {
mActivityChart.setBackgroundColor(BACKGROUND_COLOR);
mActivityChart.getDescription().setTextColor(DESCRIPTION_COLOR);
@ -448,14 +442,10 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
YAxis y = mActivityChart.getAxisLeft();
y.setDrawGridLines(false);
// y.setDrawLabels(false);
// TODO: make fixed max value optional
y.setAxisMaximum(1f);
y.setAxisMinimum(0);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
// y.setLabelCount(5);
y.setEnabled(true);
YAxis yAxisRight = mActivityChart.getAxisRight();
@ -513,59 +503,61 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
@Override
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
// temporary fix for totally wrong sleep amounts
// Temporary fix for totally wrong sleep amounts.
return super.getAllSamples(db, device, tsFrom, tsTo);
}
protected List<SleepScoreSample> getSleepScoreSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
TimeSampleProvider<? extends SleepScoreSample> provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession());
return (List<SleepScoreSample>) provider.getAllSamples(tsFrom * 1000L, tsTo * 1000L);
}
@Override
protected void renderCharts() {
mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
mSleepAmountChart.invalidate();
}
private static class MySleepChartsData extends ChartsData {
private String totalSleep;
private String totalAwake;
private String totalRem;
private String totalDeep;
private String totalLight;
private final PieData pieData;
private long totalSleep;
private long totalAwake;
private long totalRem;
private long totalDeep;
private long totalLight;
private int sleepScore;
private final List<SleepSession> sleepSessions;
public MySleepChartsData(PieData pieData, List<SleepSession> sleepSessions, String totalSleep, String totalAwake, String totalRem, String totalDeep, String totalLight) {
this.pieData = pieData;
public MySleepChartsData(List<SleepSession> sleepSessions, long totalSleep, long totalAwake, long totalRem, long totalDeep, long totalLight, int sleepScore) {
this.sleepSessions = sleepSessions;
this.totalAwake = totalAwake;
this.totalSleep = totalSleep;
this.totalRem = totalRem;
this.totalDeep = totalDeep;
this.totalLight = totalLight;
this.sleepScore = sleepScore;
}
public PieData getPieData() {
return pieData;
}
public CharSequence getTotalSleep() {
public long getTotalSleep() {
return totalSleep;
}
public CharSequence getTotalAwake() {
public long getTotalAwake() {
return totalAwake;
}
public CharSequence getTotalRem() {
public long getTotalRem() {
return totalRem;
}
public CharSequence getTotalDeep() {
public long getTotalDeep() {
return totalDeep;
}
public CharSequence getTotalLight() {
public long getTotalLight() {
return totalLight;
}
public int getSleepScore() {return sleepScore;}
public List<SleepSession> getSleepSessions() {
return sleepSessions;
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, José Rebelo,
Pavel Elagin, Petr Vaněk
Pavel Elagin, Petr Vaněk, a0z
This file is part of Gadgetbridge.
@ -25,8 +25,11 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.formatter.ValueFormatter;
@ -60,6 +63,8 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
private TextView lightSleepTimeText;
private TextView sleepDatesText;
private MySleepWeeklyData mySleepWeeklyData;
private LinearLayout sleepScoreWrapper;
private LineChart sleepScoreChart;
public static WeekSleepChartFragment newInstance ( int totalDays ) {
WeekSleepChartFragment fragmentFirst = new WeekSleepChartFragment();
@ -118,6 +123,8 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
}
mWeekChart = rootView.findViewById(R.id.weekstepschart);
sleepScoreWrapper = rootView.findViewById(R.id.sleep_score_wrapper);
sleepScoreChart = rootView.findViewById(R.id.sleep_score_chart);
remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
awakeSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_awake_time);
@ -128,8 +135,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
mBalanceView = rootView.findViewById(R.id.balance);
setupWeekChart();
if (!supportsSleepScore()) {
sleepScoreWrapper.setVisibility(View.GONE);
} else {
setupSleepScoreChart();
}
setupWeekChart();
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@ -155,6 +167,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
mWeekChart.getBarData().setValueTextSize(10f);
if (supportsSleepScore()) {
sleepScoreChart.setData(null);
sleepScoreChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
sleepScoreChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
sleepScoreChart.setData(mcd.getWeekBeforeData().getSleepScoreData());
}
// The last value is for awake time, which we do not want to include in the "total sleep time"
final int barIgnoreLast = supportsAwakeSleep(getChartsHost().getDevice()) ? 1 : 0;
mWeekChart.getBarData().setValueFormatter(new BarChartStackedTimeValueFormatter(false, "", 0, barIgnoreLast));
@ -200,12 +219,48 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
WeekChartsData<BarData> weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device);
mySleepWeeklyData = getMySleepWeeklyData(db, day, device);
return new MyChartsData(null, weekBeforeData);
return new MyChartsData(weekBeforeData);
}
private void setupSleepScoreChart() {
final XAxis xAxisBottom = sleepScoreChart.getXAxis();
xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxisBottom.setDrawLabels(true);
xAxisBottom.setDrawGridLines(false);
xAxisBottom.setEnabled(true);
xAxisBottom.setDrawLimitLinesBehindData(true);
xAxisBottom.setTextColor(CHART_TEXT_COLOR);
xAxisBottom.setAxisMinimum(0f);
xAxisBottom.setAxisMaximum(TOTAL_DAYS-1);
xAxisBottom.setGranularity(1f);
xAxisBottom.setGranularityEnabled(true);
final YAxis yAxisLeft = sleepScoreChart.getAxisLeft();
yAxisLeft.setDrawGridLines(true);
yAxisLeft.setAxisMaximum(100);
yAxisLeft.setAxisMinimum(0);
yAxisLeft.setDrawTopYLabelEntry(true);
yAxisLeft.setEnabled(true);
yAxisLeft.setTextColor(CHART_TEXT_COLOR);
final YAxis yAxisRight = sleepScoreChart.getAxisRight();
yAxisRight.setEnabled(true);
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawGridLines(false);
yAxisRight.setDrawAxisLine(true);
sleepScoreChart.setDoubleTapToZoomEnabled(false);
sleepScoreChart.getDescription().setEnabled(false);
if (TOTAL_DAYS <= 7) {
sleepScoreChart.setScaleEnabled(false);
sleepScoreChart.setTouchEnabled(false);
}
}
@Override
protected void renderCharts() {
mWeekChart.invalidate();
sleepScoreChart.invalidate();
}
@Override

View File

@ -227,6 +227,19 @@ public class GaugeDrawer {
paint);
paint.setStrokeWidth(barWidth);
float remainingAngle = 360;
float gapDegree = 1f;
if (gapBetweenSegments) {
int validSegments = segments.length;
for (int i = 0; i < segments.length; i++) {
if (segments[i] == 0) {
validSegments--;
}
}
remainingAngle = 360 - (validSegments * gapDegree);
}
float angleSum = 0;
for (int i = 0; i < segments.length; i++) {
if (segments[i] == 0) {
@ -236,12 +249,8 @@ public class GaugeDrawer {
paint.setColor(colors[i]);
paint.setStrokeWidth(barWidth);
float startAngleDegrees = 270 + angleSum * 360;
float sweepAngleDegrees = segments[i] * 360;
if (gapBetweenSegments) {
sweepAngleDegrees -= 1;
}
float startAngleDegrees = 270 + (angleSum * remainingAngle);
float sweepAngleDegrees = segments[i] * remainingAngle;
canvas.drawArc(
barMargin,
@ -254,6 +263,9 @@ public class GaugeDrawer {
paint
);
angleSum += segments[i];
if (gapBetweenSegments) {
angleSum += (gapDegree / 360f);
}
}
Paint textPaint = new Paint();

View File

@ -23,13 +23,15 @@
android:text="@string/stats_empty_value"
android:textSize="12sp" />
<com.github.mikephil.charting.charts.PieChart
android:id="@+id/sleepchart_pie_light_deep"
android:layout_width="fill_parent"
android:layout_height="200dp"
<ImageView
android:layout_weight="2"
android:layout_marginTop="15dp"
android:layout_marginBottom="10dp"
android:layout_weight="2" />
android:id="@+id/sleep_stages_gauge"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_gravity="center"
android:scaleType="fitStart" />
<LinearLayout
android:layout_width="match_parent"

View File

@ -161,6 +161,28 @@
android:layout_width="fill_parent"
android:layout_height="350dp"
/>
<LinearLayout
android:id="@+id/sleep_score_wrapper"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
>
<TextView
android:layout_marginStart="18dp"
android:layout_marginTop="15dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:textSize="20sp"
android:text="@string/sleep_score"
/>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/sleep_score_chart"
android:layout_width="fill_parent"
android:layout_height="350dp"
/>
</LinearLayout>
</LinearLayout>

View File

@ -1022,6 +1022,8 @@
<string name="sleep_avg">Sleep AVG</string>
<string name="lowest">Lowest</string>
<string name="highest">Highest</string>
<string name="sleep_score">Sleep score</string>
<string name="sleep_score_value">Score: %1d</string>
<string name="stats_empty_value">-</string>
<string name="time_empty_value" translatable="false">--:--</string>
<string name="date_placeholders__date__time">%1s, %1s</string>