Create DistanceFormatter.

Fixes #1197
This commit is contained in:
Dennis Guse
2022-04-23 16:02:15 +02:00
parent b44bab1ab5
commit f2ac1edc4b
65 changed files with 177 additions and 311 deletions
@@ -49,6 +49,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.tables.TracksColumns;
import de.dennisguse.opentracks.databinding.TrackListBinding;
@@ -190,7 +191,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
int iconId = TrackIconUtils.getIconDrawable(icon);
String name = cursor.getString(nameIndex);
String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
String totalDistance = DistanceFormatter.Builder().build(TrackListActivity.this).formatDistance(Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
int markerCount = cursor.getInt(markerCountIndex);
long startTime = cursor.getLong(startTimeIndex);
int startTimeOffset = cursor.getInt(startTimeOffsetIndex);
@@ -0,0 +1,82 @@
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 DistanceFormatter {
private final Resources resources;
private final int decimalCount;
private DistanceFormatter(Resources resources, int decimalCount) {
this.resources = resources;
this.decimalCount = decimalCount;
}
public String formatDistance(Distance distance, boolean metricUnits) {
if (distance.isInvalid()) {
return resources.getString(R.string.value_unknown);
}
Pair<String, String> distanceParts = getDistanceParts(distance, metricUnits);
return resources.getString(R.string.distance_with_unit, distanceParts.first, distanceParts.second);
}
/**
* Get the formatted distance with unit.
*
* @param metricUnits true to use metric unit
* @return the formatted distance (or null) and it's unit as {@link Pair}
*/
public Pair<String, String> getDistanceParts(Distance distance, boolean metricUnits) {
if (distance.isInvalid()) {
return new Pair<>(null, resources.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
}
if (metricUnits) {
if (distance.greaterThan(Distance.of(500))) {
return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer));
} else {
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
}
} else {
if (distance.greaterThan(Distance.ofMile(0.5))) {
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
} else {
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
}
}
}
public static Builder Builder() {
return new Builder();
}
public static class Builder {
private int decimalCount;
public Builder() {
decimalCount = 2;
}
public Builder setDecimalCount(int decimalCount) {
this.decimalCount = decimalCount;
return this;
}
public DistanceFormatter build(Resources resource) {
return new DistanceFormatter(resource, decimalCount);
}
public DistanceFormatter build(Context context) {
return build(context.getResources());
}
}
}
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.R;
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.Track;
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -186,7 +187,7 @@ public class StatisticsRecordedFragment extends Fragment {
TrackStatistics trackStatistics = track.getTrackStatistics();
// Set total distance
{
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), trackStatistics.getTotalDistance(), preferenceMetricUnits);
Pair<String, String> parts = DistanceFormatter.Builder().build(getContext()).getDistanceParts(trackStatistics.getTotalDistance(), preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first);
viewBinding.statsDistanceUnit.setText(parts.second);
@@ -17,6 +17,7 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
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.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -79,8 +80,10 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
void updateTrackPoint(Context context, TrackStatistics trackStatistics, TrackPoint trackPoint, Distance recordingGpsAccuracy) {
String formattedAccuracy = context.getString(R.string.value_none);
DistanceFormatter formatter = DistanceFormatter.Builder().build(context);
if (trackPoint.hasHorizontalAccuracy()) {
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getHorizontalAccuracy(), metricUnits);
formattedAccuracy = formatter.formatDistance(trackPoint.getHorizontalAccuracy(), metricUnits);
boolean currentLocationWasAccurate = trackPoint.getHorizontalAccuracy().lessThan(recordingGpsAccuracy);
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
@@ -88,7 +91,7 @@ class TrackRecordingServiceNotificationManager implements SharedPreferences.OnSh
previousLocationWasAccurate = currentLocationWasAccurate;
}
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, StringUtils.formatDistance(context, trackStatistics.getTotalDistance(), metricUnits)));
notificationBuilder.setContentTitle(context.getString(R.string.track_distance_notification, formatter.formatDistance(trackStatistics.getTotalDistance(), metricUnits)));
notificationBuilder.setContentText(context.getString(R.string.track_speed_notification, StringUtils.formatSpeed(context, trackPoint.getSpeed(), metricUnits, true)));
notificationBuilder.setSubText(context.getString(R.string.track_recording_notification_accuracy, formattedAccuracy));
updateNotification();
@@ -28,6 +28,6 @@ public class AnnouncementsSettingsFragment extends PreferenceFragmentCompat {
voiceFrequency.setEntries(PreferencesUtils.getVoiceAnnouncementFrequencyEntries());
ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key));
voiceDistance.setEntries(PreferencesUtils.getVoiceAnnouncementDistanceEntries(getContext()));
voiceDistance.setEntries(PreferencesUtils.getVoiceAnnouncementDistanceEntries());
}
}
@@ -9,7 +9,7 @@ import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
public class GpsSettingsFragment extends PreferenceFragmentCompat {
@@ -17,12 +17,16 @@ public class GpsSettingsFragment extends PreferenceFragmentCompat {
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_gps);
final DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(0)
.build(getContext());
findPreference(getString(R.string.recording_distance_interval_key))
.setSummaryProvider(
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getRecordingDistanceInterval();
return getString(R.string.settings_recording_location_frequency_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
return getString(R.string.settings_recording_location_frequency_summary, formatter.formatDistance(distance, metricUnits));
}
);
@@ -31,7 +35,7 @@ public class GpsSettingsFragment extends PreferenceFragmentCompat {
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getMaxRecordingDistance();
return getString(R.string.settings_recording_max_recording_distance_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
return getString(R.string.settings_recording_max_recording_distance_summary, formatter.formatDistance(distance, metricUnits));
}
);
@@ -40,7 +44,7 @@ public class GpsSettingsFragment extends PreferenceFragmentCompat {
preference -> {
boolean metricUnits = PreferencesUtils.isMetricUnits();
Distance distance = PreferencesUtils.getThresholdHorizontalAccuracy();
return getString(R.string.settings_recording_min_required_accuracy_summary, StringUtils.formatDistance(getContext(), distance, metricUnits));
return getString(R.string.settings_recording_min_required_accuracy_summary, formatter.formatDistance(distance, metricUnits));
}
);
@@ -39,12 +39,11 @@ import java.util.stream.Stream;
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.Speed;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
/**
@@ -304,17 +303,20 @@ public class PreferencesUtils {
/**
* @return Result depends on isMetricUnits
*/
@Deprecated //TODO Context should not be used here; requires refactoring of StringUtils.
static String[] getVoiceAnnouncementDistanceEntries(Context context) {
static String[] getVoiceAnnouncementDistanceEntries() {
String[] values = resources.getStringArray(R.array.voice_announcement_distance_values);
String[] options = new String[values.length];
boolean metricUnits = isMetricUnits();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(0)
.build(resources);
for (int i = 0; i < values.length; i++) {
if (resources.getString(R.string.announcement_off).equals(values[i])) {
options[i] = resources.getString(R.string.value_off);
} else {
Distance distance = Distance.one(metricUnits).multipliedBy(Double.parseDouble(values[i]));
options[i] = StringUtils.formatDistance(context, distance, metricUnits, 0);
options[i] = formatter.formatDistance(distance, metricUnits);
}
}
return options;
@@ -353,21 +355,23 @@ public class PreferencesUtils {
final int recordingDistanceIntervalDefault = (int) getRecordingDistanceIntervalDefault().toM();
boolean metricUnits = isMetricUnits();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(0)
.build(resources);
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
Distance distance = Distance.of(1).multipliedBy(value);
String displayValue = formatter.formatDistance(distance, metricUnits);
if (metricUnits) {
displayValue = resources.getString(R.string.value_integer_meter, value);
if (value == recordingDistanceIntervalDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = resources.getString(R.string.value_integer_feet, feet);
if (value == recordingDistanceIntervalDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, feet);
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
} else {
entries[i] = displayValue;
}
@@ -389,21 +393,23 @@ public class PreferencesUtils {
final int maxRecordingDistanceDefault = Integer.parseInt(resources.getString(R.string.max_recording_distance_default));
boolean metricUnits = isMetricUnits();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(0)
.build(resources);
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
Distance distance = Distance.of(1).multipliedBy(value);
String displayValue = formatter.formatDistance(distance, metricUnits);
if (metricUnits) {
displayValue = resources.getString(R.string.value_integer_meter, value);
if (value == maxRecordingDistanceDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = resources.getString(R.string.value_integer_feet, feet);
if (value == maxRecordingDistanceDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, feet);
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
} else {
entries[i] = displayValue;
}
@@ -453,11 +459,16 @@ public class PreferencesUtils {
boolean metricUnits = isMetricUnits();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(0)
.build(resources);
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
String displayValue;
Distance distance = Distance.of(1).multipliedBy(value);
String displayValue = formatter.formatDistance(distance, metricUnits);
if (metricUnits) {
displayValue = resources.getString(R.string.value_integer_meter, value);
if (value == recordingGPSAccuracyDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else if (value == recordingGPSAccuracyExcellent) {
@@ -468,13 +479,10 @@ public class PreferencesUtils {
entries[i] = displayValue;
}
} else {
int feet = (int) (value * UnitConversions.M_TO_FT);
displayValue = resources.getString(R.string.value_integer_feet, feet);
if (value == recordingGPSAccuracyDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, feet);
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
} else if (value == recordingGPSAccuracyExcellent) {
entries[i] = resources.getString(R.string.value_integer_feet_excellent_gps, feet);
entries[i] = resources.getString(R.string.value_integer_feet_excellent_gps, (int) distance.toFT());
} else {
entries[i] = displayValue;
}
@@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -161,7 +162,7 @@ public class AggregatedStatisticsAdapter extends BaseAdapter {
typeLabel.setText(category);
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.getCountTracks())));
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStatistic.getTrackStatistics().getTotalDistance(), metricsUnits);
Pair<String, String> parts = DistanceFormatter.Builder().build(context).getDistanceParts(aggregatedStatistic.getTrackStatistics().getTotalDistance(), metricsUnits);
distance.setText(parts.first);
distanceUnit.setText(parts.second);
@@ -13,6 +13,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.util.StringUtils;
public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@@ -54,7 +55,7 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
} else {
sumDistance = interval.getDistance().multipliedBy(nextPosition);
}
viewHolder.distance.setText(StringUtils.formatDistance(context, sumDistance, metricUnits));
viewHolder.distance.setText(DistanceFormatter.Builder().build(context).formatDistance(sumDistance, metricUnits));
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed(), metricUnits, isReportSpeed));
@@ -20,10 +20,10 @@ import java.util.Objects;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
/**
* A fragment to display the intervals from recorded track.
@@ -106,6 +106,10 @@ public class IntervalsFragment extends Fragment {
// TODO handle empty view: before we did viewBinding.intervalList.setEmptyView(viewBinding.intervalListEmptyView);
viewBinding.intervalList.setAdapter(adapter);
final DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(0)
.build(getContext());
intervalsAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.values()) {
@NonNull
@Override
@@ -113,7 +117,7 @@ public class IntervalsFragment extends Fragment {
TextView v = (TextView) super.getView(position, convertView, parent);
IntervalStatisticsModel.IntervalOption option = getItem(position);
String stringValue = StringUtils.formatDistance(getContext(), option.getDistance(metricUnits), metricUnits, 0);
String stringValue = formatter.formatDistance(option.getDistance(metricUnits), metricUnits);
v.setText(stringValue);
return v;
}
@@ -130,13 +134,13 @@ public class IntervalsFragment extends Fragment {
//TODO This duplicates the intervalAdapter code
IntervalStatisticsModel.IntervalOption option = selectedInterval != null ? selectedInterval : IntervalStatisticsModel.IntervalOption.DEFAULT;
String stringValue = StringUtils.formatDistance(getContext(), option.getDistance(metricUnits), metricUnits, 0);
String stringValue = formatter.formatDistance(option.getDistance(metricUnits), metricUnits);
viewBinding.intervalsDropdown.setText(stringValue, false);
});
//TODO This duplicates the intervalAdapter code
IntervalStatisticsModel.IntervalOption option = selectedInterval != null ? selectedInterval : IntervalStatisticsModel.IntervalOption.DEFAULT;
String stringValue = StringUtils.formatDistance(getContext(), option.getDistance(metricUnits), metricUnits, 0);
String stringValue = formatter.formatDistance(option.getDistance(metricUnits), metricUnits);
viewBinding.intervalsDropdown.setText(stringValue, false);
}
@@ -128,27 +128,6 @@ public class StringUtils {
return TextUtils.split(value, ":").length == 2 ? "0:" + value : value;
}
/**
* Formats the distance in meters.
*
* @param context the context
* @param distance the distance
* @param metricUnits true to use metric units. False to use imperial units
*/
public static String formatDistance(Context context, Distance distance, boolean metricUnits) {
return formatDistance(context, distance, metricUnits, 2);
}
public static String formatDistance(Context context, Distance distance, boolean metricUnits, int decimalCount) {
if (distance.isInvalid()) {
return context.getString(R.string.value_unknown);
}
Pair<String, String> distanceParts = getDistanceParts(context, distance, metricUnits, decimalCount);
return context.getString(R.string.distance_with_unit, distanceParts.first, distanceParts.second);
}
public static String formatSpeed(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) {
Pair<String, String> distanceParts = getSpeedParts(context, speed, metricUnits, reportSpeed);
@@ -191,38 +170,6 @@ public class StringUtils {
return context.getString(R.string.location_latitude_longitude, Location.convert(latitude, Location.FORMAT_DEGREES), Location.convert(longitude, Location.FORMAT_DEGREES));
}
/**
* Get the formatted distance with unit.
*
* @param context the context
* @param distance the distance
* @param metricUnits true to use metric unit
* @return the formatted distance (or null) and it's unit as {@link Pair}
*/
public static Pair<String, String> getDistanceParts(Context context, Distance distance, boolean metricUnits) {
return getDistanceParts(context, distance, metricUnits, 2);
}
public static Pair<String, String> getDistanceParts(Context context, Distance distance, boolean metricUnits, int decimalCount) {
if (distance.isInvalid()) {
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
}
if (metricUnits) {
if (distance.greaterThan(Distance.of(500))) {
return new Pair<>(formatDecimal(distance.toKM(), decimalCount), context.getString(R.string.unit_kilometer));
} else {
return new Pair<>(formatDecimal(distance.toM(), decimalCount), context.getString(R.string.unit_meter));
}
} else {
if (distance.greaterThan(Distance.ofMile(0.5))) {
return new Pair<>(formatDecimal(distance.toMI(), decimalCount), context.getString(R.string.unit_mile));
} else {
return new Pair<>(formatDecimal(distance.toFT(), decimalCount), context.getString(R.string.unit_feet));
}
}
}
/**
* Gets the formatted speed with unit.
*
@@ -12,6 +12,7 @@ import java.util.stream.Collectors;
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.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
@@ -53,7 +54,7 @@ public class StatisticDataBuilder {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()), null);
title = context.getString(R.string.stats_moving_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
valueAndUnit = StringUtils.getDistanceParts(context, trackStatistics.getTotalDistance(), metricUnits);
valueAndUnit = DistanceFormatter.Builder().build(context).getDistanceParts(trackStatistics.getTotalDistance(), metricUnits);
title = context.getString(R.string.stats_distance);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
boolean reportSpeed = fieldKey.equals("speed");