mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Add sleep score to sleeping tabs for supported devices
This commit is contained in:
parent
51a3b5d036
commit
c3433f55cb
@ -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.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -31,7 +31,6 @@ import org.apache.commons.lang3.NotImplementedException;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.GregorianCalendar;
|
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) {
|
protected LineDataSet createDataSet(List<Entry> values, Integer color, String label) {
|
||||||
LineDataSet set1 = new LineDataSet(values, label);
|
LineDataSet set1 = new LineDataSet(values, label);
|
||||||
set1.setColor(color);
|
set1.setColor(color);
|
||||||
// set1.setDrawCubic(true);
|
|
||||||
// set1.setCubicIntensity(0.2f);
|
|
||||||
set1.setDrawFilled(true);
|
set1.setDrawFilled(true);
|
||||||
set1.setDrawCircles(false);
|
set1.setDrawCircles(false);
|
||||||
// set1.setLineWidth(2f);
|
|
||||||
// set1.setCircleSize(5f);
|
|
||||||
set1.setFillColor(color);
|
set1.setFillColor(color);
|
||||||
set1.setFillAlpha(255);
|
set1.setFillAlpha(255);
|
||||||
set1.setDrawValues(false);
|
set1.setDrawValues(false);
|
||||||
// set1.setHighLightColor(Color.rgb(128, 0, 255));
|
|
||||||
// set1.setColor(Color.rgb(89, 178, 44));
|
|
||||||
set1.setValueTextColor(CHART_TEXT_COLOR);
|
set1.setValueTextColor(CHART_TEXT_COLOR);
|
||||||
set1.setAxisDependency(YAxis.AxisDependency.LEFT);
|
set1.setAxisDependency(YAxis.AxisDependency.LEFT);
|
||||||
return set1;
|
return set1;
|
||||||
@ -366,17 +359,9 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
|
|||||||
LineDataSet set1 = new LineDataSet(values, label);
|
LineDataSet set1 = new LineDataSet(values, label);
|
||||||
set1.setLineWidth(2.2f);
|
set1.setLineWidth(2.2f);
|
||||||
set1.setColor(HEARTRATE_COLOR);
|
set1.setColor(HEARTRATE_COLOR);
|
||||||
// set1.setDrawCubic(true);
|
|
||||||
set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
||||||
set1.setCubicIntensity(0.1f);
|
set1.setCubicIntensity(0.1f);
|
||||||
set1.setDrawCircles(false);
|
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.setDrawValues(true);
|
||||||
set1.setValueTextColor(CHART_TEXT_COLOR);
|
set1.setValueTextColor(CHART_TEXT_COLOR);
|
||||||
set1.setAxisDependency(YAxis.AxisDependency.RIGHT);
|
set1.setAxisDependency(YAxis.AxisDependency.RIGHT);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* Copyright (C) 2017-2024 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
/* 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.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.BarChart;
|
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.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;
|
||||||
@ -35,10 +34,11 @@ import com.github.mikephil.charting.data.BarData;
|
|||||||
import com.github.mikephil.charting.data.BarDataSet;
|
import com.github.mikephil.charting.data.BarDataSet;
|
||||||
import com.github.mikephil.charting.data.BarEntry;
|
import com.github.mikephil.charting.data.BarEntry;
|
||||||
import com.github.mikephil.charting.data.ChartData;
|
import com.github.mikephil.charting.data.ChartData;
|
||||||
import com.github.mikephil.charting.data.PieData;
|
import com.github.mikephil.charting.data.Entry;
|
||||||
import com.github.mikephil.charting.data.PieDataSet;
|
import com.github.mikephil.charting.data.LineData;
|
||||||
import com.github.mikephil.charting.data.PieEntry;
|
import com.github.mikephil.charting.data.LineDataSet;
|
||||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
|
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -51,9 +51,11 @@ import java.util.Locale;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
|
||||||
|
|
||||||
|
|
||||||
@ -65,47 +67,11 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
|
|||||||
protected Locale mLocale;
|
protected Locale mLocale;
|
||||||
protected int mTargetValue = 0;
|
protected int mTargetValue = 0;
|
||||||
|
|
||||||
protected PieChart mTodayPieChart;
|
|
||||||
protected BarChart mWeekChart;
|
protected BarChart mWeekChart;
|
||||||
protected TextView mBalanceView;
|
protected TextView mBalanceView;
|
||||||
|
|
||||||
private int mOffsetHours = getOffsetHours();
|
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){
|
protected String getWeeksChartsLabel(Calendar day){
|
||||||
if (TOTAL_DAYS > 7) {
|
if (TOTAL_DAYS > 7) {
|
||||||
//month, show day date
|
//month, show day date
|
||||||
@ -125,19 +91,39 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
|
|||||||
long balance = 0;
|
long balance = 0;
|
||||||
long daily_balance = 0;
|
long daily_balance = 0;
|
||||||
TOTAL_DAYS_FOR_AVERAGE=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++) {
|
for (int counter = 0; counter < TOTAL_DAYS; counter++) {
|
||||||
|
// Sleep stages
|
||||||
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
|
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
|
||||||
daily_balance=calculateBalance(amounts);
|
daily_balance=calculateBalance(amounts);
|
||||||
if (daily_balance > 0) {
|
if (daily_balance > 0) {
|
||||||
TOTAL_DAYS_FOR_AVERAGE++;
|
TOTAL_DAYS_FOR_AVERAGE++;
|
||||||
}
|
}
|
||||||
|
|
||||||
balance += daily_balance;
|
balance += daily_balance;
|
||||||
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts)));
|
||||||
labels.add(getWeeksChartsLabel(day));
|
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);
|
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, "");
|
BarDataSet set = new BarDataSet(entries, "");
|
||||||
set.setColors(getColors());
|
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));
|
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();
|
day = (Calendar) day.clone(); // do not modify the caller's argument
|
||||||
List<PieEntry> entries = new ArrayList<>();
|
day.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
PieDataSet set = new PieDataSet(entries, "");
|
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);
|
TimeSampleProvider<? extends SleepScoreSample> provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession());
|
||||||
float[] totalValues = getTotalsForActivityAmounts(amounts);
|
return (List<SleepScoreSample>) provider.getAllSamples(startTs * 1000L, endTs * 1000L);
|
||||||
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]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set.setColors(getColors());
|
protected LineDataSet createSleepScoreDataSet(final List<Entry> values) {
|
||||||
|
final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.sleep_score));
|
||||||
if (totalValues.length < 2) {
|
lineDataSet.setColor(getResources().getColor(R.color.chart_light_sleep_light));
|
||||||
if (totalValue < mTargetValue) {
|
lineDataSet.setDrawCircles(false);
|
||||||
entries.add(new PieEntry((mTargetValue - totalValue)));
|
lineDataSet.setLineWidth(2f);
|
||||||
set.addColor(Color.GRAY);
|
lineDataSet.setFillAlpha(255);
|
||||||
}
|
lineDataSet.setCircleRadius(5f);
|
||||||
}
|
lineDataSet.setDrawCircles(true);
|
||||||
|
lineDataSet.setDrawCircleHole(true);
|
||||||
data.setDataSet(set);
|
lineDataSet.setCircleColor(getResources().getColor(R.color.chart_light_sleep_light));
|
||||||
|
lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT);
|
||||||
if (totalValues.length < 2) {
|
lineDataSet.setDrawValues(true);
|
||||||
data.setDrawValues(false);
|
lineDataSet.setValueTextSize(10f);
|
||||||
}
|
lineDataSet.setValueTextColor(CHART_TEXT_COLOR);
|
||||||
else {
|
lineDataSet.setValueFormatter(new ValueFormatter() {
|
||||||
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
|
@Override
|
||||||
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
|
public String getFormattedValue(float value) {
|
||||||
set.setValueTextColor(DESCRIPTION_COLOR);
|
return String.format(Locale.ROOT, "%d", (int) value);
|
||||||
set.setValueTextSize(13f);
|
|
||||||
set.setValueFormatter(getPieValueFormatter());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DayData(data, formatPieValue((long) totalValue));
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return lineDataSet;
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
@ -241,12 +228,10 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
|
|||||||
mTargetValue = goal;
|
mTargetValue = goal;
|
||||||
}
|
}
|
||||||
|
|
||||||
mTodayPieChart = rootView.findViewById(R.id.todaystepschart);
|
|
||||||
mWeekChart = rootView.findViewById(R.id.weekstepschart);
|
mWeekChart = rootView.findViewById(R.id.weekstepschart);
|
||||||
mBalanceView = rootView.findViewById(R.id.balance);
|
mBalanceView = rootView.findViewById(R.id.balance);
|
||||||
|
|
||||||
setupWeekChart();
|
setupWeekChart();
|
||||||
setupTodayPieChart();
|
|
||||||
|
|
||||||
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
||||||
refresh();
|
refresh();
|
||||||
@ -254,20 +239,6 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
|
|||||||
return rootView;
|
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() {
|
protected void setupWeekChart() {
|
||||||
mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
|
mWeekChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||||
mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||||
@ -323,29 +294,13 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
|
|||||||
return super.getAllSamples(db, device, tsFrom, tsTo);
|
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 {
|
protected static class MyChartsData extends ChartsData {
|
||||||
private final WeekChartsData<BarData> weekBeforeData;
|
private final WeekChartsData<BarData> weekBeforeData;
|
||||||
private final DayData dayData;
|
|
||||||
|
|
||||||
MyChartsData(DayData dayData, WeekChartsData<BarData> weekBeforeData) {
|
MyChartsData(WeekChartsData<BarData> weekBeforeData) {
|
||||||
this.dayData = dayData;
|
|
||||||
this.weekBeforeData = weekBeforeData;
|
this.weekBeforeData = weekBeforeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
DayData getDayData() {
|
|
||||||
return dayData;
|
|
||||||
}
|
|
||||||
|
|
||||||
WeekChartsData<BarData> getWeekBeforeData() {
|
WeekChartsData<BarData> getWeekBeforeData() {
|
||||||
return weekBeforeData;
|
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 String getAverage(float value);
|
||||||
|
|
||||||
abstract int getGoal();
|
abstract int getGoal();
|
||||||
@ -410,14 +370,23 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra
|
|||||||
|
|
||||||
protected class WeekChartsData<T extends ChartData<?>> extends DefaultChartsData<T> {
|
protected class WeekChartsData<T extends ChartData<?>> extends DefaultChartsData<T> {
|
||||||
private final String balanceMessage;
|
private final String balanceMessage;
|
||||||
|
private LineData sleepScoresLineData;
|
||||||
|
|
||||||
public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) {
|
public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) {
|
||||||
super(data, xIndexLabelFormatter);
|
super(data, xIndexLabelFormatter);
|
||||||
this.balanceMessage = balanceMessage;
|
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() {
|
public String getBalanceMessage() {
|
||||||
return balanceMessage;
|
return balanceMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LineData getSleepScoreData() { return sleepScoresLineData; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
/* 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.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -23,21 +23,21 @@ import android.graphics.Color;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.method.ScrollingMovementMethod;
|
import android.text.method.ScrollingMovementMethod;
|
||||||
|
import android.util.TypedValue;
|
||||||
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.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.github.mikephil.charting.animation.Easing;
|
import com.github.mikephil.charting.animation.Easing;
|
||||||
import com.github.mikephil.charting.charts.Chart;
|
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.components.*;
|
import com.github.mikephil.charting.components.*;
|
||||||
import com.github.mikephil.charting.data.LineData;
|
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.apache.commons.lang3.tuple.Triple;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -52,10 +52,13 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis.SleepSession;
|
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.database.DBHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
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.SleepScoreSample;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
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);
|
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
|
||||||
|
|
||||||
private LineChart mActivityChart;
|
private LineChart mActivityChart;
|
||||||
private PieChart mSleepAmountChart;
|
private ImageView sleepStagesGauge;
|
||||||
private TextView mSleepchartInfo;
|
private TextView mSleepchartInfo;
|
||||||
private TextView remSleepTimeText;
|
private TextView remSleepTimeText;
|
||||||
private LinearLayout remSleepTimeTextWrapper;
|
private LinearLayout remSleepTimeTextWrapper;
|
||||||
@ -106,8 +109,11 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
} else {
|
} else {
|
||||||
samples = getSamplesofSleep(db, device);
|
samples = getSamplesofSleep(db, device);
|
||||||
}
|
}
|
||||||
|
List<? extends SleepScoreSample> sleepScoreSamples = new ArrayList<>();
|
||||||
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
|
if (supportsSleepScore()) {
|
||||||
|
sleepScoreSamples = getSleepScoreSamples(db, device, getTSStart(), getTSEnd());
|
||||||
|
}
|
||||||
|
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples, sleepScoreSamples);
|
||||||
|
|
||||||
if (!CHARTS_SLEEP_RANGE_24H) {
|
if (!CHARTS_SLEEP_RANGE_24H) {
|
||||||
if (mySleepChartsData.sleepSessions.size() > 0) {
|
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();
|
SleepAnalysis sleepAnalysis = new SleepAnalysis();
|
||||||
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
|
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
|
||||||
|
|
||||||
PieData data = new PieData();
|
|
||||||
|
|
||||||
|
|
||||||
final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
|
final long lightSleepDuration = calculateLightSleepDuration(sleepSessions);
|
||||||
final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
|
final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions);
|
||||||
final long remSleepDuration = calculateRemSleepDuration(sleepSessions);
|
final long remSleepDuration = calculateRemSleepDuration(sleepSessions);
|
||||||
final long awakeSleepDuration = calculateAwakeSleepDuration(sleepSessions);
|
final long awakeSleepDuration = calculateAwakeSleepDuration(sleepSessions);
|
||||||
final long totalSeconds = lightSleepDuration + deepSleepDuration + remSleepDuration;
|
final long totalSeconds = lightSleepDuration + deepSleepDuration + remSleepDuration;
|
||||||
|
|
||||||
final List<PieEntry> entries = new ArrayList<>();
|
int sleepScore = 0;
|
||||||
final List<Integer> colors = new ArrayList<>();
|
if (!sleepScoreSamples.isEmpty()) {
|
||||||
|
sleepScore = sleepScoreSamples.get(0).getSleepScore();
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportsAwakeSleep(mGBDevice)) {
|
return new MySleepChartsData(sleepSessions, totalSeconds, awakeSleepDuration, remSleepDuration, deepSleepDuration, lightSleepDuration, sleepScore);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long calculateLightSleepDuration(List<SleepSession> sleepSessions) {
|
private long calculateLightSleepDuration(List<SleepSession> sleepSessions) {
|
||||||
@ -216,6 +186,45 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
return result;
|
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
|
@Override
|
||||||
protected void updateChartsnUIThread(MyChartsData mcd) {
|
protected void updateChartsnUIThread(MyChartsData mcd) {
|
||||||
MySleepChartsData pieData = mcd.getPieData();
|
MySleepChartsData pieData = mcd.getPieData();
|
||||||
@ -224,15 +233,13 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
|
String formattedDate = new SimpleDateFormat("E, MMM dd").format(date);
|
||||||
sleepDateText.setText(formattedDate);
|
sleepDateText.setText(formattedDate);
|
||||||
|
|
||||||
pieData.pieData.setDrawValues(false);
|
sleepStagesGaugeUpdate(pieData);
|
||||||
mSleepAmountChart.setTouchEnabled(false);
|
|
||||||
mSleepAmountChart.setCenterTextColor(GBApplication.getTextColor(getContext()));
|
|
||||||
mSleepAmountChart.setCenterText(pieData.getTotalSleep());
|
|
||||||
if (!pieData.sleepSessions.isEmpty()) {
|
if (!pieData.sleepSessions.isEmpty()) {
|
||||||
awakeSleepTimeText.setText(pieData.getTotalAwake());
|
awakeSleepTimeText.setText(timeStringFormat(pieData.getTotalAwake()));
|
||||||
remSleepTimeText.setText(pieData.getTotalRem());
|
remSleepTimeText.setText(timeStringFormat(pieData.getTotalRem()));
|
||||||
deepSleepTimeText.setText(pieData.getTotalDeep());
|
deepSleepTimeText.setText(timeStringFormat(pieData.getTotalDeep()));
|
||||||
lightSleepTimeText.setText(pieData.getTotalLight());
|
lightSleepTimeText.setText(timeStringFormat(pieData.getTotalLight()));
|
||||||
} else {
|
} else {
|
||||||
awakeSleepTimeText.setText("-");
|
awakeSleepTimeText.setText("-");
|
||||||
remSleepTimeText.setText("-");
|
remSleepTimeText.setText("-");
|
||||||
@ -245,9 +252,6 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
if (!supportsAwakeSleep(getChartsHost().getDevice())) {
|
if (!supportsAwakeSleep(getChartsHost().getDevice())) {
|
||||||
awakeSleepTimeTextWrapper.setVisibility(View.GONE);
|
awakeSleepTimeTextWrapper.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
mSleepAmountChart.setCenterTextSize(18f);
|
|
||||||
mSleepAmountChart.setHoleColor(getContext().getResources().getColor(R.color.transparent));
|
|
||||||
mSleepAmountChart.setData(pieData.getPieData());
|
|
||||||
mSleepchartInfo.setText(buildYouSleptText(pieData));
|
mSleepchartInfo.setText(buildYouSleptText(pieData));
|
||||||
mSleepchartInfo.setMovementMethod(new ScrollingMovementMethod());
|
mSleepchartInfo.setMovementMethod(new ScrollingMovementMethod());
|
||||||
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
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);
|
movementIntensityTextWrapper.setVisibility(intensityTotal > 0 ? View.VISIBLE : View.GONE);
|
||||||
dummyTile.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
|
if (!CHARTS_SLEEP_RANGE_24H
|
||||||
&& supportsHeartrate(getChartsHost().getDevice())
|
&& supportsHeartrate(getChartsHost().getDevice())
|
||||||
&& SHOW_CHARTS_AVERAGE) {
|
&& SHOW_CHARTS_AVERAGE) {
|
||||||
@ -366,6 +366,11 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean supportsSleepScore() {
|
||||||
|
final GBDevice device = getChartsHost().getDevice();
|
||||||
|
return device.getDeviceCoordinator().supportsSleepScore();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
return getString(R.string.sleepchart_your_sleep);
|
return getString(R.string.sleepchart_your_sleep);
|
||||||
@ -383,7 +388,7 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
}
|
}
|
||||||
|
|
||||||
mActivityChart = rootView.findViewById(R.id.sleepchart);
|
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);
|
mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
|
||||||
remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
|
remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
|
||||||
remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
|
remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
|
||||||
@ -401,7 +406,6 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
mSleepchartInfo.setMaxLines(sleepLinesLimit);
|
mSleepchartInfo.setMaxLines(sleepLinesLimit);
|
||||||
|
|
||||||
setupActivityChart();
|
setupActivityChart();
|
||||||
setupSleepAmountChart();
|
|
||||||
|
|
||||||
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
||||||
refresh();
|
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() {
|
private void setupActivityChart() {
|
||||||
mActivityChart.setBackgroundColor(BACKGROUND_COLOR);
|
mActivityChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||||
mActivityChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
mActivityChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||||
@ -448,14 +442,10 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
|
|
||||||
YAxis y = mActivityChart.getAxisLeft();
|
YAxis y = mActivityChart.getAxisLeft();
|
||||||
y.setDrawGridLines(false);
|
y.setDrawGridLines(false);
|
||||||
// y.setDrawLabels(false);
|
|
||||||
// TODO: make fixed max value optional
|
|
||||||
y.setAxisMaximum(1f);
|
y.setAxisMaximum(1f);
|
||||||
y.setAxisMinimum(0);
|
y.setAxisMinimum(0);
|
||||||
y.setDrawTopYLabelEntry(false);
|
y.setDrawTopYLabelEntry(false);
|
||||||
y.setTextColor(CHART_TEXT_COLOR);
|
y.setTextColor(CHART_TEXT_COLOR);
|
||||||
|
|
||||||
// y.setLabelCount(5);
|
|
||||||
y.setEnabled(true);
|
y.setEnabled(true);
|
||||||
|
|
||||||
YAxis yAxisRight = mActivityChart.getAxisRight();
|
YAxis yAxisRight = mActivityChart.getAxisRight();
|
||||||
@ -513,59 +503,61 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment<DaySlee
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
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);
|
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
|
@Override
|
||||||
protected void renderCharts() {
|
protected void renderCharts() {
|
||||||
mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||||
mSleepAmountChart.invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MySleepChartsData extends ChartsData {
|
private static class MySleepChartsData extends ChartsData {
|
||||||
private String totalSleep;
|
private long totalSleep;
|
||||||
private String totalAwake;
|
private long totalAwake;
|
||||||
private String totalRem;
|
private long totalRem;
|
||||||
private String totalDeep;
|
private long totalDeep;
|
||||||
private String totalLight;
|
private long totalLight;
|
||||||
private final PieData pieData;
|
private int sleepScore;
|
||||||
private final List<SleepSession> sleepSessions;
|
private final List<SleepSession> sleepSessions;
|
||||||
|
|
||||||
public MySleepChartsData(PieData pieData, List<SleepSession> sleepSessions, String totalSleep, String totalAwake, String totalRem, String totalDeep, String totalLight) {
|
public MySleepChartsData(List<SleepSession> sleepSessions, long totalSleep, long totalAwake, long totalRem, long totalDeep, long totalLight, int sleepScore) {
|
||||||
this.pieData = pieData;
|
|
||||||
this.sleepSessions = sleepSessions;
|
this.sleepSessions = sleepSessions;
|
||||||
this.totalAwake = totalAwake;
|
this.totalAwake = totalAwake;
|
||||||
this.totalSleep = totalSleep;
|
this.totalSleep = totalSleep;
|
||||||
this.totalRem = totalRem;
|
this.totalRem = totalRem;
|
||||||
this.totalDeep = totalDeep;
|
this.totalDeep = totalDeep;
|
||||||
this.totalLight = totalLight;
|
this.totalLight = totalLight;
|
||||||
|
this.sleepScore = sleepScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PieData getPieData() {
|
public long getTotalSleep() {
|
||||||
return pieData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CharSequence getTotalSleep() {
|
|
||||||
return totalSleep;
|
return totalSleep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getTotalAwake() {
|
public long getTotalAwake() {
|
||||||
return totalAwake;
|
return totalAwake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getTotalRem() {
|
public long getTotalRem() {
|
||||||
return totalRem;
|
return totalRem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getTotalDeep() {
|
public long getTotalDeep() {
|
||||||
return totalDeep;
|
return totalDeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getTotalLight() {
|
public long getTotalLight() {
|
||||||
return totalLight;
|
return totalLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSleepScore() {return sleepScore;}
|
||||||
|
|
||||||
public List<SleepSession> getSleepSessions() {
|
public List<SleepSession> getSleepSessions() {
|
||||||
return sleepSessions;
|
return sleepSessions;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, José Rebelo,
|
/* 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.
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
@ -25,8 +25,11 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.Chart;
|
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.Legend;
|
||||||
import com.github.mikephil.charting.components.LegendEntry;
|
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.data.BarData;
|
||||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||||
|
|
||||||
@ -60,6 +63,8 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
|||||||
private TextView lightSleepTimeText;
|
private TextView lightSleepTimeText;
|
||||||
private TextView sleepDatesText;
|
private TextView sleepDatesText;
|
||||||
private MySleepWeeklyData mySleepWeeklyData;
|
private MySleepWeeklyData mySleepWeeklyData;
|
||||||
|
private LinearLayout sleepScoreWrapper;
|
||||||
|
private LineChart sleepScoreChart;
|
||||||
|
|
||||||
public static WeekSleepChartFragment newInstance ( int totalDays ) {
|
public static WeekSleepChartFragment newInstance ( int totalDays ) {
|
||||||
WeekSleepChartFragment fragmentFirst = new WeekSleepChartFragment();
|
WeekSleepChartFragment fragmentFirst = new WeekSleepChartFragment();
|
||||||
@ -118,6 +123,8 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mWeekChart = rootView.findViewById(R.id.weekstepschart);
|
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);
|
remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time);
|
||||||
remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
|
remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper);
|
||||||
awakeSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_awake_time);
|
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);
|
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 immediately instead of use refreshIfVisible(), for perceived performance
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
@ -155,6 +167,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
|||||||
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
|
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
|
||||||
mWeekChart.getBarData().setValueTextSize(10f);
|
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"
|
// 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;
|
final int barIgnoreLast = supportsAwakeSleep(getChartsHost().getDevice()) ? 1 : 0;
|
||||||
mWeekChart.getBarData().setValueFormatter(new BarChartStackedTimeValueFormatter(false, "", 0, barIgnoreLast));
|
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);
|
WeekChartsData<BarData> weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device);
|
||||||
mySleepWeeklyData = getMySleepWeeklyData(db, 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
|
@Override
|
||||||
protected void renderCharts() {
|
protected void renderCharts() {
|
||||||
mWeekChart.invalidate();
|
mWeekChart.invalidate();
|
||||||
|
sleepScoreChart.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -227,6 +227,19 @@ public class GaugeDrawer {
|
|||||||
paint);
|
paint);
|
||||||
paint.setStrokeWidth(barWidth);
|
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;
|
float angleSum = 0;
|
||||||
for (int i = 0; i < segments.length; i++) {
|
for (int i = 0; i < segments.length; i++) {
|
||||||
if (segments[i] == 0) {
|
if (segments[i] == 0) {
|
||||||
@ -236,12 +249,8 @@ public class GaugeDrawer {
|
|||||||
paint.setColor(colors[i]);
|
paint.setColor(colors[i]);
|
||||||
paint.setStrokeWidth(barWidth);
|
paint.setStrokeWidth(barWidth);
|
||||||
|
|
||||||
float startAngleDegrees = 270 + angleSum * 360;
|
float startAngleDegrees = 270 + (angleSum * remainingAngle);
|
||||||
float sweepAngleDegrees = segments[i] * 360;
|
float sweepAngleDegrees = segments[i] * remainingAngle;
|
||||||
|
|
||||||
if (gapBetweenSegments) {
|
|
||||||
sweepAngleDegrees -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawArc(
|
canvas.drawArc(
|
||||||
barMargin,
|
barMargin,
|
||||||
@ -254,6 +263,9 @@ public class GaugeDrawer {
|
|||||||
paint
|
paint
|
||||||
);
|
);
|
||||||
angleSum += segments[i];
|
angleSum += segments[i];
|
||||||
|
if (gapBetweenSegments) {
|
||||||
|
angleSum += (gapDegree / 360f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Paint textPaint = new Paint();
|
Paint textPaint = new Paint();
|
||||||
|
@ -23,13 +23,15 @@
|
|||||||
android:text="@string/stats_empty_value"
|
android:text="@string/stats_empty_value"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp" />
|
||||||
|
|
||||||
<com.github.mikephil.charting.charts.PieChart
|
<ImageView
|
||||||
android:id="@+id/sleepchart_pie_light_deep"
|
android:layout_weight="2"
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="200dp"
|
|
||||||
android:layout_marginTop="15dp"
|
android:layout_marginTop="15dp"
|
||||||
android:layout_marginBottom="10dp"
|
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
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -161,6 +161,28 @@
|
|||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="350dp"
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -1022,6 +1022,8 @@
|
|||||||
<string name="sleep_avg">Sleep AVG</string>
|
<string name="sleep_avg">Sleep AVG</string>
|
||||||
<string name="lowest">Lowest</string>
|
<string name="lowest">Lowest</string>
|
||||||
<string name="highest">Highest</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="stats_empty_value">-</string>
|
||||||
<string name="time_empty_value" translatable="false">--:--</string>
|
<string name="time_empty_value" translatable="false">--:--</string>
|
||||||
<string name="date_placeholders__date__time">%1s, %1s</string>
|
<string name="date_placeholders__date__time">%1s, %1s</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user