forked from upstream-mirrors/OpenTracks
Added intervals in StatisticsRecordedFragment with distance, speed and pace for all activity types.
Users can select the length of the interval (in a spinner with several options). The intervals will be in km or mi depending on settings.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
|
||||
public class IntervalStatisticsAdapter extends ArrayAdapter<IntervalStatistics.Interval> {
|
||||
|
||||
private boolean metricUnits;
|
||||
|
||||
public IntervalStatisticsAdapter(Context context, List<IntervalStatistics.Interval> intervalList) {
|
||||
super(context, R.layout.interval_stats_list_item, intervalList);
|
||||
metricUnits = PreferencesUtils.isMetricUnits(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View intervalView, @NonNull ViewGroup parent) {
|
||||
IntervalStatistics.Interval interval = getItem(position);
|
||||
ViewHolder viewHolder;
|
||||
|
||||
if (intervalView == null) {
|
||||
viewHolder = new ViewHolder();
|
||||
|
||||
intervalView = LayoutInflater.from(getContext()).inflate(R.layout.interval_stats_list_item, parent, false);
|
||||
|
||||
viewHolder.distance = intervalView.findViewById(R.id.interval_item_distance);
|
||||
viewHolder.speed = intervalView.findViewById(R.id.interval_item_speed);
|
||||
viewHolder.pace = intervalView.findViewById(R.id.interval_item_pace);
|
||||
|
||||
intervalView.setTag(viewHolder);
|
||||
} else {
|
||||
viewHolder = (ViewHolder) intervalView.getTag();
|
||||
}
|
||||
|
||||
viewHolder.distance.setText(StringUtils.formatDistance(getContext(), interval.getDistance(), metricUnits));
|
||||
Pair<String, String> speedParts = StringUtils.getSpeedParts(getContext(), interval.getSpeed(), metricUnits, true);
|
||||
viewHolder.speed.setText(speedParts.first + " " + speedParts.second);
|
||||
viewHolder.pace.setText(StringUtils.getSpeedParts(getContext(), interval.getSpeed(), metricUnits, false).first);
|
||||
|
||||
return intervalView;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private TextView distance;
|
||||
private TextView speed;
|
||||
private TextView pace;
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,14 @@ import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
@@ -39,6 +41,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||
import de.dennisguse.opentracks.views.IntervalListView;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user.
|
||||
@@ -46,7 +50,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
* @author Sandor Dornbush
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatisticsRecordedFragment extends Fragment {
|
||||
public class StatisticsRecordedFragment extends Fragment implements IntervalListView.IntervalListListener {
|
||||
|
||||
private static final String TRACK_ID_KEY = "trackId";
|
||||
|
||||
@@ -54,6 +58,9 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
private String category = "";
|
||||
private Track track;
|
||||
|
||||
private IntervalStatisticsModel viewModel = null;
|
||||
private IntervalListView intervalListView = null;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||
if (isResumed()) {
|
||||
@@ -72,6 +79,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
/* Views */
|
||||
private ViewGroup rootView;
|
||||
private TextView totalTimeValueView;
|
||||
private TextView distanceValue;
|
||||
private TextView distanceUnit;
|
||||
@@ -110,6 +118,8 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
rootView = view.findViewById(R.id.root_view);
|
||||
|
||||
totalTimeValueView = view.findViewById(R.id.stats_total_time_value);
|
||||
|
||||
distanceValue = view.findViewById(R.id.stats_distance_value);
|
||||
@@ -153,6 +163,21 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void addIntervals() {
|
||||
intervalListView = new IntervalListView(getActivity(), this);
|
||||
intervalListView.setId(View.generateViewId());
|
||||
LinearLayout linearLayoutExtra = rootView.findViewById(R.id.linear_layout_extra);
|
||||
linearLayoutExtra.removeAllViews();
|
||||
linearLayoutExtra.addView(intervalListView);
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(IntervalStatisticsModel.class);
|
||||
viewModel.getIntervalStats(track.getId()).observe(getActivity(), intervalStatistics -> {
|
||||
if (intervalStatistics != null) {
|
||||
intervalListView.display(intervalStatistics.getIntervalList());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
@@ -269,5 +294,20 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
speedMovingValue.setText(parts.first);
|
||||
speedMovingUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set intervals.
|
||||
addIntervals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void intervalChanged(IntervalStatisticsModel.IntervalOption interval) {
|
||||
if (viewModel != null && intervalListView != null) {
|
||||
viewModel = new ViewModelProvider(this).get(IntervalStatisticsModel.class);
|
||||
viewModel.getIntervalStats(track.getId(), interval).observe(getActivity(), intervalStatistics -> {
|
||||
if (intervalStatistics != null) {
|
||||
intervalListView.display(intervalStatistics.getIntervalList());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public class UnitConversions {
|
||||
public static final double MM_TO_M = 0.001;
|
||||
|
||||
// Distance
|
||||
// multiplication factor to convert miles to meters
|
||||
public static final double MI_TO_M = 1609.344;
|
||||
// multiplication factor to convert miles to feet
|
||||
public static final double MI_TO_FT = 5280.0;
|
||||
// multiplication factor to convert feet to miles
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class IntervalStatistics {
|
||||
List<Interval> intervalList;
|
||||
|
||||
public IntervalStatistics() {
|
||||
intervalList = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void build(List<TrackPoint> trackPointList, float distanceInterval) {
|
||||
intervalList = new ArrayList<>();
|
||||
|
||||
if (trackPointList == null || trackPointList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Interval interval = new Interval();
|
||||
for (int i = 1; i < trackPointList.size(); i++) {
|
||||
TrackPoint prevTrackPoint = trackPointList.get(i - 1);
|
||||
TrackPoint trackPoint = trackPointList.get(i);
|
||||
|
||||
if (LocationUtils.isValidLocation(trackPoint.getLocation()) && LocationUtils.isValidLocation(prevTrackPoint.getLocation())) {
|
||||
interval.distance += prevTrackPoint.distanceTo(trackPoint);
|
||||
interval.time += trackPoint.getTime() - prevTrackPoint.getTime();
|
||||
|
||||
if (interval.distance >= distanceInterval) {
|
||||
intervalList.add(interval);
|
||||
interval = new Interval();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (interval.distance > 1f) {
|
||||
intervalList.add(interval);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Interval> getIntervalList() {
|
||||
return intervalList;
|
||||
}
|
||||
|
||||
public static class Interval {
|
||||
float distance = 0f;
|
||||
float time = 0f;
|
||||
|
||||
public float getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return speed of the interval in m/s.
|
||||
*/
|
||||
public float getSpeed() {
|
||||
if (distance == 0f) {
|
||||
return 0f;
|
||||
}
|
||||
return distance / (float) (time * UnitConversions.MS_TO_S);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<IntervalStatistics> intervalStats;
|
||||
private IntervalOption interval;
|
||||
|
||||
public IntervalStatisticsModel(@NonNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
public LiveData<IntervalStatistics> getIntervalStats(long trackId, IntervalOption interval) {
|
||||
if (intervalStats == null || this.interval != interval) {
|
||||
intervalStats = new MutableLiveData<>();
|
||||
this.interval = interval;
|
||||
loadIntervalStats(trackId);
|
||||
}
|
||||
return intervalStats;
|
||||
}
|
||||
|
||||
public LiveData<IntervalStatistics> getIntervalStats(long trackId) {
|
||||
return getIntervalStats(trackId, IntervalOption.OPTION_1);
|
||||
}
|
||||
|
||||
private void loadIntervalStats(long trackId) {
|
||||
new Thread(() -> {
|
||||
Context context = getApplication().getApplicationContext();
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
List<TrackPoint> trackPointList = contentProviderUtils.getTrackPoints(trackId);
|
||||
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics();
|
||||
float distanceInterval = PreferencesUtils.isMetricUnits(context) ? (float) (interval.getValue() * UnitConversions.KM_TO_M) : (float) (interval.getValue() * UnitConversions.MI_TO_M);
|
||||
intervalStatistics.build(trackPointList, distanceInterval);
|
||||
|
||||
intervalStats.postValue(intervalStatistics);
|
||||
}).start();
|
||||
}
|
||||
|
||||
public enum IntervalOption {
|
||||
OPTION_1(1),
|
||||
OPTION_2(2),
|
||||
OPTION_3(3),
|
||||
OPTION_4(4),
|
||||
OPTION_5(5),
|
||||
OPTION_10(10);
|
||||
|
||||
private int value;
|
||||
|
||||
IntervalOption(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos position of the interval option.
|
||||
* @return the interval option that is in the position pos.
|
||||
*/
|
||||
public static IntervalOption getIntervalOption(int pos) {
|
||||
if (values().length > pos) {
|
||||
return values()[pos];
|
||||
} else {
|
||||
return OPTION_1;
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string array with all options.
|
||||
*/
|
||||
public static String[] getAllValues() {
|
||||
IntervalOption[] options = values();
|
||||
String[] values = new String[options.length];
|
||||
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
values[i] = String.valueOf(options[i].getValue());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package de.dennisguse.opentracks.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||
|
||||
public class IntervalListView extends LinearLayout {
|
||||
|
||||
private IntervalStatisticsAdapter adapter;
|
||||
private LinearLayout linearLayoutIntervals;
|
||||
private Spinner spinnerIntervals;
|
||||
private TextView spinnerIntervalsUnit;
|
||||
|
||||
private Context context;
|
||||
private IntervalListListener listener;
|
||||
|
||||
public IntervalListView(Context context, IntervalListListener listener) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.listener = listener;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
inflate(getContext(), R.layout.interval_list_view, this);
|
||||
linearLayoutIntervals = findViewById(R.id.interval_list);
|
||||
spinnerIntervals = findViewById(R.id.spinner_intervals);
|
||||
spinnerIntervals.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, IntervalStatisticsModel.IntervalOption.getAllValues()));
|
||||
spinnerIntervalsUnit = findViewById(R.id.spinner_intervals_unit);
|
||||
spinnerIntervalsUnit.setText(PreferencesUtils.isMetricUnits(context) ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile));
|
||||
|
||||
spinnerIntervals.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
IntervalStatisticsModel.IntervalOption interval = IntervalStatisticsModel.IntervalOption.getIntervalOption(i);
|
||||
listener.intervalChanged(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void display(List<IntervalStatistics.Interval> intervalList) {
|
||||
if (intervalList != null) {
|
||||
adapter = new IntervalStatisticsAdapter(getContext(), intervalList);
|
||||
linearLayoutIntervals.removeAllViews();
|
||||
for (int i = 0; i < adapter.getCount(); i++) {
|
||||
View intervalView = adapter.getView(i, null, linearLayoutIntervals);
|
||||
linearLayoutIntervals.addView(intervalView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IntervalListListener {
|
||||
void intervalChanged(IntervalStatisticsModel.IntervalOption interval);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_intervals" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/stats_split_interval"
|
||||
style="@style/TextMedium" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_intervals"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/SpinnerCustom"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spinner_intervals_unit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="@string/unit_kilometer"
|
||||
style="@style/TextMedium" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interval_distance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextMedium"
|
||||
android:layout_margin="1dp"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/stats_distance" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interval_speed"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextMedium"
|
||||
android:layout_margin="1dp"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/stats_speed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interval_pace"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextMedium"
|
||||
android:layout_margin="1dp"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/stats_pace" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/interval_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interval_item_distance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextMedium"
|
||||
tools:text="1 km" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interval_item_speed"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextMedium"
|
||||
tools:text="15 km/h" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/interval_item_pace"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextMedium"
|
||||
tools:text="4:00" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -219,5 +219,15 @@
|
||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stats_max_speed_value" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_layout_extra"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stats_speed_horizontal_end_line"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
@@ -417,6 +417,8 @@ limitations under the License.
|
||||
<string name="stats_speed">Speed</string>
|
||||
<string name="stats_distance">Distance</string>
|
||||
<string name="stats_total_time">Total time</string>
|
||||
<string name="stats_intervals">Intervals</string>
|
||||
<string name="stats_split_interval">Split interval</string>
|
||||
<!-- Track -->
|
||||
<string name="track_name_format">Track %1$d</string>
|
||||
<string name="track_record_notification">Recording your track…</string>
|
||||
|
||||
Reference in New Issue
Block a user