diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 795931d76..609082ebd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -514,7 +514,7 @@ android:name=".devices.lenovo.LenovoWatchCalibrationActivity" android:label="@string/title_activity_LenovoWatch_calibration" /> { private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesChartFragment.class); private LineChart mChart; @@ -139,7 +139,7 @@ public class ActivitySummariesChartFragment extends AbstractChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { List legendEntries = new ArrayList<>(5); LegendEntry activityEntry = new LegendEntry(); 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 new file mode 100644 index 000000000..c0cf26e8b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractActivityChartFragment.java @@ -0,0 +1,475 @@ +/* Copyright (C) 2015-2020 0nse, Andreas Shimokawa, Carsten Pfeiffer, + Daniele Gobbetti, Dikay900, Pavel Elagin, vanous, walkjivefly + + 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.util.TypedValue; + +import androidx.core.content.ContextCompat; + +import com.github.mikephil.charting.components.YAxis; +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; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public abstract class AbstractActivityChartFragment extends AbstractChartFragment { + private static final Logger LOG = LoggerFactory.getLogger(AbstractActivityChartFragment.class); + + public boolean supportsHeartrate(GBDevice device) { + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); + return coordinator != null && coordinator.supportsHeartRateMeasurement(device); + } + + public boolean supportsRemSleep(GBDevice device) { + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); + return coordinator != null && coordinator.supportsRemSleep(); + } + + protected static final class ActivityConfig { + public final int type; + public final String label; + public final Integer color; + + public ActivityConfig(int kind, String label, Integer color) { + this.type = kind; + this.label = label; + this.color = color; + } + } + + protected ActivityConfig akActivity; + protected ActivityConfig akLightSleep; + protected ActivityConfig akDeepSleep; + protected ActivityConfig akRemSleep; + protected ActivityConfig akNotWorn; + + protected int BACKGROUND_COLOR; + protected int DESCRIPTION_COLOR; + protected int CHART_TEXT_COLOR; + protected int LEGEND_TEXT_COLOR; + protected int HEARTRATE_COLOR; + protected int HEARTRATE_FILL_COLOR; + protected int AK_ACTIVITY_COLOR; + protected int AK_DEEP_SLEEP_COLOR; + protected int AK_REM_SLEEP_COLOR; + protected int AK_LIGHT_SLEEP_COLOR; + protected int AK_NOT_WORN_COLOR; + + protected String HEARTRATE_LABEL; + protected String HEARTRATE_AVERAGE_LABEL; + + protected void init() { + Prefs prefs = GBApplication.getPrefs(); + TypedValue runningColor = new TypedValue(); + BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext()); + LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext()); + CHART_TEXT_COLOR = ContextCompat.getColor(getContext(), R.color.secondarytext); + if (prefs.getBoolean("chart_heartrate_color", false)) { + HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative); + }else{ + HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate); + } + HEARTRATE_FILL_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_fill); + + getContext().getTheme().resolveAttribute(R.attr.chart_activity, runningColor, true); + AK_ACTIVITY_COLOR = runningColor.data; + getContext().getTheme().resolveAttribute(R.attr.chart_deep_sleep, runningColor, true); + AK_DEEP_SLEEP_COLOR = runningColor.data; + getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true); + AK_LIGHT_SLEEP_COLOR = runningColor.data; + getContext().getTheme().resolveAttribute(R.attr.chart_rem_sleep, runningColor, true); + AK_REM_SLEEP_COLOR = runningColor.data; + getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true); + AK_NOT_WORN_COLOR = runningColor.data; + + HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate); + HEARTRATE_AVERAGE_LABEL = getContext().getString(R.string.charts_legend_heartrate_average); + + akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR); + akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR); + akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR); + akRemSleep = new ActivityConfig(ActivityKind.TYPE_REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_rem_sleep), AK_REM_SLEEP_COLOR); + akNotWorn = new ActivityConfig(ActivityKind.TYPE_NOT_WORN, getString(R.string.abstract_chart_fragment_kind_not_worn), AK_NOT_WORN_COLOR); + } + + protected Integer getColorFor(int activityKind) { + switch (activityKind) { + case ActivityKind.TYPE_DEEP_SLEEP: + return akDeepSleep.color; + case ActivityKind.TYPE_LIGHT_SLEEP: + return akLightSleep.color; + case ActivityKind.TYPE_REM_SLEEP: + return akRemSleep.color; + case ActivityKind.TYPE_ACTIVITY: + return akActivity.color; + } + return akActivity.color; + } + + protected SampleProvider getProvider(DBHandler db, GBDevice device) { + DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); + return coordinator.getSampleProvider(device, db.getDaoSession()); + } + + /** + * Returns all kinds of samples for the given device. + * To be called from a background thread. + * + * @param device + * @param tsFrom + * @param tsTo + */ + protected List getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + SampleProvider provider = getProvider(db, device); + return provider.getAllActivitySamples(tsFrom, tsTo); + } + + protected List getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + SampleProvider provider = getProvider(db, device); + return provider.getActivitySamples(tsFrom, tsTo); + } + + + protected List getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + SampleProvider provider = getProvider(db, device); + return provider.getSleepSamples(tsFrom, tsTo); + } + + public DefaultChartsData refresh(GBDevice gbDevice, List samples) { +// Calendar cal = GregorianCalendar.getInstance(); +// cal.clear(); + TimestampTranslation tsTranslation = new TimestampTranslation(); +// Date date; +// String dateStringFrom = ""; +// String dateStringTo = ""; +// ArrayList xLabels = null; + + LOG.info("" + getTitle() + ": number of samples:" + samples.size()); + LineData lineData; + if (samples.size() > 1) { + boolean annotate = true; + boolean use_steps_as_movement; + + int last_type = ActivityKind.TYPE_UNKNOWN; + + int numEntries = samples.size(); + List activityEntries = new ArrayList<>(numEntries); + List deepSleepEntries = new ArrayList<>(numEntries); + List lightSleepEntries = new ArrayList<>(numEntries); + List remSleepEntries = new ArrayList<>(numEntries); + List notWornEntries = new ArrayList<>(numEntries); + boolean hr = supportsHeartrate(gbDevice); + List heartrateEntries = hr ? new ArrayList(numEntries) : null; + List colors = new ArrayList<>(numEntries); // this is kinda inefficient... + int lastHrSampleIndex = -1; + HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance(); + + for (int i = 0; i < numEntries; i++) { + ActivitySample sample = samples.get(i); + int type = sample.getKind(); + int ts = tsTranslation.shorten(sample.getTimestamp()); + +// System.out.println(ts); +// ts = i; + // determine start and end dates +// if (i == 0) { +// cal.setTimeInMillis(ts * 1000L); // make sure it's converted to long +// date = cal.getTime(); +// dateStringFrom = dateFormat.format(date); +// } else if (i == samples.size() - 1) { +// cal.setTimeInMillis(ts * 1000L); // same here +// date = cal.getTime(); +// dateStringTo = dateFormat.format(date); +// } + + float movement = sample.getIntensity(); + + float value = movement; + switch (type) { + case ActivityKind.TYPE_DEEP_SLEEP: + if (last_type != type) { //FIXME: this is ugly but it works (repeated in each case) + deepSleepEntries.add(createLineEntry(0, ts - 1)); + + lightSleepEntries.add(createLineEntry(0, ts)); + remSleepEntries.add(createLineEntry(0, ts)); + notWornEntries.add(createLineEntry(0, ts)); + activityEntries.add(createLineEntry(0, ts)); + } + deepSleepEntries.add(createLineEntry(value + SleepUtils.Y_VALUE_DEEP_SLEEP, ts)); + break; + case ActivityKind.TYPE_LIGHT_SLEEP: + if (last_type != type) { + lightSleepEntries.add(createLineEntry(0, ts - 1)); + + deepSleepEntries.add(createLineEntry(0, ts)); + remSleepEntries.add(createLineEntry(0, ts)); + notWornEntries.add(createLineEntry(0, ts)); + activityEntries.add(createLineEntry(0, ts)); + } + lightSleepEntries.add(createLineEntry(value, ts)); + break; + case ActivityKind.TYPE_REM_SLEEP: + if (last_type != type) { + remSleepEntries.add(createLineEntry(0, ts - 1)); + + lightSleepEntries.add(createLineEntry(0, ts)); + deepSleepEntries.add(createLineEntry(0, ts)); + notWornEntries.add(createLineEntry(0, ts)); + activityEntries.add(createLineEntry(0, ts)); + } + remSleepEntries.add(createLineEntry(value, ts)); + break; + case ActivityKind.TYPE_NOT_WORN: + if (last_type != type) { + notWornEntries.add(createLineEntry(0, ts - 1)); + + lightSleepEntries.add(createLineEntry(0, ts)); + deepSleepEntries.add(createLineEntry(0, ts)); + remSleepEntries.add(createLineEntry(0, ts)); + activityEntries.add(createLineEntry(0, ts)); + } + notWornEntries.add(createLineEntry(SleepUtils.Y_VALUE_DEEP_SLEEP, ts)); //a small value, just to show something on the graphs + break; + default: +// short steps = sample.getSteps(); +// if (use_steps_as_movement && steps != 0) { +// // I'm not sure using steps for this is actually a good idea +// movement = steps; +// } +// value = ((float) movement) / movement_divisor; + if (last_type != type) { + activityEntries.add(createLineEntry(0, ts - 1)); + + lightSleepEntries.add(createLineEntry(0, ts)); + notWornEntries.add(createLineEntry(0, ts)); + deepSleepEntries.add(createLineEntry(0, ts)); + remSleepEntries.add(createLineEntry(0, ts)); + } + activityEntries.add(createLineEntry(value, ts)); + } + if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) { + if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) { + heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1)); + heartrateEntries.add(createLineEntry(0, ts - 1)); + } + + heartrateEntries.add(createLineEntry(sample.getHeartRate(), ts)); + lastHrSampleIndex = ts; + } + + String xLabel = ""; + if (annotate) { +// cal.setTimeInMillis((ts + tsOffset) * 1000L); +// date = cal.getTime(); +// String dateString = annotationDateFormat.format(date); +// xLabel = dateString; +// if (last_type != type) { +// if (isSleep(last_type) && !isSleep(type)) { +// // woken up +// LimitLine line = new LimitLine(i, dateString); +// line.enableDashedLine(8, 8, 0); +// line.setTextColor(Color.WHITE); +// line.setTextSize(15); +// chart.getXAxis().addLimitLine(line); +// } else if (!isSleep(last_type) && isSleep(type)) { +// // fallen asleep +// LimitLine line = new LimitLine(i, dateString); +// line.enableDashedLine(8, 8, 0); +// line.setTextSize(15); +// line.setTextColor(Color.WHITE); +// chart.getXAxis().addLimitLine(line); +// } +// } + } + last_type = type; + } + + + List lineDataSets = new ArrayList<>(); + LineDataSet activitySet = createDataSet(activityEntries, akActivity.color, "Activity"); + lineDataSets.add(activitySet); + LineDataSet deepSleepSet = createDataSet(deepSleepEntries, akDeepSleep.color, "Deep Sleep"); + lineDataSets.add(deepSleepSet); + LineDataSet lightSleepSet = createDataSet(lightSleepEntries, akLightSleep.color, "Light Sleep"); + lineDataSets.add(lightSleepSet); + if (supportsRemSleep(gbDevice)) { + LineDataSet remSleepSet = createDataSet(remSleepEntries, akRemSleep.color, "REM Sleep"); + lineDataSets.add(remSleepSet); + } + LineDataSet notWornSet = createDataSet(notWornEntries, akNotWorn.color, "Not worn"); + lineDataSets.add(notWornSet); + + if (hr && heartrateEntries.size() > 0) { + LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate"); + + lineDataSets.add(heartrateSet); + } + lineData = new LineData(lineDataSets); + +// chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo)); +// chart.setDescriptionPosition(?, ?); + } else { + lineData = new LineData(); + } + + ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation); + return new DefaultChartsData(lineData, xValueFormatter); + } + + protected Entry createLineEntry(float value, int xValue) { + return new Entry(xValue, value); + } + + 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; + } + + protected LineDataSet createHeartrateSet(List values, String label) { + 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); + return set1; + } + + /** + * Implement this to supply the samples to be displayed. + * + * @param db + * @param device + * @param tsFrom + * @param tsTo + * @return + */ + protected abstract List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo); + + protected List getSamples(DBHandler db, GBDevice device) { + int tsStart = getTSStart(); + int tsEnd = getTSEnd(); + List samples = (List) getSamples(db, device, tsStart, tsEnd); + ensureStartAndEndSamples(samples, tsStart, tsEnd); +// List samples2 = new ArrayList<>(); +// int min = Math.min(samples.size(), 10); +// int min = Math.min(samples.size(), 10); +// for (int i = 0; i < min; i++) { +// samples2.add(samples.get(i)); +// } +// return samples2; + return samples; + } + + protected List getSamplesofSleep(DBHandler db, GBDevice device) { + int SLEEP_HOUR_LIMIT = 12; + + int tsStart = getTSStart(); + Calendar day = GregorianCalendar.getInstance(); + day.setTimeInMillis(tsStart * 1000L); + day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT); + day.set(Calendar.MINUTE, 0); + day.set(Calendar.SECOND, 0); + tsStart = toTimestamp(day.getTime()); + + int tsEnd = getTSEnd(); + day.setTimeInMillis(tsEnd* 1000L); + day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT); + day.set(Calendar.MINUTE, 0); + day.set(Calendar.SECOND, 0); + tsEnd = toTimestamp(day.getTime()); + + List samples = (List) getSamples(db, device, tsStart, tsEnd); + ensureStartAndEndSamples(samples, tsStart, tsEnd); + return samples; + } + + protected void ensureStartAndEndSamples(List samples, int tsStart, int tsEnd) { + if (samples == null || samples.isEmpty()) { + return; + } + ActivitySample lastSample = samples.get(samples.size() - 1); + if (lastSample.getTimestamp() < tsEnd) { + samples.add(createTrailingActivitySample(lastSample, tsEnd)); + } + + ActivitySample firstSample = samples.get(0); + if (firstSample.getTimestamp() > tsStart) { + samples.add(createTrailingActivitySample(firstSample, tsStart)); + } + } + + private ActivitySample createTrailingActivitySample(ActivitySample referenceSample, int timestamp) { + TrailingActivitySample sample = new TrailingActivitySample(); + if (referenceSample instanceof AbstractActivitySample) { + AbstractActivitySample reference = (AbstractActivitySample) referenceSample; + sample.setUserId(reference.getUserId()); + sample.setDeviceId(reference.getDeviceId()); + sample.setProvider(reference.getProvider()); + } + sample.setTimestamp(timestamp); + return sample; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java index ed211e41e..3d3398f3e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java @@ -17,57 +17,37 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.activities.charts; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentActivity; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.github.mikephil.charting.charts.BarChart; import com.github.mikephil.charting.charts.BarLineChartBase; import com.github.mikephil.charting.charts.Chart; -import com.github.mikephil.charting.components.YAxis; 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; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; import java.util.HashSet; -import java.util.List; import java.util.Set; -import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment; -import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils; import nodomain.freeyourgadget.gadgetbridge.database.DBAccess; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; -import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; -import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; -import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; /** * A base class fragment to be used with ChartsActivity. The fragment can supply @@ -87,7 +67,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; * The default implementations #handleDatePrev(Date,Date) and #handleDateNext(Date,Date) * shift the date by one day. */ -public abstract class AbstractChartFragment extends AbstractGBFragment { +public abstract class AbstractChartFragment extends AbstractGBFragment { protected final int ANIM_TIME = 250; private static final Logger LOG = LoggerFactory.getLogger(AbstractChartFragment.class); @@ -99,60 +79,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { AbstractChartFragment.this.onReceive(context, intent); } }; + private boolean mChartDirty = true; private AsyncTask refreshTask; - public boolean isChartDirty() { - return mChartDirty; - } - - @Override - public abstract String getTitle(); - - public boolean supportsHeartrate(GBDevice device) { - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); - return coordinator != null && coordinator.supportsHeartRateMeasurement(device); - } - - public boolean supportsRemSleep(GBDevice device) { - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); - return coordinator != null && coordinator.supportsRemSleep(); - } - - protected static final class ActivityConfig { - public final int type; - public final String label; - public final Integer color; - - public ActivityConfig(int kind, String label, Integer color) { - this.type = kind; - this.label = label; - this.color = color; - } - } - - protected ActivityConfig akActivity; - protected ActivityConfig akLightSleep; - protected ActivityConfig akDeepSleep; - protected ActivityConfig akRemSleep; - protected ActivityConfig akNotWorn; - - - protected int BACKGROUND_COLOR; - protected int DESCRIPTION_COLOR; - protected int CHART_TEXT_COLOR; - protected int LEGEND_TEXT_COLOR; - protected int HEARTRATE_COLOR; - protected int HEARTRATE_FILL_COLOR; - protected int AK_ACTIVITY_COLOR; - protected int AK_DEEP_SLEEP_COLOR; - protected int AK_REM_SLEEP_COLOR; - protected int AK_LIGHT_SLEEP_COLOR; - protected int AK_NOT_WORN_COLOR; - - protected String HEARTRATE_LABEL; - protected String HEARTRATE_AVERAGE_LABEL; - protected AbstractChartFragment(String... intentFilterActions) { mIntentFilterActions = new HashSet<>(); if (intentFilterActions != null) { @@ -167,62 +97,73 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { mIntentFilterActions.add(ChartsHost.REFRESH); } + @Override + public abstract String getTitle(); + + /** + * Called in the fragment's onCreate, initializes this fragment. + */ + protected abstract void init(); + + /** + * This method reads the data from the database, analyzes and prepares it for + * the charts. This will be called from a background task, so there must not be + * any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method. + */ + protected abstract D refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device); + + /** + * Triggers the actual (re-) rendering of the chart. + * Always called from the UI thread. + */ + protected abstract void renderCharts(); + + protected abstract void setupLegend(Chart chart); + + protected abstract void updateChartsnUIThread(D chartsData); + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); init(); - IntentFilter filter = new IntentFilter(); + final IntentFilter filter = new IntentFilter(); for (String action : mIntentFilterActions) { filter.addAction(action); } - LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, filter); + LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(mReceiver, filter); } - protected void init() { - Prefs prefs = GBApplication.getPrefs(); - TypedValue runningColor = new TypedValue(); - BACKGROUND_COLOR = GBApplication.getBackgroundColor(getContext()); - LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = GBApplication.getTextColor(getContext()); - CHART_TEXT_COLOR = ContextCompat.getColor(getContext(), R.color.secondarytext); - if (prefs.getBoolean("chart_heartrate_color", false)) { - HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_alternative); - }else{ - HEARTRATE_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate); + @Override + public void onDestroy() { + super.onDestroy(); + LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver); + } + + /** + * Called when this fragment has been fully scrolled into the activity. + * + * @see #isVisibleInActivity() + * @see #onMadeInvisibleInActivity() + */ + @Override + protected void onMadeVisibleInActivity() { + super.onMadeVisibleInActivity(); + showDateBar(true); + if (mChartDirty) { + refresh(); } - HEARTRATE_FILL_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_fill); + } - getContext().getTheme().resolveAttribute(R.attr.chart_activity, runningColor, true); - AK_ACTIVITY_COLOR = runningColor.data; - getContext().getTheme().resolveAttribute(R.attr.chart_deep_sleep, runningColor, true); - AK_DEEP_SLEEP_COLOR = runningColor.data; - getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true); - AK_LIGHT_SLEEP_COLOR = runningColor.data; - getContext().getTheme().resolveAttribute(R.attr.chart_rem_sleep, runningColor, true); - AK_REM_SLEEP_COLOR = runningColor.data; - getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true); - AK_NOT_WORN_COLOR = runningColor.data; - - HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate); - HEARTRATE_AVERAGE_LABEL = getContext().getString(R.string.charts_legend_heartrate_average); - - akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR); - akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR); - akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR); - akRemSleep = new ActivityConfig(ActivityKind.TYPE_REM_SLEEP, getString(R.string.abstract_chart_fragment_kind_rem_sleep), AK_REM_SLEEP_COLOR); - akNotWorn = new ActivityConfig(ActivityKind.TYPE_NOT_WORN, getString(R.string.abstract_chart_fragment_kind_not_worn), AK_NOT_WORN_COLOR); + protected ChartsHost getChartsHost() { + return (ChartsHost) requireActivity(); } private void setStartDate(Date date) { getChartsHost().setStartDate(date); } - @Nullable - protected ChartsHost getChartsHost() { - return (ChartsHost) getActivity(); - } - private void setEndDate(Date date) { getChartsHost().setEndDate(date); } @@ -235,56 +176,47 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { return getChartsHost().getEndDate(); } - /** - * Called when this fragment has been fully scrolled into the activity. - * - * @see #isVisibleInActivity() - * @see #onMadeInvisibleInActivity() - */ - @Override - protected void onMadeVisibleInActivity() { - super.onMadeVisibleInActivity(); - showDateBar(true); - if (isChartDirty()) { - refresh(); - } + protected int getTSEnd() { + return toTimestamp(getEndDate()); + } + + protected int getTSStart() { + return toTimestamp(getStartDate()); + } + + protected int toTimestamp(Date date) { + return (int) ((date.getTime() / 1000)); } protected void showDateBar(boolean show) { getChartsHost().getDateBar().setVisibility(show ? View.VISIBLE : View.GONE); } - @Override - public void onDestroy() { - super.onDestroy(); - LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver); - } - protected void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ChartsHost.REFRESH.equals(action)) { refresh(); } else if (ChartsHost.DATE_NEXT_DAY.equals(action)) { - handleDate(getStartDate(), getEndDate(),+1); + handleDate(getStartDate(), getEndDate(), +1); } else if (ChartsHost.DATE_PREV_DAY.equals(action)) { - handleDate(getStartDate(), getEndDate(),-1); + handleDate(getStartDate(), getEndDate(), -1); } else if (ChartsHost.DATE_NEXT_WEEK.equals(action)) { - handleDate(getStartDate(), getEndDate(),+7); + handleDate(getStartDate(), getEndDate(), +7); } else if (ChartsHost.DATE_PREV_WEEK.equals(action)) { - handleDate(getStartDate(), getEndDate(),-7); + handleDate(getStartDate(), getEndDate(), -7); } else if (ChartsHost.DATE_NEXT_MONTH.equals(action)) { //calculate dates to jump by month but keep subsequent logic working - int time1 = DateTimeUtils.shiftMonths((int )(getStartDate().getTime()/1000), 1); - int time2 = DateTimeUtils.shiftMonths((int )(getEndDate().getTime()/1000), 1); + int time1 = DateTimeUtils.shiftMonths((int) (getStartDate().getTime() / 1000), 1); + int time2 = DateTimeUtils.shiftMonths((int) (getEndDate().getTime() / 1000), 1); Date date1 = DateTimeUtils.shiftByDays(new Date(time1 * 1000L), 30); Date date2 = DateTimeUtils.shiftByDays(new Date(time2 * 1000L), 30); - handleDate(date1, date2,-30); + handleDate(date1, date2, -30); } else if (ChartsHost.DATE_PREV_MONTH.equals(action)) { - int time1 = DateTimeUtils.shiftMonths((int )(getStartDate().getTime()/1000), -1); - int time2 = DateTimeUtils.shiftMonths((int )(getEndDate().getTime()/1000), -1); + int time1 = DateTimeUtils.shiftMonths((int) (getStartDate().getTime() / 1000), -1); + int time2 = DateTimeUtils.shiftMonths((int) (getEndDate().getTime() / 1000), -1); Date date1 = DateTimeUtils.shiftByDays(new Date(time1 * 1000L), -30); Date date2 = DateTimeUtils.shiftByDays(new Date(time2 * 1000L), -30); - handleDate(date1, date2,30); + handleDate(date1, date2, 30); } } @@ -292,21 +224,20 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { * Default implementation shifts the dates by one day, if visible * and calls #refreshIfVisible(). * - * @param startDate - * @param endDate - * @param Offset + * @param startDate the start date + * @param endDate the end date + * @param offset the offset, in days */ - protected void handleDate(Date startDate, Date endDate, Integer Offset) { + private void handleDate(Date startDate, Date endDate, Integer offset) { if (isVisibleInActivity()) { - if (!shiftDates(startDate, endDate, Offset)) { + if (!shiftDates(startDate, endDate, offset)) { return; } } refreshIfVisible(); } - - protected void refreshIfVisible() { + private void refreshIfVisible() { if (isVisibleInActivity()) { refresh(); } else { @@ -317,65 +248,22 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { /** * Shifts the given dates by offset days. offset may be positive or negative. * - * @param startDate - * @param endDate + * @param startDate the start date + * @param endDate the end date * @param offset a positive or negative number of days to shift the dates * @return true if the shift was successful and false otherwise */ - protected boolean shiftDates(Date startDate, Date endDate, int offset) { + private boolean shiftDates(Date startDate, Date endDate, int offset) { Date newStart = DateTimeUtils.shiftByDays(startDate, offset); Date newEnd = DateTimeUtils.shiftByDays(endDate, offset); Date now = new Date(); if (newEnd.after(now)) { //allow to jump to the end (now) if week/month reach after now - newEnd=now; - newStart=DateTimeUtils.shiftByDays(now,-1); + newEnd = now; + newStart = DateTimeUtils.shiftByDays(now, -1); } return setDateRange(newStart, newEnd); } - protected Integer getColorFor(int activityKind) { - switch (activityKind) { - case ActivityKind.TYPE_DEEP_SLEEP: - return akDeepSleep.color; - case ActivityKind.TYPE_LIGHT_SLEEP: - return akLightSleep.color; - case ActivityKind.TYPE_REM_SLEEP: - return akRemSleep.color; - case ActivityKind.TYPE_ACTIVITY: - return akActivity.color; - } - return akActivity.color; - } - - protected SampleProvider getProvider(DBHandler db, GBDevice device) { - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); - return coordinator.getSampleProvider(device, db.getDaoSession()); - } - - /** - * Returns all kinds of samples for the given device. - * To be called from a background thread. - * - * @param device - * @param tsFrom - * @param tsTo - */ - protected List getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { - SampleProvider provider = getProvider(db, device); - return provider.getAllActivitySamples(tsFrom, tsTo); - } - - protected List getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { - SampleProvider provider = getProvider(db, device); - return provider.getActivitySamples(tsFrom, tsTo); - } - - - protected List getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { - SampleProvider provider = getProvider(db, device); - return provider.getSleepSamples(tsFrom, tsTo); - } - protected void configureChartDefaults(Chart chart) { chart.getXAxis().setValueFormatter(new TimestampValueFormatter()); chart.getDescription().setText(""); @@ -432,271 +320,21 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { } } - /** - * This method reads the data from the database, analyzes and prepares it for - * the charts. This will be called from a background task, so there must not be - * any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method. - */ - protected abstract ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device); - - /** - * Triggers the actual (re-) rendering of the chart. - * Always called from the UI thread. - */ - protected abstract void renderCharts(); - - public DefaultChartsData refresh(GBDevice gbDevice, List samples) { -// Calendar cal = GregorianCalendar.getInstance(); -// cal.clear(); - TimestampTranslation tsTranslation = new TimestampTranslation(); -// Date date; -// String dateStringFrom = ""; -// String dateStringTo = ""; -// ArrayList xLabels = null; - - LOG.info("" + getTitle() + ": number of samples:" + samples.size()); - LineData lineData; - if (samples.size() > 1) { - boolean annotate = true; - boolean use_steps_as_movement; - - int last_type = ActivityKind.TYPE_UNKNOWN; - - int numEntries = samples.size(); - List activityEntries = new ArrayList<>(numEntries); - List deepSleepEntries = new ArrayList<>(numEntries); - List lightSleepEntries = new ArrayList<>(numEntries); - List remSleepEntries = new ArrayList<>(numEntries); - List notWornEntries = new ArrayList<>(numEntries); - boolean hr = supportsHeartrate(gbDevice); - List heartrateEntries = hr ? new ArrayList(numEntries) : null; - List colors = new ArrayList<>(numEntries); // this is kinda inefficient... - int lastHrSampleIndex = -1; - HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance(); - - for (int i = 0; i < numEntries; i++) { - ActivitySample sample = samples.get(i); - int type = sample.getKind(); - int ts = tsTranslation.shorten(sample.getTimestamp()); - -// System.out.println(ts); -// ts = i; - // determine start and end dates -// if (i == 0) { -// cal.setTimeInMillis(ts * 1000L); // make sure it's converted to long -// date = cal.getTime(); -// dateStringFrom = dateFormat.format(date); -// } else if (i == samples.size() - 1) { -// cal.setTimeInMillis(ts * 1000L); // same here -// date = cal.getTime(); -// dateStringTo = dateFormat.format(date); -// } - - float movement = sample.getIntensity(); - - float value = movement; - switch (type) { - case ActivityKind.TYPE_DEEP_SLEEP: - if (last_type != type) { //FIXME: this is ugly but it works (repeated in each case) - deepSleepEntries.add(createLineEntry(0, ts - 1)); - - lightSleepEntries.add(createLineEntry(0, ts)); - remSleepEntries.add(createLineEntry(0, ts)); - notWornEntries.add(createLineEntry(0, ts)); - activityEntries.add(createLineEntry(0, ts)); - } - deepSleepEntries.add(createLineEntry(value + SleepUtils.Y_VALUE_DEEP_SLEEP, ts)); - break; - case ActivityKind.TYPE_LIGHT_SLEEP: - if (last_type != type) { - lightSleepEntries.add(createLineEntry(0, ts - 1)); - - deepSleepEntries.add(createLineEntry(0, ts)); - remSleepEntries.add(createLineEntry(0, ts)); - notWornEntries.add(createLineEntry(0, ts)); - activityEntries.add(createLineEntry(0, ts)); - } - lightSleepEntries.add(createLineEntry(value, ts)); - break; - case ActivityKind.TYPE_REM_SLEEP: - if (last_type != type) { - remSleepEntries.add(createLineEntry(0, ts - 1)); - - lightSleepEntries.add(createLineEntry(0, ts)); - deepSleepEntries.add(createLineEntry(0, ts)); - notWornEntries.add(createLineEntry(0, ts)); - activityEntries.add(createLineEntry(0, ts)); - } - remSleepEntries.add(createLineEntry(value, ts)); - break; - case ActivityKind.TYPE_NOT_WORN: - if (last_type != type) { - notWornEntries.add(createLineEntry(0, ts - 1)); - - lightSleepEntries.add(createLineEntry(0, ts)); - deepSleepEntries.add(createLineEntry(0, ts)); - remSleepEntries.add(createLineEntry(0, ts)); - activityEntries.add(createLineEntry(0, ts)); - } - notWornEntries.add(createLineEntry(SleepUtils.Y_VALUE_DEEP_SLEEP, ts)); //a small value, just to show something on the graphs - break; - default: -// short steps = sample.getSteps(); -// if (use_steps_as_movement && steps != 0) { -// // I'm not sure using steps for this is actually a good idea -// movement = steps; -// } -// value = ((float) movement) / movement_divisor; - if (last_type != type) { - activityEntries.add(createLineEntry(0, ts - 1)); - - lightSleepEntries.add(createLineEntry(0, ts)); - notWornEntries.add(createLineEntry(0, ts)); - deepSleepEntries.add(createLineEntry(0, ts)); - remSleepEntries.add(createLineEntry(0, ts)); - } - activityEntries.add(createLineEntry(value, ts)); - } - if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) { - if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) { - heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1)); - heartrateEntries.add(createLineEntry(0, ts - 1)); - } - - heartrateEntries.add(createLineEntry(sample.getHeartRate(), ts)); - lastHrSampleIndex = ts; - } - - String xLabel = ""; - if (annotate) { -// cal.setTimeInMillis((ts + tsOffset) * 1000L); -// date = cal.getTime(); -// String dateString = annotationDateFormat.format(date); -// xLabel = dateString; -// if (last_type != type) { -// if (isSleep(last_type) && !isSleep(type)) { -// // woken up -// LimitLine line = new LimitLine(i, dateString); -// line.enableDashedLine(8, 8, 0); -// line.setTextColor(Color.WHITE); -// line.setTextSize(15); -// chart.getXAxis().addLimitLine(line); -// } else if (!isSleep(last_type) && isSleep(type)) { -// // fallen asleep -// LimitLine line = new LimitLine(i, dateString); -// line.enableDashedLine(8, 8, 0); -// line.setTextSize(15); -// line.setTextColor(Color.WHITE); -// chart.getXAxis().addLimitLine(line); -// } -// } - } - last_type = type; - } - - - List lineDataSets = new ArrayList<>(); - LineDataSet activitySet = createDataSet(activityEntries, akActivity.color, "Activity"); - lineDataSets.add(activitySet); - LineDataSet deepSleepSet = createDataSet(deepSleepEntries, akDeepSleep.color, "Deep Sleep"); - lineDataSets.add(deepSleepSet); - LineDataSet lightSleepSet = createDataSet(lightSleepEntries, akLightSleep.color, "Light Sleep"); - lineDataSets.add(lightSleepSet); - if (supportsRemSleep(gbDevice)) { - LineDataSet remSleepSet = createDataSet(remSleepEntries, akRemSleep.color, "REM Sleep"); - lineDataSets.add(remSleepSet); - } - LineDataSet notWornSet = createDataSet(notWornEntries, akNotWorn.color, "Not worn"); - lineDataSets.add(notWornSet); - - if (hr && heartrateEntries.size() > 0) { - LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate"); - - lineDataSets.add(heartrateSet); - } - lineData = new LineData(lineDataSets); - -// chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo)); -// chart.setDescriptionPosition(?, ?); - } else { - lineData = new LineData(); - } - - ValueFormatter xValueFormatter = new SampleXLabelFormatter(tsTranslation); - return new DefaultChartsData(lineData, xValueFormatter); - } - - /** - * Implement this to supply the samples to be displayed. - * - * @param db - * @param device - * @param tsFrom - * @param tsTo - * @return - */ - protected abstract List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo); - - protected abstract void setupLegend(Chart chart); - - protected Entry createLineEntry(float value, int xValue) { - return new Entry(xValue, value); - } - - 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; - } - - protected LineDataSet createHeartrateSet(List values, String label) { - 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); - return set1; - } - - protected RefreshTask createRefreshTask(String task, Context context) { + private RefreshTask createRefreshTask(final String task, final Context context) { return new RefreshTask(task, context); } - public class RefreshTask extends DBAccess { - private ChartsData chartsData; + @SuppressLint("StaticFieldLeak") + private final class RefreshTask extends DBAccess { + private D chartsData; - public RefreshTask(String task, Context context) { + public RefreshTask(final String task, final Context context) { super(task, context); } @Override - protected void doInBackground(DBHandler db) { - ChartsHost chartsHost = getChartsHost(); + protected void doInBackground(final DBHandler db) { + final ChartsHost chartsHost = getChartsHost(); if (chartsHost != null) { chartsData = refreshInBackground(chartsHost, db, chartsHost.getDevice()); } else { @@ -705,9 +343,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { } @Override - protected void onPostExecute(Object o) { + protected void onPostExecute(final Object o) { super.onPostExecute(o); - FragmentActivity activity = getActivity(); + final FragmentActivity activity = getActivity(); if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) { updateChartsnUIThread(chartsData); renderCharts(); @@ -717,22 +355,20 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { } } - protected abstract void updateChartsnUIThread(ChartsData chartsData); - /** * Returns true if the date was successfully shifted, and false if the shift * was ignored, e.g. when the to-value is in the future. * - * @param from - * @param to + * @param from the start date + * @param to the end date */ - public boolean setDateRange(Date from, Date to) { + private boolean setDateRange(final Date from, final Date to) { if (from.compareTo(to) > 0) { throw new IllegalArgumentException("Bad date range: " + from + ".." + to); } - Date now = new Date(); + final Date now = new Date(); if (to.after(now) || //do not refresh chart if we reached now - to.getTime()/10000 == (getEndDate().getTime()/10000)) { + to.getTime() / 10000 == (getEndDate().getTime() / 10000)) { return false; } setStartDate(from); @@ -740,88 +376,11 @@ public abstract class AbstractChartFragment extends AbstractGBFragment { return true; } - protected void updateDateInfo(Date from, Date to) { + private void updateDateInfo(final Date from, final Date to) { if (from.equals(to)) { getChartsHost().setDateInfo(DateTimeUtils.formatDate(from)); } else { getChartsHost().setDateInfo(DateTimeUtils.formatDateRange(from, to)); } } - - protected List getSamples(DBHandler db, GBDevice device) { - int tsStart = getTSStart(); - int tsEnd = getTSEnd(); - List samples = (List) getSamples(db, device, tsStart, tsEnd); - ensureStartAndEndSamples(samples, tsStart, tsEnd); -// List samples2 = new ArrayList<>(); -// int min = Math.min(samples.size(), 10); -// int min = Math.min(samples.size(), 10); -// for (int i = 0; i < min; i++) { -// samples2.add(samples.get(i)); -// } -// return samples2; - return samples; - } - - protected List getSamplesofSleep(DBHandler db, GBDevice device) { - int SLEEP_HOUR_LIMIT = 12; - - int tsStart = getTSStart(); - Calendar day = GregorianCalendar.getInstance(); - day.setTimeInMillis(tsStart * 1000L); - day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT); - day.set(Calendar.MINUTE, 0); - day.set(Calendar.SECOND, 0); - tsStart = toTimestamp(day.getTime()); - - int tsEnd = getTSEnd(); - day.setTimeInMillis(tsEnd* 1000L); - day.set(Calendar.HOUR_OF_DAY, SLEEP_HOUR_LIMIT); - day.set(Calendar.MINUTE, 0); - day.set(Calendar.SECOND, 0); - tsEnd = toTimestamp(day.getTime()); - - List samples = (List) getSamples(db, device, tsStart, tsEnd); - ensureStartAndEndSamples(samples, tsStart, tsEnd); - return samples; - } - - protected void ensureStartAndEndSamples(List samples, int tsStart, int tsEnd) { - if (samples == null || samples.isEmpty()) { - return; - } - ActivitySample lastSample = samples.get(samples.size() - 1); - if (lastSample.getTimestamp() < tsEnd) { - samples.add(createTrailingActivitySample(lastSample, tsEnd)); - } - - ActivitySample firstSample = samples.get(0); - if (firstSample.getTimestamp() > tsStart) { - samples.add(createTrailingActivitySample(firstSample, tsStart)); - } - } - - private ActivitySample createTrailingActivitySample(ActivitySample referenceSample, int timestamp) { - TrailingActivitySample sample = new TrailingActivitySample(); - if (referenceSample instanceof AbstractActivitySample) { - AbstractActivitySample reference = (AbstractActivitySample) referenceSample; - sample.setUserId(reference.getUserId()); - sample.setDeviceId(reference.getDeviceId()); - sample.setProvider(reference.getProvider()); - } - sample.setTimestamp(timestamp); - return sample; - } - - private int getTSEnd() { - return toTimestamp(getEndDate()); - } - - private int getTSStart() { - return toTimestamp(getStartDate()); - } - - private int toTimestamp(Date date) { - return (int) ((date.getTime() / 1000)); - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java new file mode 100644 index 000000000..83de45923 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java @@ -0,0 +1,285 @@ +/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, vanous, Vebryn + + 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.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import androidx.viewpager.widget.ViewPager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; + +public abstract class AbstractChartsActivity extends AbstractGBFragmentActivity implements ChartsHost { + private static final Logger LOG = LoggerFactory.getLogger(AbstractChartsActivity.class); + + public static final String EXTRA_FRAGMENT_ID = "fragment"; + public static final int REQUEST_CODE_PREFERENCES = 1; + + private TextView mDateControl; + + private Date mStartDate; + private Date mEndDate; + private SwipeRefreshLayout swipeLayout; + + List enabledTabsList; + + private GBDevice mGBDevice; + private ViewGroup dateBar; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + switch (Objects.requireNonNull(action)) { + case GBDevice.ACTION_DEVICE_CHANGED: + GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); + if (dev != null) { + refreshBusyState(dev); + } + break; + } + } + }; + + private void refreshBusyState(GBDevice dev) { + if (dev.isBusy()) { + swipeLayout.setRefreshing(true); + } else { + boolean wasBusy = swipeLayout.isRefreshing(); + swipeLayout.setRefreshing(false); + if (wasBusy) { + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(REFRESH)); + } + } + enableSwipeRefresh(true); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_charts); + int tabFragmentToOpen = -1; + + initDates(); + + final IntentFilter filterLocal = new IntentFilter(); + filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED); + LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); + + final Bundle extras = getIntent().getExtras(); + if (extras != null) { + mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); + tabFragmentToOpen = extras.getInt(EXTRA_FRAGMENT_ID); + } else { + throw new IllegalArgumentException("Must provide a device when invoking this activity"); + } + enabledTabsList = fillChartsTabsList(); + + swipeLayout = findViewById(R.id.activity_swipe_layout); + swipeLayout.setOnRefreshListener(this::fetchRecordedData); + enableSwipeRefresh(true); + + // Set up the ViewPager with the sections adapter. + final NonSwipeableViewPager viewPager = findViewById(R.id.charts_pager); + viewPager.setAdapter(getPagerAdapter()); + if (tabFragmentToOpen > -1) { + viewPager.setCurrentItem(tabFragmentToOpen); // open the tab as specified in the intent + } + + viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + } + + @Override + public void onPageScrollStateChanged(int state) { + enableSwipeRefresh(state == ViewPager.SCROLL_STATE_IDLE); + } + }); + + dateBar = findViewById(R.id.charts_date_bar); + mDateControl = findViewById(R.id.charts_text_date); + mDateControl.setOnClickListener(v -> { + String detailedDuration = formatDetailedDuration(); + new ShowDurationDialog(detailedDuration, AbstractChartsActivity.this).show(); + }); + + Button mPrevButton = findViewById(R.id.charts_previous_day); + mPrevButton.setOnClickListener(v -> handleButtonClicked(DATE_PREV_DAY)); + Button mNextButton = findViewById(R.id.charts_next_day); + mNextButton.setOnClickListener(v -> handleButtonClicked(DATE_NEXT_DAY)); + + Button mPrevWeekButton = findViewById(R.id.charts_previous_week); + mPrevWeekButton.setOnClickListener(v -> handleButtonClicked(DATE_PREV_WEEK)); + Button mNextWeekButton = findViewById(R.id.charts_next_week); + mNextWeekButton.setOnClickListener(v -> handleButtonClicked(DATE_NEXT_WEEK)); + + Button mPrevMonthButton = findViewById(R.id.charts_previous_month); + mPrevMonthButton.setOnClickListener(v -> handleButtonClicked(DATE_PREV_MONTH)); + Button mNextMonthButton = findViewById(R.id.charts_next_month); + mNextMonthButton.setOnClickListener(v -> handleButtonClicked(DATE_NEXT_MONTH)); + } + + protected abstract List fillChartsTabsList(); + + private String formatDetailedDuration() { + final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm"); + final String dateStringFrom = dateFormat.format(getStartDate()); + final String dateStringTo = dateFormat.format(getEndDate()); + + return getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo); + } + + protected void initDates() { + setEndDate(new Date()); + setStartDate(DateTimeUtils.shiftByDays(getEndDate(), -1)); + } + + @Override + public GBDevice getDevice() { + return mGBDevice; + } + + @Override + public void setStartDate(Date startDate) { + mStartDate = startDate; + } + + @Override + public void setEndDate(Date endDate) { + mEndDate = endDate; + } + + @Override + public Date getStartDate() { + return mStartDate; + } + + @Override + public Date getEndDate() { + return mEndDate; + } + + @Override + public void setDateInfo(final String dateInfo) { + mDateControl.setText(dateInfo); + } + + @Override + public ViewGroup getDateBar() { + return dateBar; + } + + private void handleButtonClicked(final String action) { + LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(action)); + } + + @Override + protected void onDestroy() { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.menu_charts, menu); + + if (!mGBDevice.isConnected() || !supportsRefresh()) { + menu.removeItem(R.id.charts_fetch_activity_data); + } + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_PREFERENCES) { + this.recreate(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.charts_fetch_activity_data: + fetchRecordedData(); + return true; + case R.id.prefs_charts_menu: + Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class); + startActivityForResult(settingsIntent, REQUEST_CODE_PREFERENCES); + return true; + default: + break; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void enableSwipeRefresh(boolean enable) { + swipeLayout.setEnabled(enable && allowRefresh()); + } + + protected abstract boolean supportsRefresh(); + + protected abstract boolean allowRefresh(); + + protected abstract int getRecordedDataType(); + + private void fetchRecordedData() { + if (getDevice().isInitialized()) { + GBApplication.deviceService(getDevice()).onFetchRecordedData(getRecordedDataType()); + } else { + swipeLayout.setRefreshing(false); + GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR); + } + } +} 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 c14b34a3d..d8c22f75c 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 @@ -60,7 +60,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; -public abstract class AbstractWeekChartFragment extends AbstractChartFragment { +public abstract class AbstractWeekChartFragment extends AbstractActivityChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class); protected final int TOTAL_DAYS = getRangeDays(); protected int TOTAL_DAYS_FOR_AVERAGE = 0; @@ -76,20 +76,18 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment { ImageView stepsStreaksButton; @Override - protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { + 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); + WeekChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device); return new MyChartsData(dayData, weekBeforeData); } @Override - protected void updateChartsnUIThread(ChartsData chartsData) { - MyChartsData mcd = (MyChartsData) chartsData; - + protected void updateChartsnUIThread(MyChartsData mcd) { setupLegend(mWeekChart); mTodayPieChart.setCenterText(mcd.getDayData().centerText); mTodayPieChart.setData(mcd.getDayData().data); @@ -353,7 +351,7 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment { } } - private static class MyChartsData extends ChartsData { + protected static class MyChartsData extends ChartsData { private final WeekChartsData weekBeforeData; private final DayData dayData; @@ -379,7 +377,7 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment { Activity activity = getActivity(); int key = (int) (day.getTimeInMillis() / 1000) + (mOffsetHours * 3600); if (activity != null) { - activityAmountCache = ((ChartsActivity) activity).mActivityAmountCache; + activityAmountCache = ((ActivityChartsActivity) activity).mActivityAmountCache; amounts = (ActivityAmounts) (activityAmountCache.lookup(key)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java new file mode 100644 index 000000000..3e3be81e1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityChartsActivity.java @@ -0,0 +1,167 @@ +/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, vanous, Vebryn + + 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 androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; +import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class ActivityChartsActivity extends AbstractChartsActivity { + LimitedQueue mActivityAmountCache = new LimitedQueue(60); + + @Override + protected AbstractFragmentPagerAdapter createFragmentPagerAdapter(final FragmentManager fragmentManager) { + return new SectionsPagerAdapter(fragmentManager); + } + + @Override + protected int getRecordedDataType() { + return RecordedDataTypes.TYPE_ACTIVITY; + } + + @Override + protected boolean supportsRefresh() { + final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(getDevice()); + return coordinator.supportsActivityDataFetching(); + } + + @Override + protected boolean allowRefresh() { + final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(getDevice()); + return coordinator.allowFetchActivityData(getDevice()) && supportsRefresh(); + } + + @Override + protected List fillChartsTabsList() { + return fillChartsTabsList(getDevice(), this); + } + + private static List fillChartsTabsList(final GBDevice device, final Context context) { + final List tabList; + final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); + final String myTabs = prefs.getString(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS, null); + + if (myTabs == null) { + //make list mutable to be able to remove items later + tabList = new ArrayList<>(Arrays.asList(context.getResources().getStringArray(R.array.pref_charts_tabs_items_default))); + } else { + tabList = new ArrayList<>(Arrays.asList(myTabs.split(","))); + } + final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); + if (!coordinator.supportsRealtimeData()) { + tabList.remove("livestats"); + } + return tabList; + } + + public static int getChartsTabIndex(final String tab, final GBDevice device, final Context context) { + final List enabledTabsList = fillChartsTabsList(device, context); + return enabledTabsList.indexOf(tab); + } + + /** + * A {@link FragmentStatePagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ + private class SectionsPagerAdapter extends AbstractFragmentPagerAdapter { + SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + switch (enabledTabsList.get(position)) { + case "activity": + return new ActivitySleepChartFragment(); + case "activitylist": + return new ActivityListingChartFragment(); + case "sleep": + return new SleepChartFragment(); + case "sleepweek": + return new WeekSleepChartFragment(); + case "stepsweek": + return new WeekStepsChartFragment(); + case "speedzones": + return new SpeedZonesFragment(); + case "livestats": + return new LiveActivityFragment(); + } + return null; + } + + @Override + public int getCount() { + return enabledTabsList.toArray().length; + } + + private String getSleepTitle() { + if (GBApplication.getPrefs().getBoolean("charts_range", true)) { + return getString(R.string.weeksleepchart_sleep_a_month); + } else { + return getString(R.string.weeksleepchart_sleep_a_week); + } + } + + public String getStepsTitle() { + if (GBApplication.getPrefs().getBoolean("charts_range", true)) { + return getString(R.string.weekstepschart_steps_a_month); + } else { + return getString(R.string.weekstepschart_steps_a_week); + } + } + + @Override + public CharSequence getPageTitle(int position) { + switch (enabledTabsList.get(position)) { + case "activity": + return getString(R.string.activity_sleepchart_activity_and_sleep); + case "activitylist": + return getString(R.string.charts_activity_list); + case "sleep": + return getString(R.string.sleepchart_your_sleep); + case "sleepweek": + return getSleepTitle(); + case "stepsweek": + return getStepsTitle(); + case "speedzones": + return getString(R.string.stats_title); + case "livestats": + return getString(R.string.liveactivity_live_activity); + } + return super.getPageTitle(position); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java index 386c9a0d5..7fc773038 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java @@ -40,7 +40,6 @@ import org.slf4j.LoggerFactory; import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -51,7 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySession; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -public class ActivityListingChartFragment extends AbstractChartFragment { +public class ActivityListingChartFragment extends AbstractActivityChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class); int tsDateTo; @@ -114,7 +113,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment { } @Override - protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { + protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { List activitySamples; activitySamples = getSamples(db, device); List stepSessions = null; @@ -138,16 +137,14 @@ public class ActivityListingChartFragment extends AbstractChartFragment { } @Override - protected void updateChartsnUIThread(ChartsData chartsData) { - MyChartsData mcd = (MyChartsData) chartsData; - + protected void updateChartsnUIThread(MyChartsData mcd) { if (mcd == null) { return; } if (mcd.getStepSessions() == null) { return; } - + if (mcd.getStepSessions().toArray().length == 0) { getChartsHost().enableSwipeRefresh(true); //enable pull to refresh, might be needed } else { @@ -170,7 +167,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { } @Override @@ -200,7 +197,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment { final Snackbar snackbar = Snackbar.make(rootView, text, 1000 * 8); View snackbarView = snackbar.getView(); - snackbarView.setBackgroundColor(getContext().getResources().getColor(R.color.accent)); + snackbarView.setBackgroundColor(requireContext().getResources().getColor(R.color.accent)); snackbar.setActionTextColor(Color.WHITE); snackbar.setAction(getString(R.string.dialog_hide).toUpperCase(), new View.OnClickListener() { @Override @@ -213,18 +210,18 @@ public class ActivityListingChartFragment extends AbstractChartFragment { } private void showDashboard(int date, GBDevice device) { - FragmentManager fm = getActivity().getSupportFragmentManager(); + FragmentManager fm = requireActivity().getSupportFragmentManager(); ActivityListingDashboard listingDashboardFragment = ActivityListingDashboard.newInstance(date, device); listingDashboardFragment.show(fm, "activity_list_total_dashboard"); } private void showDetail(int tsFrom, int tsTo, ActivitySession item, GBDevice device) { - FragmentManager fm = getActivity().getSupportFragmentManager(); + FragmentManager fm = requireActivity().getSupportFragmentManager(); ActivityListingDetail listingDetailFragment = ActivityListingDetail.newInstance(tsFrom, tsTo, item, device); listingDetailFragment.show(fm, "activity_list_detail"); } - private static class MyChartsData extends ChartsData { + protected static final class MyChartsData extends ChartsData { private final List stepSessions; private final ActivitySession ongoingSession; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java index 1504e8640..8b42186b9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java @@ -46,7 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; -public class ActivitySleepChartFragment extends AbstractChartFragment { +public class ActivitySleepChartFragment extends AbstractActivityChartFragment> { protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class); private LineChart mChart; @@ -127,14 +127,13 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { } @Override - protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { + protected DefaultChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { List samples = getSamples(db, device); return refresh(device, samples); } @Override - protected void updateChartsnUIThread(ChartsData chartsData) { - DefaultChartsData dcd = (DefaultChartsData) chartsData; + protected void updateChartsnUIThread(DefaultChartsData dcd) { mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR); mChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 mChart.getXAxis().setValueFormatter(dcd.getXValueFormatter()); @@ -148,7 +147,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { List legendEntries = new ArrayList<>(5); LegendEntry activityEntry = new LegendEntry(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java deleted file mode 100644 index 2ec238169..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java +++ /dev/null @@ -1,435 +0,0 @@ -/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele - Gobbetti, vanous, Vebryn - - 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.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.viewpager.widget.ViewPager; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.Objects; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter; -import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; -import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; -import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; -import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; -import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; - -public class ChartsActivity extends AbstractGBFragmentActivity implements ChartsHost { - private static final Logger LOG = LoggerFactory.getLogger(ChartsActivity.class); - public static final String EXTRA_FRAGMENT_ID = "fragment"; - - private TextView mDateControl; - - private Date mStartDate; - private Date mEndDate; - private SwipeRefreshLayout swipeLayout; - - LimitedQueue mActivityAmountCache = new LimitedQueue(60); - ArrayList enabledTabsList; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - switch (Objects.requireNonNull(action)) { - case GBDevice.ACTION_DEVICE_CHANGED: - GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE); - refreshBusyState(dev); - break; - } - } - }; - private GBDevice mGBDevice; - private ViewGroup dateBar; - - private void refreshBusyState(GBDevice dev) { - if (dev.isBusy()) { - swipeLayout.setRefreshing(true); - } else { - boolean wasBusy = swipeLayout.isRefreshing(); - swipeLayout.setRefreshing(false); - if (wasBusy) { - LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(REFRESH)); - } - } - enableSwipeRefresh(true); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_charts); - int tabFragmentToOpen = -1; - - initDates(); - - IntentFilter filterLocal = new IntentFilter(); - filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED); - LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); - - Bundle extras = getIntent().getExtras(); - if (extras != null) { - mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); - tabFragmentToOpen = extras.getInt(EXTRA_FRAGMENT_ID); - - } else { - throw new IllegalArgumentException("Must provide a device when invoking this activity"); - } - enabledTabsList = fillChartsTabsList(getDevice(), this); - - swipeLayout = findViewById(R.id.activity_swipe_layout); - swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - fetchActivityData(); - } - }); - enableSwipeRefresh(true); - - // Set up the ViewPager with the sections adapter. - NonSwipeableViewPager viewPager = findViewById(R.id.charts_pager); - viewPager.setAdapter(getPagerAdapter()); - if (tabFragmentToOpen > -1) { - viewPager.setCurrentItem(tabFragmentToOpen); //open the tab as specified in the intent - } - - viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { - } - - @Override - public void onPageScrollStateChanged(int state) { - enableSwipeRefresh(state == ViewPager.SCROLL_STATE_IDLE); - } - }); - - dateBar = findViewById(R.id.charts_date_bar); - mDateControl = findViewById(R.id.charts_text_date); - mDateControl.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String detailedDuration = formatDetailedDuration(); - new ShowDurationDialog(detailedDuration, ChartsActivity.this).show(); - } - }); - - Button mPrevButton = findViewById(R.id.charts_previous_day); - mPrevButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - handleButtonClicked(DATE_PREV_DAY); - } - }); - Button mNextButton = findViewById(R.id.charts_next_day); - mNextButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - handleButtonClicked(DATE_NEXT_DAY); - } - }); - - Button mPrevWeekButton = findViewById(R.id.charts_previous_week); - mPrevWeekButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - handleButtonClicked(DATE_PREV_WEEK); - } - }); - Button mNextWeekButton = findViewById(R.id.charts_next_week); - mNextWeekButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - handleButtonClicked(DATE_NEXT_WEEK); - } - }); - - Button mPrevMonthButton = findViewById(R.id.charts_previous_month); - mPrevMonthButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - handleButtonClicked(DATE_PREV_MONTH); - } - }); - Button mNextMonthButton = findViewById(R.id.charts_next_month); - mNextMonthButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - handleButtonClicked(DATE_NEXT_MONTH); - } - }); - - - } - - private static ArrayList fillChartsTabsList(GBDevice device, Context context) { - ArrayList arrayList = new ArrayList(); - Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress())); - String myTabs = prefs.getString(DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS, null); - - if (myTabs == null) { - //make list mutable to be able to remove items later - arrayList = new ArrayList(Arrays.asList(context.getResources().getStringArray(R.array.pref_charts_tabs_items_default))); - } else { - arrayList = new ArrayList(Arrays.asList(myTabs.split(","))); - } - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); - if (!coordinator.supportsRealtimeData()) { - arrayList.remove("livestats"); - } - return arrayList; - } - - public static int getChartsTabIndex(String tab, GBDevice device, Context context) { - ArrayList enabledTabsList = new ArrayList(); - enabledTabsList = fillChartsTabsList(device, context); - return enabledTabsList.indexOf(tab); - } - - private String formatDetailedDuration() { - SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm"); - String dateStringFrom = dateFormat.format(getStartDate()); - String dateStringTo = dateFormat.format(getEndDate()); - - return getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo); - } - - protected void initDates() { - setEndDate(new Date()); - setStartDate(DateTimeUtils.shiftByDays(getEndDate(), -1)); - } - - @Override - public GBDevice getDevice() { - return mGBDevice; - } - - @Override - public void setStartDate(Date startDate) { - mStartDate = startDate; - } - - @Override - public void setEndDate(Date endDate) { - mEndDate = endDate; - } - - @Override - public Date getStartDate() { - return mStartDate; - } - - @Override - public Date getEndDate() { - return mEndDate; - } - - private void handleButtonClicked(String Action) { - LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(Action)); - } - - @Override - protected void onDestroy() { - LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); - super.onDestroy(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.menu_charts, menu); - - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); - if (!mGBDevice.isConnected() || !coordinator.supportsActivityDataFetching()) { - menu.removeItem(R.id.charts_fetch_activity_data); - } - return true; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == 1) { - this.recreate(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.charts_fetch_activity_data: - fetchActivityData(); - return true; - case R.id.prefs_charts_menu: - Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class); - startActivityForResult(settingsIntent,1); - return true; - default: - break; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void enableSwipeRefresh(boolean enable) { - DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); - swipeLayout.setEnabled(enable && coordinator.allowFetchActivityData(mGBDevice)); - } - - private void fetchActivityData() { - if (getDevice().isInitialized()) { - GBApplication.deviceService(getDevice()).onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY); - } else { - swipeLayout.setRefreshing(false); - GB.toast(this, getString(R.string.device_not_connected), Toast.LENGTH_SHORT, GB.ERROR); - } - } - - @Override - public void setDateInfo(String dateInfo) { - mDateControl.setText(dateInfo); - } - - @Override - protected AbstractFragmentPagerAdapter createFragmentPagerAdapter(FragmentManager fragmentManager) { - return new SectionsPagerAdapter(fragmentManager); - } - - @Override - public ViewGroup getDateBar() { - return dateBar; - } - - - - - /** - * A {@link FragmentStatePagerAdapter} that returns a fragment corresponding to - * one of the sections/tabs/pages. - */ - public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter { - SectionsPagerAdapter(FragmentManager fm) { - super(fm); - } - - - @Override - public Fragment getItem(int position) { - // getItem is called to instantiate the fragment for the given page. - switch (enabledTabsList.get(position)) { - case "activity": - return new ActivitySleepChartFragment(); - case "activitylist": - return new ActivityListingChartFragment(); - case "sleep": - return new SleepChartFragment(); - case "sleepweek": - return new WeekSleepChartFragment(); - case "stepsweek": - return new WeekStepsChartFragment(); - case "speedzones": - return new SpeedZonesFragment(); - case "livestats": - return new LiveActivityFragment(); - } - return null; - } - - @Override - public int getCount() { - return enabledTabsList.toArray().length; - } - - private String getSleepTitle() { - if (GBApplication.getPrefs().getBoolean("charts_range", true)) { - return getString(R.string.weeksleepchart_sleep_a_month); - } - else{ - return getString(R.string.weeksleepchart_sleep_a_week); - } - } - - public String getStepsTitle() { - if (GBApplication.getPrefs().getBoolean("charts_range", true)) { - return getString(R.string.weekstepschart_steps_a_month); - } - else{ - return getString(R.string.weekstepschart_steps_a_week); - } - } - - @Override - public CharSequence getPageTitle(int position) { - - switch (enabledTabsList.get(position)) { - case "activity": - return getString(R.string.activity_sleepchart_activity_and_sleep); - case "activitylist": - return getString(R.string.charts_activity_list); - case "sleep": - return getString(R.string.sleepchart_your_sleep); - case "sleepweek": - return getSleepTitle(); - case "stepsweek": - return getStepsTitle(); - case "speedzones": - return getString(R.string.stats_title); - case "livestats": - return getString(R.string.liveactivity_live_activity); - } - return super.getPageTitle(position); - } - } -} - diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java index 8f93350d6..f1f1d257f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java @@ -23,15 +23,14 @@ import java.util.Date; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; public interface ChartsHost { - String DATE_PREV_DAY = ChartsActivity.class.getName().concat(".date_prev_day"); - String DATE_NEXT_DAY = ChartsActivity.class.getName().concat(".date_next_day"); - String DATE_PREV_WEEK = ChartsActivity.class.getName().concat(".date_prev_week"); - String DATE_NEXT_WEEK = ChartsActivity.class.getName().concat(".date_next_week"); - String DATE_PREV_MONTH = ChartsActivity.class.getName().concat(".date_prev_month"); - String DATE_NEXT_MONTH = ChartsActivity.class.getName().concat(".date_next_month"); + String DATE_PREV_DAY = ChartsHost.class.getName().concat(".date_prev_day"); + String DATE_NEXT_DAY = ChartsHost.class.getName().concat(".date_next_day"); + String DATE_PREV_WEEK = ChartsHost.class.getName().concat(".date_prev_week"); + String DATE_NEXT_WEEK = ChartsHost.class.getName().concat(".date_next_week"); + String DATE_PREV_MONTH = ChartsHost.class.getName().concat(".date_prev_month"); + String DATE_NEXT_MONTH = ChartsHost.class.getName().concat(".date_next_month"); - - String REFRESH = ChartsActivity.class.getName().concat(".refresh"); + String REFRESH = ChartsHost.class.getName().concat(".refresh"); GBDevice getDevice(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java index 2f7489c15..8e3cec074 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java @@ -66,7 +66,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.util.GB; -public class LiveActivityFragment extends AbstractChartFragment { +public class LiveActivityFragment extends AbstractActivityChartFragment { private static final Logger LOG = LoggerFactory.getLogger(LiveActivityFragment.class); private static final int MAX_STEPS_PER_MINUTE = 300; private static final int MIN_STEPS_PER_MINUTE = 60; @@ -533,7 +533,7 @@ public class LiveActivityFragment extends AbstractChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { // no legend } } 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 2d571137b..345030990 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 @@ -67,7 +67,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; -public class SleepChartFragment extends AbstractChartFragment { +public class SleepChartFragment extends AbstractActivityChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class); private LineChart mActivityChart; @@ -94,7 +94,7 @@ public class SleepChartFragment extends AbstractChartFragment { @Override - protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { + protected MyChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { List samples; if (CHARTS_SLEEP_RANGE_24H) { samples = getSamples(db, device); @@ -117,7 +117,7 @@ public class SleepChartFragment extends AbstractChartFragment { } } } - DefaultChartsData chartsData = refresh(device, samples); + DefaultChartsData chartsData = refresh(device, samples); Triple hrData = calculateHrData(samples); Triple intensityData = calculateIntensityData(samples); return new MyChartsData(mySleepChartsData, chartsData, hrData.getLeft(), hrData.getMiddle(), hrData.getRight(), intensityData.getLeft(), intensityData.getMiddle(), intensityData.getRight()); @@ -197,8 +197,7 @@ public class SleepChartFragment extends AbstractChartFragment { } @Override - protected void updateChartsnUIThread(ChartsData chartsData) { - MyChartsData mcd = (MyChartsData) chartsData; + protected void updateChartsnUIThread(MyChartsData mcd) { MySleepChartsData pieData = mcd.getPieData(); mSleepAmountChart.setCenterText(pieData.getTotalSleep()); mSleepAmountChart.setData(pieData.getPieData()); @@ -420,7 +419,7 @@ public class SleepChartFragment extends AbstractChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { List legendEntries = new ArrayList<>(3); LegendEntry lightSleepEntry = new LegendEntry(); lightSleepEntry.label = akLightSleep.label; @@ -497,7 +496,7 @@ public class SleepChartFragment extends AbstractChartFragment { } } - private static class MyChartsData extends ChartsData { + protected static class MyChartsData extends ChartsData { private final DefaultChartsData chartsData; private final MySleepChartsData pieData; private final float heartRateAverage; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java index e0f24db43..eaea07b73 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SpeedZonesFragment.java @@ -45,7 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; -public class SpeedZonesFragment extends AbstractChartFragment { +public class SpeedZonesFragment extends AbstractActivityChartFragment { protected static final Logger LOG = LoggerFactory.getLogger(SpeedZonesFragment.class); private HorizontalBarChart mStatsChart; @@ -139,7 +139,7 @@ public class SpeedZonesFragment extends AbstractChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { // no legend here, it is all about the steps here chart.getLegend().setEnabled(false); } 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 d893e1727..3c79cf0a6 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 @@ -170,7 +170,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { List legendEntries = new ArrayList<>(2); LegendEntry lightSleepEntry = new LegendEntry(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java index b31e18832..35547b401 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java @@ -101,7 +101,7 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment { } @Override - protected void setupLegend(Chart chart) { + protected void setupLegend(Chart chart) { // no legend here, it is all about the steps here chart.getLegend().setEnabled(false); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java index 2e71223b9..c37971f2f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java @@ -96,7 +96,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog; import nodomain.freeyourgadget.gadgetbridge.activities.OpenFwAppInstallerActivity; import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity; -import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; +import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; @@ -496,7 +496,7 @@ public class GBDeviceAdapterv2 extends ListAdapter> activitiesStatusMiniCharts = new Hashtable<>(); - activitiesStatusMiniCharts.put(holder.TotalStepsChart, new Pair<>(showActivitySteps && steps > 0, ChartsActivity.getChartsTabIndex("stepsweek", device, context))); - activitiesStatusMiniCharts.put(holder.SleepTimeChart, new Pair<>(showActivitySleep && sleep > 0, ChartsActivity.getChartsTabIndex("sleep", device, context))); - activitiesStatusMiniCharts.put(holder.TotalDistanceChart, new Pair<>(showActivityDistance && steps > 0, ChartsActivity.getChartsTabIndex("activity", device, context))); + activitiesStatusMiniCharts.put(holder.TotalStepsChart, new Pair<>(showActivitySteps && steps > 0, ActivityChartsActivity.getChartsTabIndex("stepsweek", device, context))); + activitiesStatusMiniCharts.put(holder.SleepTimeChart, new Pair<>(showActivitySleep && sleep > 0, ActivityChartsActivity.getChartsTabIndex("sleep", device, context))); + activitiesStatusMiniCharts.put(holder.TotalDistanceChart, new Pair<>(showActivityDistance && steps > 0, ActivityChartsActivity.getChartsTabIndex("activity", device, context))); for (Map.Entry> miniCharts : activitiesStatusMiniCharts.entrySet()) { PieChart miniChart = miniCharts.getKey(); @@ -1327,9 +1327,9 @@ public class GBDeviceAdapterv2 extends ListAdapter + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment"> + tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">