mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
charts: spo2 and temperature overlays added
This commit is contained in:
-35
@@ -39,7 +39,6 @@ import java.util.List;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.SimpleSleepDetailsHROverlay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.SleepDetailsView;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
@@ -345,40 +344,6 @@ public abstract class AbstractActivityChartFragment<D extends ChartsData> extend
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] prepareHR(List<? extends ActivitySample> samples) {
|
||||
if (samples.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//NOTE: The start and end times of the samples should correspond to the sleep stages.
|
||||
// The time interval between samples is not important (it can be in minutes, seconds, etc.), but all intervals must be filled.
|
||||
// If there is a gap in the raw data, 'SimpleSleepDetailsHROverlay.NO_DATA' should be used.
|
||||
// As we exactly know the intervals, we can use primitive types (which have better performance during drawing).
|
||||
// Samples already prepared in minute's interval can simply be copied with filtering.
|
||||
|
||||
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||
|
||||
int[] result = new int[samples.size()];
|
||||
int idx = 0;
|
||||
int lastTsShorten = 0;
|
||||
for (ActivitySample sample : samples) {
|
||||
if (sample.getKind() == ActivityKind.NOT_WORN || !heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
||||
result[idx++] = -1;
|
||||
continue;
|
||||
}
|
||||
int tsShorten = tsTranslation.shorten(sample.getTimestamp());
|
||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
result[idx++] = sample.getHeartRate();
|
||||
} else {
|
||||
result[idx++] = SimpleSleepDetailsHROverlay.NO_DATA;
|
||||
}
|
||||
lastTsShorten = tsShorten;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected int getIndexOfActivity(ActivityKind kind) {
|
||||
return switch (kind) {
|
||||
case DEEP_SLEEP -> 0;
|
||||
|
||||
+188
-20
@@ -27,7 +27,6 @@ import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -50,6 +49,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
@@ -60,14 +60,22 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.SleepAnalysis.SleepSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.SimpleSleepDetailsHROverlay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.AbstractOverlayData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.OverlayDataFloat;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.OverlayDataInt;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.SimpleSleepDetailsOverlay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.SleepDetailsOverlay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep.SleepDetailsView;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.TimeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.SleepScoreSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
@@ -144,10 +152,119 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
|
||||
List<SleepDetailsView.SleepDetail> stages = prepareStages(samples);
|
||||
|
||||
int[] hr = hrOverlay?prepareHR(samples):null;
|
||||
return new MyChartsData(mySleepChartsData, chartsData, hrData.getLeft(), hrData.getMiddle(), hrData.getRight(), intensityData.getLeft(), intensityData.getMiddle(), intensityData.getRight(), stages, hr);
|
||||
AbstractOverlayData overlay = null;
|
||||
if(currentOverlay == OverlayType.HEART_RATE) {
|
||||
int average = SHOW_CHARTS_AVERAGE?Math.round(hrData.getLeft()):OverlayDataInt.NO_DATA;
|
||||
overlay = new OverlayDataInt(20, 140, prepareHR(samples), average, HEARTRATE_COLOR, Color.RED);
|
||||
} else if(currentOverlay == OverlayType.SPO2) {
|
||||
overlay = new OverlayDataInt(68, 100, prepareSpO2Overlay(db, device, samples.get(0).getTimestamp() * 1000L, samples.get(samples.size() - 1).getTimestamp() * 1000L), OverlayDataInt.NO_DATA, getResources().getColor(R.color.spo2_color), Color.RED);
|
||||
} else if(currentOverlay == OverlayType.TEMPERATURE) {
|
||||
overlay = new OverlayDataFloat(28, 45, prepareTemperature(db, device, samples.get(0).getTimestamp() * 1000L, samples.get(samples.size() - 1).getTimestamp() * 1000L), OverlayDataFloat.NO_DATA, CHART_TEXT_COLOR, Color.RED);
|
||||
}
|
||||
return new MyChartsData(mySleepChartsData, chartsData, hrData.getLeft(), hrData.getMiddle(), hrData.getRight(), intensityData.getLeft(), intensityData.getMiddle(), intensityData.getRight(), stages, overlay);
|
||||
}
|
||||
|
||||
public float[] prepareTemperature(DBHandler db, GBDevice device, long tsStart, long tsEnd) {
|
||||
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
TimeSampleProvider<TemperatureSample> provider = (TimeSampleProvider<TemperatureSample>) coordinator.getTemperatureSampleProvider(device, db.getDaoSession());
|
||||
|
||||
List<TemperatureSample> samples = provider.getAllSamples(tsStart, tsEnd);
|
||||
if(samples.isEmpty())
|
||||
return null;
|
||||
|
||||
long interval = Long.MAX_VALUE;
|
||||
for(int i = 1; i < samples.size(); i++) {
|
||||
long delta = samples.get(i).getTimestamp() - samples.get(i - 1).getTimestamp();
|
||||
if(delta < interval) {
|
||||
interval = delta;
|
||||
}
|
||||
}
|
||||
int count = (int)((float)(tsEnd - tsStart) / interval);
|
||||
if(count == 0)
|
||||
return null;
|
||||
|
||||
float[] result = new float[count];
|
||||
Arrays.fill(result, -1);
|
||||
|
||||
for(TemperatureSample sp: samples) {
|
||||
int idx = (int)((float)(sp.getTimestamp() - tsStart) / interval);
|
||||
if(idx > 0 && idx < count) {
|
||||
result[idx] = sp.getTemperature();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] prepareSpO2Overlay(DBHandler db, GBDevice device, long tsStart, long tsEnd) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
TimeSampleProvider<Spo2Sample> provider = (TimeSampleProvider<Spo2Sample>) coordinator.getSpo2SampleProvider(device, db.getDaoSession());
|
||||
|
||||
List<Spo2Sample> samples = provider.getAllSamples(tsStart, tsEnd);
|
||||
|
||||
if(samples.isEmpty())
|
||||
return null;
|
||||
|
||||
long interval = Long.MAX_VALUE;
|
||||
for(int i = 1; i < samples.size(); i++) {
|
||||
long delta = samples.get(i).getTimestamp() - samples.get(i - 1).getTimestamp();
|
||||
if(delta < interval) {
|
||||
interval = delta;
|
||||
}
|
||||
}
|
||||
int count = (int)((float)(tsEnd - tsStart) / interval);
|
||||
|
||||
if(count == 0)
|
||||
return null;
|
||||
|
||||
int[] result = new int[count];
|
||||
Arrays.fill(result, -1);
|
||||
|
||||
for(Spo2Sample sp: samples) {
|
||||
int idx = (int)((float)(sp.getTimestamp() - tsStart) / interval);
|
||||
if(idx > 0 && idx < count) {
|
||||
result[idx] = sp.getSpo2();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] prepareHR(List<? extends ActivitySample> samples) {
|
||||
if (samples.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//NOTE: The start and end times of the samples should correspond to the sleep stages.
|
||||
// The time interval between samples is not important (it can be in minutes, seconds, etc.), but all intervals must be filled.
|
||||
// If there is a gap in the raw data, 'SimpleSleepDetailsHROverlay.NO_DATA' should be used.
|
||||
// As we exactly know the intervals, we can use primitive types (which have better performance during drawing).
|
||||
// Samples already prepared in minute's interval can simply be copied with filtering.
|
||||
|
||||
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
|
||||
|
||||
int[] result = new int[samples.size()];
|
||||
int idx = 0;
|
||||
int lastTsShorten = 0;
|
||||
for (ActivitySample sample : samples) {
|
||||
if (sample.getKind() == ActivityKind.NOT_WORN || !heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
|
||||
result[idx++] = -1;
|
||||
continue;
|
||||
}
|
||||
int tsShorten = tsTranslation.shorten(sample.getTimestamp());
|
||||
if (lastTsShorten == 0 || (tsShorten - lastTsShorten) <= 60 * HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
result[idx++] = sample.getHeartRate();
|
||||
} else {
|
||||
result[idx++] = OverlayDataInt.NO_DATA;
|
||||
}
|
||||
lastTsShorten = tsShorten;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples, List<? extends SleepScoreSample> sleepScoreSamples) {
|
||||
SleepAnalysis sleepAnalysis = new SleepAnalysis();
|
||||
List<SleepSession> sleepSessions = sleepAnalysis.calculateSleepSessions(samples);
|
||||
@@ -242,11 +359,8 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
MySleepChartsData pieData = mcd.getPieData();
|
||||
|
||||
if (mcd.getStages() != null) {
|
||||
mSleepDetailsView.setData(mcd.getStages());
|
||||
}
|
||||
if(mcd.getHr() != null) {
|
||||
int average = SHOW_CHARTS_AVERAGE?Math.round(mcd.getHeartRateAverage()):SimpleSleepDetailsHROverlay.NO_DATA;
|
||||
mSleepDetailsView.setOverlay(new SimpleSleepDetailsHROverlay(20, 140, mcd.getHr(), average, HEARTRATE_COLOR, Color.BLACK, Color.RED));
|
||||
SleepDetailsOverlay overlay = (mcd.getOverlayData() != null)?new SimpleSleepDetailsOverlay(mcd.getOverlayData() , Color.BLACK): null;
|
||||
mSleepDetailsView.setData(mcd.getStages(), overlay);
|
||||
}
|
||||
|
||||
Date date = new Date((long) this.getTSEnd() * 1000);
|
||||
@@ -384,7 +498,24 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private boolean hrOverlay = false;
|
||||
enum OverlayType {
|
||||
NONE,
|
||||
HEART_RATE,
|
||||
SPO2,
|
||||
TEMPERATURE,
|
||||
}
|
||||
|
||||
private OverlayType currentOverlay = OverlayType.NONE;
|
||||
|
||||
public boolean supportsSpO2(GBDevice device) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsSpo2(device);
|
||||
}
|
||||
|
||||
public boolean supportsTemperature(GBDevice device) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsTemperatureMeasurement(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
@@ -415,18 +546,55 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
|
||||
ChipGroup chipGroup = rootView.findViewById(R.id.sleep_chart_overlay_group);
|
||||
|
||||
chipGroup.setOnCheckedStateChangeListener(new ChipGroup.OnCheckedStateChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(@NonNull ChipGroup chipGroup, @NonNull List<Integer> list) {
|
||||
if(list.isEmpty()) {
|
||||
currentOverlay = OverlayType.NONE;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(supportsHeartrate(getChartsHost().getDevice())) {
|
||||
Chip hrChip = (Chip) inflater.inflate(R.layout.layout_chart_chip, chipGroup, false);
|
||||
hrChip.setText(ContextCompat.getString(GBApplication.getContext(), R.string.heart_rate));
|
||||
hrChip.setChipIconVisible(true);
|
||||
hrChip.setChipIconResource(R.drawable.ic_heartrate);
|
||||
chipGroup.addView(hrChip);
|
||||
hrChip.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked) {
|
||||
hrOverlay = isChecked;
|
||||
refresh();
|
||||
hrChip.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if(isChecked) {
|
||||
currentOverlay = OverlayType.HEART_RATE;
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
if(supportsSpO2(getChartsHost().getDevice())) {
|
||||
Chip spo2Chip = (Chip) inflater.inflate(R.layout.layout_chart_chip, chipGroup, false);
|
||||
spo2Chip.setText(ContextCompat.getString(GBApplication.getContext(), R.string.menuitem_spo2));
|
||||
spo2Chip.setChipIconVisible(true);
|
||||
spo2Chip.setChipIconResource(R.drawable.ic_spo2);
|
||||
chipGroup.addView(spo2Chip);
|
||||
spo2Chip.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if(isChecked) {
|
||||
currentOverlay = OverlayType.SPO2;
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
if(supportsTemperature(getChartsHost().getDevice())) {
|
||||
Chip tempChip = (Chip) inflater.inflate(R.layout.layout_chart_chip, chipGroup, false);
|
||||
tempChip.setText(ContextCompat.getString(GBApplication.getContext(), R.string.menuitem_temperature));
|
||||
tempChip.setChipIconVisible(true);
|
||||
tempChip.setChipIconResource(R.drawable.ic_temperature);
|
||||
chipGroup.addView(tempChip);
|
||||
tempChip.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if(isChecked) {
|
||||
currentOverlay = OverlayType.TEMPERATURE;
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -580,9 +748,9 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
|
||||
private final List<SleepDetailsView.SleepDetail> stages;
|
||||
|
||||
private final int[] hr;
|
||||
private final AbstractOverlayData overlayData;
|
||||
|
||||
public MyChartsData(MySleepChartsData pieData, DefaultChartsData<LineData> chartsData, float heartRateAverage, int heartRateAxisMin, int heartRateAxisMax, float intensityTotal, float intensityAxisMin, float intensityAxisMax, List<SleepDetailsView.SleepDetail> stages, int[] hr) {
|
||||
public MyChartsData(MySleepChartsData pieData, DefaultChartsData<LineData> chartsData, float heartRateAverage, int heartRateAxisMin, int heartRateAxisMax, float intensityTotal, float intensityAxisMin, float intensityAxisMax, List<SleepDetailsView.SleepDetail> stages, AbstractOverlayData overlayData) {
|
||||
this.pieData = pieData;
|
||||
this.chartsData = chartsData;
|
||||
this.heartRateAverage = heartRateAverage;
|
||||
@@ -592,7 +760,7 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
this.intensityAxisMin = intensityAxisMin;
|
||||
this.intensityAxisMax = intensityAxisMax;
|
||||
this.stages = stages;
|
||||
this.hr = hr;
|
||||
this.overlayData = overlayData;
|
||||
}
|
||||
|
||||
public MySleepChartsData getPieData() {
|
||||
@@ -631,8 +799,8 @@ public class SleepDailyFragment extends SleepFragment<SleepDailyFragment.MyChart
|
||||
return stages;
|
||||
}
|
||||
|
||||
public int[] getHr() {
|
||||
return hr;
|
||||
public AbstractOverlayData getOverlayData() {
|
||||
return overlayData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep;
|
||||
|
||||
|
||||
public abstract class AbstractOverlayData {
|
||||
|
||||
private final float yMin;
|
||||
private final float yMax;
|
||||
private final float yDelta;
|
||||
|
||||
public AbstractOverlayData(float yMin, float yMax) {
|
||||
this.yMin = yMin;
|
||||
this.yMax = yMax;
|
||||
this.yDelta = yMax - yMin;
|
||||
}
|
||||
|
||||
abstract boolean isData(int i);
|
||||
|
||||
abstract boolean isMeasured(int i);
|
||||
|
||||
abstract int getMainColor();
|
||||
|
||||
abstract int getAverageColor();
|
||||
|
||||
public float getYDelta() {
|
||||
return yDelta;
|
||||
}
|
||||
|
||||
abstract int getLength();
|
||||
|
||||
protected float getAdjustedValueInternal(float val) {
|
||||
if(val < yMin) {
|
||||
return 0;
|
||||
}
|
||||
if(val > yMax) {
|
||||
return yDelta;
|
||||
}
|
||||
return val - yMin;
|
||||
}
|
||||
|
||||
abstract float getAdjustedValue(int i);
|
||||
|
||||
abstract boolean hasAverage();
|
||||
|
||||
abstract float getAdjustedAverageValue();
|
||||
|
||||
abstract String getCurrentValue(int i);
|
||||
|
||||
abstract float adjustYLabelDelta(float delta);
|
||||
|
||||
abstract String getYLabelValue(float val);
|
||||
|
||||
String[] getYLabels(int horizontalLineCount) {
|
||||
float yLabelsDelta = adjustYLabelDelta(yDelta / horizontalLineCount);
|
||||
String[] res = new String[horizontalLineCount + 1];
|
||||
float yStart = yMax;
|
||||
for (int i = 0; i <= horizontalLineCount; i++) {
|
||||
res[i] = getYLabelValue(yStart);
|
||||
yStart -= yLabelsDelta;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class OverlayDataFloat extends AbstractOverlayData{
|
||||
|
||||
public static float NO_DATA = Float.MIN_VALUE;
|
||||
|
||||
private final float average;
|
||||
|
||||
private final float[] data;
|
||||
|
||||
private final int mainColor;
|
||||
private final int averageColor;
|
||||
|
||||
|
||||
public OverlayDataFloat(float yMin, float yMax, float[] data, float average, int mainColor, int averageColor) {
|
||||
super(yMin, yMax);
|
||||
this.average = average;
|
||||
this.data = data;
|
||||
this.mainColor = mainColor;
|
||||
this.averageColor = averageColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isData(int i) {
|
||||
if(data == null || i < 0 || i > data.length) {
|
||||
return false;
|
||||
}
|
||||
return this.data[i] != NO_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMeasured(int i) {
|
||||
if(data == null || i < 0 || i > data.length) {
|
||||
return false;
|
||||
}
|
||||
return this.data[i] >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMainColor() {
|
||||
return mainColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageColor() {
|
||||
return averageColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
if(data == null) {
|
||||
return 0;
|
||||
}
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAdjustedValue(int i) {
|
||||
if(data == null || i < 0 || i > data.length) {
|
||||
return 0;
|
||||
}
|
||||
return getAdjustedValueInternal(this.data[i]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAverage() {
|
||||
return average != NO_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAdjustedAverageValue() {
|
||||
return getAdjustedValueInternal(average);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentValue(int i) {
|
||||
return String.format(Locale.ROOT, "%.2f", this.data[i]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float adjustYLabelDelta(float delta) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getYLabelValue(float val) {
|
||||
return String.format(Locale.ROOT, "%.2f", val);
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class OverlayDataInt extends AbstractOverlayData{
|
||||
|
||||
public static int NO_DATA = Integer.MIN_VALUE;
|
||||
|
||||
private final int average;
|
||||
|
||||
|
||||
private final int[] data;
|
||||
|
||||
private final int mainColor;
|
||||
private final int averageColor;
|
||||
|
||||
|
||||
public OverlayDataInt(int yMin, int yMax, int[] data, int average, int mainColor, int averageColor) {
|
||||
super(yMin, yMax);
|
||||
this.average = average;
|
||||
this.data = data;
|
||||
this.mainColor = mainColor;
|
||||
this.averageColor = averageColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isData(int i) {
|
||||
if(data == null || i < 0 || i > data.length) {
|
||||
return false;
|
||||
}
|
||||
return this.data[i] != NO_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMeasured(int i) {
|
||||
if(data == null || i < 0 || i > data.length) {
|
||||
return false;
|
||||
}
|
||||
return this.data[i] >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMainColor() {
|
||||
return mainColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAverageColor() {
|
||||
return averageColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
if(data == null) {
|
||||
return 0;
|
||||
}
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAdjustedValue(int i) {
|
||||
if(data == null || i < 0 || i > data.length) {
|
||||
return 0;
|
||||
}
|
||||
return getAdjustedValueInternal(this.data[i]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAverage() {
|
||||
return average != NO_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAdjustedAverageValue() {
|
||||
return getAdjustedValueInternal(average);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentValue(int i) {
|
||||
return String.format(Locale.ROOT, "%d", this.data[i]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float adjustYLabelDelta(float delta) {
|
||||
return (int)delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getYLabelValue(float val) {
|
||||
return String.format(Locale.ROOT, "%d", (int)val);
|
||||
}
|
||||
}
|
||||
-209
@@ -1,209 +0,0 @@
|
||||
/* Copyright (C) 2025 Me7c7
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class SimpleSleepDetailsHROverlay implements SleepDetailsOverlay {
|
||||
|
||||
public static int NO_DATA = Integer.MIN_VALUE;
|
||||
|
||||
private final int[] data;
|
||||
|
||||
private final int average;
|
||||
|
||||
private final int yMin;
|
||||
private final int yMax;
|
||||
private final int yDelta;
|
||||
|
||||
private final Path linePath = new Path();
|
||||
private final Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final Paint averagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final Paint dotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint hudPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint hudBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final Paint labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Rect labelRect = new Rect();
|
||||
|
||||
private final Paint scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public SimpleSleepDetailsHROverlay(int yMin, int yMax, int[] data, int average, int mainColor, int hubBackgroundColor, int averageColor) {
|
||||
linePaint.setStyle(Paint.Style.STROKE);
|
||||
linePaint.setStrokeWidth(4);
|
||||
linePaint.setColor(mainColor);
|
||||
|
||||
dotPaint.setStyle(Paint.Style.FILL);
|
||||
dotPaint.setColor(mainColor);
|
||||
|
||||
hudPaint.setStyle(Paint.Style.STROKE);
|
||||
hudPaint.setStrokeWidth(2);
|
||||
hudPaint.setColor(mainColor);
|
||||
|
||||
hudBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||
hudBackgroundPaint.setColor(hubBackgroundColor);
|
||||
|
||||
labelPaint.setColor(Color.GRAY);
|
||||
labelPaint.setTextAlign(Paint.Align.CENTER);
|
||||
labelPaint.setTextSize(25);
|
||||
|
||||
scalePaint.setColor(Color.GRAY);
|
||||
scalePaint.setTextAlign(Paint.Align.RIGHT);
|
||||
scalePaint.setTextSize(25);
|
||||
|
||||
averagePaint.setColor(averageColor);
|
||||
averagePaint.setStrokeWidth(2);
|
||||
averagePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
averagePaint.setStyle(Paint.Style.STROKE);
|
||||
averagePaint.setPathEffect(new DashPathEffect(new float[]{15f, 5f}, 0f));
|
||||
|
||||
this.data = data;
|
||||
this.yMax = yMax;
|
||||
this.yMin = yMin;
|
||||
|
||||
this.average = average;
|
||||
|
||||
this.yDelta = this.yMax - this.yMin;
|
||||
}
|
||||
|
||||
private float getAdjustedValue(float val) {
|
||||
if(val < yMin) {
|
||||
return 0;
|
||||
}
|
||||
if(val > yMax) {
|
||||
return yDelta;
|
||||
}
|
||||
return val - yMin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlayScale(Canvas canvas, int lineSpacing, int horizontalLineCount, int chartTopStart, int x) {
|
||||
|
||||
int yLabelsDelta = yDelta / horizontalLineCount;
|
||||
int yStart = yMax;
|
||||
for (int i = 0; i <= horizontalLineCount; i++) {
|
||||
final float y = (i * lineSpacing);
|
||||
final String currentValue = String.format(Locale.ROOT, "%d", yStart);
|
||||
canvas.drawText(currentValue, x, chartTopStart + y, scalePaint);
|
||||
yStart -= yLabelsDelta;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlay(Canvas canvas, int left, int top, int height, int width) {
|
||||
if (data == null || data.length == 0)
|
||||
return;
|
||||
|
||||
float unitHeight = ((float) height / yDelta);
|
||||
float unitWidth = ((float) width / data.length);
|
||||
linePath.reset();
|
||||
boolean first = true;
|
||||
float prevX = 0;
|
||||
float prevY = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if(data[i] == NO_DATA) {
|
||||
if(!first) {
|
||||
canvas.drawPath(linePath, linePaint);
|
||||
linePath.reset();
|
||||
}
|
||||
first = true;
|
||||
}
|
||||
if (data[i] < 0) {
|
||||
continue;
|
||||
}
|
||||
float curX = left + i * unitWidth;
|
||||
float curY = top + (height - (getAdjustedValue(data[i]) * unitHeight));
|
||||
if (first) {
|
||||
linePath.moveTo(curX, curY);
|
||||
first = false;
|
||||
} else {
|
||||
float conX1, conY1, conX2, conY2;
|
||||
conX1 = (prevX + curX) / 2;
|
||||
conY1 = prevY;
|
||||
conX2 = (prevX + curX) / 2;
|
||||
conY2 = curY;
|
||||
linePath.cubicTo(
|
||||
conX1, conY1, conX2, conY2,
|
||||
curX, curY
|
||||
);
|
||||
}
|
||||
prevX = curX;
|
||||
prevY = curY;
|
||||
}
|
||||
canvas.drawPath(linePath, linePaint);
|
||||
if(average > 0) {
|
||||
float avgY = top + (height - (getAdjustedValue(average) * unitHeight));
|
||||
canvas.drawLine(left, avgY, left + width, avgY, averagePaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlaySelector(Canvas canvas, int left, int top, int height, int width, float selectorPos) {
|
||||
final float unitWidth = ((float) width / data.length);
|
||||
|
||||
int idx = (int) ((selectorPos - left) / unitWidth);
|
||||
if (idx > 0 && idx < data.length && data[idx] > 0) {
|
||||
float unitHeight = ((float) height / yDelta);
|
||||
|
||||
final int verticalPadding = 12;
|
||||
final int horizontalPadding = 16;
|
||||
final float cornerRadius = 10F;
|
||||
final float lineLength = 15F;
|
||||
|
||||
final float dataValue = data[idx];
|
||||
|
||||
final float centerX = left + idx * unitWidth;
|
||||
final float centerY = top + height - (getAdjustedValue(dataValue) * unitHeight);
|
||||
|
||||
final String currentValue = String.format(Locale.ROOT, "%d", (int) dataValue);
|
||||
|
||||
canvas.drawCircle(centerX, centerY, 7, dotPaint);
|
||||
|
||||
labelPaint.getTextBounds(currentValue, 0, currentValue.length(), labelRect);
|
||||
|
||||
float bgLeft = centerX - (labelRect.width() / 2f) - horizontalPadding;
|
||||
float bgBottom = centerY - lineLength - verticalPadding;
|
||||
float bgTop = bgBottom - labelRect.height() - 2 * verticalPadding;
|
||||
float bgRight = centerX + (labelRect.width() / 2f) + horizontalPadding;
|
||||
|
||||
if (bgLeft < left + verticalPadding) {
|
||||
bgLeft = left + verticalPadding;
|
||||
bgRight = bgLeft + labelRect.width() + 2 * horizontalPadding;
|
||||
}
|
||||
if (bgRight > left + width - verticalPadding) {
|
||||
bgRight = left + width - verticalPadding;
|
||||
bgLeft = bgRight - (labelRect.width() + 2 * horizontalPadding);
|
||||
}
|
||||
|
||||
float adjustedCenterX = (bgLeft + bgRight) / 2f;
|
||||
|
||||
canvas.drawLine(centerX, centerY, adjustedCenterX, bgBottom, dotPaint);
|
||||
canvas.drawRoundRect(bgLeft, bgTop, bgRight, bgBottom, cornerRadius, cornerRadius, hudBackgroundPaint);
|
||||
canvas.drawRoundRect(bgLeft, bgTop, bgRight, bgBottom, cornerRadius, cornerRadius, hudPaint);
|
||||
canvas.drawText(currentValue, adjustedCenterX, bgBottom - verticalPadding, labelPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/* Copyright (C) 2025 Me7c7
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts.sleep;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
|
||||
public class SimpleSleepDetailsOverlay implements SleepDetailsOverlay {
|
||||
|
||||
private final AbstractOverlayData data;
|
||||
private int horizontalLineCount = 8;
|
||||
private String[] labels = new String[horizontalLineCount + 1];
|
||||
|
||||
private final Path linePath = new Path();
|
||||
private final Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final Paint averagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final Paint dotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint hudPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint hudBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
private final Paint labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Rect labelRect = new Rect();
|
||||
|
||||
private final Paint scalePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
public SimpleSleepDetailsOverlay(AbstractOverlayData data, int hubBackgroundColor) {
|
||||
|
||||
this.data = data;
|
||||
|
||||
linePaint.setStyle(Paint.Style.STROKE);
|
||||
linePaint.setStrokeWidth(4);
|
||||
linePaint.setColor(data.getMainColor());
|
||||
|
||||
dotPaint.setStyle(Paint.Style.FILL);
|
||||
dotPaint.setColor(data.getMainColor());
|
||||
|
||||
hudPaint.setStyle(Paint.Style.STROKE);
|
||||
hudPaint.setStrokeWidth(2);
|
||||
hudPaint.setColor(data.getMainColor());
|
||||
|
||||
hudBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||
hudBackgroundPaint.setColor(hubBackgroundColor);
|
||||
|
||||
labelPaint.setColor(Color.GRAY);
|
||||
labelPaint.setTextAlign(Paint.Align.CENTER);
|
||||
labelPaint.setTextSize(25);
|
||||
|
||||
scalePaint.setColor(Color.GRAY);
|
||||
scalePaint.setTextAlign(Paint.Align.RIGHT);
|
||||
scalePaint.setTextSize(25);
|
||||
|
||||
averagePaint.setColor(data.getAverageColor());
|
||||
averagePaint.setStrokeWidth(2);
|
||||
averagePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
averagePaint.setStyle(Paint.Style.STROKE);
|
||||
averagePaint.setPathEffect(new DashPathEffect(new float[]{15f, 5f}, 0f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(int horizontalLineCount) {
|
||||
this.horizontalLineCount = horizontalLineCount;
|
||||
labels = data.getYLabels(horizontalLineCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlayScale(Canvas canvas, int lineSpacing, int chartTopStart, int x) {
|
||||
for (int i = 0; i <= horizontalLineCount; i++) {
|
||||
final float y = (i * lineSpacing);
|
||||
canvas.drawText(labels[i], x, chartTopStart + y, scalePaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlay(Canvas canvas, int left, int top, int height, int width) {
|
||||
if (data == null || data.getLength() == 0)
|
||||
return;
|
||||
|
||||
float unitHeight = ((float) height / data.getYDelta());
|
||||
float unitWidth = ((float) width / data.getLength());
|
||||
linePath.reset();
|
||||
boolean first = true;
|
||||
float prevX = 0;
|
||||
float prevY = 0;
|
||||
for (int i = 0; i < data.getLength(); i++) {
|
||||
if (!data.isData(i)) {
|
||||
if (!first) {
|
||||
canvas.drawPath(linePath, linePaint);
|
||||
linePath.reset();
|
||||
}
|
||||
first = true;
|
||||
}
|
||||
if (!data.isMeasured(i)) {
|
||||
continue;
|
||||
}
|
||||
float curX = left + i * unitWidth;
|
||||
float curY = top + (height - (data.getAdjustedValue(i) * unitHeight));
|
||||
if (first) {
|
||||
linePath.moveTo(curX, curY);
|
||||
first = false;
|
||||
} else {
|
||||
float conX1, conY1, conX2, conY2;
|
||||
conX1 = (prevX + curX) / 2;
|
||||
conY1 = prevY;
|
||||
conX2 = (prevX + curX) / 2;
|
||||
conY2 = curY;
|
||||
linePath.cubicTo(
|
||||
conX1, conY1, conX2, conY2,
|
||||
curX, curY
|
||||
);
|
||||
}
|
||||
prevX = curX;
|
||||
prevY = curY;
|
||||
}
|
||||
canvas.drawPath(linePath, linePaint);
|
||||
if (data.hasAverage()) {
|
||||
float avgY = top + (height - (data.getAdjustedAverageValue() * unitHeight));
|
||||
canvas.drawLine(left, avgY, left + width, avgY, averagePaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOverlaySelector(Canvas canvas, int left, int top, int height, int width, float selectorPos) {
|
||||
final float unitWidth = ((float) width / data.getLength());
|
||||
|
||||
int idx = (int) ((selectorPos - left) / unitWidth);
|
||||
if (idx > 0 && idx < data.getLength() && data.isMeasured(idx)) {
|
||||
float unitHeight = ((float) height / data.getYDelta());
|
||||
|
||||
final int verticalPadding = 12;
|
||||
final int horizontalPadding = 16;
|
||||
final float cornerRadius = 10F;
|
||||
final float lineLength = 15F;
|
||||
|
||||
final float centerX = left + idx * unitWidth;
|
||||
final float centerY = top + height - (data.getAdjustedValue(idx) * unitHeight);
|
||||
|
||||
final String currentValue = data.getCurrentValue(idx);
|
||||
|
||||
canvas.drawCircle(centerX, centerY, 7, dotPaint);
|
||||
|
||||
labelPaint.getTextBounds(currentValue, 0, currentValue.length(), labelRect);
|
||||
|
||||
float bgLeft = centerX - (labelRect.width() / 2f) - horizontalPadding;
|
||||
float bgBottom = centerY - lineLength - verticalPadding;
|
||||
float bgTop = bgBottom - labelRect.height() - 2 * verticalPadding;
|
||||
float bgRight = centerX + (labelRect.width() / 2f) + horizontalPadding;
|
||||
|
||||
if (bgLeft < left + verticalPadding) {
|
||||
bgLeft = left + verticalPadding;
|
||||
bgRight = bgLeft + labelRect.width() + 2 * horizontalPadding;
|
||||
}
|
||||
if (bgRight > left + width - verticalPadding) {
|
||||
bgRight = left + width - verticalPadding;
|
||||
bgLeft = bgRight - (labelRect.width() + 2 * horizontalPadding);
|
||||
}
|
||||
|
||||
float adjustedCenterX = (bgLeft + bgRight) / 2f;
|
||||
|
||||
canvas.drawLine(centerX, centerY, adjustedCenterX, bgBottom, dotPaint);
|
||||
canvas.drawRoundRect(bgLeft, bgTop, bgRight, bgBottom, cornerRadius, cornerRadius, hudBackgroundPaint);
|
||||
canvas.drawRoundRect(bgLeft, bgTop, bgRight, bgBottom, cornerRadius, cornerRadius, hudPaint);
|
||||
canvas.drawText(currentValue, adjustedCenterX, bgBottom - verticalPadding, labelPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -20,7 +20,9 @@ import android.graphics.Canvas;
|
||||
|
||||
public interface SleepDetailsOverlay {
|
||||
|
||||
void drawOverlayScale(Canvas canvas, int lineSpacing, int horizontalLineCount, int chartTopStart, int x);
|
||||
void init(int horizontalLineCount);
|
||||
|
||||
void drawOverlayScale(Canvas canvas, int lineSpacing, int chartTopStart, int x);
|
||||
|
||||
void drawOverlay(Canvas canvas, int left, int top, int height, int width);
|
||||
|
||||
|
||||
+10
-11
@@ -184,7 +184,7 @@ public class SleepDetailsView extends View {
|
||||
canvas.drawLine(chartLeftStart + chartWidth, chartTopStart, chartLeftStart + chartWidth, chartTopStart + chartHeight, gridPaint);
|
||||
|
||||
if(curOverlay != null) {
|
||||
curOverlay.drawOverlayScale(canvas, lineSpacing, horizontalLineCount, chartTopStart, chartLeftStart + chartWidth);
|
||||
curOverlay.drawOverlayScale(canvas, lineSpacing, chartTopStart, chartLeftStart + chartWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,14 +432,15 @@ public class SleepDetailsView extends View {
|
||||
//draw selector
|
||||
if (selectorPos > chartLeftStart && selectorPos < chartLeftStart + chartWidth) {
|
||||
canvas.drawLine(selectorPos, chartTopStart, selectorPos, chartTopStart + chartHeight, selectorPaint);
|
||||
if (curOverlay != null) {
|
||||
curOverlay.drawOverlaySelector(canvas, chartLeftStart, chartTopStart, chartHeight, chartWidth, selectorPos);
|
||||
}
|
||||
if (selectorPos > (chartLeftStart + timeInfoTextRect.width()) && selectorPos < (chartLeftStart + chartWidth - timeInfoTextRect.width())) {
|
||||
final String selectedDate = formatDurationHoursMinutes(this.startTimestamp + (long) ((selectorPos - chartLeftStart) / unitWidth) * 60000L);
|
||||
canvas.drawText(selectedDate, selectorPos, timeInfoY, timeInfoTextPaint);
|
||||
}
|
||||
drawSelectedInfo(canvas);
|
||||
|
||||
if (curOverlay != null) {
|
||||
curOverlay.drawOverlaySelector(canvas, chartLeftStart, chartTopStart, chartHeight, chartWidth, selectorPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +450,7 @@ public class SleepDetailsView extends View {
|
||||
heightUnit = (float) (1.0 / (config.length * 2));
|
||||
}
|
||||
|
||||
public void setData(List<SleepDetail> sleepDetails) {
|
||||
public void setData(List<SleepDetail> sleepDetails, SleepDetailsOverlay overlay) {
|
||||
curOverlay = null;
|
||||
this.sleepDetails = sleepDetails;
|
||||
this.sleepMinutesCount = 0;
|
||||
@@ -462,15 +463,13 @@ public class SleepDetailsView extends View {
|
||||
this.endTimestamp = last.startTimestamp + ((last.durationMinutes) * 60000L);
|
||||
}
|
||||
selectorPos = -1.0F;
|
||||
curOverlay = overlay;
|
||||
if(curOverlay != null) {
|
||||
curOverlay.init(horizontalLineCount);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setOverlay(SleepDetailsOverlay overlay) {
|
||||
curOverlay = overlay;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
super.performClick();
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_marginBottom="25dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:layout_weight="4" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
@@ -189,7 +189,8 @@
|
||||
android:layout_weight="4"
|
||||
app:singleSelection="true"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"/>
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"/>
|
||||
|
||||
<GridLayout
|
||||
android:background="@color/gauge_line_color"
|
||||
|
||||
Reference in New Issue
Block a user