diff --git a/app/res/values/pref_keys.xml b/app/res/values/pref_keys.xml
index d4b2bc169..f1160aac1 100644
--- a/app/res/values/pref_keys.xml
+++ b/app/res/values/pref_keys.xml
@@ -22,6 +22,7 @@
pref_countdown_time
pref_unit
pref_speedunit
+ pref_speedunit_opposite_sports
pref_startgps
pref_pollInterval
pref_pollDistance
diff --git a/app/res/values/strings.xml b/app/res/values/strings.xml
index e97cba835..0649455ef 100644
--- a/app/res/values/strings.xml
+++ b/app/res/values/strings.xml
@@ -18,4 +18,6 @@
+
-
3
+ Activities using opposite speed unit
+ No activity overrides. All activities use the global speed unit.
diff --git a/app/res/xml/settings_units.xml b/app/res/xml/settings_units.xml
index 0a93aa6d5..d70c38c7a 100644
--- a/app/res/xml/settings_units.xml
+++ b/app/res/xml/settings_units.xml
@@ -37,4 +37,10 @@
android:title="@string/Speed_unit_preference"
app:iconSpaceReserved="false" />
+
+
diff --git a/app/src/main/org/runnerup/export/RunnerUpLiveSynchronizer.java b/app/src/main/org/runnerup/export/RunnerUpLiveSynchronizer.java
index c77f716db..a5fab6c8d 100644
--- a/app/src/main/org/runnerup/export/RunnerUpLiveSynchronizer.java
+++ b/app/src/main/org/runnerup/export/RunnerUpLiveSynchronizer.java
@@ -198,7 +198,8 @@ public void workoutEvent(WorkoutInfo workoutInfo, int type) {
LiveService.PARAM_IN_PACE,
formatter.formatVelocityByPreferredUnit(
Formatter.Format.TXT_SHORT,
- elapsedTimeMillis == 0 ? 0 : elapsedDistanceMeter * 1000.0 / elapsedTimeMillis))
+ elapsedTimeMillis == 0 ? 0 : elapsedDistanceMeter * 1000.0 / elapsedTimeMillis,
+ workoutInfo.getSport()))
.putExtra(LiveService.PARAM_IN_USERNAME, username)
.putExtra(LiveService.PARAM_IN_PASSWORD, password)
.putExtra(LiveService.PARAM_IN_SERVERADRESS, postUrl);
diff --git a/app/src/main/org/runnerup/notification/OngoingState.java b/app/src/main/org/runnerup/notification/OngoingState.java
index 7cc9ef0ce..670e6299b 100644
--- a/app/src/main/org/runnerup/notification/OngoingState.java
+++ b/app/src/main/org/runnerup/notification/OngoingState.java
@@ -79,7 +79,7 @@ public Notification createNotification() {
Formatter.Format.TXT_LONG, Math.round(workoutInfo.getTime(Scope.ACTIVITY)));
String pace =
formatter.formatVelocityByPreferredUnit(
- Formatter.Format.TXT_SHORT, workoutInfo.getSpeed(Scope.ACTIVITY));
+ Formatter.Format.TXT_SHORT, workoutInfo.getSpeed(Scope.ACTIVITY), workoutInfo.getSport());
String content =
String.format(
@@ -88,7 +88,7 @@ public Notification createNotification() {
distance,
context.getString(org.runnerup.common.R.string.time),
time,
- context.getString(org.runnerup.common.R.string.pace),
+ formatter.formatVelocityLabel(workoutInfo.getSport()),
pace);
builder.setContentText(content);
diff --git a/app/src/main/org/runnerup/util/Formatter.java b/app/src/main/org/runnerup/util/Formatter.java
index 82487a1b1..d6f400108 100644
--- a/app/src/main/org/runnerup/util/Formatter.java
+++ b/app/src/main/org/runnerup/util/Formatter.java
@@ -33,6 +33,7 @@
import java.util.Locale;
import org.runnerup.R;
import org.runnerup.common.util.Constants;
+import org.runnerup.common.util.Constants.DB;
import org.runnerup.workout.Dimension;
import org.runnerup.workout.SpeedUnit;
@@ -217,7 +218,19 @@ private static boolean guessDefaultUnit(Resources res, Editor editor) {
* @return Configured Speed Unit (falls back to pace, if the configured value is invalid)
*/
public static SpeedUnit getPreferredSpeedUnit(Context context) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return getGlobalPreferredSpeedUnit(
+ context, PreferenceManager.getDefaultSharedPreferences(context));
+ }
+
+ public static SpeedUnit getPreferredSpeedUnit(Context context, int sport) {
+ SpeedUnit globalSpeedUnit = getPreferredSpeedUnit(context);
+ if (!usesOppositeSpeedUnit(context, sport)) {
+ return globalSpeedUnit;
+ }
+ return globalSpeedUnit == SpeedUnit.PACE ? SpeedUnit.SPEED : SpeedUnit.PACE;
+ }
+
+ private static SpeedUnit getGlobalPreferredSpeedUnit(Context context, SharedPreferences prefs) {
// use either pace or speed according to the user's preference
String speedUnit =
prefs.getString(
@@ -232,6 +245,69 @@ public static SpeedUnit getPreferredSpeedUnit(Context context) {
}
}
+ public static int[] getOppositeSpeedUnitSports(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String storedSports =
+ prefs.getString(context.getString(R.string.pref_speedunit_opposite_sports), null);
+ if (storedSports == null || storedSports.isEmpty()) {
+ return new int[0];
+ }
+ return normalizeSportIds(SafeParse.parseIntList(storedSports));
+ }
+
+ public static void setOppositeSpeedUnitSports(Context context, int[] sports) {
+ int[] normalizedSports = normalizeSportIds(sports);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ Editor editor = prefs.edit();
+ String key = context.getString(R.string.pref_speedunit_opposite_sports);
+ if (normalizedSports.length == 0) {
+ editor.remove(key).apply();
+ return;
+ }
+ editor.putString(key, SafeParse.storeIntList(normalizedSports)).apply();
+ }
+
+ private static boolean usesOppositeSpeedUnit(Context context, int sport) {
+ if (sport < 0) {
+ return false;
+ }
+ for (int oppositeSport : getOppositeSpeedUnitSports(context)) {
+ if (oppositeSport == sport) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static int[] normalizeSportIds(int[] sports) {
+ if (sports == null || sports.length == 0) {
+ return new int[0];
+ }
+
+ boolean[] includedSports = new boolean[DB.ACTIVITY.SPORT_MAX + 1];
+ for (int sport : sports) {
+ if (0 <= sport && sport <= DB.ACTIVITY.SPORT_MAX) {
+ includedSports[sport] = true;
+ }
+ }
+
+ int count = 0;
+ for (boolean includedSport : includedSports) {
+ if (includedSport) {
+ count++;
+ }
+ }
+
+ int[] normalizedSports = new int[count];
+ int index = 0;
+ for (int sport = 0; sport < includedSports.length; sport++) {
+ if (includedSports[sport]) {
+ normalizedSports[index++] = sport;
+ }
+ }
+ return normalizedSports;
+ }
+
public double getUnitMeters() {
return this.base_meters;
}
@@ -483,15 +559,14 @@ public String formatPace(Format target, double seconds_per_meter) {
* @return display value
*/
public String formatVelocityByPreferredUnit(Format target, double meters_per_second) {
- String paceTextUnit =
- this.sharedPreferences.getString(
- context.getResources().getString(R.string.pref_speedunit), SpeedUnit.PACE.getValue());
- assert paceTextUnit != null;
- if (paceTextUnit.contentEquals(SpeedUnit.PACE.getValue())) {
+ return formatVelocityByPreferredUnit(target, meters_per_second, -1);
+ }
+
+ public String formatVelocityByPreferredUnit(Format target, double meters_per_second, int sport) {
+ if (getPreferredSpeedUnit(context, sport) == SpeedUnit.PACE) {
return this.formatPaceSpeed(target, meters_per_second);
- } else {
- return this.formatSpeed(target, meters_per_second);
}
+ return this.formatSpeed(target, meters_per_second);
}
/**
@@ -500,15 +575,14 @@ public String formatVelocityByPreferredUnit(Format target, double meters_per_sec
* @return value
*/
public String formatVelocityLabel() {
- String paceTextUnit =
- this.sharedPreferences.getString(
- context.getResources().getString(R.string.pref_speedunit), SpeedUnit.PACE.getValue());
- assert paceTextUnit != null;
- if (paceTextUnit.contentEquals(SpeedUnit.PACE.getValue())) {
+ return formatVelocityLabel(-1);
+ }
+
+ public String formatVelocityLabel(int sport) {
+ if (getPreferredSpeedUnit(context, sport) == SpeedUnit.PACE) {
return this.context.getString(org.runnerup.common.R.string.Pace);
- } else {
- return this.context.getString(org.runnerup.common.R.string.Speed);
}
+ return this.context.getString(org.runnerup.common.R.string.Speed);
}
/**
@@ -538,11 +612,16 @@ public String formatPaceSpeed(Format target, double meters_per_second) {
*/
String getVelocityUnit(
Context context) { // Resources resources, SharedPreferences sharedPreferences) {
+ return getVelocityUnit(context, -1);
+ }
+
+ String getVelocityUnit(
+ Context context, int sport) { // Resources resources, SharedPreferences sharedPreferences) {
int du =
metric
? org.runnerup.common.R.string.metrics_distance_km
: org.runnerup.common.R.string.metrics_distance_mi;
- switch (getPreferredSpeedUnit(context)) {
+ switch (getPreferredSpeedUnit(context, sport)) {
case SPEED:
return resources.getString(du)
+ "/"
diff --git a/app/src/main/org/runnerup/util/GraphWrapper.java b/app/src/main/org/runnerup/util/GraphWrapper.java
index 0aa5c29a9..3203c85a5 100644
--- a/app/src/main/org/runnerup/util/GraphWrapper.java
+++ b/app/src/main/org/runnerup/util/GraphWrapper.java
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import org.runnerup.R;
import org.runnerup.common.util.Constants;
import org.runnerup.db.entities.LocationEntity;
@@ -58,6 +59,9 @@ public class GraphWrapper implements Constants {
private final Handler handler = new Handler(Looper.getMainLooper());
private final XAxis distanceXAxis;
private final XAxis timeXAxis;
+ private int sport;
+ private volatile int loadGeneration = 0;
+ private Future> pendingGraphLoad;
boolean firstLoad = true;
boolean useDistanceAsX = true;
private XAxis xAxis;
@@ -70,10 +74,12 @@ public GraphWrapper(
final Formatter formatter,
SQLiteDatabase mDB,
long mID,
+ int sport,
boolean use_distance_as_x) {
this.graphTab = graphTab;
this.hrzonesBarLayout = hrzonesBarLayout;
this.formatter = formatter;
+ this.sport = sport;
this.distanceXAxis =
new XAxis() {
@@ -121,7 +127,6 @@ public double getX(double distance, double time_ms) {
loadParam = new LoadParam(context, mDB, mID);
graphView = new GraphView(context);
- graphView.setTitle(formatter.formatVelocityLabel());
graphView
.getGridLabelRenderer()
.setLabelFormatter(
@@ -131,12 +136,12 @@ public String formatLabel(double value, boolean isValueX) {
if (isValueX) {
return xAxis.formatValue(value);
} else {
- return formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, value);
+ return formatter.formatVelocityByPreferredUnit(
+ Formatter.Format.TXT_SHORT, value, GraphWrapper.this.sport);
}
}
});
- graphView.getGridLabelRenderer().setVerticalAxisTitle(formatter.getVelocityUnit(context));
- graphView.getGridLabelRenderer().setHorizontalAxisTitle(xAxis.label());
+ updateVelocityAxisLabels();
// enable zoom
graphView.getViewport().setScalable(true);
graphView.getViewport().setScrollable(true);
@@ -158,6 +163,7 @@ public String formatLabel(double value, boolean isValueX) {
}
}
});
+ updateXAxisLabels();
graphView2.getViewport().setScalable(true);
graphView2.getViewport().setScrollable(true);
@@ -175,56 +181,114 @@ public void setUseDistanceAsX(boolean val) {
} else {
xAxis = timeXAxis;
}
+ updateXAxisLabels();
graphView.removeAllSeries();
graphView2.removeAllSeries();
loadGraph();
}
+ public void setSport(int sport) {
+ if (this.sport == sport) {
+ return;
+ }
+ this.sport = sport;
+ updateVelocityAxisLabels();
+ graphView.removeAllSeries();
+ graphView2.removeAllSeries();
+ loadGraph();
+ }
+
+ private void updateVelocityAxisLabels() {
+ graphView.setTitle(formatter.formatVelocityLabel(sport));
+ graphView
+ .getGridLabelRenderer()
+ .setVerticalAxisTitle(formatter.getVelocityUnit(graphView.getContext(), sport));
+ }
+
+ private void updateXAxisLabels() {
+ final String axisLabel = xAxis.label();
+ graphView.getGridLabelRenderer().setHorizontalAxisTitle(axisLabel);
+ graphView2.getGridLabelRenderer().setHorizontalAxisTitle(axisLabel);
+ }
+
private void loadGraph() {
- executor.execute(
+ final GraphLoadRequest request = new GraphLoadRequest(++loadGeneration, sport, xAxis);
+ if (pendingGraphLoad != null) {
+ pendingGraphLoad.cancel(true);
+ }
+ pendingGraphLoad =
+ executor.submit(
() -> {
+ if (isObsoleteRequest(request)) {
+ return;
+ }
// Background work
- GraphProducer producer = doLoadGraphInBackground(loadParam);
+ GraphProducer producer = doLoadGraphInBackground(loadParam, request);
+ if (producer == null || isObsoleteRequest(request)) {
+ return;
+ }
// Post result to UI thread
- handler.post(() -> onPostExecute(producer));
+ handler.post(() -> onPostExecute(request, producer));
});
}
- private GraphProducer doLoadGraphInBackground(LoadParam params) {
+ private GraphProducer doLoadGraphInBackground(LoadParam params, GraphLoadRequest request) {
+ if (isObsoleteRequest(request)) {
+ return null;
+ }
LocationEntity.LocationList ll =
new LocationEntity.LocationList<>(params.mDB, params.mID);
- GraphProducer graphData = new GraphProducer(params.context, ll.getCount());
- double lastDistance = 0;
- long lastTime = 0;
- int lastLap = -1;
- Double tot_distance = 0.0;
- double tot_time = 0.0;
- for (LocationEntity loc : ll) {
- Long time = loc.getElapsed();
- time = time != null ? time : lastTime;
- tot_time = time.doubleValue();
- Integer lap = loc.getLap();
- lap = lap != null ? lap : 0;
- tot_distance = tot_distance != null ? loc.getDistance() : lastDistance;
-
- double tot_X = xAxis.getX(tot_distance, time);
- if (lap != lastLap) {
- graphData.clearSmooth(tot_X);
- lastLap = lap;
- }
+ try {
+ GraphProducer graphData = new GraphProducer(params.context, ll.getCount(), request.sport());
+ double lastDistance = 0;
+ long lastTime = 0;
+ int lastLap = -1;
+ Double tot_distance = 0.0;
+ double tot_time = 0.0;
+ for (LocationEntity loc : ll) {
+ if (isObsoleteRequest(request)) {
+ return null;
+ }
- graphData.addObservation(time - lastTime, tot_distance - lastDistance, tot_X, loc);
- lastTime = time;
- lastDistance = tot_distance;
+ Long time = loc.getElapsed();
+ time = time != null ? time : lastTime;
+ tot_time = time.doubleValue();
+ Integer lap = loc.getLap();
+ lap = lap != null ? lap : 0;
+ tot_distance = tot_distance != null ? loc.getDistance() : lastDistance;
+
+ double tot_X = request.xAxis().getX(tot_distance, time);
+ if (lap != lastLap) {
+ graphData.clearSmooth(tot_X);
+ lastLap = lap;
+ }
+
+ graphData.addObservation(time - lastTime, tot_distance - lastDistance, tot_X, loc);
+ lastTime = time;
+ lastDistance = tot_distance;
+ }
+ if (isObsoleteRequest(request)) {
+ return null;
+ }
+ graphData.clearSmooth(request.xAxis().getX(tot_distance, tot_time));
+ return graphData;
+ } finally {
+ ll.close();
}
- graphData.clearSmooth(xAxis.getX(tot_distance, tot_time));
+ }
- ll.close();
- return graphData;
+ private boolean isObsoleteRequest(GraphLoadRequest request) {
+ return Thread.currentThread().isInterrupted()
+ || request.generation() != loadGeneration
+ || request.sport() != sport
+ || request.xAxis() != xAxis;
}
- private void onPostExecute(GraphProducer graphData) {
+ private void onPostExecute(GraphLoadRequest request, GraphProducer graphData) {
if (graphData == null) return;
+ if (request.generation() != loadGeneration) return;
+ if (request.sport() != sport) return;
+ if (request.xAxis() != xAxis) return;
graphData.complete(graphView);
graphTab.removeView(graphView);
@@ -302,7 +366,7 @@ class GraphProducer {
boolean showHR = false;
boolean showHRZhist = false;
- public GraphProducer(Context context, int noPoints) {
+ public GraphProducer(Context context, int noPoints, int sport) {
final int GRAPH_INTERVAL_SECONDS = 5; // 1 point every 5 sec
final int GRAPH_AVERAGE_SECONDS = 30; // moving average 30 sec
@@ -331,7 +395,7 @@ public GraphProducer(Context context, int noPoints) {
Arrays.fill(this.hrzHist, 0);
showHRZhist = true;
}
- this.preferred_speedunit = Formatter.getPreferredSpeedUnit(context);
+ this.preferred_speedunit = Formatter.getPreferredSpeedUnit(context, sport);
clearSmooth(0);
}
@@ -503,10 +567,10 @@ public void complete(final GraphView graphView) {
"%s: %s\n%s: %s %s",
graphView.getContext().getString(org.runnerup.common.R.string.Distance),
xAxis.formatValue(dataPoint.getX()),
- formatter.formatVelocityLabel(),
+ formatter.formatVelocityLabel(sport),
formatter.formatVelocityByPreferredUnit(
- Formatter.Format.TXT_SHORT, dataPoint.getY()),
- formatter.getVelocityUnit(graphView.getContext()));
+ Formatter.Format.TXT_SHORT, dataPoint.getY(), sport),
+ formatter.getVelocityUnit(graphView.getContext(), sport));
Toast.makeText(graphView.getContext(), msg, Toast.LENGTH_SHORT).show();
});
if (showHR) {
@@ -692,4 +756,6 @@ void KolmogorovZurbenko(int n, int len) {
}
record LoadParam(Context context, SQLiteDatabase mDB, long mID) {}
+
+ record GraphLoadRequest(int generation, int sport, XAxis xAxis) {}
}
diff --git a/app/src/main/org/runnerup/view/DetailActivity.java b/app/src/main/org/runnerup/view/DetailActivity.java
index f53d9a4eb..727950534 100644
--- a/app/src/main/org/runnerup/view/DetailActivity.java
+++ b/app/src/main/org/runnerup/view/DetailActivity.java
@@ -121,7 +121,7 @@ public class DetailActivity extends AppCompatActivity implements Constants {
private View graphTab;
private MapWrapper mapWrapper = null;
- private final GraphWrapper graphWrapper = null;
+ private GraphWrapper graphWrapper = null;
private SyncManager syncManager = null;
private Formatter formatter = null;
@@ -186,6 +186,7 @@ public int preSetValue(int newValue) throws IllegalArgumentException {
updateViewForSport(newValue);
ViewCompat.requestApplyInsets(rootView);
headerData.put(DB.ACTIVITY.SPORT, newValue);
+ refreshVelocityDisplays(newValue);
return newValue;
}
});
@@ -317,8 +318,16 @@ public WindowInsetsCompat onApplyWindowInsets(
LinearLayout hrzonesBarLayout = findViewById(R.id.hrzonesBarLayout);
boolean use_distance_as_x = !Sport.isWithoutGps(sport.getValueInt());
// variable not needed
- new GraphWrapper(this, graphTabLayout, hrzonesBarLayout,
- formatter, mDB, mID, use_distance_as_x);
+ graphWrapper =
+ new GraphWrapper(
+ this,
+ graphTabLayout,
+ hrzonesBarLayout,
+ formatter,
+ mDB,
+ mID,
+ sport.getValueInt(),
+ use_distance_as_x);
if (this.mode == MODE_SAVE) {
resumeButton.setOnClickListener(resumeButtonClick);
@@ -361,10 +370,36 @@ private void updateViewForSport(int sportValue) {
}
if (graphWrapper != null) {
boolean use_distance_as_x = !Sport.isWithoutGps(sportValue);
+ graphWrapper.setSport(sportValue);
graphWrapper.setUseDistanceAsX(use_distance_as_x);
}
}
+ private int getActivitySport(ContentValues data) {
+ if (data.containsKey(DB.ACTIVITY.SPORT)) {
+ return data.getAsInteger(DB.ACTIVITY.SPORT);
+ }
+ if (sport != null) {
+ return sport.getValueInt();
+ }
+ return DB.ACTIVITY.SPORT_RUNNING;
+ }
+
+ private void refreshVelocityDisplays(int sportValue) {
+ if (headerData.containsKey(DB.ACTIVITY.DISTANCE) && headerData.containsKey(DB.ACTIVITY.TIME)) {
+ double distance = headerData.getAsDouble(DB.ACTIVITY.DISTANCE);
+ long time = headerData.getAsLong(DB.ACTIVITY.TIME);
+ if (time != 0) {
+ activityPace.setText(
+ formatter.formatVelocityByPreferredUnit(
+ Formatter.Format.TXT_LONG, distance / time, sportValue));
+ }
+ }
+ for (BaseAdapter adapter : adapters) {
+ adapter.notifyDataSetChanged();
+ }
+ }
+
private void setUploadVisibility() {
boolean enabled = !pendingSynchronizers.isEmpty();
if (enabled) {
@@ -628,6 +663,7 @@ private void fillHeaderData() {
}
private void updateHeader(ContentValues data, boolean fromManualDistance) {
+ int sportValue = getActivitySport(data);
double d = 0;
if (data.containsKey(DB.ACTIVITY.DISTANCE)) {
d = data.getAsDouble(DB.ACTIVITY.DISTANCE);
@@ -661,7 +697,7 @@ private void updateHeader(ContentValues data, boolean fromManualDistance) {
activityPace.setVisibility(View.VISIBLE);
activityPaceSeparator.setVisibility(View.VISIBLE);
activityPace.setText(
- formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / t));
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / t, sportValue));
} else {
activityPace.setVisibility(View.GONE);
activityPaceSeparator.setVisibility(View.GONE);
@@ -745,7 +781,8 @@ public View getView(int position, View convertView, ViewGroup parent) {
viewHolder.tv3.setText(formatter.formatElapsedTime(Formatter.Format.TXT_SHORT, t));
if (t != 0) {
viewHolder.tv4.setText(
- formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / t));
+ formatter.formatVelocityByPreferredUnit(
+ Formatter.Format.TXT_LONG, d / t, sport.getValueInt()));
} else {
viewHolder.tv4.setText("");
}
diff --git a/app/src/main/org/runnerup/view/HistoryFragment.java b/app/src/main/org/runnerup/view/HistoryFragment.java
index cf544215a..956e452bc 100644
--- a/app/src/main/org/runnerup/view/HistoryFragment.java
+++ b/app/src/main/org/runnerup/view/HistoryFragment.java
@@ -231,7 +231,7 @@ public void bindView(View view, Context context, Cursor cursor) {
String paceTextContents = "";
if (d != null && dur != null && dur != 0) {
paceTextContents =
- formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / dur);
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / dur, s);
}
paceText.setText(paceTextContents);
}
diff --git a/app/src/main/org/runnerup/view/ManualActivity.java b/app/src/main/org/runnerup/view/ManualActivity.java
index f653f8d50..21bab460a 100644
--- a/app/src/main/org/runnerup/view/ManualActivity.java
+++ b/app/src/main/org/runnerup/view/ManualActivity.java
@@ -82,6 +82,7 @@ public void onCreate(Bundle savedInstanceState) {
ViewUtil.Insets(findViewById(R.id.tab_manual), true);
manualSport.setArrayEntries(Sport.getStringArray(getResources()));
+ manualSport.setOnSetValueListener(onSetManualSport);
}
@Override
@@ -123,6 +124,10 @@ public void onActivityResult(
}
void setManualPace(String distance, String duration) {
+ setManualPace(distance, duration, manualSport.getValueInt());
+ }
+
+ void setManualPace(String distance, String duration, int sportValue) {
Log.d(getClass().getName(), "distance: >" + distance + "< duration: >" + duration + "<");
double dist = SafeParse.parseDouble(distance, 0); // convert to meters
long seconds = SafeParse.parseSeconds(duration, 0);
@@ -131,10 +136,26 @@ void setManualPace(String distance, String duration) {
return;
}
manualPace.setValue(
- formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, dist / seconds));
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, dist / seconds, sportValue));
manualPace.setVisibility(View.VISIBLE);
}
+ final OnSetValueListener onSetManualSport =
+ new OnSetValueListener() {
+
+ @Override
+ public String preSetValue(String newValue) throws IllegalArgumentException {
+ return newValue;
+ }
+
+ @Override
+ public int preSetValue(int newValue) throws IllegalArgumentException {
+ setManualPace(
+ manualDistance.getValue().toString(), manualDuration.getValue().toString(), newValue);
+ return newValue;
+ }
+ };
+
final OnSetValueListener onSetManualDistance =
new OnSetValueListener() {
diff --git a/app/src/main/org/runnerup/view/RunActivity.java b/app/src/main/org/runnerup/view/RunActivity.java
index 00c2d12b8..98b0fa163 100644
--- a/app/src/main/org/runnerup/view/RunActivity.java
+++ b/app/src/main/org/runnerup/view/RunActivity.java
@@ -101,6 +101,7 @@ public class RunActivity extends AppCompatActivity implements TickListener {
private TextView currentHr;
private TextView activityHeaderHr;
private TextView hrDebug;
+ private TextView velocityLabel;
// A circular buffer for tap events
private final long[] mTapArray = {0, 0, 0, 0};
private int mTapIndex = 0;
@@ -123,8 +124,8 @@ public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.run);
formatter = new Formatter(this);
// HRZones hrZones = new HRZones(this);
- TextView velocity = findViewById(R.id.velocity_label);
- velocity.setText(formatter.formatVelocityLabel());
+ velocityLabel = findViewById(R.id.velocity_label);
+ velocityLabel.setText(formatter.formatVelocityLabel());
final Button stopButton = findViewById(R.id.stop_button);
stopButton.setOnClickListener(stopButtonClick);
@@ -265,6 +266,7 @@ private void onGpsTrackerBound() {
workout.onBind(workout, bindValues);
}
+ velocityLabel.setText(formatter.formatVelocityLabel(workout.getSport()));
startTimer();
populateWorkoutList();
@@ -440,20 +442,24 @@ private void updateView() {
}
setPauseButtonEnabled(!workout.isPaused());
+ int sport = workout.getSport();
+ velocityLabel.setText(formatter.formatVelocityLabel(sport));
double ad = workout.getDistance(Scope.ACTIVITY);
double at = workout.getTime(Scope.ACTIVITY);
double ap = workout.getSpeed(Scope.ACTIVITY);
activityTime.setText(formatter.formatElapsedTime(Formatter.Format.TXT_SHORT, Math.round(at)));
activityDistance.setText(
formatter.formatDistance(Formatter.Format.TXT_SHORT, Math.round(ad)));
- activityPace.setText(formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, ap));
+ activityPace.setText(
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, ap, sport));
double ld = workout.getDistance(Scope.LAP);
double lt = workout.getTime(Scope.LAP);
double lp = workout.getSpeed(Scope.LAP);
lapTime.setText(formatter.formatElapsedTime(Formatter.Format.TXT_SHORT, Math.round(lt)));
lapDistance.setText(formatter.formatDistance(Formatter.Format.TXT_LONG, Math.round(ld)));
- lapPace.setText(formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, lp));
+ lapPace.setText(
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, lp, sport));
if (tableRowInterval != null
&& this.currentStep != null
@@ -469,14 +475,15 @@ private void updateView() {
intervalDistance.setText(
formatter.formatDistance(Formatter.Format.TXT_LONG, Math.round(id)));
intervalPace.setText(
- formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, ip));
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, ip, sport));
} else {
// Do not show Interval Step row if no reason
tableRowInterval.setVisibility(View.GONE);
}
double cp = workout.getSpeed(Scope.CURRENT);
- currentPace.setText(formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, cp));
+ currentPace.setText(
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_SHORT, cp, sport));
if (mTracker.isComponentConnected(TrackerHRM.NAME)) {
double ahr = workout.getHeartRate(Scope.ACTIVITY);
diff --git a/app/src/main/org/runnerup/view/SettingsUnitsFragment.java b/app/src/main/org/runnerup/view/SettingsUnitsFragment.java
index 73f628d20..373c365d7 100644
--- a/app/src/main/org/runnerup/view/SettingsUnitsFragment.java
+++ b/app/src/main/org/runnerup/view/SettingsUnitsFragment.java
@@ -1,13 +1,133 @@
package org.runnerup.view;
+import android.content.SharedPreferences;
import android.os.Bundle;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import org.runnerup.R;
+import org.runnerup.common.util.Constants.DB;
+import org.runnerup.util.Formatter;
+import org.runnerup.workout.Sport;
-public class SettingsUnitsFragment extends PreferenceFragmentCompat {
+public class SettingsUnitsFragment extends PreferenceFragmentCompat
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private Preference oppositeSpeedUnitSportsPreference;
+ private String[] sportEntries;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.settings_units, rootKey);
+
+ sportEntries = Sport.getStringArray(getResources());
+ oppositeSpeedUnitSportsPreference =
+ findPreference(getString(R.string.pref_speedunit_opposite_sports));
+ if (oppositeSpeedUnitSportsPreference == null) {
+ return;
+ }
+
+ oppositeSpeedUnitSportsPreference.setOnPreferenceClickListener(
+ preference -> {
+ showOppositeSportDialog();
+ return true;
+ });
+ updateOppositeSportsSummary();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ updateOppositeSportsSummary();
+ }
+
+ @Override
+ public void onPause() {
+ getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ super.onPause();
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key != null && key.contentEquals(getString(R.string.pref_speedunit_opposite_sports))) {
+ updateOppositeSportsSummary();
+ }
+ }
+
+ private void showOppositeSportDialog() {
+ final boolean[] checked = createCheckedSports();
+
+ new AlertDialog.Builder(requireContext())
+ .setTitle(R.string.speed_unit_opposite_activities_title)
+ .setMultiChoiceItems(
+ sportEntries,
+ checked,
+ (dialog, which, isChecked) -> {
+ if (0 <= which && which < checked.length) {
+ checked[which] = isChecked;
+ }
+ })
+ .setPositiveButton(
+ android.R.string.ok,
+ (dialog, which) -> {
+ Formatter.setOppositeSpeedUnitSports(requireContext(), getCheckedSportIds(checked));
+ updateOppositeSportsSummary();
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ private boolean[] createCheckedSports() {
+ boolean[] checked = new boolean[Math.min(sportEntries.length, DB.ACTIVITY.SPORT_MAX + 1)];
+ for (int sportId : Formatter.getOppositeSpeedUnitSports(requireContext())) {
+ if (0 <= sportId && sportId < checked.length) {
+ checked[sportId] = true;
+ }
+ }
+ return checked;
+ }
+
+ private int[] getCheckedSportIds(boolean[] checked) {
+ int count = 0;
+ for (boolean isChecked : checked) {
+ if (isChecked) {
+ count++;
+ }
+ }
+
+ int[] selectedSports = new int[count];
+ int index = 0;
+ for (int sportId = 0; sportId < checked.length; sportId++) {
+ if (checked[sportId]) {
+ selectedSports[index++] = sportId;
+ }
+ }
+ return selectedSports;
+ }
+
+ private void updateOppositeSportsSummary() {
+ if (oppositeSpeedUnitSportsPreference == null) {
+ return;
+ }
+
+ int[] selectedSports = Formatter.getOppositeSpeedUnitSports(requireContext());
+ if (selectedSports.length == 0) {
+ oppositeSpeedUnitSportsPreference.setSummary(
+ R.string.speed_unit_opposite_activities_summary_empty);
+ return;
+ }
+
+ StringBuilder summary = new StringBuilder();
+ for (int sportId : selectedSports) {
+ if (0 > sportId || sportId >= sportEntries.length) {
+ continue;
+ }
+ if (summary.length() > 0) {
+ summary.append(", ");
+ }
+ summary.append(sportEntries[sportId]);
+ }
+ oppositeSpeedUnitSportsPreference.setSummary(summary);
}
}
diff --git a/app/src/main/org/runnerup/view/UploadActivity.java b/app/src/main/org/runnerup/view/UploadActivity.java
index e6c66179e..26b897c7f 100644
--- a/app/src/main/org/runnerup/view/UploadActivity.java
+++ b/app/src/main/org/runnerup/view/UploadActivity.java
@@ -351,6 +351,10 @@ public View getView(int arg0, View convertView, ViewGroup parent) {
}
viewHolder.activityID = getItemId(arg0);
SyncActivityItem ai = allSyncActivities.get(arg0);
+ int sport =
+ ai.getSport() == null
+ ? DB.ACTIVITY.SPORT_RUNNING
+ : Sport.valueOf(ai.getSport()).getDbValue();
Double d = ai.getDistance();
Long t = ai.getDuration();
@@ -376,7 +380,7 @@ public View getView(int arg0, View convertView, ViewGroup parent) {
if (d != null && t != null && t != 0) {
viewHolder.tvPace.setText(
- formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / t));
+ formatter.formatVelocityByPreferredUnit(Formatter.Format.TXT_LONG, d / t, sport));
} else {
viewHolder.tvPace.setText("");
}
@@ -384,7 +388,6 @@ public View getView(int arg0, View convertView, ViewGroup parent) {
if (ai.getSport() == null) {
viewHolder.tvSport.setText(Sport.textOf(getResources(), DB.ACTIVITY.SPORT_RUNNING));
} else {
- int sport = Sport.valueOf(ai.getSport()).getDbValue();
viewHolder.tvSport.setText(Sport.textOf(getResources(), sport));
}