1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-26 06:50:07 +02: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:
José Rebelo 2023-06-16 23:53:28 +01:00
parent 9d3c480414
commit fec48c4340
28 changed files with 1086 additions and 1043 deletions

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"