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 edec1cd90..f4069ecc0 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 @@ -65,12 +65,12 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra protected final int TOTAL_DAYS = getRangeDays(); protected int TOTAL_DAYS_FOR_AVERAGE = 0; - private Locale mLocale; - private int mTargetValue = 0; + protected Locale mLocale; + protected int mTargetValue = 0; - private PieChart mTodayPieChart; - private BarChart mWeekChart; - private TextView mBalanceView; + protected PieChart mTodayPieChart; + protected BarChart mWeekChart; + protected TextView mBalanceView; private int mOffsetHours = getOffsetHours(); ImageView stepsStreaksButton; @@ -113,7 +113,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra } } - private boolean enableStepStreaksButton(){ + protected boolean enableStepStreaksButton(){ return this.getClass().getSimpleName().equals("WeekStepsChartFragment"); } @@ -124,7 +124,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra // mBalanceView.setText(getBalanceMessage(balance)); } - private String getWeeksChartsLabel(Calendar day){ + protected String getWeeksChartsLabel(Calendar day){ if (GBApplication.getPrefs().getBoolean("charts_range", true)) { //month, show day date return String.valueOf(day.get(Calendar.DAY_OF_MONTH)); @@ -134,10 +134,9 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra return day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale); } } - - private WeekChartsData refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) { + protected WeekChartsData refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) { day = (Calendar) day.clone(); // do not modify the caller's argument - day.add(Calendar.DATE, -TOTAL_DAYS); + day.add(Calendar.DATE, -TOTAL_DAYS + 1); List entries = new ArrayList<>(); ArrayList labels = new ArrayList(); @@ -191,10 +190,10 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra } } - return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue)); + return new WeekChartsData(barData, new PreformattedXIndexLabelFormatter(labels), getBalanceMessage(balance, mTargetValue)); } - private DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) { + protected DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) { PieData data = new PieData(); List entries = new ArrayList<>(); @@ -276,7 +275,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra - private void setupTodayPieChart() { + protected void setupTodayPieChart() { mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR); mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR); mTodayPieChart.setEntryLabelColor(DESCRIPTION_COLOR); @@ -286,7 +285,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra mTodayPieChart.getLegend().setEnabled(false); } - private void setupWeekChart() { + protected void setupWeekChart() { mWeekChart.setBackgroundColor(BACKGROUND_COLOR); mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR); mWeekChart.getDescription().setText(""); @@ -369,7 +368,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra } } - private ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) { + protected ActivityAmounts getActivityAmountsForDay(DBHandler db, Calendar day, GBDevice device) { LimitedQueue activityAmountCache = null; ActivityAmounts amounts = null; @@ -426,7 +425,7 @@ public abstract class AbstractWeekChartFragment extends AbstractActivityChartFra protected abstract String getBalanceMessage(long balance, int targetValue); - private class WeekChartsData> extends DefaultChartsData { + protected class WeekChartsData> extends DefaultChartsData { private final String balanceMessage; public WeekChartsData(T data, PreformattedXIndexLabelFormatter xIndexLabelFormatter, String balanceMessage) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java index 6b6cee71c..fdf5a02f3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java @@ -20,12 +20,14 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.content.Context; import android.content.Intent; import android.graphics.Color; +import android.os.Build; import android.os.Bundle; import android.text.method.ScrollingMovementMethod; 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.constraintlayout.widget.ConstraintLayout; @@ -34,10 +36,7 @@ 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.LegendEntry; -import com.github.mikephil.charting.components.LimitLine; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.components.*; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; @@ -49,10 +48,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.text.SimpleDateFormat; +import java.util.*; import java.util.concurrent.TimeUnit; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -73,10 +70,14 @@ public class SleepChartFragment extends AbstractActivityChartFragment entries = new ArrayList<>(); @@ -151,16 +151,14 @@ public class SleepChartFragment extends AbstractActivityChartFragment sleepSessions) { @@ -199,16 +201,47 @@ public class SleepChartFragment extends AbstractActivityChartFragment 0) { - result.append('\n'); + result.append(" | "); } - result.append(getContext().getString( - R.string.you_slept, - DateTimeUtils.timeToString(sleepSession.getSleepStart()), - DateTimeUtils.timeToString(sleepSession.getSleepEnd()))); + String from = DateTimeUtils.timeToString(sleepSession.getSleepStart()); + String to = DateTimeUtils.timeToString(sleepSession.getSleepEnd()); + result.append(String.format("%s - %s", from, to)); } } return result.toString(); @@ -323,33 +350,26 @@ public class SleepChartFragment extends AbstractActivityChartFragment= Build.VERSION_CODES.M) { + rootView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + getChartsHost().enableSwipeRefresh(scrollY == 0); + }); + } + mActivityChart = rootView.findViewById(R.id.sleepchart); mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep); mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info); - heartRateIcon = rootView.findViewById(R.id.heartrate_widget_icon); - heartRateAverageLabel = rootView.findViewById(R.id.heartrate_widget_label); - intensityTotalIcon = rootView.findViewById(R.id.intensity_widget_icon); - intensityTotalLabel = rootView.findViewById(R.id.intensity_widget_label); + remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time); + remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper); + deepSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_deep_time); + lightSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_light_time); + lowestHrText = rootView.findViewById(R.id.sleep_hr_lowest); + highestHrText = rootView.findViewById(R.id.sleep_hr_highest); + movementIntensityText = rootView.findViewById(R.id.sleep_movement_intensity); + sleepDateText = rootView.findViewById(R.id.sleep_date); - ConstraintLayout intensityTotalWidgetLayout = rootView.findViewById(R.id.intensity_widget_layout); - ConstraintLayout heartRateWidgetLayout = rootView.findViewById(R.id.heartrate_widget_layout); mSleepchartInfo.setMaxLines(sleepLinesLimit); - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View v) { - DecimalFormat df = new DecimalFormat("###.#"); - String detailedDuration = String.format(getString(R.string.charts_min_max_heartrate_popup), heartRateMin, heartRateMax, df.format(intensityTotal)); - new ShowDurationDialog(detailedDuration, getContext()).show(); - } - }; - - heartRateWidgetLayout.setOnClickListener(listener); - intensityTotalWidgetLayout.setOnClickListener(listener); - intensityTotalIcon.setOnClickListener(listener); - intensityTotalLabel.setOnClickListener(listener); - - setupActivityChart(); setupSleepAmountChart(); @@ -438,9 +458,6 @@ public class SleepChartFragment extends AbstractActivityChartFragment sleepSessions; - public MySleepChartsData(String totalSleep, PieData pieData, List sleepSessions) { - this.totalSleep = totalSleep; + public MySleepChartsData(PieData pieData, List sleepSessions, String totalSleep, String totalRem, String totalDeep, String totalLight) { this.pieData = pieData; this.sleepSessions = sleepSessions; + this.totalSleep = totalSleep; + this.totalRem = totalRem; + this.totalDeep = totalDeep; + this.totalLight = totalLight; } public PieData getPieData() { @@ -491,6 +512,18 @@ public class SleepChartFragment extends AbstractActivityChartFragment 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 a3f0368a2..01b5569bf 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 @@ -17,14 +17,27 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.activities.charts; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.LegendEntry; +import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.formatter.ValueFormatter; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.time.DateUtils; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; @@ -37,6 +50,122 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; public class WeekSleepChartFragment extends AbstractWeekChartFragment { + + private TextView remSleepTimeText; + private LinearLayout remSleepTimeTextWrapper; + private TextView deepSleepTimeText; + private TextView lightSleepTimeText; + private TextView sleepDatesText; + private MySleepWeeklyData mySleepWeeklyData; + + private MySleepWeeklyData getMySleepWeeklyData(DBHandler db, Calendar day, GBDevice device) { + day = (Calendar) day.clone(); // do not modify the caller's argument + day.add(Calendar.DATE, -TOTAL_DAYS + 1); + TOTAL_DAYS_FOR_AVERAGE=0; + long remWeeklyTotal = 0; + long deepWeeklyTotal = 0; + long lightWeeklyTotal = 0; + + for (int counter = 0; counter < TOTAL_DAYS; counter++) { + ActivityAmounts amounts = getActivityAmountsForDay(db, day, device); + if (calculateBalance(amounts) > 0) { + TOTAL_DAYS_FOR_AVERAGE++; + } + + float[] totalAmounts = getTotalsForActivityAmounts(amounts); + deepWeeklyTotal += (long) totalAmounts[0]; + lightWeeklyTotal += (long) totalAmounts[1]; + if (supportsRemSleep(device)) { + remWeeklyTotal += (long) totalAmounts[2]; + } + day.add(Calendar.DATE, 1); + } + + return new MySleepWeeklyData(remWeeklyTotal, deepWeeklyTotal, lightWeeklyTotal); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mLocale = getResources().getConfiguration().locale; + View rootView = inflater.inflate(R.layout.fragment_weeksleep_chart, container, false); + + final int goal = getGoal(); + if (goal >= 0) { + mTargetValue = goal; + } + + mWeekChart = rootView.findViewById(R.id.weekstepschart); + remSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_rem_time); + remSleepTimeTextWrapper = rootView.findViewById(R.id.sleep_chart_legend_rem_time_wrapper); + deepSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_deep_time); + lightSleepTimeText = rootView.findViewById(R.id.sleep_chart_legend_light_time); + sleepDatesText = rootView.findViewById(R.id.sleep_dates); + + setupWeekChart(); + + // refresh immediately instead of use refreshIfVisible(), for perceived performance + refresh(); + + return rootView; + } + + @Override + protected void updateChartsnUIThread(MyChartsData mcd) { + setupLegend(mWeekChart); + + //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()); + mWeekChart.getBarData().setValueTextSize(14f); + mWeekChart.setScaleEnabled(false); + mWeekChart.setTouchEnabled(false); + + if (TOTAL_DAYS_FOR_AVERAGE > 0) { + float avgDeep = Math.abs(this.mySleepWeeklyData.getTotalDeep() / TOTAL_DAYS_FOR_AVERAGE); + deepSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgDeep, TimeUnit.MINUTES)); + float avgLight = Math.abs(this.mySleepWeeklyData.getTotalLight() / TOTAL_DAYS_FOR_AVERAGE); + lightSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgLight, TimeUnit.MINUTES)); + float avgRem = Math.abs(this.mySleepWeeklyData.getTotalRem() / TOTAL_DAYS_FOR_AVERAGE); + remSleepTimeText.setText(DateTimeUtils.formatDurationHoursMinutes((int) avgRem, TimeUnit.MINUTES)); + } else { + deepSleepTimeText.setText("-"); + lightSleepTimeText.setText("-"); + remSleepTimeText.setText("-"); + } + + if (!supportsRemSleep(getChartsHost().getDevice())) { + remSleepTimeTextWrapper.setVisibility(View.GONE); + } + + Date to = new Date((long) this.getTSEnd() * 1000); + Date from = DateUtils.addDays(to,-(TOTAL_DAYS - 1)); + String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to); + String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from); + sleepDatesText.setText(fromFormattedDate + " - " + toFormattedDate); + } + + @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 + WeekChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device); + mySleepWeeklyData = getMySleepWeeklyData(db, day, device); + + return new MyChartsData(null, weekBeforeData); + } + + @Override + protected void renderCharts() { + mWeekChart.invalidate(); + } + @Override public String getTitle() { if (GBApplication.getPrefs().getBoolean("charts_range", true)) { @@ -205,4 +334,33 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { return getHM((long)value); } + private static class MySleepWeeklyData { + private long totalRem; + private long totalDeep; + private long totalLight; + private int totalDaysForAverage; + + public MySleepWeeklyData(long totalRem, long totalDeep, long totalLight) { + this.totalDeep = totalDeep; + this.totalRem = totalRem; + this.totalLight = totalLight; + this.totalDaysForAverage = 0; + } + + public long getTotalRem() { + return this.totalRem; + } + + public long getTotalDeep() { + return this.totalDeep; + } + + public long getTotalLight() { + return this.totalLight; + } + + public int getTotalDaysForAverage() { + return this.totalDaysForAverage; + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java index 42aaaf146..213187a7f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/AbstractDashboardWidget.java @@ -42,9 +42,9 @@ public abstract class AbstractDashboardWidget extends Fragment { protected @ColorInt int color_worn = Color.rgb(128, 128, 128); protected @ColorInt int color_activity = Color.GREEN; protected @ColorInt int color_exercise = Color.rgb(255, 128, 0); - protected @ColorInt int color_deep_sleep = Color.BLUE; - protected @ColorInt int color_light_sleep = Color.rgb(150, 150, 255); - protected @ColorInt int color_rem_sleep = Color.rgb(182, 191, 255); + protected @ColorInt int color_deep_sleep = Color.rgb(0, 84, 163); + protected @ColorInt int color_light_sleep = Color.rgb(7, 158, 243); + protected @ColorInt int color_rem_sleep = Color.rgb(228, 39, 199); protected @ColorInt int color_distance = Color.BLUE; protected @ColorInt int color_active_time = Color.rgb(170, 0, 255); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java index d80bed817..44927aa92 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java @@ -54,10 +54,11 @@ public class ActivityKind { public static final int TYPE_HIKING = 0x00400000; public static final int TYPE_CLIMBING = 0x00800000; public static final int TYPE_REM_SLEEP = 0x01000000; + public static final int TYPE_AWAKE_SLEEP = 0x02000000; private static final int TYPES_COUNT = 26; - public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP | TYPE_REM_SLEEP; + public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP | TYPE_REM_SLEEP | TYPE_AWAKE_SLEEP; public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN; public static int[] mapToDBActivityTypes(int types, SampleProvider provider) { diff --git a/app/src/main/res/layout/fragment_sleepchart.xml b/app/src/main/res/layout/fragment_sleepchart.xml index 8b9174b00..14b7d4150 100644 --- a/app/src/main/res/layout/fragment_sleepchart.xml +++ b/app/src/main/res/layout/fragment_sleepchart.xml @@ -1,53 +1,266 @@ - + android:layout_height="match_parent"> + android:layout_marginTop="15dp" + android:gravity="center" + android:textSize="20sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_height="300dp" + android:layout_marginBottom="25dp" + android:layout_weight="4" /> - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_weeksleep_chart.xml b/app/src/main/res/layout/fragment_weeksleep_chart.xml new file mode 100644 index 000000000..0bf1b4eb6 --- /dev/null +++ b/app/src/main/res/layout/fragment_weeksleep_chart.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4d43ebf04..87f916d16 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -21,14 +21,15 @@ #ffab40 #8B0000 #fadab1 - #0071b7 - #4c5aff - #46acea - #b6bfff + #0054a3 + #0054a3 - #b6bfff - #46acea + #079ef3 + #079ef3 + + #e427c7 + #e427c7 #60bd6d #59b22c @@ -39,6 +40,8 @@ #FFEDEDED #545254 + #00000000 + @color/accent \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6c88769a8..73c38ef4b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -951,8 +951,18 @@ Light sleep Deep sleep REM sleep + Awake sleep Not worn - You slept from %1$s to %2$s + Deep + Light + REM + Deep AVG + Light AVG + REM AVG + - + Lowest HR + Highest HR + %1$s - %2$s You did not sleep Lowest heart rate: %1$d \nHighest heart rate: %2$d \nMovement intensity: %3$s Not connected.