diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBActivitySample.java index e1f17dfa..2bc341ef 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBActivitySample.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBActivitySample.java @@ -33,6 +33,9 @@ public class GBActivitySample { this.type = type; } + /** + * Timestamp of the sample, resolution is seconds! + */ public int getTimestamp() { return timestamp; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractChartFragment.java index 96313ce3..b004cccd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractChartFragment.java @@ -133,14 +133,11 @@ public abstract class AbstractChartFragment extends Fragment { chart.setDrawGridBackground(false); } - protected void refresh(GBDevice mGBDevice, BarLineChartBase chart) { - if (mGBDevice == null) { + protected void refresh(GBDevice gbDevice, BarLineChartBase chart, List samples) { + if (gbDevice == null) { return; } -// ArrayList samples = getTestSamples(mGBDevice, -1, -1); - List samples = getSamples(mGBDevice, -1, -1); - Calendar cal = GregorianCalendar.getInstance(); cal.clear(); Date date; @@ -152,7 +149,7 @@ public abstract class AbstractChartFragment extends Fragment { float movement_divisor; boolean annotate = true; boolean use_steps_as_movement; - switch (getProvider(mGBDevice)) { + switch (getProvider(gbDevice)) { case GBActivitySample.PROVIDER_MIBAND: // maybe this should be configurable 256 seems way off, though. movement_divisor = 180.0f; //256.0f; @@ -278,7 +275,7 @@ public abstract class AbstractChartFragment extends Fragment { protected abstract List getSamples(GBDevice device, int tsFrom, int tsTo); - protected abstract void setupLegend(BarLineChartBase chart); + protected abstract void setupLegend(Chart chart); protected BarEntry createBarEntry(float value, int index) { return new BarEntry(value, index); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySleepChartFragment.java index 77499bcb..8e4bb121 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySleepChartFragment.java @@ -11,6 +11,7 @@ import android.view.View; import android.view.ViewGroup; import com.github.mikephil.charting.charts.BarLineChartBase; +import com.github.mikephil.charting.charts.Chart; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.YAxis; @@ -47,7 +48,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1); mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1); mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1); - refresh(mGBDevice, mChart); + List samples = getSamples(mGBDevice, -1, -1); + refresh(mGBDevice, mChart, samples); } } }; @@ -68,7 +70,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, filter); - mChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart); + mChart = (BarLineChartBase) rootView.findViewById(R.id.activitysleepchart); setupChart(); @@ -106,7 +108,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setTextColor(CHART_TEXT_COLOR); - refresh(mGBDevice, mChart); + List samples = getSamples(mGBDevice, -1, -1); + refresh(mGBDevice, mChart, samples); mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR); // mChart.getLegend().setEnabled(false); @@ -123,7 +126,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { super.onDestroy(); } - protected void setupLegend(BarLineChartBase chart) { + protected void setupLegend(Chart chart) { List legendColors = new ArrayList<>(3); List legendLabels = new ArrayList<>(3); legendColors.add(akActivity.color); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartFragment.java index 7f782d9d..1ef20c9b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartFragment.java @@ -11,8 +11,13 @@ import android.view.View; import android.view.ViewGroup; import com.github.mikephil.charting.charts.BarLineChartBase; +import com.github.mikephil.charting.charts.Chart; +import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,12 +29,16 @@ import nodomain.freeyourgadget.gadgetbridge.ControlCenter; import nodomain.freeyourgadget.gadgetbridge.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.GBDevice; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAmount; +import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAmounts; +import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAnalysis; public class SleepChartFragment extends AbstractChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class); - private BarLineChartBase mChart; + private BarLineChartBase mActivityChart; + private PieChart mSleepAmountChart; private int mSmartAlarmFrom = -1; private int mSmartAlarmTo = -1; @@ -47,15 +56,62 @@ public class SleepChartFragment extends AbstractChartFragment { mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1); mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1); mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1); - refresh(mGBDevice, mChart); + refresh(); } } }; + private void refresh() { + List samples = getSamples(); + refresh(mGBDevice, mActivityChart, getSamples()); + refreshSleepAmounts(mGBDevice, mSleepAmountChart, samples); + + mActivityChart.invalidate(); + mSleepAmountChart.invalidate(); + } + + private List getSamples() { + return getSamples(mGBDevice, -1, -1); + } + + private void refreshSleepAmounts(GBDevice mGBDevice, PieChart pieChart, List samples) { + ActivityAnalysis analysis = new ActivityAnalysis(); + ActivityAmounts amounts = analysis.calculateActivityAmounts(samples); + float hoursOfSleep = amounts.getTotalSeconds() / (float) (60 * 60); + pieChart.setCenterText((int)hoursOfSleep + "h"); // FIXME + PieData data = new PieData(); + List entries = new ArrayList<>(); + List colors = new ArrayList<>(); + int index = 0; + for (ActivityAmount amount : amounts.getAmounts()) { + entries.add(new Entry(amount.getTotalSeconds(), index++)); + colors.add(getColorFor(amount.getActivityKind())); + data.addXValue(amount.getName(getActivity())); + } + PieDataSet set = new PieDataSet(entries, "Sleep comparison"); + set.setColors(colors); + data.setDataSet(set); + pieChart.setData(data); + + setupLegend(pieChart); + + pieChart.invalidate(); + } + + private Integer getColorFor(int activityKind) { + switch (activityKind) { + case nodomain.freeyourgadget.gadgetbridge.charts.ActivityKind.TYPE_DEEP_SLEEP: + return akDeepSleep.color; + case nodomain.freeyourgadget.gadgetbridge.charts.ActivityKind.TYPE_LIGHT_SLEEP: + return akLightSleep.color; + } + return akActivity.color; + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_charts, container, false); + View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false); Bundle extras = getActivity().getIntent().getExtras(); if (extras != null) { @@ -68,32 +124,42 @@ public class SleepChartFragment extends AbstractChartFragment { LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, filter); - mChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart); + mActivityChart = (BarLineChartBase) rootView.findViewById(R.id.sleepchart); + mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep); - setupChart(); + setupActivityChart(); + setupSleepAmountChart(); + + refresh(); return rootView; } + private void setupSleepAmountChart() { + mSleepAmountChart.setBackgroundColor(BACKGROUND_COLOR); + mSleepAmountChart.setDescriptionColor(DESCRIPTION_COLOR); + } + @Override public void onDestroy() { LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver); super.onDestroy(); } - private void setupChart() { - mChart.setBackgroundColor(BACKGROUND_COLOR); - mChart.setDescriptionColor(DESCRIPTION_COLOR); - configureBarLineChartDefaults(mChart); - XAxis x = mChart.getXAxis(); + private void setupActivityChart() { + mActivityChart.setBackgroundColor(BACKGROUND_COLOR); + mActivityChart.setDescriptionColor(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 = mChart.getAxisLeft(); + YAxis y = mActivityChart.getAxisLeft(); y.setDrawGridLines(false); // y.setDrawLabels(false); // TODO: make fixed max value optional @@ -104,33 +170,31 @@ public class SleepChartFragment extends AbstractChartFragment { // y.setLabelCount(5); y.setEnabled(true); - YAxis yAxisRight = mChart.getAxisRight(); + YAxis yAxisRight = mActivityChart.getAxisRight(); yAxisRight.setDrawGridLines(false); yAxisRight.setEnabled(false); yAxisRight.setDrawLabels(false); yAxisRight.setDrawTopYLabelEntry(false); yAxisRight.setTextColor(CHART_TEXT_COLOR); - refresh(mGBDevice, mChart); - - mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR); -// mChart.getLegend().setEnabled(false); +// mActivityChart.getLegend().setEnabled(false); // -// mChart.animateXY(2000, 2000); +// mActivityChart.animateXY(2000, 2000); // don't forget to refresh the drawing - mChart.invalidate(); +// mActivityChart.invalidate(); } - protected void setupLegend(BarLineChartBase chart) { - List legendColors = new ArrayList<>(3); - List legendLabels = new ArrayList<>(3); + protected void setupLegend(Chart chart) { + List legendColors = new ArrayList<>(2); + List legendLabels = new ArrayList<>(2); legendColors.add(akLightSleep.color); legendLabels.add(akLightSleep.label); legendColors.add(akDeepSleep.color); legendLabels.add(akDeepSleep.label); chart.getLegend().setColors(legendColors); chart.getLegend().setLabels(legendLabels); + chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAmount.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAmount.java new file mode 100644 index 00000000..278b7019 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAmount.java @@ -0,0 +1,43 @@ +package nodomain.freeyourgadget.gadgetbridge.charts; + +import android.content.Context; + +public class ActivityAmount { + private int activityKind; + private short percent; + private long totalSeconds; + + public ActivityAmount(int activityKind) { + this.activityKind = activityKind; + } + + public void addSeconds(long seconds) { + totalSeconds += seconds; + } + + public long getTotalSeconds() { + return totalSeconds; + } + + public int getActivityKind() { + return activityKind; + } + + public short getPercent() { + return percent; + } + + public void setPercent(short percent) { + this.percent = percent; + } + + public String getName(Context context) { + switch (activityKind) { + case ActivityKind.TYPE_DEEP_SLEEP: + return "Deep Sleep"; + case ActivityKind.TYPE_LIGHT_SLEEP: + return "Light Sleep"; + } + return "Activity"; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAmounts.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAmounts.java new file mode 100644 index 00000000..45f8655e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAmounts.java @@ -0,0 +1,29 @@ +package nodomain.freeyourgadget.gadgetbridge.charts; + +import java.util.ArrayList; +import java.util.List; + +public class ActivityAmounts { + private List amounts = new ArrayList<>(4); + private long totalSeconds; + + public void addAmount(ActivityAmount amount) { + amounts.add(amount); + totalSeconds += amount.getTotalSeconds(); + } + + public List getAmounts() { + return amounts; + } + + public long getTotalSeconds() { + return totalSeconds; + } + + public void calculatePercentages() { + for (ActivityAmount amount : amounts) { + float fraction = amount.getTotalSeconds() / (float) totalSeconds; + amount.setPercent((short) (fraction * 100)); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAnalysis.java new file mode 100644 index 00000000..02629d78 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityAnalysis.java @@ -0,0 +1,62 @@ +package nodomain.freeyourgadget.gadgetbridge.charts; + +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBActivitySample; + +public class ActivityAnalysis { + public ActivityAmounts calculateActivityAmounts(List samples) { + ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP); + ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP); + ActivityAmount activity = new ActivityAmount(ActivityKind.TYPE_ACTIVITY); + + ActivityAmount previousAmount = null; + GBActivitySample previousSample = null; + for(GBActivitySample sample : samples) { + ActivityAmount amount = null; + switch (sample.getType()) { + case GBActivitySample.TYPE_DEEP_SLEEP: + amount = deepSleep; + break; + case GBActivitySample.TYPE_LIGHT_SLEEP: + amount = lightSleep; + break; + case GBActivitySample.TYPE_UNKNOWN: + default: + amount = activity; + break; + } + + if (previousSample != null) { + long timeDifference = sample.getTimestamp() - previousSample.getTimestamp(); + if (previousSample.getType() == sample.getType()) { + amount.addSeconds(timeDifference); + } else { + long sharedTimeDifference = (long) (timeDifference / 2.0f); + previousAmount.addSeconds(sharedTimeDifference); + amount.addSeconds(sharedTimeDifference); + } + } else { + // nothing to do, we can only calculate when we have the next sample + } + + previousAmount = amount; + previousSample = sample; + } + + ActivityAmounts result = new ActivityAmounts(); + if (deepSleep.getTotalSeconds() > 0) { + result.addAmount(deepSleep); + } + if (lightSleep.getTotalSeconds() > 0) { + result.addAmount(lightSleep); + } + if (activity.getTotalSeconds() > 0) { + result.addAmount(activity); + } + result.calculatePercentages(); + + return result; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityKind.java new file mode 100644 index 00000000..6f9a8a06 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/charts/ActivityKind.java @@ -0,0 +1,29 @@ +package nodomain.freeyourgadget.gadgetbridge.charts; + +import java.util.Arrays; + +import nodomain.freeyourgadget.gadgetbridge.GBActivitySample; + +public class ActivityKind { + public static final int TYPE_ACTIVITY = 1; + public static final int TYPE_LIGHT_SLEEP = 2; + public static final int TYPE_DEEP_SLEEP = 4; + public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP; + public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP; + + public static byte[] mapToDBActivityTypes(int types) { + byte[] result = new byte[3]; + int i = 0; + if ((types & ActivityKind.TYPE_ACTIVITY) != 0) { + result[i++] = GBActivitySample.TYPE_UNKNOWN; + } + if ((types & ActivityKind.TYPE_DEEP_SLEEP) != 0) { + result[i++] = GBActivitySample.TYPE_DEEP_SLEEP; + } + if ((types & ActivityKind.TYPE_LIGHT_SLEEP) != 0) { + result[i++] = GBActivitySample.TYPE_LIGHT_SLEEP; + } + return Arrays.copyOf(result, i); + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java index 5bf7b046..3570f946 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java @@ -15,6 +15,7 @@ import java.util.Arrays; import nodomain.freeyourgadget.gadgetbridge.GB; import nodomain.freeyourgadget.gadgetbridge.GBActivitySample; +import nodomain.freeyourgadget.gadgetbridge.charts.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME; @@ -27,13 +28,6 @@ import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GB public class ActivityDatabaseHandler extends SQLiteOpenHelper { - private static final int TYPE_ACTIVITY = 1; - private static final int TYPE_LIGHT_SLEEP = 2; - private static final int TYPE_DEEP_SLEEP = 4; - private static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP; - private static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP; - - private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class); private static final int DATABASE_VERSION = 5; @@ -124,15 +118,15 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper { } public ArrayList getSleepSamples(int timestamp_from, int timestamp_to, byte provider) { - return getGBActivitySamples(timestamp_from, timestamp_to, TYPE_SLEEP, provider); + return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_SLEEP, provider); } public ArrayList getActivitySamples(int timestamp_from, int timestamp_to, byte provider) { - return getGBActivitySamples(timestamp_from, timestamp_to, TYPE_ACTIVITY, provider); + return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ACTIVITY, provider); } public ArrayList getAllActivitySamples(int timestamp_from, int timestamp_to, byte provider) { - return getGBActivitySamples(timestamp_from, timestamp_to, TYPE_ALL, provider); + return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL, provider); } /** @@ -171,12 +165,12 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper { } private String getWhereClauseFor(int activityTypes) { - if (activityTypes == TYPE_ALL) { + if (activityTypes == ActivityKind.TYPE_ALL) { return ""; // no further restriction } StringBuilder builder = new StringBuilder(" and ("); - byte[] dbActivityTypes = mapToDBActivityTypes(activityTypes); + byte[] dbActivityTypes = ActivityKind.mapToDBActivityTypes(activityTypes); for (int i = 0; i < dbActivityTypes.length; i++) { builder.append(" type=").append(dbActivityTypes[i]); if (i + 1 < dbActivityTypes.length) { @@ -186,19 +180,4 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper { builder.append(')'); return builder.toString(); } - - private byte[] mapToDBActivityTypes(int types) { - byte[] result = new byte[3]; - int i = 0; - if ((types & TYPE_ACTIVITY) != 0) { - result[i++] = GBActivitySample.TYPE_UNKNOWN; - } - if ((types & TYPE_DEEP_SLEEP) != 0) { - result[i++] = GBActivitySample.TYPE_DEEP_SLEEP; - } - if ((types & TYPE_LIGHT_SLEEP) != 0) { - result[i++] = GBActivitySample.TYPE_LIGHT_SLEEP; - } - return Arrays.copyOf(result, i); - } } diff --git a/app/src/main/res/layout/fragment_charts.xml b/app/src/main/res/layout/fragment_charts.xml index bdd6ffc2..788d31c2 100644 --- a/app/src/main/res/layout/fragment_charts.xml +++ b/app/src/main/res/layout/fragment_charts.xml @@ -4,7 +4,7 @@ tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ChartsActivity$PlaceholderFragment"> diff --git a/app/src/main/res/layout/fragment_sleepchart.xml b/app/src/main/res/layout/fragment_sleepchart.xml new file mode 100644 index 00000000..a80c2b10 --- /dev/null +++ b/app/src/main/res/layout/fragment_sleepchart.xml @@ -0,0 +1,27 @@ + + + + + + + + + +