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. 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);

View File

@ -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; }
} }
} }

View File

@ -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;
} }

View File

@ -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

View File

@ -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();

View File

@ -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"

View File

@ -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>

View File

@ -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>