From eda181539370b17020e206b671f8b7d4a1438684 Mon Sep 17 00:00:00 2001 From: Vasja Volin Date: Wed, 22 Jan 2025 21:05:17 -0800 Subject: [PATCH] Add settings toggles for chart series Adds toggles to the User Interface section of the app Settings that allow the user to display or hide the three series available in the chart: Elevation, Speed/Pace, and Heart Rate. As a user of a heart rate monitor primarily during strength workouts and combat sports, the distance and elevation graphs are not helpful to me - in addition, their scale means that I am not able to easily see my exact heart rate. This should allow users in a similar situation to better determine exactly what is most helpful for them to be seen on the graph. Also updates heart rate series possible intervals - the smallest interval was 25, which was too low for most zone 1 cardio charts - if your heart rate goes from 70 to 125, with a 25 interval, most of the chart will be whitespace. --- .../opentracks/chart/ChartFragment.java | 25 ++++++ .../opentracks/chart/ChartView.java | 82 ++++++++++++++----- .../opentracks/settings/PreferencesUtils.java | 15 ++++ src/main/res/values/settings.xml | 31 +++++-- src/main/res/values/strings.xml | 5 ++ src/main/res/xml/settings_user_interface.xml | 19 +++++ 6 files changed, 150 insertions(+), 27 deletions(-) diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartFragment.java b/src/main/java/de/dennisguse/opentracks/chart/ChartFragment.java index a847f8a26..11a735734 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartFragment.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartFragment.java @@ -86,6 +86,7 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener { }); } } + if (PreferencesUtils.isKey(R.string.stats_rate_key, key)) { boolean reportSpeed = PreferencesUtils.isReportSpeed(activityTypeLocalized); if (reportSpeed != viewBinding.chartView.getReportSpeed()) { @@ -99,6 +100,21 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener { }); } } + + if (PreferencesUtils.isKey(R.string.chart_display_elevation_key, key)) { + viewBinding.chartView.setShowElevation(PreferencesUtils.shouldShowElevation()); + refreshChart(); + } + + if (PreferencesUtils.isKey(R.string.chart_display_pace_or_speed_key, key)) { + viewBinding.chartView.setShowPaceOrSpeed(PreferencesUtils.shouldShowPaceOrSpeed()); + refreshChart(); + } + + if (PreferencesUtils.isKey(R.string.chart_display_heart_rate_key, key)) { + viewBinding.chartView.setShowHeartRate(PreferencesUtils.shouldShowHeartRate()); + refreshChart(); + } } }; @@ -273,4 +289,13 @@ public class ChartFragment extends Fragment implements TrackDataHub.Listener { fragmentActivity.runOnUiThread(runnable); } } + + private void refreshChart() { + runOnUiThread(() -> { + if (isResumed()) { + viewBinding.chartView.invalidate(); + viewBinding.chartView.requestLayout(); + } + }); + } } diff --git a/src/main/java/de/dennisguse/opentracks/chart/ChartView.java b/src/main/java/de/dennisguse/opentracks/chart/ChartView.java index ff96cac67..b95a390cd 100644 --- a/src/main/java/de/dennisguse/opentracks/chart/ChartView.java +++ b/src/main/java/de/dennisguse/opentracks/chart/ChartView.java @@ -86,8 +86,10 @@ public class ChartView extends View { } private final List seriesList = new LinkedList<>(); + private final ChartValueSeries elevationSeries; private final ChartValueSeries speedSeries; private final ChartValueSeries paceSeries; + private final ChartValueSeries heartRateSeries; private final LinkedList chartPoints = new LinkedList<>(); private final List markers = new LinkedList<>(); @@ -123,6 +125,7 @@ public class ChartView extends View { private boolean chartByDistance = false; private UnitSystem unitSystem = UnitSystem.defaultUnitSystem(); private boolean reportSpeed = true; + private boolean showPaceOrSpeed = true; private boolean showPointer = false; private final GestureDetectorCompat detectorScrollFlingTab = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() { @@ -176,17 +179,17 @@ public class ChartView extends View { int fontSizeSmall = ThemeUtils.getFontSizeSmallInPx(context); int fontSizeMedium = ThemeUtils.getFontSizeMediumInPx(context); - seriesList.add(new ChartValueSeries(context, - Integer.MIN_VALUE, - Integer.MAX_VALUE, - new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}, - R.string.description_altitude_metric, - R.string.description_altitude_imperial, - R.string.description_altitude_imperial, - R.color.chart_altitude_fill, - R.color.chart_altitude_border, - fontSizeSmall, - fontSizeMedium) { + elevationSeries = new ChartValueSeries(context, + Integer.MIN_VALUE, + Integer.MAX_VALUE, + new int[]{5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}, + R.string.description_altitude_metric, + R.string.description_altitude_imperial, + R.string.description_altitude_imperial, + R.color.chart_altitude_fill, + R.color.chart_altitude_border, + fontSizeSmall, + fontSizeMedium) { @Override protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) { return chartPoint.altitude(); @@ -196,7 +199,8 @@ public class ChartView extends View { protected boolean drawIfChartPointHasNoData() { return false; } - }); + }; + seriesList.add(elevationSeries); speedSeries = new ChartValueSeries(context, 0, @@ -244,10 +248,15 @@ public class ChartView extends View { }; seriesList.add(paceSeries); - seriesList.add(new ChartValueSeries(context, + heartRateSeries = new ChartValueSeries(context, 0, Integer.MAX_VALUE, - new int[]{25, 50}, + // For Zone 5 cardio, the 25 value should result in nice visuals, as the values + // will range from ~70 - ~180 (around 4.5 intervals). + // For Zone 1 cardio, the 15 value should result in nice visuals, as the values + // will range from ~70 - ~120 (around 3.5 intervals) + // The fallback of 50 should give appropriate visuals for values outside this range. + new int[]{15, 25, 50}, R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate, R.string.description_sensor_heart_rate, @@ -264,7 +273,8 @@ public class ChartView extends View { protected boolean drawIfChartPointHasNoData() { return false; } - }); + }; + seriesList.add(heartRateSeries); seriesList.add(new ChartValueSeries(context, 0, @@ -343,9 +353,15 @@ public class ChartView extends View { setClickable(true); updateDimensions(); - // either speedSeries or paceSeries should be enabled. - speedSeries.setEnabled(reportSpeed); - paceSeries.setEnabled(!reportSpeed); + // Either speedSeries or paceSeries should be enabled, if one is shown. + if (showPaceOrSpeed) { + speedSeries.setEnabled(reportSpeed); + paceSeries.setEnabled(!reportSpeed); + } + + // Defaults for our chart series. + heartRateSeries.setEnabled(true); + elevationSeries.setEnabled(true); } @Override @@ -379,6 +395,12 @@ public class ChartView extends View { } public boolean applyReportSpeed() { + if (!showPaceOrSpeed) { + paceSeries.setEnabled(false); + speedSeries.setEnabled(false); + return true; + } + if (reportSpeed) { if (!speedSeries.isEnabled()) { speedSeries.setEnabled(true); @@ -396,6 +418,20 @@ public class ChartView extends View { return false; } + void setShowElevation(boolean value) { + elevationSeries.setEnabled(value); + } + void setShowPaceOrSpeed(boolean value) { + showPaceOrSpeed = value; + + // we want to make sure we show whatever version the user has + // selected when we turn this back on. + applyReportSpeed(); + } + void setShowHeartRate(boolean value) { + heartRateSeries.setEnabled(value); + } + public void setShowPointer(boolean value) { showPointer = value; } @@ -644,10 +680,18 @@ public class ChartView extends View { */ private void drawSeriesTitles(Canvas canvas) { Iterator tpI = titleDimensions.titlePositions.iterator(); + for (ChartValueSeries chartValueSeries : seriesList) { if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) { String title = getContext().getString(chartValueSeries.getTitleId(unitSystem)); Paint paint = chartValueSeries.getTitlePaint(); + + // It is possible for the titlePositions to become empty temporarily, while switching between + // chart screens quickly. + if (!tpI.hasNext()) { + return; + } + TitlePosition tp = tpI.next(); int y = topBorder - spacer - (titleDimensions.lineCount - tp.line) * (titleDimensions.lineHeight + spacer); canvas.drawText(title, tp.xPos + getScrollX(), y, paint); @@ -774,7 +818,7 @@ public class ChartView extends View { //TODO int markerXPosition = x - spacer; - int index = titleDimensions.titlePositions.size() - 1; // index only onver the visible chart series + int index = titleDimensions.titlePositions.size() - 1; // index only over the visible chart series final int lastDrawn2ndLineMarkerIndex = getYmarkerCountOn1stLine(); for (int i = seriesList.size()-1; i>=0 ;--i) { // draw markers from the last series to achieve right alignment ChartValueSeries chartValueSeries = seriesList.get(i); diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 38cfbe60a..af3f002c2 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -318,6 +318,21 @@ public class PreferencesUtils { return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT); } + public static boolean shouldShowElevation() { + final boolean DEFAULT = resources.getBoolean(R.bool.chart_display_elevation_default); + return getBoolean(R.string.chart_display_elevation_key, DEFAULT); + } + + public static boolean shouldShowPaceOrSpeed() { + final boolean DEFAULT = resources.getBoolean(R.bool.chart_display_pace_or_speed_default); + return getBoolean(R.string.chart_display_pace_or_speed_key, DEFAULT); + } + + public static boolean shouldShowHeartRate() { + final boolean DEFAULT = resources.getBoolean(R.bool.chart_display_heart_rate_default); + return getBoolean(R.string.chart_display_heart_rate_key, DEFAULT); + } + public static boolean shouldUseDynamicColors() { final boolean DEFAULT = resources.getBoolean(R.bool.settings_ui_dynamic_colors_default); return getBoolean(R.string.settings_ui_dynamic_colors_key, DEFAULT); diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 4a3ac2651..db5faac93 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -1,14 +1,6 @@ - trackdetail_show_on_lockscreen_while_recording - false - - trackdetail_keep_screen_on_while_recording - false - - trackdetail_fullscreen_while_recording - false settingsDefaults @@ -279,9 +271,32 @@ showIntroduction true + + uiDynamicColors false + + trackdetail_show_on_lockscreen_while_recording + false + + trackdetail_keep_screen_on_while_recording + false + + trackdetail_fullscreen_while_recording + false + + + chart_display_elevation + true + + chart_display_pace_or_speed + true + + chart_display_heart_rate + true + + localeKey diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index f9de7b82f..baf41380f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -344,6 +344,11 @@ limitations under the License. By distance By time + Charts + Display elevation + Display pace or speed + Display heart rate + Recording Theme changes may require a manual restart. diff --git a/src/main/res/xml/settings_user_interface.xml b/src/main/res/xml/settings_user_interface.xml index e1eb4fa90..9596502f6 100644 --- a/src/main/res/xml/settings_user_interface.xml +++ b/src/main/res/xml/settings_user_interface.xml @@ -58,4 +58,23 @@ android:title="@string/settings_recording_fullscreen_on_while_recording_title" /> + + + + + + + \ No newline at end of file