Implement SpeedFormatter.

This commit is contained in:
Dennis Guse
2022-04-24 16:51:31 +02:00
parent 16f238f34b
commit db5c9479df
10 changed files with 220 additions and 85 deletions
@@ -0,0 +1,103 @@
package de.dennisguse.opentracks.data.models;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.StringUtils;
public class SpeedFormatter {
private final Resources resources;
private final int decimalCount;
private final boolean metricUnits;
private final boolean reportSpeedOrPace;
private SpeedFormatter(Resources resources, int decimalCount, boolean metricUnits, boolean reportSpeedOrPace) {
this.resources = resources;
this.decimalCount = decimalCount;
this.metricUnits = metricUnits;
this.reportSpeedOrPace = reportSpeedOrPace;
}
public String formatSpeed(Speed speed) {
Pair<String, String> distanceParts = getSpeedParts(speed);
return resources.getString(R.string.speed_with_unit, distanceParts.first, distanceParts.second);
}
/**
* Gets the formatted speed with unit.
*
* @return the formatted speed (or null) and it's unit as {@link Pair}
*/
public Pair<String, String> getSpeedParts(Speed speed) {
int unitId;
if (metricUnits) {
unitId = reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
} else {
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
}
String unitString = resources.getString(unitId);
if (speed == null) {
speed = Speed.zero();
}
if (reportSpeedOrPace) {
return new Pair<>(StringUtils.formatDecimal(speed.to(metricUnits), 1), unitString);
}
int pace = (int) speed.toPace(metricUnits).getSeconds();
int minutes = pace / 60;
int seconds = pace % 60;
return new Pair<>(resources.getString(R.string.time, minutes, seconds), unitString);
}
public static Builder Builder() {
return new Builder();
}
public static class Builder {
private int decimalCount;
private boolean metricUnits;
private boolean reportSpeedOrPace;
public Builder() {
decimalCount = 2;
metricUnits = true;
reportSpeedOrPace = true;
}
public Builder setDecimalCount(int decimalCount) {
this.decimalCount = decimalCount;
return this;
}
public Builder setMetricUnits(boolean metricUnits) {
this.metricUnits = metricUnits;
return this;
}
public Builder setReportSpeedOrPace(boolean reportSpeedOrPace) {
this.reportSpeedOrPace = reportSpeedOrPace;
return this;
}
public SpeedFormatter build(Resources resource) {
return new SpeedFormatter(resource, decimalCount, metricUnits, reportSpeedOrPace);
}
public SpeedFormatter build(Context context) {
return build(context.getResources());
}
}
}
@@ -38,6 +38,7 @@ import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -207,11 +208,12 @@ public class StatisticsRecordedFragment extends Fragment {
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()));
}
SpeedFormatter formatter = SpeedFormatter.Builder().setMetricUnits(preferenceMetricUnits).setReportSpeedOrPace(preferenceReportSpeed).build(getContext());
// Set average speed/pace
{
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageSpeed(), preferenceMetricUnits, preferenceReportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(trackStatistics.getAverageSpeed());
viewBinding.statsAverageSpeedValue.setText(parts.first);
viewBinding.statsAverageSpeedUnit.setText(parts.second);
}
@@ -220,7 +222,7 @@ public class StatisticsRecordedFragment extends Fragment {
{
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getMaxSpeed(), preferenceMetricUnits, preferenceReportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(trackStatistics.getMaxSpeed());
viewBinding.statsMaxSpeedValue.setText(parts.first);
viewBinding.statsMaxSpeedUnit.setText(parts.second);
}
@@ -229,7 +231,7 @@ public class StatisticsRecordedFragment extends Fragment {
{
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageMovingSpeed(), preferenceMetricUnits, preferenceReportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(trackStatistics.getAverageMovingSpeed());
viewBinding.statsMovingSpeedValue.setText(parts.first);
viewBinding.statsMovingSpeedUnit.setText(parts.second);
}
@@ -20,12 +20,12 @@ import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.TrackRecordingActivity;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
* Manages the content of the notification shown by {@link TrackRecordingService}.
@@ -97,7 +97,8 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
}
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(trackStatistics.getTotalDistance())));
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, StringUtils.formatSpeed(context, trackPoint.getSpeed(), metricUnits, true)));
String formattedSpeed = SpeedFormatter.Builder().setMetricUnits(metricUnits).setReportSpeedOrPace(true).build(context).formatSpeed(trackPoint.getSpeed());
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, formattedSpeed));
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
updateNotification();
@@ -28,6 +28,7 @@ import java.time.ZoneId;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -209,8 +210,8 @@ public class DescriptionGenerator {
*/
@VisibleForTesting
void writePace(Speed speed, StringBuilder builder, int resId, String lineBreak) {
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
Pair<String, String> paceInMetrics = SpeedFormatter.Builder().setMetricUnits(true).setReportSpeedOrPace(false).build(context).getSpeedParts(speed);
Pair<String, String> paceInImperial = SpeedFormatter.Builder().setMetricUnits(false).setReportSpeedOrPace(false).build(context).getSpeedParts(speed);
String formattedPaceMetrics = paceInMetrics.first != null ? paceInMetrics.first : context.getString(R.string.value_unknown);
String formattedPaceImperial = paceInImperial.first != null ? paceInImperial.first : context.getString(R.string.value_unknown);
@@ -14,6 +14,7 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -119,15 +120,17 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
setCommonValues(aggregatedStatistic);
SpeedFormatter formatter = SpeedFormatter.Builder().setMetricUnits(metricsUnits).setReportSpeedOrPace(reportSpeed).build(context);
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed());
avgSpeed.setText(parts.first);
avgSpeedUnit.setText(parts.second);
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
}
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed());
maxSpeed.setText(parts.first);
maxSpeedUnit.setText(parts.second);
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
@@ -135,16 +138,18 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
}
public void setPace(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
SpeedFormatter formatter = SpeedFormatter.Builder().setMetricUnits(metricsUnits).setReportSpeedOrPace(reportSpeed).build(context);
setCommonValues(aggregatedStatistic);
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed());
avgSpeed.setText(parts.first);
avgSpeedUnit.setText(parts.second);
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_pace));
}
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed());
maxSpeed.setText(parts.first);
maxSpeedUnit.setText(parts.second);
maxSpeedLabel.setText(R.string.stats_fastest_pace);
@@ -14,6 +14,7 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.util.StringUtils;
public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@@ -59,7 +60,8 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
.setMetricUnits(metricUnits)
.build(context).formatDistance(sumDistance));
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
SpeedFormatter formatter = SpeedFormatter.Builder().setMetricUnits(metricUnits).setReportSpeedOrPace(isReportSpeed).build(context);
viewHolder.rate.setText(formatter.formatSpeed(interval.getSpeed()));
viewHolder.gain.setText(StringUtils.formatAltitudeChange(context, interval.getGain_m(), metricUnits));
viewHolder.loss.setText(StringUtils.formatAltitudeChange(context, interval.getLoss_m(), metricUnits));
@@ -45,7 +45,6 @@ import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
/**
* Various string manipulation methods.
@@ -128,15 +127,6 @@ public class StringUtils {
return TextUtils.split(value, ":").length == 2 ? "0:" + value : value;
}
public static String formatSpeed(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) {
Pair<String, String> distanceParts = getSpeedParts(context, speed, metricUnits, reportSpeed);
return context.getString(R.string.speed_with_unit, distanceParts.first, distanceParts.second);
}
private static String formatDecimal(double value) {
return StringUtils.formatDecimal(value, 2);
}
/**
* Format a decimal number while removing trailing zeros of the decimal part (if present).
@@ -170,39 +160,6 @@ public class StringUtils {
return context.getString(R.string.location_latitude_longitude, Location.convert(latitude, Location.FORMAT_DEGREES), Location.convert(longitude, Location.FORMAT_DEGREES));
}
/**
* Gets the formatted speed with unit.
*
* @param context the context
* @param speed the speed
* @param metricUnits true to use metric unit
* @param reportSpeed true to report speed; false for pace
* @return the formatted speed (or null) and it's unit as {@link Pair}
*/
public static Pair<String, String> getSpeedParts(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) {
int unitId;
if (metricUnits) {
unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
} else {
unitId = reportSpeed ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
}
String unitString = context.getString(unitId);
if (speed == null) {
speed = Speed.zero();
}
if (reportSpeed) {
return new Pair<>(StringUtils.formatDecimal(speed.to(metricUnits), 1), unitString);
}
int pace = (int) speed.toPace(metricUnits).getSeconds();
int minutes = pace / 60;
int seconds = pace % 60;
return new Pair<>(context.getString(R.string.time, minutes, seconds), unitString);
}
public static Pair<String, String> getHeartRateParts(Context context, HeartRate heartrate) {
String value = context.getString(R.string.value_unknown);
if (heartrate != null) {
@@ -14,6 +14,7 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
@@ -47,6 +48,10 @@ public class StatisticDataBuilder {
final String sensorUnknown = context.getString(R.string.value_unknown);
SpeedFormatter speedFormatterPace = SpeedFormatter.Builder().setMetricUnits(metricUnits).setReportSpeedOrPace(false).build(context);
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder().setMetricUnits(metricUnits).setReportSpeedOrPace(true).build(context);
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()), null);
title = context.getString(R.string.stats_total_time);
@@ -62,31 +67,32 @@ public class StatisticDataBuilder {
boolean reportSpeed = fieldKey.equals("speed");
title = reportSpeed ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder().setMetricUnits(metricUnits).setReportSpeedOrPace(reportSpeed).build(context);
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
valueAndUnit = StringUtils.getSpeedParts(context, sensorDataSet.getSpeed().first, metricUnits, reportSpeed);
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
description = sensorDataSet.getSpeed().second;
} else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed);
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
description = context.getString(R.string.description_speed_source_gps);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageMovingSpeed(), metricUnits, true);
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageMovingSpeed());
title = context.getString(R.string.stats_average_moving_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageSpeed(), metricUnits, true);
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageSpeed());
title = context.getString(R.string.stats_average_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getMaxSpeed(), metricUnits, true);
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getMaxSpeed());
title = context.getString(R.string.stats_max_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageMovingSpeed(), metricUnits, false);
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageMovingSpeed());
title = context.getString(R.string.stats_average_moving_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getAverageSpeed(), metricUnits, false);
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageSpeed());
title = context.getString(R.string.stats_average_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, trackStatistics.getMaxSpeed(), metricUnits, true);
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getMaxSpeed());
title = context.getString(R.string.stats_fastest_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;