mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2025-01-27 10:07:32 +01:00
Generalize charts logic for non-activity data
- Make ChartsHost independent from ChartsActivity - Rename ChartsActivity to ActivityChartsActivity - Rename AbstractChartFragment to AbstractActivityChartFragment - Pull common charts logic to parent classes: - From ActivityChartsActivity to AbstractChartsActivity - From AbstractActivityChartFragment to AbstractChartsFragment
This commit is contained in:
parent
9d3c480414
commit
fec48c4340
@ -514,7 +514,7 @@
|
||||
android:name=".devices.lenovo.LenovoWatchCalibrationActivity"
|
||||
android:label="@string/title_activity_LenovoWatch_calibration" />
|
||||
<activity
|
||||
android:name=".activities.charts.ChartsActivity"
|
||||
android:name=".activities.charts.ActivityChartsActivity"
|
||||
android:label="@string/title_activity_charts"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
|
@ -59,7 +59,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
|
||||
@ -126,7 +126,7 @@ public class Widget extends AppWidgetProvider {
|
||||
views.setOnClickPendingIntent(R.id.todaywidget_header_alarm_icon, startAlarmListPIntent);
|
||||
|
||||
//charts
|
||||
Intent startChartsIntent = new Intent(context, ChartsActivity.class);
|
||||
Intent startChartsIntent = new Intent(context, ActivityChartsActivity.class);
|
||||
startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, deviceForWidget);
|
||||
PendingIntent startChartsPIntent = PendingIntentUtils.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT, false);
|
||||
views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent);
|
||||
|
@ -41,7 +41,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.AbstractChartFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.AbstractActivityChartFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsHost;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.DefaultChartsData;
|
||||
@ -51,7 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
|
||||
public class ActivitySummariesChartFragment extends AbstractChartFragment {
|
||||
public class ActivitySummariesChartFragment extends AbstractActivityChartFragment<ChartsData> {
|
||||
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<LegendEntry> legendEntries = new ArrayList<>(5);
|
||||
|
||||
LegendEntry activityEntry = new LegendEntry();
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<D extends ChartsData> extends AbstractChartFragment<D> {
|
||||
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<? extends AbstractActivitySample> 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<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||
return provider.getAllActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends AbstractActivitySample> provider = getProvider(db, device);
|
||||
return provider.getActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
|
||||
protected List<? extends ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||
return provider.getSleepSamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
public DefaultChartsData<LineData> refresh(GBDevice gbDevice, List<? extends ActivitySample> samples) {
|
||||
// Calendar cal = GregorianCalendar.getInstance();
|
||||
// cal.clear();
|
||||
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||
// Date date;
|
||||
// String dateStringFrom = "";
|
||||
// String dateStringTo = "";
|
||||
// ArrayList<String> 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<Entry> activityEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> deepSleepEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> lightSleepEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> remSleepEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> notWornEntries = new ArrayList<>(numEntries);
|
||||
boolean hr = supportsHeartrate(gbDevice);
|
||||
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
|
||||
List<Integer> 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<ILineDataSet> 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<Entry> 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<Entry> 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<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
|
||||
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device) {
|
||||
int tsStart = getTSStart();
|
||||
int tsEnd = getTSEnd();
|
||||
List<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
|
||||
ensureStartAndEndSamples(samples, tsStart, tsEnd);
|
||||
// List<ActivitySample> 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<? extends ActivitySample> 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<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
|
||||
ensureStartAndEndSamples(samples, tsStart, tsEnd);
|
||||
return samples;
|
||||
}
|
||||
|
||||
protected void ensureStartAndEndSamples(List<ActivitySample> 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;
|
||||
}
|
||||
}
|
@ -17,57 +17,37 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
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<D extends ChartsData> 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<? extends AbstractActivitySample> 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<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||
return provider.getAllActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends AbstractActivitySample> provider = getProvider(db, device);
|
||||
return provider.getActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
|
||||
protected List<? extends ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends ActivitySample> 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<LineData> refresh(GBDevice gbDevice, List<? extends ActivitySample> samples) {
|
||||
// Calendar cal = GregorianCalendar.getInstance();
|
||||
// cal.clear();
|
||||
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||
// Date date;
|
||||
// String dateStringFrom = "";
|
||||
// String dateStringTo = "";
|
||||
// ArrayList<String> 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<Entry> activityEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> deepSleepEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> lightSleepEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> remSleepEntries = new ArrayList<>(numEntries);
|
||||
List<Entry> notWornEntries = new ArrayList<>(numEntries);
|
||||
boolean hr = supportsHeartrate(gbDevice);
|
||||
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
|
||||
List<Integer> 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<ILineDataSet> 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<? extends ActivitySample> 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<Entry> 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<Entry> 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<? extends ActivitySample> getSamples(DBHandler db, GBDevice device) {
|
||||
int tsStart = getTSStart();
|
||||
int tsEnd = getTSEnd();
|
||||
List<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
|
||||
ensureStartAndEndSamples(samples, tsStart, tsEnd);
|
||||
// List<ActivitySample> 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<? extends ActivitySample> 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<ActivitySample> samples = (List<ActivitySample>) getSamples(db, device, tsStart, tsEnd);
|
||||
ensureStartAndEndSamples(samples, tsStart, tsEnd);
|
||||
return samples;
|
||||
}
|
||||
|
||||
protected void ensureStartAndEndSamples(List<ActivitySample> 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));
|
||||
}
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<AbstractWeekChartFragment.MyChartsData> {
|
||||
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<BarData> 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<BarData> 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));
|
||||
}
|
||||
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<String> fillChartsTabsList() {
|
||||
return fillChartsTabsList(getDevice(), this);
|
||||
}
|
||||
|
||||
private static List<String> fillChartsTabsList(final GBDevice device, final Context context) {
|
||||
final List<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<ActivityListingChartFragment.MyChartsData> {
|
||||
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<? extends ActivitySample> activitySamples;
|
||||
activitySamples = getSamples(db, device);
|
||||
List<ActivitySession> 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<ActivitySession> stepSessions;
|
||||
private final ActivitySession ongoingSession;
|
||||
|
||||
|
@ -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<DefaultChartsData<LineData>> {
|
||||
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<LineData> refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<? extends ActivitySample> samples = getSamples(db, device);
|
||||
return refresh(device, samples);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
DefaultChartsData dcd = (DefaultChartsData) chartsData;
|
||||
protected void updateChartsnUIThread(DefaultChartsData<LineData> 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<LegendEntry> legendEntries = new ArrayList<>(5);
|
||||
|
||||
LegendEntry activityEntry = new LegendEntry();
|
||||
|
@ -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 <http://www.gnu.org/licenses/>. */
|
||||
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<String> 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<String> fillChartsTabsList(GBDevice device, Context context) {
|
||||
ArrayList<String> 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<String>(Arrays.asList(context.getResources().getStringArray(R.array.pref_charts_tabs_items_default)));
|
||||
} else {
|
||||
arrayList = new ArrayList<String>(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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<ChartsData> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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<SleepChartFragment.MyChartsData> {
|
||||
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<? extends ActivitySample> 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<LineData> chartsData = refresh(device, samples);
|
||||
Triple<Float, Integer, Integer> hrData = calculateHrData(samples);
|
||||
Triple<Float, Float, Float> 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<LegendEntry> 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<LineData> chartsData;
|
||||
private final MySleepChartsData pieData;
|
||||
private final float heartRateAverage;
|
||||
|
@ -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<ChartsData> {
|
||||
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);
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
protected void setupLegend(Chart<?> chart) {
|
||||
List<LegendEntry> legendEntries = new ArrayList<>(2);
|
||||
|
||||
LegendEntry lightSleepEntry = new LegendEntry();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<GBDevice, GBDeviceAdapterv2.V
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(context, ChartsActivity.class);
|
||||
startIntent = new Intent(context, ActivityChartsActivity.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
@ -1315,9 +1315,9 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
|
||||
|
||||
//do the multiple mini-charts for activities in a loop
|
||||
Hashtable<PieChart, Pair<Boolean, Integer>> 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<PieChart, Pair<Boolean, Integer>> miniCharts : activitiesStatusMiniCharts.entrySet()) {
|
||||
PieChart miniChart = miniCharts.getKey();
|
||||
@ -1327,9 +1327,9 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent startIntent;
|
||||
startIntent = new Intent(context, ChartsActivity.class);
|
||||
startIntent = new Intent(context, ActivityChartsActivity.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
startIntent.putExtra(ChartsActivity.EXTRA_FRAGMENT_ID, parameters.second);
|
||||
startIntent.putExtra(ActivityChartsActivity.EXTRA_FRAGMENT_ID, parameters.second);
|
||||
context.startActivity(startIntent);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
|
@ -8,7 +8,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -4,7 +4,7 @@
|
||||
android:id="@+id/activity_swipe_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/charts_main_layout"
|
||||
@ -17,7 +17,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/charts_pagerTabStrip"
|
||||
|
@ -2,7 +2,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<com.github.mikephil.charting.charts.LineChart
|
||||
android:id="@+id/activitysleepchart"
|
||||
|
@ -2,7 +2,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -3,7 +3,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -2,7 +2,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<com.github.mikephil.charting.charts.HorizontalBarChart
|
||||
android:id="@+id/statschart"
|
||||
|
@ -4,7 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stepsDateView"
|
||||
|
@ -8,7 +8,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/balance"
|
||||
|
@ -1,7 +1,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity">
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity">
|
||||
<item
|
||||
android:id="@+id/charts_fetch_activity_data"
|
||||
app:showAsAction="ifRoom"
|
||||
|
Loading…
x
Reference in New Issue
Block a user