mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-11 01:21:56 +01:00
Plotting Temperature (#3381)
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3381 Co-authored-by: ahormann <ahormann@gmx.net> Co-committed-by: ahormann <ahormann@gmx.net>
This commit is contained in:
parent
ec6fa23176
commit
f5b46b295b
@ -79,8 +79,13 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
||||
tabList = new ArrayList<>(Arrays.asList(myTabs.split(",")));
|
||||
}
|
||||
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
if (!coordinator.supportsRealtimeData()) {
|
||||
tabList.remove("livestats");
|
||||
if (!coordinator.supportsActivityTabs()) {
|
||||
tabList.remove("activity");
|
||||
tabList.remove("activitylist");
|
||||
}
|
||||
if (!coordinator.supportsSleepMeasurement()) {
|
||||
tabList.remove("sleep");
|
||||
tabList.remove("sleepweek");
|
||||
}
|
||||
if (!coordinator.supportsStressMeasurement()) {
|
||||
tabList.remove("stress");
|
||||
@ -91,6 +96,18 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
||||
if (!coordinator.supportsSpo2()) {
|
||||
tabList.remove("spo2");
|
||||
}
|
||||
if (!coordinator.supportsStepCounter()) {
|
||||
tabList.remove("stepsweek");
|
||||
}
|
||||
if (!coordinator.supportsSpeedzones()) {
|
||||
tabList.remove("speedzones");
|
||||
}
|
||||
if (!coordinator.supportsRealtimeData()) {
|
||||
tabList.remove("livestats");
|
||||
}
|
||||
if (!coordinator.supportsTemperatureMeasurement()) {
|
||||
tabList.remove("temperature");
|
||||
}
|
||||
return tabList;
|
||||
}
|
||||
|
||||
@ -132,6 +149,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
||||
return new LiveActivityFragment();
|
||||
case "spo2":
|
||||
return new Spo2ChartFragment();
|
||||
case "temperature":
|
||||
return new TemperatureChartFragment();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -180,6 +199,8 @@ public class ActivityChartsActivity extends AbstractChartsActivity {
|
||||
return getString(R.string.liveactivity_live_activity);
|
||||
case "spo2":
|
||||
return getString(R.string.pref_header_spo2);
|
||||
case "temperature":
|
||||
return getString(R.string.temperature);
|
||||
}
|
||||
return super.getPageTitle(position);
|
||||
}
|
||||
|
@ -0,0 +1,239 @@
|
||||
/* Copyright (C) 2023 Alicia Hormann
|
||||
|
||||
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.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.mikephil.charting.animation.Easing;
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.charts.LineChart;
|
||||
import com.github.mikephil.charting.components.XAxis;
|
||||
import com.github.mikephil.charting.components.YAxis;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.data.LineData;
|
||||
import com.github.mikephil.charting.data.LineDataSet;
|
||||
import com.github.mikephil.charting.formatter.ValueFormatter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
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.TemperatureSample;
|
||||
|
||||
public class TemperatureChartFragment extends AbstractChartFragment<TemperatureChartFragment.TemperatureChartsData> {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(ActivitySleepChartFragment.class);
|
||||
|
||||
private LineChart mTemperatureChart;
|
||||
private int BACKGROUND_COLOR;
|
||||
private int DESCRIPTION_COLOR;
|
||||
private int CHART_TEXT_COLOR;
|
||||
|
||||
protected final int TOTAL_DAYS = getRangeDays();
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
BACKGROUND_COLOR = GBApplication.getBackgroundColor(requireContext());
|
||||
DESCRIPTION_COLOR = GBApplication.getTextColor(requireContext());
|
||||
CHART_TEXT_COLOR = GBApplication.getSecondaryTextColor(requireContext());
|
||||
|
||||
}
|
||||
private int getRangeDays() {
|
||||
if (GBApplication.getPrefs().getBoolean("charts_range", true)) {
|
||||
return 30;
|
||||
} else {
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TemperatureChartsData refreshInBackground(final ChartsHost chartsHost, final DBHandler db, final GBDevice device) {
|
||||
final List<? extends TemperatureSample> samples = getSamples(db, device);
|
||||
|
||||
LOG.info("Got {} temperature samples", samples.size());
|
||||
return new TemperatureChartsDataBuilder(samples).build();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void updateChartsnUIThread(final TemperatureChartsData temperatureData) {
|
||||
mTemperatureChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||
mTemperatureChart.getXAxis().setValueFormatter(temperatureData.getXValueFormatter());
|
||||
mTemperatureChart.getXAxis().setAvoidFirstLastClipping(true);
|
||||
|
||||
// Using approximately the range of survivable body-temperatures (in celsius), rounded to multiples of 5
|
||||
mTemperatureChart.getAxisLeft().setAxisMinimum(30f);
|
||||
mTemperatureChart.getAxisLeft().setAxisMaximum(45f);
|
||||
|
||||
mTemperatureChart.setData(temperatureData.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getString(R.string.menuitem_temperature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater,
|
||||
final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final View rootView = inflater.inflate(R.layout.fragment_temperaturechart, container, false);
|
||||
|
||||
mTemperatureChart = rootView.findViewById(R.id.temperature_line_chart);
|
||||
|
||||
setupLineChart();
|
||||
|
||||
// refresh immediately instead of use refreshIfVisible(), for perceived performance
|
||||
refresh();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void setupLineChart() {
|
||||
mTemperatureChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||
mTemperatureChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||
configureBarLineChartDefaults(mTemperatureChart);
|
||||
|
||||
final XAxis x = mTemperatureChart.getXAxis();
|
||||
x.setDrawLabels(true);
|
||||
x.setDrawGridLines(false);
|
||||
x.setEnabled(true);
|
||||
x.setTextColor(CHART_TEXT_COLOR);
|
||||
x.setDrawLimitLinesBehindData(true);
|
||||
|
||||
final YAxis yAxisLeft = mTemperatureChart.getAxisLeft();
|
||||
yAxisLeft.setDrawGridLines(true);
|
||||
yAxisLeft.setDrawTopYLabelEntry(false);
|
||||
yAxisLeft.setTextColor(CHART_TEXT_COLOR);
|
||||
yAxisLeft.setEnabled(true);
|
||||
|
||||
final YAxis yAxisRight = mTemperatureChart.getAxisRight();
|
||||
yAxisRight.setDrawGridLines(false);
|
||||
yAxisRight.setDrawLabels(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(final Chart<?> chart) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderCharts() {
|
||||
mTemperatureChart.animateX(ANIM_TIME, Easing.EaseInOutQuart);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTSStart() {
|
||||
return getTSEnd() - TOTAL_DAYS*24*60*60;
|
||||
}
|
||||
|
||||
private List<? extends TemperatureSample> getSamples(final DBHandler db, final GBDevice device) {
|
||||
final int tsStart = getTSStart();
|
||||
final int tsEnd = getTSEnd();
|
||||
final DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
final TimeSampleProvider<? extends TemperatureSample> sampleProvider = coordinator.getTemperatureSampleProvider(device, db.getDaoSession());
|
||||
return sampleProvider.getAllSamples(tsStart * 1000L, tsEnd * 1000L);
|
||||
}
|
||||
|
||||
protected class TemperatureChartsDataBuilder {
|
||||
private final List<? extends TemperatureSample> samples;
|
||||
|
||||
public TemperatureChartsDataBuilder(final List<? extends TemperatureSample> samples) {
|
||||
this.samples = samples;
|
||||
}
|
||||
|
||||
public TemperatureChartsData build() {
|
||||
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
long firstTs = 0;
|
||||
|
||||
for (TemperatureSample sample : samples) {
|
||||
int timestamp_in_seconds = (int) (sample.getTimestamp() / 1000L);
|
||||
entries.add(new Entry(tsTranslation.shorten(timestamp_in_seconds), sample.getTemperature()));
|
||||
if (firstTs == 0) {
|
||||
firstTs = sample.getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
LineDataSet dataSet = new LineDataSet(entries, getString(R.string.temperature));
|
||||
dataSet.setLineWidth(2.2f);
|
||||
dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
|
||||
dataSet.setCubicIntensity(0.1f);
|
||||
dataSet.setDrawCircles(true);
|
||||
dataSet.setCircleRadius(5f);
|
||||
dataSet.setDrawCircleHole(false);
|
||||
dataSet.setDrawValues(true);
|
||||
dataSet.setValueTextSize(10f);
|
||||
dataSet.setValueTextColor(CHART_TEXT_COLOR);
|
||||
dataSet.setHighlightEnabled(true);
|
||||
dataSet.setValueFormatter(new MyValueFormatter());
|
||||
LineData lineData = new LineData(dataSet);
|
||||
|
||||
return new TemperatureChartsData(lineData, tsTranslation);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class TemperatureChartsData extends DefaultChartsData<LineData> {
|
||||
public TemperatureChartsData(LineData lineData, TimestampTranslation tsTranslation) {
|
||||
super(lineData, new dateFormatter(tsTranslation));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class dateFormatter extends ValueFormatter {
|
||||
private final TimestampTranslation tsTranslation;
|
||||
SimpleDateFormat annotationDateFormat = new SimpleDateFormat("dd.MM.");
|
||||
Calendar cal = GregorianCalendar.getInstance();
|
||||
|
||||
public dateFormatter(TimestampTranslation tsTranslation) {
|
||||
this.tsTranslation = tsTranslation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedValue(float value) {
|
||||
cal.clear();
|
||||
int ts = (int) value;
|
||||
cal.setTimeInMillis(tsTranslation.toOriginalValue(ts) * 1000L);
|
||||
Date date = cal.getTime();
|
||||
return annotationDateFormat.format(date);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MyValueFormatter extends ValueFormatter {
|
||||
private final DecimalFormat formatter = new DecimalFormat("0.00");
|
||||
|
||||
@Override
|
||||
public String getPointLabel(Entry entry) {
|
||||
return formatter.format(entry.getY());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -411,6 +411,28 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTabs() {
|
||||
return supportsActivityTracking();
|
||||
}
|
||||
@Override
|
||||
public boolean supportsSleepMeasurement() {
|
||||
return supportsActivityTracking();
|
||||
}
|
||||
@Override
|
||||
public boolean supportsStepCounter() {
|
||||
return supportsActivityTracking();
|
||||
}
|
||||
@Override
|
||||
public boolean supportsSpeedzones() {
|
||||
return supportsActivityTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTemperatureMeasurement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSpo2() {
|
||||
return false;
|
||||
|
@ -192,6 +192,17 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsStressMeasurement();
|
||||
|
||||
boolean supportsSleepMeasurement();
|
||||
boolean supportsStepCounter();
|
||||
boolean supportsSpeedzones();
|
||||
boolean supportsActivityTabs();
|
||||
|
||||
/**
|
||||
* Returns true if measurement and fetching of body temperature is supported by the device
|
||||
* (with this coordinator).
|
||||
*/
|
||||
boolean supportsTemperatureMeasurement();
|
||||
|
||||
/**
|
||||
* Returns true if SpO2 measurement and fetching is supported by the device
|
||||
* (with this coordinator).
|
||||
|
@ -99,6 +99,34 @@ public class FemometerVinca2DeviceCoordinator extends AbstractDeviceCoordinator
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTemperatureMeasurement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsActivityTracking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSleepMeasurement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsStepCounter() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean supportsSpeedzones() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean supportsActivityTabs() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
|
19
app/src/main/res/layout/fragment_temperaturechart.xml
Normal file
19
app/src/main/res/layout/fragment_temperaturechart.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity$PlaceholderFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.github.mikephil.charting.charts.LineChart
|
||||
android:id="@+id/temperature_line_chart"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="2" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
@ -3020,6 +3020,7 @@
|
||||
<item>@string/p_speed_zones</item>
|
||||
<item>@string/p_live_stats</item>
|
||||
<item>@string/p_spo2</item>
|
||||
<item>@string/p_temperature</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
|
@ -905,6 +905,7 @@
|
||||
<string name="minutes_10">10 minutes</string>
|
||||
<string name="minutes_30">30 minutes</string>
|
||||
<string name="liveactivity_live_activity">Live activity</string>
|
||||
<string name="temperature">Temperature</string>
|
||||
<string name="weeksteps_today_steps_description">Steps today, target: %1$s</string>
|
||||
<string name="lack_of_step">Lack of steps: %1$d</string>
|
||||
<string name="overstep">Overstep: %1$d</string>
|
||||
|
@ -106,6 +106,7 @@
|
||||
<item name="p_speed_zones" type="string">speedzones</item>
|
||||
<item name="p_live_stats" type="string">livestats</item>
|
||||
<item name="p_spo2" type="string">spo2</item>
|
||||
<item name="p_temperature" type="string">temperature</item>
|
||||
|
||||
<item name="p_message_privacy_mode_off" type="string">off</item>
|
||||
<item name="p_message_privacy_mode_complete" type="string">complete</item>
|
||||
|
Loading…
Reference in New Issue
Block a user