/* Copyright (C) 2015-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti, Dikay900, Pavel Elagin This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.Nullable; 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.XAxis; import com.github.mikephil.charting.components.YAxis; 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 com.github.mikephil.charting.formatter.ValueFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount; import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; public class SleepChartFragment extends AbstractChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class); private LineChart mActivityChart; private PieChart mSleepAmountChart; private TextView mSleepchartInfo; private int mSmartAlarmFrom = -1; private int mSmartAlarmTo = -1; private int mTimestampFrom = -1; private int mSmartAlarmGoneOff = -1; @Override protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { List samples = getSamples(db, device); MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples); DefaultChartsData chartsData = refresh(device, samples); return new MyChartsData(mySleepChartsData, chartsData); } private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List samples) { ActivityAnalysis analysis = new ActivityAnalysis(); ActivityAmounts amounts = analysis.calculateActivityAmounts(samples); PieData data = new PieData(); List entries = new ArrayList<>(); List colors = new ArrayList<>(); // int index = 0; long totalSeconds = 0; Date startSleep = null; Date endSleep = null; for (ActivityAmount amount : amounts.getAmounts()) { if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) { long value = amount.getTotalSeconds(); if(startSleep == null){ startSleep = amount.getStartDate(); } else { if(startSleep.after(amount.getStartDate())) startSleep = amount.getStartDate(); } if(endSleep == null){ endSleep = amount.getEndDate(); } else { if(endSleep.before(amount.getEndDate())) endSleep = amount.getEndDate(); } totalSeconds += value; // entries.add(new PieEntry(value, index++)); entries.add(new PieEntry(value, amount.getName(getActivity()))); colors.add(getColorFor(amount.getActivityKind())); // data.addXValue(amount.getName(getActivity())); } } String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS); PieDataSet set = new PieDataSet(entries, ""); set.setValueFormatter(new ValueFormatter() { @Override public String getFormattedValue(float value) { return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS); } }); 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); //setupLegend(pieChart); return new MySleepChartsData(totalSleep, data, startSleep, endSleep); } @Override protected void updateChartsnUIThread(ChartsData chartsData) { MyChartsData mcd = (MyChartsData) chartsData; mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep()); mSleepAmountChart.setData(mcd.getPieData().getPieData()); mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter()); mActivityChart.setData(mcd.getChartsData().getData()); if (mcd.getPieData().getStartSleep() != null && mcd.getPieData().getEndSleep() != null) { mSleepchartInfo.setText(getContext().getString( R.string.you_slept, DateTimeUtils.timeToString(mcd.getPieData().getStartSleep()), DateTimeUtils.timeToString(mcd.getPieData().getEndSleep()))); } else { mSleepchartInfo.setText(getContext().getString(R.string.you_did_not_sleep)); } } @Override public String getTitle() { return getString(R.string.sleepchart_your_sleep); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false); mActivityChart = rootView.findViewById(R.id.sleepchart); mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep); mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info); setupActivityChart(); setupSleepAmountChart(); // refresh immediately instead of use refreshIfVisible(), for perceived performance refresh(); return rootView; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(ChartsHost.REFRESH)) { // TODO: use LimitLines to visualize smart alarms? mSmartAlarmFrom = intent.getIntExtra("smartalarm_from", -1); mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1); mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1); mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1); refresh(); } else { super.onReceive(context, intent); } } private void setupSleepAmountChart() { mSleepAmountChart.setBackgroundColor(BACKGROUND_COLOR); mSleepAmountChart.getDescription().setTextColor(DESCRIPTION_COLOR); mSleepAmountChart.setEntryLabelColor(DESCRIPTION_COLOR); mSleepAmountChart.getDescription().setText(""); // mSleepAmountChart.getDescription().setNoDataTextDescription(""); mSleepAmountChart.setNoDataText(""); mSleepAmountChart.getLegend().setEnabled(false); } private void setupActivityChart() { mActivityChart.setBackgroundColor(BACKGROUND_COLOR); mActivityChart.getDescription().setTextColor(DESCRIPTION_COLOR); configureBarLineChartDefaults(mActivityChart); XAxis x = mActivityChart.getXAxis(); x.setDrawLabels(true); x.setDrawGridLines(false); x.setEnabled(true); x.setTextColor(CHART_TEXT_COLOR); x.setDrawLimitLinesBehindData(true); YAxis y = mActivityChart.getAxisLeft(); y.setDrawGridLines(false); // y.setDrawLabels(false); // TODO: make fixed max value optional y.setAxisMaximum(1f); y.setAxisMinimum(0); y.setDrawTopYLabelEntry(false); y.setTextColor(CHART_TEXT_COLOR); // y.setLabelCount(5); y.setEnabled(true); YAxis yAxisRight = mActivityChart.getAxisRight(); yAxisRight.setDrawGridLines(false); yAxisRight.setEnabled(supportsHeartrate(getChartsHost().getDevice())); yAxisRight.setDrawLabels(true); yAxisRight.setDrawTopYLabelEntry(true); yAxisRight.setTextColor(CHART_TEXT_COLOR); yAxisRight.setAxisMaxValue(HeartRateUtils.getInstance().getMaxHeartRate()); yAxisRight.setAxisMinValue(HeartRateUtils.getInstance().getMinHeartRate()); } @Override protected void setupLegend(Chart chart) { List legendEntries = new ArrayList<>(3); LegendEntry lightSleepEntry = new LegendEntry(); lightSleepEntry.label = akLightSleep.label; lightSleepEntry.formColor = akLightSleep.color; legendEntries.add(lightSleepEntry); LegendEntry deepSleepEntry = new LegendEntry(); deepSleepEntry.label = akDeepSleep.label; deepSleepEntry.formColor = akDeepSleep.color; legendEntries.add(deepSleepEntry); if (supportsHeartrate(getChartsHost().getDevice())) { LegendEntry hrEntry = new LegendEntry(); hrEntry.label = HEARTRATE_LABEL; hrEntry.formColor = HEARTRATE_COLOR; legendEntries.add(hrEntry); } chart.getLegend().setCustom(legendEntries); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); } @Override protected List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { // temporary fix for totally wrong sleep amounts // return super.getSleepSamples(db, device, tsFrom, tsTo); return super.getAllSamples(db, device, tsFrom, tsTo); } @Override protected void renderCharts() { mActivityChart.animateX(ANIM_TIME, Easing.EaseInOutQuart); mSleepAmountChart.invalidate(); } private static class MySleepChartsData extends ChartsData { private String totalSleep; private final PieData pieData; private @Nullable Date startSleep; private @Nullable Date endSleep; public MySleepChartsData(String totalSleep, PieData pieData, @Nullable Date startSleep, @Nullable Date endSleep) { this.totalSleep = totalSleep; this.pieData = pieData; this.startSleep = startSleep; this.endSleep = endSleep; } public PieData getPieData() { return pieData; } public CharSequence getTotalSleep() { return totalSleep; } @Nullable public Date getStartSleep() { return startSleep; } @Nullable public Date getEndSleep() { return endSleep; } } private static class MyChartsData extends ChartsData { private final DefaultChartsData chartsData; private final MySleepChartsData pieData; public MyChartsData(MySleepChartsData pieData, DefaultChartsData chartsData) { this.pieData = pieData; this.chartsData = chartsData; } public MySleepChartsData getPieData() { return pieData; } public DefaultChartsData getChartsData() { return chartsData; } } }