mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-27 02:55:50 +01:00
VO2Max: replace GENERAL vo2max with ANY, add widgets
This commit is contained in:
parent
9f0d426a9f
commit
f2227bb083
@ -127,7 +127,7 @@ public class GBApplication extends Application {
|
|||||||
private static SharedPreferences sharedPrefs;
|
private static SharedPreferences sharedPrefs;
|
||||||
private static final String PREFS_VERSION = "shared_preferences_version";
|
private static final String PREFS_VERSION = "shared_preferences_version";
|
||||||
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
||||||
private static final int CURRENT_PREFS_VERSION = 40;
|
private static final int CURRENT_PREFS_VERSION = 41;
|
||||||
|
|
||||||
private static final LimitedQueue<Integer, String> mIDSenderLookup = new LimitedQueue<>(16);
|
private static final LimitedQueue<Integer, String> mIDSenderLookup = new LimitedQueue<>(16);
|
||||||
private static GBPrefs prefs;
|
private static GBPrefs prefs;
|
||||||
@ -1823,6 +1823,14 @@ public class GBApplication extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 41) {
|
||||||
|
// Add vo2max widget.
|
||||||
|
final String dashboardWidgetsOrder = sharedPrefs.getString("pref_dashboard_widgets_order", null);
|
||||||
|
if (!StringUtils.isBlank(dashboardWidgetsOrder) && !dashboardWidgetsOrder.contains("vo2max")) {
|
||||||
|
editor.putString("pref_dashboard_widgets_order", dashboardWidgetsOrder + ",vo2max");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,9 @@ import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStress
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressSegmentedWidget;
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressSegmentedWidget;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressSimpleWidget;
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStressSimpleWidget;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardTodayWidget;
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardTodayWidget;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxCyclingWidget;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxAnyWidget;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardVO2MaxRunningWidget;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.DashboardUtils;
|
||||||
@ -298,6 +301,15 @@ public class DashboardFragment extends Fragment implements MenuProvider {
|
|||||||
case "hrv":
|
case "hrv":
|
||||||
widget = DashboardHrvWidget.newInstance(dashboardData);
|
widget = DashboardHrvWidget.newInstance(dashboardData);
|
||||||
break;
|
break;
|
||||||
|
case "vo2max_running":
|
||||||
|
widget = DashboardVO2MaxRunningWidget.newInstance(dashboardData);
|
||||||
|
break;
|
||||||
|
case "vo2max_cycling":
|
||||||
|
widget = DashboardVO2MaxCyclingWidget.newInstance(dashboardData);
|
||||||
|
break;
|
||||||
|
case "vo2max":
|
||||||
|
widget = DashboardVO2MaxAnyWidget.newInstance(dashboardData);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.error("Unknown dashboard widget {}", widgetName);
|
LOG.error("Unknown dashboard widget {}", widgetName);
|
||||||
continue;
|
continue;
|
||||||
|
@ -246,7 +246,7 @@ public class HeartRateDailyFragment extends AbstractChartFragment<HeartRateDaily
|
|||||||
hrLineChart.getAxisRight().setAxisMaximum(maximum + 30);
|
hrLineChart.getAxisRight().setAxisMaximum(maximum + 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
hrLineChart.getXAxis().setValueFormatter(new SampleXLabelFormatter(tsTranslation));
|
hrLineChart.getXAxis().setValueFormatter(new SampleXLabelFormatter(tsTranslation, "HH:mm"));
|
||||||
hrLineChart.setData(new LineData(dataSet));
|
hrLineChart.setData(new LineData(dataSet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.AbstractDashboardVO2MaxWidget;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
|
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.GaugeDrawer;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
@ -49,17 +50,14 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
protected static final Logger LOG = LoggerFactory.getLogger(VO2MaxFragment.class);
|
protected static final Logger LOG = LoggerFactory.getLogger(VO2MaxFragment.class);
|
||||||
|
|
||||||
private TextView mDateView;
|
private TextView mDateView;
|
||||||
private TextView vo2MaxGeneralValue;
|
|
||||||
private TextView vo2MaxRunningValue;
|
private TextView vo2MaxRunningValue;
|
||||||
private TextView vo2MaxCyclingValue;
|
private TextView vo2MaxCyclingValue;
|
||||||
private ImageView vo2MaxGeneralGauge;
|
|
||||||
private ImageView vo2MaxRunningGauge;
|
private ImageView vo2MaxRunningGauge;
|
||||||
private ImageView vo2MaxCyclingGauge;
|
private ImageView vo2MaxCyclingGauge;
|
||||||
protected GaugeDrawer gaugeDrawer = new GaugeDrawer();
|
protected GaugeDrawer gaugeDrawer = new GaugeDrawer();
|
||||||
private LineChart vo2MaxChart;
|
private LineChart vo2MaxChart;
|
||||||
private RelativeLayout vo2maxCyclingWrapper;
|
private RelativeLayout vo2maxCyclingWrapper;
|
||||||
private RelativeLayout vo2maxRunningWrapper;
|
private RelativeLayout vo2maxRunningWrapper;
|
||||||
private RelativeLayout vo2maxGeneralWrapper;
|
|
||||||
private GridLayout tilesGridWrapper;
|
private GridLayout tilesGridWrapper;
|
||||||
private int tsFrom;
|
private int tsFrom;
|
||||||
GBDevice device;
|
GBDevice device;
|
||||||
@ -73,15 +71,12 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
View rootView = inflater.inflate(R.layout.fragment_vo2max, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_vo2max, container, false);
|
||||||
|
|
||||||
mDateView = rootView.findViewById(R.id.vo2max_date_view);
|
mDateView = rootView.findViewById(R.id.vo2max_date_view);
|
||||||
vo2MaxGeneralValue = rootView.findViewById(R.id.vo2max_general_gauge_value);
|
|
||||||
vo2MaxRunningValue = rootView.findViewById(R.id.vo2max_running_gauge_value);
|
vo2MaxRunningValue = rootView.findViewById(R.id.vo2max_running_gauge_value);
|
||||||
vo2MaxCyclingValue = rootView.findViewById(R.id.vo2max_cycling_gauge_value);
|
vo2MaxCyclingValue = rootView.findViewById(R.id.vo2max_cycling_gauge_value);
|
||||||
vo2MaxGeneralGauge = rootView.findViewById(R.id.vo2max_general_gauge);
|
|
||||||
vo2MaxRunningGauge = rootView.findViewById(R.id.vo2max_running_gauge);
|
vo2MaxRunningGauge = rootView.findViewById(R.id.vo2max_running_gauge);
|
||||||
vo2MaxCyclingGauge = rootView.findViewById(R.id.vo2max_cycling_gauge);
|
vo2MaxCyclingGauge = rootView.findViewById(R.id.vo2max_cycling_gauge);
|
||||||
vo2MaxChart = rootView.findViewById(R.id.vo2max_chart);
|
vo2MaxChart = rootView.findViewById(R.id.vo2max_chart);
|
||||||
vo2maxCyclingWrapper = rootView.findViewById(R.id.vo2max_cycling_card_layout);
|
vo2maxCyclingWrapper = rootView.findViewById(R.id.vo2max_cycling_card_layout);
|
||||||
vo2maxGeneralWrapper = rootView.findViewById(R.id.vo2max_general_card_layout);
|
|
||||||
vo2maxRunningWrapper = rootView.findViewById(R.id.vo2max_running_card_layout);
|
vo2maxRunningWrapper = rootView.findViewById(R.id.vo2max_running_card_layout);
|
||||||
tilesGridWrapper = rootView.findViewById(R.id.tiles_grid_wrapper);
|
tilesGridWrapper = rootView.findViewById(R.id.tiles_grid_wrapper);
|
||||||
device = getChartsHost().getDevice();
|
device = getChartsHost().getDevice();
|
||||||
@ -91,11 +86,9 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
if (!supportsVO2MaxRunning(device)) {
|
if (!supportsVO2MaxRunning(device)) {
|
||||||
tilesGridWrapper.removeView(vo2maxRunningWrapper);
|
tilesGridWrapper.removeView(vo2maxRunningWrapper);
|
||||||
}
|
}
|
||||||
if (!supportsVO2MaxGeneral(device)) {
|
|
||||||
tilesGridWrapper.removeView(vo2maxGeneralWrapper);
|
|
||||||
}
|
|
||||||
setupVO2MaxChart();
|
setupVO2MaxChart();
|
||||||
refresh();
|
refresh();
|
||||||
|
setupLegend(vo2MaxChart);
|
||||||
|
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
@ -106,11 +99,6 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
return coordinator != null && coordinator.supportsVO2MaxCycling();
|
return coordinator != null && coordinator.supportsVO2MaxCycling();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportsVO2MaxGeneral(GBDevice device) {
|
|
||||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
|
||||||
return coordinator != null && coordinator.supportsVO2MaxGeneral();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsVO2MaxRunning(GBDevice device) {
|
public boolean supportsVO2MaxRunning(GBDevice device) {
|
||||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||||
return coordinator != null && coordinator.supportsVO2MaxRunning();
|
return coordinator != null && coordinator.supportsVO2MaxRunning();
|
||||||
@ -160,7 +148,6 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
TimestampTranslation tsTranslation = new TimestampTranslation();
|
TimestampTranslation tsTranslation = new TimestampTranslation();
|
||||||
List<Entry> runningEntries = new ArrayList<>();
|
List<Entry> runningEntries = new ArrayList<>();
|
||||||
List<Entry> cyclingEntries = new ArrayList<>();
|
List<Entry> cyclingEntries = new ArrayList<>();
|
||||||
List<Entry> generalEntries = new ArrayList<>();
|
|
||||||
vo2MaxData.records.forEach((record) -> {
|
vo2MaxData.records.forEach((record) -> {
|
||||||
float nd = (float) (record.timestamp - this.tsFrom) / (60 * 60 * 24);
|
float nd = (float) (record.timestamp - this.tsFrom) / (60 * 60 * 24);
|
||||||
switch (record.type) {
|
switch (record.type) {
|
||||||
@ -170,54 +157,25 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
case CYCLING:
|
case CYCLING:
|
||||||
cyclingEntries.add(new Entry(nd, record.value));
|
cyclingEntries.add(new Entry(nd, record.value));
|
||||||
break;
|
break;
|
||||||
case GENERAL:
|
|
||||||
generalEntries.add(new Entry(nd, record.value));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
final int[] colors = {
|
final int[] colors = AbstractDashboardVO2MaxWidget.getColors();
|
||||||
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
|
final float[] segments = AbstractDashboardVO2MaxWidget.getSegments();
|
||||||
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
|
float[] vo2MaxRanges = AbstractDashboardVO2MaxWidget.getVO2MaxRanges();
|
||||||
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
|
|
||||||
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_excellent_color),
|
|
||||||
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_superior_color),
|
|
||||||
};
|
|
||||||
final float[] segments = {
|
|
||||||
0.20F,
|
|
||||||
0.20F,
|
|
||||||
0.20F,
|
|
||||||
0.20F,
|
|
||||||
0.20F,
|
|
||||||
};
|
|
||||||
float[] vo2MaxRanges = {
|
|
||||||
55.4F,
|
|
||||||
51.1F,
|
|
||||||
45.4F,
|
|
||||||
41.7F,
|
|
||||||
0.0F,
|
|
||||||
};
|
|
||||||
|
|
||||||
final List<ILineDataSet> lineDataSets = new ArrayList<>();
|
final List<ILineDataSet> lineDataSets = new ArrayList<>();
|
||||||
if (supportsVO2MaxGeneral(device)) {
|
|
||||||
VO2MaxRecord latestGeneralRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.GENERAL);
|
|
||||||
float generalVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestGeneralRecord != null ? latestGeneralRecord.value : 0);
|
|
||||||
gaugeDrawer.drawSegmentedGauge(vo2MaxGeneralGauge, colors, segments, generalVO2MaxValue, false, true);
|
|
||||||
vo2MaxGeneralValue.setText(String.valueOf(latestGeneralRecord != null ? Math.round(latestGeneralRecord.value) : "-"));
|
|
||||||
lineDataSets.add(createDataSet(generalEntries, getResources().getColor(R.color.vo2max_general_char_line_color), getString(R.string.vo2_max_general)));
|
|
||||||
}
|
|
||||||
if (supportsVO2MaxRunning(device)) {
|
if (supportsVO2MaxRunning(device)) {
|
||||||
VO2MaxRecord latestRunningRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.RUNNING);
|
VO2MaxRecord latestRunningRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.RUNNING);
|
||||||
float runningVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestRunningRecord != null ? latestRunningRecord.value : 0);
|
float runningVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestRunningRecord != null ? latestRunningRecord.value : 0);
|
||||||
vo2MaxRunningValue.setText(String.valueOf(latestRunningRecord != null ? Math.round(latestRunningRecord.value) : "-"));
|
vo2MaxRunningValue.setText(String.valueOf(latestRunningRecord != null ? Math.round(latestRunningRecord.value) : "-"));
|
||||||
gaugeDrawer.drawSegmentedGauge(vo2MaxRunningGauge, colors, segments, runningVO2MaxValue, false, true);
|
gaugeDrawer.drawSegmentedGauge(vo2MaxRunningGauge, colors, segments, runningVO2MaxValue, false, true);
|
||||||
lineDataSets.add(createDataSet(runningEntries, getResources().getColor(R.color.vo2max_running_char_line_color), getString(R.string.vo2_max_running)));
|
lineDataSets.add(createDataSet(runningEntries, getResources().getColor(R.color.vo2max_running_char_line_color), getString(R.string.vo2max_running)));
|
||||||
}
|
}
|
||||||
if (supportsVO2MaxCycling(device)) {
|
if (supportsVO2MaxCycling(device)) {
|
||||||
VO2MaxRecord latestCyclingRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.CYCLING);
|
VO2MaxRecord latestCyclingRecord = vo2MaxData.getLatestValue(Vo2MaxSample.Type.CYCLING);
|
||||||
float cyclingVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestCyclingRecord != null ? latestCyclingRecord.value : 0);
|
float cyclingVO2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, latestCyclingRecord != null ? latestCyclingRecord.value : 0);
|
||||||
gaugeDrawer.drawSegmentedGauge(vo2MaxCyclingGauge, colors, segments, cyclingVO2MaxValue, false, true);
|
gaugeDrawer.drawSegmentedGauge(vo2MaxCyclingGauge, colors, segments, cyclingVO2MaxValue, false, true);
|
||||||
vo2MaxCyclingValue.setText(String.valueOf(latestCyclingRecord != null ? Math.round(latestCyclingRecord.value) : "-"));
|
vo2MaxCyclingValue.setText(String.valueOf(latestCyclingRecord != null ? Math.round(latestCyclingRecord.value) : "-"));
|
||||||
lineDataSets.add(createDataSet(cyclingEntries, getResources().getColor(R.color.vo2max_cycling_char_line_color), getString(R.string.vo2_max_cycling)));
|
lineDataSets.add(createDataSet(cyclingEntries, getResources().getColor(R.color.vo2max_cycling_char_line_color), getString(R.string.vo2max_cycling)));
|
||||||
}
|
}
|
||||||
final LineData lineData = new LineData(lineDataSets);
|
final LineData lineData = new LineData(lineDataSets);
|
||||||
vo2MaxChart.getXAxis().setValueFormatter(getVO2MaxLineChartValueFormatter());
|
vo2MaxChart.getXAxis().setValueFormatter(getVO2MaxLineChartValueFormatter());
|
||||||
@ -320,7 +278,10 @@ public class VO2MaxFragment extends AbstractChartFragment<VO2MaxFragment.VO2MaxD
|
|||||||
yAxisRight.setDrawAxisLine(true);
|
yAxisRight.setDrawAxisLine(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setupLegend(Chart<?> chart) {}
|
protected void setupLegend(Chart<?> chart) {
|
||||||
|
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||||
|
chart.getLegend().setWordWrapEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
protected static class VO2MaxRecord {
|
protected static class VO2MaxRecord {
|
||||||
float value;
|
float value;
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.Vo2MaxSampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
|
||||||
|
|
||||||
|
public abstract class AbstractDashboardVO2MaxWidget extends AbstractGaugeWidget implements DashboardVO2MaxWidgetInterface {
|
||||||
|
protected static final Logger LOG = LoggerFactory.getLogger(AbstractDashboardVO2MaxWidget.class);
|
||||||
|
|
||||||
|
public AbstractDashboardVO2MaxWidget(int label, @Nullable String targetActivityTab) {
|
||||||
|
super(label, targetActivityTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void populateData(final DashboardFragment.DashboardData dashboardData) {
|
||||||
|
final List<GBDevice> devices = getSupportedDevices(dashboardData);
|
||||||
|
final VO2MaxData data = new VO2MaxData();
|
||||||
|
|
||||||
|
// Latest vo2max sample.
|
||||||
|
Vo2MaxSample sample = null;
|
||||||
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
|
for (GBDevice dev : devices) {
|
||||||
|
final Vo2MaxSampleProvider sampleProvider = (Vo2MaxSampleProvider) dev.getDeviceCoordinator().getVo2MaxSampleProvider(dev, dbHandler.getDaoSession());
|
||||||
|
final Vo2MaxSample latestSample = sampleProvider.getLatestSample(getVO2MaxType(), dashboardData.timeTo * 1000L);
|
||||||
|
if (latestSample != null && (sample == null || latestSample.getTimestamp() > sample.getTimestamp())) {
|
||||||
|
sample = latestSample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sample != null) {
|
||||||
|
data.value = sample.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Could not get vo2max for today", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardData.put(getWidgetKey(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] getColors() {
|
||||||
|
return new int[]{
|
||||||
|
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_poor_color),
|
||||||
|
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_fair_color),
|
||||||
|
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_good_color),
|
||||||
|
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_excellent_color),
|
||||||
|
ContextCompat.getColor(GBApplication.getContext(), R.color.vo2max_value_superior_color),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] getSegments() {
|
||||||
|
return new float[] {
|
||||||
|
0.20F,
|
||||||
|
0.20F,
|
||||||
|
0.20F,
|
||||||
|
0.20F,
|
||||||
|
0.20F,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] getVO2MaxRanges() {
|
||||||
|
return new float[] {
|
||||||
|
55.4F,
|
||||||
|
51.1F,
|
||||||
|
45.4F,
|
||||||
|
41.7F,
|
||||||
|
0.0F,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void draw(final DashboardFragment.DashboardData dashboardData) {
|
||||||
|
final VO2MaxData vo2MaxData = (VO2MaxData) dashboardData.get(getWidgetKey());
|
||||||
|
if (vo2MaxData == null) {
|
||||||
|
drawSimpleGauge(0, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int[] colors = getColors();
|
||||||
|
final float[] segments = getSegments();
|
||||||
|
final float[] vo2MaxRanges = getVO2MaxRanges();
|
||||||
|
float vo2MaxValue = calculateVO2maxGaugeValue(vo2MaxRanges, vo2MaxData.value != -1 ? vo2MaxData.value : 0);
|
||||||
|
setText(String.valueOf(vo2MaxData.value != -1 ? Math.round(vo2MaxData.value) : "-"));
|
||||||
|
drawSegmentedGauge(
|
||||||
|
colors,
|
||||||
|
segments,
|
||||||
|
vo2MaxValue,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float calculateVO2maxGaugeValue(float[] vo2MaxRanges, float vo2MaxValue) {
|
||||||
|
float value = -1;
|
||||||
|
for (int i = 0; i < vo2MaxRanges.length; i++) {
|
||||||
|
if (vo2MaxValue - vo2MaxRanges[i] > 0) {
|
||||||
|
float rangeValue = i - 1 >= 0 ? vo2MaxRanges[i-1] : 60F;
|
||||||
|
float rangeDiff = rangeValue - vo2MaxRanges[i];
|
||||||
|
float valueDiff = vo2MaxValue - vo2MaxRanges[i];
|
||||||
|
float multiplayer = valueDiff / rangeDiff;
|
||||||
|
value = (4 - i) * 0.2F + 0.2F * (multiplayer > 1 ? 1 : multiplayer) ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VO2MaxData implements Serializable {
|
||||||
|
private float value = -1;
|
||||||
|
}
|
||||||
|
}
|
@ -16,22 +16,14 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.graphics.PorterDuffXfermode;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
|
||||||
|
|
||||||
|
public class DashboardVO2MaxAnyWidget extends AbstractDashboardVO2MaxWidget {
|
||||||
|
|
||||||
|
public DashboardVO2MaxAnyWidget() {
|
||||||
|
super(R.string.vo2max, "vo2max");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DashboardVO2MaxAnyWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
|
||||||
|
final DashboardVO2MaxAnyWidget fragment = new DashboardVO2MaxAnyWidget();
|
||||||
|
final Bundle args = new Bundle();
|
||||||
|
args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vo2MaxSample.Type getVO2MaxType() {
|
||||||
|
return Vo2MaxSample.Type.ANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWidgetKey() {
|
||||||
|
return "vo2max";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isSupportedBy(final GBDevice device) {
|
||||||
|
return device.getDeviceCoordinator().supportsVO2Max();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
|
||||||
|
|
||||||
|
public class DashboardVO2MaxCyclingWidget extends AbstractDashboardVO2MaxWidget {
|
||||||
|
|
||||||
|
public DashboardVO2MaxCyclingWidget() {
|
||||||
|
super(R.string.vo2max_cycling, "vo2max");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DashboardVO2MaxCyclingWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
|
||||||
|
final DashboardVO2MaxCyclingWidget fragment = new DashboardVO2MaxCyclingWidget();
|
||||||
|
final Bundle args = new Bundle();
|
||||||
|
args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vo2MaxSample.Type getVO2MaxType() {
|
||||||
|
return Vo2MaxSample.Type.CYCLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWidgetKey() {
|
||||||
|
return "vo2max_cycling";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isSupportedBy(final GBDevice device) {
|
||||||
|
return device.getDeviceCoordinator().supportsVO2MaxCycling();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.DashboardFragment;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
|
||||||
|
|
||||||
|
public class DashboardVO2MaxRunningWidget extends AbstractDashboardVO2MaxWidget {
|
||||||
|
|
||||||
|
public DashboardVO2MaxRunningWidget() {
|
||||||
|
super(R.string.vo2max_running, "vo2max");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DashboardVO2MaxRunningWidget newInstance(final DashboardFragment.DashboardData dashboardData) {
|
||||||
|
final DashboardVO2MaxRunningWidget fragment = new DashboardVO2MaxRunningWidget();
|
||||||
|
final Bundle args = new Bundle();
|
||||||
|
args.putSerializable(ARG_DASHBOARD_DATA, dashboardData);
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vo2MaxSample.Type getVO2MaxType() {
|
||||||
|
return Vo2MaxSample.Type.RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWidgetKey() {
|
||||||
|
return "vo2max_running";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isSupportedBy(final GBDevice device) {
|
||||||
|
return device.getDeviceCoordinator().supportsVO2MaxRunning();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Vo2MaxSample;
|
||||||
|
|
||||||
|
public interface DashboardVO2MaxWidgetInterface {
|
||||||
|
Vo2MaxSample.Type getVO2MaxType();
|
||||||
|
String getWidgetKey();
|
||||||
|
}
|
@ -490,11 +490,6 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsVO2MaxGeneral() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsActivityTabs() {
|
public boolean supportsActivityTabs() {
|
||||||
return supportsActivityTracking();
|
return supportsActivityTracking();
|
||||||
|
@ -221,7 +221,6 @@ public interface DeviceCoordinator {
|
|||||||
boolean supportsHrvMeasurement();
|
boolean supportsHrvMeasurement();
|
||||||
boolean supportsVO2Max();
|
boolean supportsVO2Max();
|
||||||
boolean supportsVO2MaxCycling();
|
boolean supportsVO2MaxCycling();
|
||||||
boolean supportsVO2MaxGeneral();
|
|
||||||
boolean supportsVO2MaxRunning();
|
boolean supportsVO2MaxRunning();
|
||||||
boolean supportsSleepMeasurement();
|
boolean supportsSleepMeasurement();
|
||||||
boolean supportsStepCounter();
|
boolean supportsStepCounter();
|
||||||
|
@ -146,7 +146,7 @@ public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSa
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Vo2MaxSample getLatestSample() {
|
public Vo2MaxSample getLatestSample() {
|
||||||
return getLatestSample(Vo2MaxSample.Type.GENERAL, 0);
|
return getLatestSample(Vo2MaxSample.Type.ANY, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -231,7 +231,7 @@ public class GarminVo2MaxSampleProvider implements Vo2MaxSampleProvider<Vo2MaxSa
|
|||||||
type = Vo2MaxSample.Type.CYCLING;
|
type = Vo2MaxSample.Type.CYCLING;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
type = Vo2MaxSample.Type.GENERAL;
|
type = Vo2MaxSample.Type.ANY;
|
||||||
}
|
}
|
||||||
return new GarminVo2maxSample(summary.getStartTime().getTime(), type, (float) value);
|
return new GarminVo2maxSample(summary.getStartTime().getTime(), type, (float) value);
|
||||||
} catch (final JSONException e) {
|
} catch (final JSONException e) {
|
||||||
|
@ -18,7 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.model;
|
|||||||
|
|
||||||
public interface Vo2MaxSample extends TimeSample {
|
public interface Vo2MaxSample extends TimeSample {
|
||||||
enum Type {
|
enum Type {
|
||||||
GENERAL,
|
ANY,
|
||||||
RUNNING,
|
RUNNING,
|
||||||
CYCLING
|
CYCLING
|
||||||
}
|
}
|
||||||
|
@ -23,41 +23,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:columnCount="2"
|
android:columnCount="2"
|
||||||
>
|
>
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/vo2max_general_card_layout"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:ignore="UselessParent"
|
|
||||||
android:layout_gravity="fill"
|
|
||||||
android:layout_columnWeight="1"
|
|
||||||
>
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/vo2max_general_gauge"
|
|
||||||
android:layout_width="150dp"
|
|
||||||
android:layout_height="75dp"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:scaleType="fitStart" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/vo2max_general_gauge_value"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="28dp"
|
|
||||||
android:text="@string/stats_empty_value"
|
|
||||||
android:textSize="30sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/vo2max_general_gauge_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/vo2max_general_gauge_value"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:text="@string/vo2max_general_gauge_label" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/vo2max_running_card_layout"
|
android:id="@+id/vo2max_running_card_layout"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -91,7 +56,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/vo2max_running_gauge_value"
|
android:layout_below="@+id/vo2max_running_gauge_value"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:text="@string/vo2max_running_gauge_label" />
|
android:text="@string/running" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
@ -127,7 +92,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/vo2max_cycling_gauge_value"
|
android:layout_below="@+id/vo2max_cycling_gauge_value"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:text="@string/vo2max_cycling_gauge_label" />
|
android:text="@string/cycling" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
@ -4177,11 +4177,13 @@
|
|||||||
<item>@string/active_time</item>
|
<item>@string/active_time</item>
|
||||||
<item>@string/menuitem_sleep</item>
|
<item>@string/menuitem_sleep</item>
|
||||||
<item>@string/body_energy</item>
|
<item>@string/body_energy</item>
|
||||||
<item>@string/vo2max</item>
|
|
||||||
<item>@string/menuitem_stress_simple</item>
|
<item>@string/menuitem_stress_simple</item>
|
||||||
<item>@string/menuitem_stress_segmented</item>
|
<item>@string/menuitem_stress_segmented</item>
|
||||||
<item>@string/menuitem_stress_breakdown</item>
|
<item>@string/menuitem_stress_breakdown</item>
|
||||||
<item>@string/hrv</item>
|
<item>@string/hrv</item>
|
||||||
|
<item>@string/vo2max</item>
|
||||||
|
<item>@string/vo2max_running</item>
|
||||||
|
<item>@string/vo2max_cycling</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_dashboard_widgets_order_values">
|
<string-array name="pref_dashboard_widgets_order_values">
|
||||||
@ -4192,11 +4194,13 @@
|
|||||||
<item>activetime</item>
|
<item>activetime</item>
|
||||||
<item>sleep</item>
|
<item>sleep</item>
|
||||||
<item>bodyenergy</item>
|
<item>bodyenergy</item>
|
||||||
<item>vo2max</item>
|
|
||||||
<item>stress_simple</item>
|
<item>stress_simple</item>
|
||||||
<item>stress_segmented</item>
|
<item>stress_segmented</item>
|
||||||
<item>stress_breakdown</item>
|
<item>stress_breakdown</item>
|
||||||
<item>hrv</item>
|
<item>hrv</item>
|
||||||
|
<item>vo2max</item>
|
||||||
|
<item>vo2max_running</item>
|
||||||
|
<item>vo2max_cycling</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="pref_dashboard_widgets_order_default">
|
<string-array name="pref_dashboard_widgets_order_default">
|
||||||
@ -4209,5 +4213,6 @@
|
|||||||
<item>bodyenergy</item>
|
<item>bodyenergy</item>
|
||||||
<item>stress_segmented</item>
|
<item>stress_segmented</item>
|
||||||
<item>hrv</item>
|
<item>hrv</item>
|
||||||
|
<item>vo2max</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
<color name="hrv_status_char_line_color" type="color">#d12a2a</color>
|
<color name="hrv_status_char_line_color" type="color">#d12a2a</color>
|
||||||
<color name="vo2max_running_char_line_color" type="color">#46acea</color>
|
<color name="vo2max_running_char_line_color" type="color">#46acea</color>
|
||||||
<color name="vo2max_cycling_char_line_color" type="color">#59b22c</color>
|
<color name="vo2max_cycling_char_line_color" type="color">#59b22c</color>
|
||||||
<color name="vo2max_general_char_line_color" type="color">#824be3</color>
|
|
||||||
<color name="vo2max_value_poor_color" type="color">#d93832</color>
|
<color name="vo2max_value_poor_color" type="color">#d93832</color>
|
||||||
<color name="vo2max_value_fair_color" type="color">#ffa703</color>
|
<color name="vo2max_value_fair_color" type="color">#ffa703</color>
|
||||||
<color name="vo2max_value_good_color" type="color">#04c79c</color>
|
<color name="vo2max_value_good_color" type="color">#04c79c</color>
|
||||||
|
@ -1623,9 +1623,8 @@
|
|||||||
<string name="hrv_status_unit">%1$d ms</string>
|
<string name="hrv_status_unit">%1$d ms</string>
|
||||||
<string name="hrv_status_baseline">%1$d-%2$d ms</string>
|
<string name="hrv_status_baseline">%1$d-%2$d ms</string>
|
||||||
<string name="hrv_status_baseline_label">Baseline</string>
|
<string name="hrv_status_baseline_label">Baseline</string>
|
||||||
<string name="vo2_max_running">VO2Max Running</string>
|
<string name="running">Running</string>
|
||||||
<string name="vo2_max_cycling">VO2Max Cycling</string>
|
<string name="cycling">Cycling</string>
|
||||||
<string name="vo2_max_general">VO2Max General</string>
|
|
||||||
<string name="bpm_value_unit">%1$d bpm</string>
|
<string name="bpm_value_unit">%1$d bpm</string>
|
||||||
<string name="steps_distance_unit">%1$,.2f km</string>
|
<string name="steps_distance_unit">%1$,.2f km</string>
|
||||||
<string name="body_energy_gained">Gained</string>
|
<string name="body_energy_gained">Gained</string>
|
||||||
@ -1993,9 +1992,6 @@
|
|||||||
<string name="warning">Warning!</string>
|
<string name="warning">Warning!</string>
|
||||||
<string name="note">Note</string>
|
<string name="note">Note</string>
|
||||||
<string name="no_data">No data</string>
|
<string name="no_data">No data</string>
|
||||||
<string name="vo2max_general_gauge_label">VO2Max</string>
|
|
||||||
<string name="vo2max_running_gauge_label">Running VO2Max</string>
|
|
||||||
<string name="vo2max_cycling_gauge_label">Cycling VO2Max</string>
|
|
||||||
<!-- LED Color -->
|
<!-- LED Color -->
|
||||||
<string name="preferences_led_color">LED Color</string>
|
<string name="preferences_led_color">LED Color</string>
|
||||||
<!-- FM transmitters -->
|
<!-- FM transmitters -->
|
||||||
@ -2537,6 +2533,8 @@
|
|||||||
<string name="pref_header_hrv_status">HRV Status</string>
|
<string name="pref_header_hrv_status">HRV Status</string>
|
||||||
<string name="body_energy">Body Energy</string>
|
<string name="body_energy">Body Energy</string>
|
||||||
<string name="vo2max">VO2 Max</string>
|
<string name="vo2max">VO2 Max</string>
|
||||||
|
<string name="vo2max_running">VO2 Max (Running)</string>
|
||||||
|
<string name="vo2max_cycling">VO2 Max (Cycling)</string>
|
||||||
<string name="thirty_days_timeline">30 Days Timeline</string>
|
<string name="thirty_days_timeline">30 Days Timeline</string>
|
||||||
<string name="pref_header_sony_ambient_sound_control">Ambient Sound Control</string>
|
<string name="pref_header_sony_ambient_sound_control">Ambient Sound Control</string>
|
||||||
<string name="pref_header_sony_sound_control">Sound Control</string>
|
<string name="pref_header_sony_sound_control">Sound Control</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user