diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java index e118ee9f9..5fef1c0a2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java @@ -1,4 +1,4 @@ -/* Copyright (C) 2023-2024 Daniel Dakhno, José Rebelo +/* Copyright (C) 2023-2024 Daniel Dakhno, José Rebelo, a0z This file is part of Gadgetbridge. @@ -31,7 +31,6 @@ import org.apache.commons.lang3.NotImplementedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; @@ -346,17 +345,11 @@ public abstract class AbstractActivityChartFragment extend protected LineDataSet createDataSet(List values, Integer color, String label) { LineDataSet set1 = new LineDataSet(values, label); set1.setColor(color); -// set1.setDrawCubic(true); -// set1.setCubicIntensity(0.2f); set1.setDrawFilled(true); set1.setDrawCircles(false); -// set1.setLineWidth(2f); -// set1.setCircleSize(5f); set1.setFillColor(color); set1.setFillAlpha(255); set1.setDrawValues(false); -// set1.setHighLightColor(Color.rgb(128, 0, 255)); -// set1.setColor(Color.rgb(89, 178, 44)); set1.setValueTextColor(CHART_TEXT_COLOR); set1.setAxisDependency(YAxis.AxisDependency.LEFT); return set1; @@ -366,17 +359,9 @@ public abstract class AbstractActivityChartFragment extend LineDataSet set1 = new LineDataSet(values, label); set1.setLineWidth(2.2f); set1.setColor(HEARTRATE_COLOR); -// set1.setDrawCubic(true); set1.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER); set1.setCubicIntensity(0.1f); set1.setDrawCircles(false); -// set1.setCircleRadius(2f); -// set1.setDrawFilled(true); -// set1.setColor(getResources().getColor(android.R.color.background_light)); -// set1.setCircleColor(HEARTRATE_COLOR); -// set1.setFillColor(ColorTemplate.getHoloBlue()); -// set1.setHighLightColor(Color.rgb(128, 0, 255)); -// set1.setColor(Color.rgb(89, 178, 44)); set1.setDrawValues(true); set1.setValueTextColor(CHART_TEXT_COLOR); set1.setAxisDependency(YAxis.AxisDependency.RIGHT); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java index 28d09bee0..96f3eafc6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java @@ -1,5 +1,5 @@ /* Copyright (C) 2017-2024 Alberto, Andreas Shimokawa, Carsten Pfeiffer, - Daniele Gobbetti, José Rebelo, Pavel Elagin, Petr Vaněk + Daniele Gobbetti, José Rebelo, Pavel Elagin, Petr Vaněk, a0z This file is part of Gadgetbridge. @@ -27,7 +27,6 @@ import android.view.ViewGroup; import android.widget.TextView; import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.components.LimitLine; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.YAxis; @@ -35,10 +34,11 @@ import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.formatter.ValueFormatter; +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,9 +51,11 @@ import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; @@ -65,47 +67,11 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra protected Locale mLocale; protected int mTargetValue = 0; - protected PieChart mTodayPieChart; protected BarChart mWeekChart; protected TextView mBalanceView; private int mOffsetHours = getOffsetHours(); - @Override - protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { - Calendar day = Calendar.getInstance(); - day.setTime(chartsHost.getEndDate()); - //NB: we could have omitted the day, but this way we can move things to the past easily - DayData dayData = refreshDayPie(db, day, device); - WeekChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device); - - return new MyChartsData(dayData, weekBeforeData); - } - - @Override - protected void updateChartsnUIThread(MyChartsData mcd) { - setupLegend(mWeekChart); - mTodayPieChart.setCenterText(mcd.getDayData().centerText); - mTodayPieChart.setData(mcd.getDayData().data); - //set custom renderer for 30days bar charts - if (GBApplication.getPrefs().getBoolean("charts_range", true)) { - mWeekChart.setRenderer(new AngledLabelsChartRenderer(mWeekChart, mWeekChart.getAnimator(), mWeekChart.getViewPortHandler())); - } - - mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 - mWeekChart.setData(mcd.getWeekBeforeData().getData()); - mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter()); - - mBalanceView.setText(mcd.getWeekBeforeData().getBalanceMessage()); - } - - @Override - protected void renderCharts() { - mWeekChart.invalidate(); - mTodayPieChart.invalidate(); -// mBalanceView.setText(getBalanceMessage(balance)); - } - protected String getWeeksChartsLabel(Calendar day){ if (TOTAL_DAYS > 7) { //month, show day date @@ -125,19 +91,39 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra long balance = 0; long daily_balance = 0; TOTAL_DAYS_FOR_AVERAGE=0; - + List sleepScoreEntities = new ArrayList<>(); + final List sleepScoreDataSets = new ArrayList<>(); for (int counter = 0; counter < TOTAL_DAYS; counter++) { + // Sleep stages ActivityAmounts amounts = getActivityAmountsForDay(db, day, device); daily_balance=calculateBalance(amounts); if (daily_balance > 0) { TOTAL_DAYS_FOR_AVERAGE++; } - balance += daily_balance; entries.add(new BarEntry(counter, getTotalsForActivityAmounts(amounts))); labels.add(getWeeksChartsLabel(day)); + // Sleep score + if (supportsSleepScore()) { + List 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 clone = new ArrayList<>(sleepScoreEntities.size()); + clone.addAll(sleepScoreEntities); + sleepScoreDataSets.add(createSleepScoreDataSet(clone)); + sleepScoreEntities.clear(); + } + } + } day.add(Calendar.DATE, 1); } + if (!sleepScoreEntities.isEmpty()) { + sleepScoreDataSets.add(createSleepScoreDataSet(sleepScoreEntities)); + } + final LineData sleepScoreLineData = new LineData(sleepScoreDataSets); + sleepScoreLineData.setHighlightEnabled(false); BarDataSet set = new BarDataSet(entries, ""); set.setColors(getColors()); @@ -179,50 +165,51 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra } } + if (supportsSleepScore()) { + return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue), sleepScoreLineData); + } return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue)); } - protected DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) { + protected List getSleepScoreSamples(DBHandler db, GBDevice device, Calendar day) { + int startTs; + int endTs; - PieData data = new PieData(); - List entries = new ArrayList<>(); - PieDataSet set = new PieDataSet(entries, ""); + day = (Calendar) day.clone(); // do not modify the caller's argument + day.set(Calendar.HOUR_OF_DAY, 0); + day.set(Calendar.MINUTE, 0); + day.set(Calendar.SECOND, 0); + day.add(Calendar.HOUR, 0); + startTs = (int) (day.getTimeInMillis() / 1000); + endTs = startTs + 24 * 60 * 60 - 1; - ActivityAmounts amounts = getActivityAmountsForDay(db, day, device); - float[] totalValues = getTotalsForActivityAmounts(amounts); - String[] pieLabels = getPieLabels(); - float totalValue = 0; - for (int i = 0; i < totalValues.length; i++) { - float value = totalValues[i]; - totalValue += value; - entries.add(new PieEntry(value, pieLabels[i])); - } - - set.setColors(getColors()); - - if (totalValues.length < 2) { - if (totalValue < mTargetValue) { - entries.add(new PieEntry((mTargetValue - totalValue))); - set.addColor(Color.GRAY); - } - } - - data.setDataSet(set); - - if (totalValues.length < 2) { - data.setDrawValues(false); - } - else { - set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); - set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); - set.setValueTextColor(DESCRIPTION_COLOR); - set.setValueTextSize(13f); - set.setValueFormatter(getPieValueFormatter()); - } - - return new DayData(data, formatPieValue((long) totalValue)); + TimeSampleProvider provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession()); + return (List) provider.getAllSamples(startTs * 1000L, endTs * 1000L); } + protected LineDataSet createSleepScoreDataSet(final List values) { + final LineDataSet lineDataSet = new LineDataSet(values, getString(R.string.sleep_score)); + lineDataSet.setColor(getResources().getColor(R.color.chart_light_sleep_light)); + lineDataSet.setDrawCircles(false); + lineDataSet.setLineWidth(2f); + lineDataSet.setFillAlpha(255); + lineDataSet.setCircleRadius(5f); + lineDataSet.setDrawCircles(true); + lineDataSet.setDrawCircleHole(true); + lineDataSet.setCircleColor(getResources().getColor(R.color.chart_light_sleep_light)); + lineDataSet.setAxisDependency(YAxis.AxisDependency.LEFT); + lineDataSet.setDrawValues(true); + lineDataSet.setValueTextSize(10f); + lineDataSet.setValueTextColor(CHART_TEXT_COLOR); + lineDataSet.setValueFormatter(new ValueFormatter() { + @Override + public String getFormattedValue(float value) { + return String.format(Locale.ROOT, "%d", (int) value); + } + }); + return lineDataSet; + }; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -241,12 +228,10 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra mTargetValue = goal; } - mTodayPieChart = rootView.findViewById(R.id.todaystepschart); mWeekChart = rootView.findViewById(R.id.weekstepschart); mBalanceView = rootView.findViewById(R.id.balance); setupWeekChart(); - setupTodayPieChart(); // refresh immediately instead of use refreshIfVisible(), for perceived performance refresh(); @@ -254,20 +239,6 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra return rootView; } - - - - - protected void setupTodayPieChart() { - mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR); - mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR); - mTodayPieChart.setEntryLabelColor(DESCRIPTION_COLOR); - mTodayPieChart.getDescription().setText(getPieDescription(mTargetValue)); -// mTodayPieChart.setNoDataTextDescription(""); - mTodayPieChart.setNoDataText(""); - mTodayPieChart.getLegend().setEnabled(false); - } - protected void setupWeekChart() { mWeekChart.setBackgroundColor(BACKGROUND_COLOR); mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR); @@ -323,29 +294,13 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra return super.getAllSamples(db, device, tsFrom, tsTo); } - private static class DayData { - private final PieData data; - private final CharSequence centerText; - - DayData(PieData data, String centerText) { - this.data = data; - this.centerText = centerText; - } - } - protected static class MyChartsData extends ChartsData { private final WeekChartsData weekBeforeData; - private final DayData dayData; - MyChartsData(DayData dayData, WeekChartsData weekBeforeData) { - this.dayData = dayData; + MyChartsData(WeekChartsData weekBeforeData) { this.weekBeforeData = weekBeforeData; } - DayData getDayData() { - return dayData; - } - WeekChartsData getWeekBeforeData() { return weekBeforeData; } @@ -382,6 +337,11 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra } } + public boolean supportsSleepScore() { + final GBDevice device = getChartsHost().getDevice(); + return device.getDeviceCoordinator().supportsSleepScore(); + } + abstract String getAverage(float value); abstract int getGoal(); @@ -410,14 +370,23 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra protected class WeekChartsData> extends DefaultChartsData { private final String balanceMessage; + private LineData sleepScoresLineData; public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) { super(data, xIndexLabelFormatter); this.balanceMessage = balanceMessage; } + public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage, LineData sleepScores) { + super(data, xIndexLabelFormatter); + this.balanceMessage = balanceMessage; + this.sleepScoresLineData = sleepScores; + } + public String getBalanceMessage() { return balanceMessage; } + + public LineData getSleepScoreData() { return sleepScoresLineData; } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java index 6482647ef..a9c91250d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/DaySleepChartFragment.java @@ -1,5 +1,5 @@ /* Copyright (C) 2015-2024 Andreas Shimokawa, Carsten Pfeiffer, Daniele - Gobbetti, Dikay900, José Rebelo, ozkanpakdil, Pavel Elagin, Petr Vaněk, Q-er + Gobbetti, Dikay900, José Rebelo, ozkanpakdil, Pavel Elagin, Petr Vaněk, Q-er, a0z This file is part of Gadgetbridge. @@ -23,21 +23,21 @@ import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.text.method.ScrollingMovementMethod; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.core.content.ContextCompat; + import com.github.mikephil.charting.animation.Easing; import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.components.*; import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; import org.apache.commons.lang3.tuple.Triple; import org.slf4j.Logger; @@ -52,10 +52,13 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis.SleepSession; +import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -64,7 +67,7 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment sleepScoreSamples = new ArrayList<>(); + if (supportsSleepScore()) { + sleepScoreSamples = getSleepScoreSamples(db, device, getTSStart(), getTSEnd()); + } + MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples, sleepScoreSamples); if (!CHARTS_SLEEP_RANGE_24H) { if (mySleepChartsData.sleepSessions.size() > 0) { @@ -130,58 +136,22 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment samples) { + private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List samples, List sleepScoreSamples) { SleepAnalysis sleepAnalysis = new SleepAnalysis(); List sleepSessions = sleepAnalysis.calculateSleepSessions(samples); - PieData data = new PieData(); - - final long lightSleepDuration = calculateLightSleepDuration(sleepSessions); final long deepSleepDuration = calculateDeepSleepDuration(sleepSessions); final long remSleepDuration = calculateRemSleepDuration(sleepSessions); final long awakeSleepDuration = calculateAwakeSleepDuration(sleepSessions); final long totalSeconds = lightSleepDuration + deepSleepDuration + remSleepDuration; - final List entries = new ArrayList<>(); - final List colors = new ArrayList<>(); - - if (!sleepSessions.isEmpty()) { - entries.add(new PieEntry(lightSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_light_sleep))); - entries.add(new PieEntry(deepSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_deep_sleep))); - colors.add(getColorFor(ActivityKind.LIGHT_SLEEP)); - colors.add(getColorFor(ActivityKind.DEEP_SLEEP)); - - if (supportsRemSleep(mGBDevice)) { - entries.add(new PieEntry(remSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_rem_sleep))); - colors.add(getColorFor(ActivityKind.REM_SLEEP)); - } - - if (supportsAwakeSleep(mGBDevice)) { - entries.add(new PieEntry(awakeSleepDuration, getActivity().getString(R.string.abstract_chart_fragment_kind_awake_sleep))); - colors.add(getColorFor(ActivityKind.AWAKE_SLEEP)); - } - } else { - entries.add(new PieEntry(1)); - colors.add(getResources().getColor(R.color.gauge_line_color)); + int sleepScore = 0; + if (!sleepScoreSamples.isEmpty()) { + sleepScore = sleepScoreSamples.get(0).getSleepScore(); } - PieDataSet set = new PieDataSet(entries, ""); - set.setSliceSpace(2f); - set.setColors(colors); - set.setValueTextColor(DESCRIPTION_COLOR); - set.setValueTextSize(13f); - set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); - set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); - data.setDataSet(set); - - String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS); - String totalAwake = DateTimeUtils.formatDurationHoursMinutes(awakeSleepDuration, TimeUnit.SECONDS); - String totalRem = DateTimeUtils.formatDurationHoursMinutes(remSleepDuration, TimeUnit.SECONDS); - String totalDeep = DateTimeUtils.formatDurationHoursMinutes(deepSleepDuration, TimeUnit.SECONDS); - String totalLight = DateTimeUtils.formatDurationHoursMinutes(lightSleepDuration, TimeUnit.SECONDS); - //setupLegend(pieChart); - return new MySleepChartsData(data, sleepSessions, totalSleep, totalAwake, totalRem, totalDeep, totalLight); + return new MySleepChartsData(sleepSessions, totalSeconds, awakeSleepDuration, remSleepDuration, deepSleepDuration, lightSleepDuration, sleepScore); } private long calculateLightSleepDuration(List sleepSessions) { @@ -216,6 +186,45 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment 0 ? (float) pieData.getTotalLight() / total : 0, + pieData.getTotalDeep() > 0 ? (float) pieData.getTotalDeep() / total : 0, + pieData.getTotalRem() > 0 ? (float) pieData.getTotalRem() / total : 0, + pieData.getTotalAwake() > 0 ? (float) pieData.getTotalAwake() / total : 0, + }; + final int width = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 300, + GBApplication.getContext().getResources().getDisplayMetrics() + ); + String lowerText = ""; + if (supportsSleepScore()) { + lowerText = GBApplication.getContext().getString(R.string.sleep_score_value, pieData.getSleepScore()); + } + sleepStagesGauge.setImageBitmap(GaugeDrawer.drawCircleGaugeSegmented( + width, + width / 15, + colors, + segments, + true, + String.valueOf(timeStringFormat(pieData.getTotalSleep())), + lowerText, + getContext() + )); + } + + private String timeStringFormat(long seconds) { + return DateTimeUtils.formatDurationHoursMinutes(seconds, TimeUnit.SECONDS); + } + @Override protected void updateChartsnUIThread(MyChartsData mcd) { MySleepChartsData pieData = mcd.getPieData(); @@ -224,15 +233,13 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment 0 ? View.VISIBLE : View.GONE); dummyTile.setVisibility(intensityTotal > 0 ? View.VISIBLE : View.GONE); - mSleepAmountChart.setHoleRadius(85); - mSleepAmountChart.setDrawEntryLabels(false); - mSleepAmountChart.getLegend().setEnabled(false); - if (!CHARTS_SLEEP_RANGE_24H && supportsHeartrate(getChartsHost().getDevice()) && SHOW_CHARTS_AVERAGE) { @@ -366,6 +366,11 @@ public class DaySleepChartFragment extends AbstractActivityChartFragment getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { -// temporary fix for totally wrong sleep amounts + // Temporary fix for totally wrong sleep amounts. return super.getAllSamples(db, device, tsFrom, tsTo); } + protected List getSleepScoreSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + TimeSampleProvider provider = device.getDeviceCoordinator().getSleepScoreProvider(device, db.getDaoSession()); + return (List) provider.getAllSamples(tsFrom * 1000L, tsTo * 1000L); + } + @Override protected void renderCharts() { mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart); - mSleepAmountChart.invalidate(); } private static class MySleepChartsData extends ChartsData { - private String totalSleep; - private String totalAwake; - private String totalRem; - private String totalDeep; - private String totalLight; - private final PieData pieData; + private long totalSleep; + private long totalAwake; + private long totalRem; + private long totalDeep; + private long totalLight; + private int sleepScore; private final List sleepSessions; - public MySleepChartsData(PieData pieData, List sleepSessions, String totalSleep, String totalAwake, String totalRem, String totalDeep, String totalLight) { - this.pieData = pieData; + public MySleepChartsData(List sleepSessions, long totalSleep, long totalAwake, long totalRem, long totalDeep, long totalLight, int sleepScore) { this.sleepSessions = sleepSessions; this.totalAwake = totalAwake; this.totalSleep = totalSleep; this.totalRem = totalRem; this.totalDeep = totalDeep; this.totalLight = totalLight; + this.sleepScore = sleepScore; } - public PieData getPieData() { - return pieData; - } - - public CharSequence getTotalSleep() { + public long getTotalSleep() { return totalSleep; } - public CharSequence getTotalAwake() { + public long getTotalAwake() { return totalAwake; } - public CharSequence getTotalRem() { + public long getTotalRem() { return totalRem; } - public CharSequence getTotalDeep() { + public long getTotalDeep() { return totalDeep; } - public CharSequence getTotalLight() { + public long getTotalLight() { return totalLight; } + public int getSleepScore() {return sleepScore;} + public List getSleepSessions() { return sleepSessions; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java index 097d22a4a..0d4aa6405 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java @@ -1,5 +1,5 @@ /* Copyright (C) 2017-2024 Andreas Shimokawa, Carsten Pfeiffer, José Rebelo, - Pavel Elagin, Petr Vaněk + Pavel Elagin, Petr Vaněk, a0z This file is part of Gadgetbridge. @@ -25,8 +25,11 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.github.mikephil.charting.charts.Chart; +import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.LegendEntry; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.formatter.ValueFormatter; @@ -60,6 +63,8 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { private TextView lightSleepTimeText; private TextView sleepDatesText; private MySleepWeeklyData mySleepWeeklyData; + private LinearLayout sleepScoreWrapper; + private LineChart sleepScoreChart; public static WeekSleepChartFragment newInstance ( int totalDays ) { WeekSleepChartFragment fragmentFirst = new WeekSleepChartFragment(); @@ -118,6 +123,8 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { } mWeekChart = rootView.findViewById(R.id.weekstepschart); + sleepScoreWrapper = rootView.findViewById(R.id.sleep_score_wrapper); + sleepScoreChart = rootView.findViewById(R.id.sleep_score_chart); remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time); remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper); awakeSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_awake_time); @@ -128,8 +135,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { mBalanceView = rootView.findViewById(R.id.balance); - setupWeekChart(); + if (!supportsSleepScore()) { + sleepScoreWrapper.setVisibility(View.GONE); + } else { + setupSleepScoreChart(); + } + setupWeekChart(); // refresh immediately instead of use refreshIfVisible(), for perceived performance refresh(); @@ -155,6 +167,13 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter()); mWeekChart.getBarData().setValueTextSize(10f); + if (supportsSleepScore()) { + sleepScoreChart.setData(null); + sleepScoreChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter()); + sleepScoreChart.getLegend().setTextColor(LEGEND_TEXT_COLOR); + sleepScoreChart.setData(mcd.getWeekBeforeData().getSleepScoreData()); + } + // The last value is for awake time, which we do not want to include in the "total sleep time" final int barIgnoreLast = supportsAwakeSleep(getChartsHost().getDevice()) ? 1 : 0; mWeekChart.getBarData().setValueFormatter(new BarChartStackedTimeValueFormatter(false, "", 0, barIgnoreLast)); @@ -200,12 +219,48 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { WeekChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device); mySleepWeeklyData = getMySleepWeeklyData(db, day, device); - return new MyChartsData(null, weekBeforeData); + return new MyChartsData(weekBeforeData); + } + + private void setupSleepScoreChart() { + final XAxis xAxisBottom = sleepScoreChart.getXAxis(); + xAxisBottom.setPosition(XAxis.XAxisPosition.BOTTOM); + xAxisBottom.setDrawLabels(true); + xAxisBottom.setDrawGridLines(false); + xAxisBottom.setEnabled(true); + xAxisBottom.setDrawLimitLinesBehindData(true); + xAxisBottom.setTextColor(CHART_TEXT_COLOR); + xAxisBottom.setAxisMinimum(0f); + xAxisBottom.setAxisMaximum(TOTAL_DAYS-1); + xAxisBottom.setGranularity(1f); + xAxisBottom.setGranularityEnabled(true); + + final YAxis yAxisLeft = sleepScoreChart.getAxisLeft(); + yAxisLeft.setDrawGridLines(true); + yAxisLeft.setAxisMaximum(100); + yAxisLeft.setAxisMinimum(0); + yAxisLeft.setDrawTopYLabelEntry(true); + yAxisLeft.setEnabled(true); + yAxisLeft.setTextColor(CHART_TEXT_COLOR); + + final YAxis yAxisRight = sleepScoreChart.getAxisRight(); + yAxisRight.setEnabled(true); + yAxisRight.setDrawLabels(false); + yAxisRight.setDrawGridLines(false); + yAxisRight.setDrawAxisLine(true); + + sleepScoreChart.setDoubleTapToZoomEnabled(false); + sleepScoreChart.getDescription().setEnabled(false); + if (TOTAL_DAYS <= 7) { + sleepScoreChart.setScaleEnabled(false); + sleepScoreChart.setTouchEnabled(false); + } } @Override protected void renderCharts() { mWeekChart.invalidate(); + sleepScoreChart.invalidate(); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java index 98828ab67..b35db2f0d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/GaugeDrawer.java @@ -227,6 +227,19 @@ public class GaugeDrawer { paint); paint.setStrokeWidth(barWidth); + float remainingAngle = 360; + float gapDegree = 1f; + if (gapBetweenSegments) { + int validSegments = segments.length; + for (int i = 0; i < segments.length; i++) { + if (segments[i] == 0) { + validSegments--; + } + } + + remainingAngle = 360 - (validSegments * gapDegree); + } + float angleSum = 0; for (int i = 0; i < segments.length; i++) { if (segments[i] == 0) { @@ -236,12 +249,8 @@ public class GaugeDrawer { paint.setColor(colors[i]); paint.setStrokeWidth(barWidth); - float startAngleDegrees = 270 + angleSum * 360; - float sweepAngleDegrees = segments[i] * 360; - - if (gapBetweenSegments) { - sweepAngleDegrees -= 1; - } + float startAngleDegrees = 270 + (angleSum * remainingAngle); + float sweepAngleDegrees = segments[i] * remainingAngle; canvas.drawArc( barMargin, @@ -254,6 +263,9 @@ public class GaugeDrawer { paint ); angleSum += segments[i]; + if (gapBetweenSegments) { + angleSum += (gapDegree / 360f); + } } Paint textPaint = new Paint(); diff --git a/app/src/main/res/layout/fragment_sleepchart.xml b/app/src/main/res/layout/fragment_sleepchart.xml index 1db840880..cfd4020b1 100644 --- a/app/src/main/res/layout/fragment_sleepchart.xml +++ b/app/src/main/res/layout/fragment_sleepchart.xml @@ -23,13 +23,15 @@ android:text="@string/stats_empty_value" android:textSize="12sp" /> - + android:id="@+id/sleep_stages_gauge" + android:layout_width="180dp" + android:layout_height="180dp" + android:layout_gravity="center" + android:scaleType="fitStart" /> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e440dcc6..ed1debef4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1022,6 +1022,8 @@ Sleep AVG Lowest Highest + Sleep score + Score: %1d - --:-- %1s, %1s