forked from upstream-mirrors/OpenTracks
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1672a5c722 | |||
| bf552a9219 | |||
| 348f697b56 | |||
| 887d4700d4 | |||
| 391dfd30e2 | |||
| b416857001 | |||
| df3720ef0d | |||
| b784a47af6 | |||
| 0f3156e71b | |||
| 0035c85922 | |||
| 976a50ca15 | |||
| c0ad53eef1 | |||
| 1192b02c0a | |||
| 7a3abe9962 | |||
| 60b989c13a | |||
| 340f16dc99 | |||
| 7cf29f7cea | |||
| e39352cc9e | |||
| e2964e805e | |||
| 8cab43d558 | |||
| 13f2a09652 | |||
| 7d4072c0d8 | |||
| 310b35713d | |||
| 7e6237de3a | |||
| 772dad4837 | |||
| 99126fa357 | |||
| b2c91d7592 | |||
| 3c5a1be16b |
+2
-2
@@ -51,8 +51,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 3981
|
||||
versionName "v3.17.0"
|
||||
versionCode 4009
|
||||
versionName "v3.17.3"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.17.1: OpenTracks
|
||||
|
||||
|
||||
Bugfix:
|
||||
- Interval length in meters instead of kilometers (introduced in v3.17.0)
|
||||
- Elevation gain/loss not recorded (introduced in v3.17.0)
|
||||
- Announcements not working on Android 11
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.17.2: OpenTracks
|
||||
|
||||
WARNING: The v3.17.0+ releases contain a memory leak.
|
||||
This issues is NOT FIXED by this release.
|
||||
It is recommended to not upgrade if you still use a release before v3.17.0
|
||||
|
||||
Bugfix:
|
||||
- Crash while using voice announcements
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.17.3: OpenTracks
|
||||
|
||||
WARNING: The v3.17.0+ releases contain a memory leak.
|
||||
This issues is NOT FIXED by this release.
|
||||
It is recommended to not upgrade if you still use a release before v3.17.0
|
||||
|
||||
Bugfix:
|
||||
- Crash if using pace for voice announcements (introduced in v3.17.0)
|
||||
- Statistics showed pace initially (introduced in v3.17.0)
|
||||
- Rare crash when deleting tracks
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package de.dennisguse.opentracks.chart;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.fragments.TrackStubUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ChartPointTest {
|
||||
|
||||
@Test
|
||||
public void create_by_time() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, false, false);
|
||||
|
||||
// then
|
||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_by_distance() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
statistics.setTotalDistance(Distance.of(1000));
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, true, true);
|
||||
|
||||
// then
|
||||
assertEquals(1, (long) point.getTimeOrDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_get_altitude_speed_and_pace() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
||||
|
||||
// then
|
||||
assertEquals(0.05, point.getAltitude(), 0.01);
|
||||
assertEquals(36, point.getSpeed(), 0.01);
|
||||
assertEquals(1.66, point.getPace(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_sensorNotAvailable() {
|
||||
// given
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
||||
|
||||
// then
|
||||
assertNull(point.getHeartRate());
|
||||
assertNull(point.getCadence());
|
||||
assertNull(point.getPower());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create_sensorAvailable() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setHeartRate_bpm(100f);
|
||||
trackPoint.setCyclingCadence_rpm(101f);
|
||||
trackPoint.setPower(102f);
|
||||
|
||||
TrackStatistics statistics = new TrackStatistics();
|
||||
|
||||
// when
|
||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), 50, false, true);
|
||||
|
||||
// then
|
||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
assertEquals(101.0, point.getCadence(), 0.01);
|
||||
assertEquals(102.0, point.getPower(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class ChartValueSeriesTest {
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
Integer.MIN_VALUE,
|
||||
Integer.MAX_VALUE,
|
||||
new int[]{100, 1000},
|
||||
new int[]{1, 100, 1000},
|
||||
R.string.description_altitude_metric,
|
||||
R.string.description_altitude_imperial,
|
||||
R.color.chart_altitude_fill,
|
||||
@@ -52,7 +52,7 @@ public class ChartValueSeriesTest {
|
||||
15,
|
||||
18) {
|
||||
@Override
|
||||
double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getAltitude();
|
||||
}
|
||||
|
||||
@@ -77,6 +77,17 @@ public class ChartValueSeriesTest {
|
||||
assertFalse(series.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerySmallUpdates() {
|
||||
series.update(new ChartPoint(1f));
|
||||
series.update(new ChartPoint(2f));
|
||||
series.update(new ChartPoint(3f));
|
||||
series.updateDimension();
|
||||
assertEquals(1, series.getInterval());
|
||||
assertEquals(1, series.getMinMarkerValue());
|
||||
assertEquals(6, series.getMaxMarkerValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallUpdates() {
|
||||
series.update(new ChartPoint(0));
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -28,23 +27,15 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests {@link ChartFragment}.
|
||||
*
|
||||
* @author Youtao Liu
|
||||
*/
|
||||
//TODO Add tests that check ChartFragment
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ChartFragmentTest {
|
||||
|
||||
@@ -71,261 +62,8 @@ public class ChartFragmentTest {
|
||||
chartFragment.setRecordingDistanceInterval(Distance.of(50));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the incorrect values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_sensorIncorrect() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(Float.NaN, point.getHeartRate(), 0.01);
|
||||
assertEquals(Float.NaN, point.getCadence(), 0.01);
|
||||
assertEquals(Float.NaN, point.getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the correct values of sensor in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_sensorCorrect() {
|
||||
// given
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setHeartRate_bpm(100f);
|
||||
trackPoint.setCyclingCadence_rpm(101f);
|
||||
trackPoint.setPower(102f);
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
|
||||
// then
|
||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||
assertEquals(101.0, point.getCadence(), 0.01);
|
||||
assertEquals(102.0, point.getPower(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of metric Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_distanceMetric() {
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Resets last location and writes first location.
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The second is a same location, just different time.
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The third location is a new location, and use metric.
|
||||
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint3.setLatitude(23);
|
||||
point = chartFragment.createPendingPoint(trackPoint3);
|
||||
|
||||
// Computes the distance between Latitude 22 and 23.
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(),
|
||||
trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM;
|
||||
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The fourth location is a new location, and use metric.
|
||||
TrackPoint trackPoint4 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint4.setLatitude(24);
|
||||
point = chartFragment.createPendingPoint(trackPoint4);
|
||||
|
||||
// Computes the distance between Latitude 23 and 24.
|
||||
Location.distanceBetween(trackPoint3.getLatitude(), trackPoint3.getLongitude(),
|
||||
trackPoint4.getLatitude(), trackPoint4.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM;
|
||||
assertEquals((distance1 + distance2), point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of imperial Distance in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_distanceImperial() {
|
||||
// By distance.
|
||||
chartFragment.setChartByDistance(true);
|
||||
// Setups to use imperial.
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// The first is a same location, just different time.
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The second location is a new location, and use imperial.
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setLatitude(23);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 22 and 23.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
float[] results = new float[4];
|
||||
Location.distanceBetween(trackPoint1.getLatitude(), trackPoint1.getLongitude(), trackPoint2.getLatitude(), trackPoint2.getLongitude(), results);
|
||||
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1, point.getTimeOrDistance(), 0.01);
|
||||
|
||||
// The third location is a new location, and use imperial.
|
||||
TrackPoint trackPoint3 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint3.setLatitude(24);
|
||||
point = chartFragment.createPendingPoint(trackPoint3);
|
||||
|
||||
/*
|
||||
* Computes the distance between Latitude 23 and 24.
|
||||
* And for we set using * imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
|
||||
*/
|
||||
Location.distanceBetween(trackPoint2.getLatitude(), trackPoint2.getLongitude(), trackPoint3.getLatitude(), trackPoint3.getLongitude(), results);
|
||||
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
|
||||
assertEquals(distance1 + distance2, point.getTimeOrDistance(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the values of time in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_time() {
|
||||
// given
|
||||
chartFragment.setChartByDistance(false);
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setTime(Instant.ofEpochMilli(TrackStubUtils.INITIAL_TIME)); //Keep old TrackPoint behavior of having time=0 for this test
|
||||
|
||||
// when
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
|
||||
// then
|
||||
assertEquals(0.0, point.getTimeOrDistance(), 0.01);
|
||||
Duration timeSpan = Duration.ofMillis(222);
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setTime(Instant.ofEpochMilli(TrackStubUtils.INITIAL_TIME).plus(timeSpan));
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(timeSpan, Duration.ofMillis((long) point.getTimeOrDistance()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of altitude in {@link ChartFragment#createPendingPoint(TrackPoint)} by one and two points.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_altitude() {
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* At first, clear old points of altitude, so give true to the second parameter.
|
||||
* Then only one value INITIAL_ALTITUDE in buffer.
|
||||
*/
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point.getAltitude(), 0.01);
|
||||
|
||||
/*
|
||||
* Send another value to buffer, now there are two values, INITIAL_ALTITUDE and INITIAL_ALTITUDE * 2.
|
||||
*/
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point.getAltitude(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get the value of speed in {@link ChartFragment#createPendingPoint(TrackPoint)}.
|
||||
* In this test, firstly remove all points in memory, and then fill in two points one by one.
|
||||
* The speed values of these points are 129, 130.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_speed() {
|
||||
/*
|
||||
* At first, clear old points of speed, so give true to the second parameter.
|
||||
* It will not be filled in to the speed buffer.
|
||||
*/
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(Speed.of(128.5f));
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
/*
|
||||
* Tests the logic when both metricUnits and reportSpeed are true.
|
||||
* This location will be filled into speed buffer.
|
||||
*/
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter.
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint1.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(Speed.of(130f));
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to compute speed when use Imperial.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_speedImperial() {
|
||||
chartFragment.setMetricUnits(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(Speed.of(100.0f));
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint2.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(Speed.of(102f));
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_pace_nonZeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
|
||||
// First data point is not added to the speed buffer
|
||||
TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint1.setSpeed(Speed.of(100.0f));
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint1);
|
||||
assertEquals(0.0, point.getSpeed(), 0.01);
|
||||
|
||||
TrackPoint trackPoint2 = TrackStubUtils.createDefaultTrackPoint();
|
||||
|
||||
/*
|
||||
* Add a time span here to make sure the second point and the speed is valid.
|
||||
* Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222
|
||||
*/
|
||||
trackPoint2.setTime(trackPoint2.getTime().plusMillis(222));
|
||||
trackPoint2.setSpeed(Speed.of(102f));
|
||||
point = chartFragment.createPendingPoint(trackPoint2);
|
||||
assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the logic to get pace value when reportSpeed is false and average speed is zero.
|
||||
*/
|
||||
@Test
|
||||
public void testCreatePendingPoint_pace_zeroSpeed() {
|
||||
chartFragment.setReportSpeed(false);
|
||||
TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint();
|
||||
trackPoint.setSpeed(Speed.of(0f));
|
||||
ChartPoint point = chartFragment.createPendingPoint(trackPoint);
|
||||
assertEquals(0.0, point.getPace(), 0.01);
|
||||
public void nothing() {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class AnnouncementUtilsTest {
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric() {
|
||||
public void getAnnouncement_metric_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
@@ -43,7 +43,7 @@ public class AnnouncementUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_metric() {
|
||||
public void getAnnouncement_withInterval_metric_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
@@ -61,4 +61,112 @@ public class AnnouncementUtilsTest {
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 18.4 kilometers per hour Lap speed of 51.2 kilometers per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_metric_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_metric_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, true, false, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 20.00 kilometers in 1 hour 5 minutes 10 seconds at 3 minutes 15 seconds per kilometer Lap time of 1 minute 10 seconds per kilometer", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_imperial_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_imperial_speed() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, true, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 11.4 miles per hour Lap speed of 31.8 miles per hour", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_imperial_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, null);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile", announcement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnouncement_withInterval_imperial_pace() {
|
||||
TrackStatistics stats = new TrackStatistics();
|
||||
stats.setTotalDistance(Distance.of(20000));
|
||||
stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10));
|
||||
stats.setMaxSpeed(Speed.of(100));
|
||||
stats.setTotalAltitudeGain(6000f);
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
|
||||
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, Distance.of(1000));
|
||||
IntervalStatistics.Interval lastInterval = intervalStatistics.getIntervalList().get(intervalStatistics.getIntervalList().size() - 1);
|
||||
|
||||
// when
|
||||
String announcement = AnnouncementUtils.getAnnouncement(context, stats, false, false, lastInterval);
|
||||
|
||||
// then
|
||||
assertEquals("OpenTracks total distance 12.43 miles in 1 hour 5 minutes 10 seconds at 5 minutes 15 seconds per mile Lap time of 1 minute 53 seconds per mile", announcement);
|
||||
}
|
||||
}
|
||||
@@ -351,4 +351,10 @@ limitations under the License.
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TTS_SERVICE" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -32,9 +31,4 @@ public class AboutActivity extends AbstractActivity {
|
||||
viewBinding = AboutBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
/**
|
||||
* An abstract class for the following common tasks across
|
||||
* {@link TrackListActivity}, {@link TrackRecordedActivity}, and {@link TrackRecordedActivity}:
|
||||
* {@link TrackListActivity} and {@link TrackRecordedActivity}:
|
||||
* <p>
|
||||
* - share track <br>
|
||||
* - delete tracks <br>
|
||||
|
||||
@@ -64,26 +64,29 @@ public class TrackDeleteActivity extends AbstractActivity {
|
||||
|
||||
runOnUiThread(TrackDeleteActivity.this::onAsyncTaskCompleted);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
deleteThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
deleteThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (deleteThread.getState() == Thread.State.TERMINATED) {
|
||||
onAsyncTaskCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
deleteThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackDeleteBinding.inflate(getLayoutInflater());
|
||||
|
||||
@@ -53,6 +53,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO Should not use TrackRecordingServiceConnection; only used to determine if there is NO current recording, to enable resume functionality.
|
||||
public class TrackRecordedActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackActivityDataHubInterface, TrackRecordingServiceStatus.Listener {
|
||||
|
||||
private static final String TAG = TrackRecordedActivity.class.getSimpleName();
|
||||
@@ -299,7 +300,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Confi
|
||||
}
|
||||
}
|
||||
|
||||
public void startPostponedEnterTransitionWith(View viewIcon, View viewName) {
|
||||
public void startPostponedEnterTransitionWith(View viewIcon) {
|
||||
ViewCompat.setTransitionName(viewIcon, TrackRecordedActivity.VIEW_TRACK_ICON);
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
// A recording track is on.
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackDataHub.setRecordingTrackId(trackId);
|
||||
|
||||
trackController.update(true, false);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
}
|
||||
@@ -209,6 +211,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
if (trackId != null) {
|
||||
trackDataHub.loadTrack(trackId);
|
||||
trackDataHub.setRecordingTrackId(trackId);
|
||||
trackDataHub.setRecordingTrackPaused(recordingTrackPaused);
|
||||
trackController.onResume(true, recordingTrackPaused);
|
||||
}
|
||||
|
||||
@@ -404,17 +408,20 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
public void onTrackRecordingPaused(boolean isPaused) {
|
||||
if (recordingTrackPaused != isPaused) {
|
||||
trackController.update(true, isPaused);
|
||||
trackDataHub.setRecordingTrackPaused(isPaused);
|
||||
}
|
||||
recordingTrackPaused = isPaused;
|
||||
setLockscreenPolicy();
|
||||
setScreenOnPolicy();
|
||||
}
|
||||
|
||||
//TODO Why should this be interesting? This activity is only used for currently recording tracks.
|
||||
@Override
|
||||
public void onTrackRecordingId(Track.Id newTrackId) {
|
||||
if (newTrackId != null && !newTrackId.equals(trackId)) {
|
||||
trackId = newTrackId;
|
||||
trackController.update(true, recordingTrackPaused);
|
||||
trackDataHub.setRecordingTrackId(newTrackId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ package de.dennisguse.opentracks.chart;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class ChartPoint {
|
||||
@@ -13,44 +14,38 @@ public class ChartPoint {
|
||||
private double timeOrDistance;
|
||||
|
||||
//Y-axis
|
||||
private double altitude;
|
||||
private double speed;
|
||||
private double pace;
|
||||
private double heartRate = Double.NaN;
|
||||
private double cadence = Double.NaN;
|
||||
private double power = Double.NaN;
|
||||
private final double altitude;
|
||||
private Double speed;
|
||||
private Double pace;
|
||||
private Double heartRate;
|
||||
private Double cadence;
|
||||
private Double power;
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
ChartPoint(double altitude) {
|
||||
this.altitude = altitude;
|
||||
}
|
||||
|
||||
public ChartPoint(@NonNull TrackStatisticsUpdater trackStatisticsUpdater, TrackPoint trackPoint, boolean chartByDistance, boolean metricUnits) {
|
||||
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, double smoothedAltitude_m, boolean chartByDistance, boolean metricUnits) {
|
||||
if (chartByDistance) {
|
||||
timeOrDistance = trackStatistics.getTotalDistance().to(metricUnits);
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||
}
|
||||
|
||||
altitude = trackStatisticsUpdater.getSmoothedAltitude();
|
||||
if (!metricUnits) {
|
||||
altitude *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
altitude = Distance.of(smoothedAltitude_m).to(metricUnits);
|
||||
|
||||
speed = trackStatisticsUpdater.getSmoothedSpeed().to(metricUnits);
|
||||
pace = trackStatisticsUpdater.getSmoothedSpeed().toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||
if (trackPoint != null) {
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = trackPoint.getHeartRate_bpm();
|
||||
}
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
cadence = trackPoint.getCyclingCadence_rpm();
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
power = trackPoint.getPower();
|
||||
}
|
||||
speed = smoothedSpeed.to(metricUnits);
|
||||
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = (double) trackPoint.getHeartRate_bpm();
|
||||
}
|
||||
if (trackPoint.hasCyclingCadence()) {
|
||||
cadence = (double) trackPoint.getCyclingCadence_rpm();
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
power = (double) trackPoint.getPower();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,39 +53,27 @@ public class ChartPoint {
|
||||
return timeOrDistance;
|
||||
}
|
||||
|
||||
public double getAltitude() {
|
||||
public Double getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
public double getSpeed() {
|
||||
public Double getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public double getPace() {
|
||||
public Double getPace() {
|
||||
return pace;
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return Double.isNaN(heartRate);
|
||||
}
|
||||
|
||||
public double getHeartRate() {
|
||||
public Double getHeartRate() {
|
||||
return heartRate;
|
||||
}
|
||||
|
||||
public boolean hasCadence() {
|
||||
return Double.isNaN(cadence);
|
||||
}
|
||||
|
||||
public double getCadence() {
|
||||
public Double getCadence() {
|
||||
return cadence;
|
||||
}
|
||||
|
||||
public boolean hasPower() {
|
||||
return Double.isNaN(power);
|
||||
}
|
||||
|
||||
public double getPower() {
|
||||
public Double getPower() {
|
||||
return power;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,10 +132,10 @@ abstract class ChartValueSeries {
|
||||
}
|
||||
}
|
||||
|
||||
abstract double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
|
||||
abstract Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint);
|
||||
|
||||
boolean isChartPointValid(@NonNull ChartPoint chartPoint) {
|
||||
return !Double.isNaN(extractDataFromChartPoint(chartPoint));
|
||||
return extractDataFromChartPoint(chartPoint) != null;
|
||||
}
|
||||
|
||||
protected abstract boolean drawIfChartPointHasNoData();
|
||||
|
||||
@@ -206,7 +206,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getAltitude();
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getSpeed();
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getPace();
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getHeartRate();
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getCadence();
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ public class ChartView extends View {
|
||||
fontSizeSmall,
|
||||
fontSizeMedium) {
|
||||
@Override
|
||||
protected double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
protected Double extractDataFromChartPoint(@NonNull ChartPoint chartPoint) {
|
||||
return chartPoint.getPower();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -38,9 +39,7 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
|
||||
/**
|
||||
* Track data hub.
|
||||
@@ -50,7 +49,8 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
//TODO register contentobserver only for exact URL (incl. trackId) to not filter here.
|
||||
public class TrackDataHub {
|
||||
|
||||
/**
|
||||
* Target number of track points displayed by the diagrams (recommended).
|
||||
@@ -75,11 +75,12 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
private HandlerThread handlerThread;
|
||||
private Handler handler;
|
||||
|
||||
private TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
|
||||
// Preference values
|
||||
private Track.Id selectedTrackId;
|
||||
@Deprecated
|
||||
//TODO Is there a better way to handle this? e.g., passing information rather from Activity than from Activity to TrackDataHub
|
||||
private Track.Id recordingTrackId;
|
||||
@Deprecated
|
||||
//TODO Is there a better way to handle this? e.g., passing information rather from Activity than from Activity to TrackDataHub
|
||||
private boolean recordingTrackPaused;
|
||||
|
||||
// Track points sampling state
|
||||
@@ -92,13 +93,6 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
private ContentObserver markersTableObserver;
|
||||
private ContentObserver trackPointsTableObserver;
|
||||
|
||||
private final Runnable bindCallback = () -> {
|
||||
TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
|
||||
if (service != null) {
|
||||
service.addListener(TrackDataHub.this);
|
||||
}
|
||||
};
|
||||
|
||||
public TrackDataHub(Context context) {
|
||||
this(context, new TrackDataManager(), new ContentProviderUtils(context), TARGET_DISPLAYED_TRACKPOINTS);
|
||||
}
|
||||
@@ -148,9 +142,6 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
};
|
||||
contentResolver.registerContentObserver(TrackPointsColumns.CONTENT_URI_BY_ID, false, trackPointsTableObserver);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
|
||||
trackRecordingServiceConnection.bind(context);
|
||||
|
||||
handler.post(() -> {
|
||||
if (started) {
|
||||
loadDataForAll();
|
||||
@@ -177,8 +168,6 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
handlerThread = null;
|
||||
}
|
||||
handler = null;
|
||||
|
||||
trackRecordingServiceConnection.unbind(context);
|
||||
}
|
||||
|
||||
public void loadTrack(final @NonNull Track.Id trackId) {
|
||||
@@ -362,6 +351,8 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
next = new TrackPoint.Id(localLastSeenTrackPointIdId.getId() + 1); //TODO startTrackPointId + 1 is an assumption assumption; should be derived from the DB.
|
||||
}
|
||||
|
||||
TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
|
||||
TrackPoint trackPoint = null;
|
||||
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(selectedTrackId, next)) {
|
||||
|
||||
@@ -383,15 +374,16 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
|
||||
}
|
||||
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, Distance.of(50)); //TODO
|
||||
|
||||
// Also include the last point if the selected track is not recording.
|
||||
if ((localNumLoadedTrackPoints % samplingFrequency == 0) || (trackPointId == lastTrackPointId && !isSelectedTrackRecording())) {
|
||||
for (TrackDataListener trackDataListener : sampledInListeners) {
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint);
|
||||
trackDataListener.onSampledInTrackPoint(trackPoint, trackStatisticsUpdater.getTrackStatistics(), trackStatisticsUpdater.getSmoothedSpeed(), trackStatisticsUpdater.getSmoothedAltitude());
|
||||
}
|
||||
} else {
|
||||
for (TrackDataListener trackDataListener : sampledOutListeners) {
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint);
|
||||
trackDataListener.onSampledOutTrackPoint(trackPoint, trackStatisticsUpdater.getTrackStatistics());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +403,7 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
|
||||
if (trackPoint != null) {
|
||||
for (TrackDataListener listener : sampledInListeners) {
|
||||
listener.onNewTrackPointsDone(trackPoint);
|
||||
listener.onNewTrackPointsDone(trackPoint, trackStatisticsUpdater.getTrackStatistics());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,13 +417,11 @@ public class TrackDataHub implements TrackRecordingServiceStatus.Listener {
|
||||
lastSeenTrackPointId = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackRecordingPaused(boolean isPaused) {
|
||||
recordingTrackPaused = isPaused;
|
||||
public void setRecordingTrackId(Track.Id recordingTrackId) {
|
||||
this.recordingTrackId = recordingTrackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackRecordingId(Track.Id trackId) {
|
||||
recordingTrackId = trackId;
|
||||
public void setRecordingTrackPaused(boolean recordingTrackPaused) {
|
||||
this.recordingTrackPaused = recordingTrackPaused;
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ package de.dennisguse.opentracks.content;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Listener for track data changes.
|
||||
@@ -47,20 +49,23 @@ public interface TrackDataListener {
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledInTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, double smoothedAltitude_m) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a sampled out track point is read.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
*/
|
||||
void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint);
|
||||
default void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when finish sending new track points.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint)} and {@link #onSampledOutTrackPoint(TrackPoint)}.
|
||||
* This gets called after every batch of calls to {@link #onSampledInTrackPoint(TrackPoint, TrackStatistics, Speed, double)} and {@link #onSampledOutTrackPoint(TrackPoint, TrackStatistics)}.
|
||||
*/
|
||||
void onNewTrackPointsDone(@NonNull TrackPoint lastTrackPoint);
|
||||
default void onNewTrackPointsDone(@NonNull TrackPoint lastTrackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to clear previously sent markers.
|
||||
|
||||
@@ -41,10 +41,10 @@ class TrackDataManager {
|
||||
}
|
||||
|
||||
void unregisterTrackDataListener(TrackDataListener trackDataListener) {
|
||||
dataListenerTracks.add(trackDataListener);
|
||||
dataListenerMarkers.add(trackDataListener);
|
||||
dataListenerTrackPoints_SampledIn.add(trackDataListener);
|
||||
dataListenerTrackPoints_SampledOut.add(trackDataListener);
|
||||
dataListenerTracks.remove(trackDataListener);
|
||||
dataListenerMarkers.remove(trackDataListener);
|
||||
dataListenerTrackPoints_SampledIn.remove(trackDataListener);
|
||||
dataListenerTrackPoints_SampledOut.remove(trackDataListener);
|
||||
}
|
||||
|
||||
boolean hasListeners() {
|
||||
|
||||
@@ -20,9 +20,13 @@ public class Distance {
|
||||
return of(distance_mile * UnitConversions.MI_TO_M);
|
||||
}
|
||||
|
||||
public static Distance ofKilometer(double distance_km) {
|
||||
return of(distance_km * UnitConversions.KM_TO_M);
|
||||
}
|
||||
|
||||
public static Distance one(boolean metricUnit) {
|
||||
if (metricUnit) {
|
||||
return Distance.of(1);
|
||||
return Distance.ofKilometer(1);
|
||||
} else {
|
||||
return Distance.ofMile(1);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||
@@ -72,7 +73,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
// Stats gathered from the received data
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
private final List<ChartPoint> pendingPoints = new ArrayList<>();
|
||||
private String category = "";
|
||||
|
||||
@@ -217,7 +217,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
@Override
|
||||
public void clearTrackPoints() {
|
||||
if (isResumed()) {
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater();
|
||||
pendingPoints.clear();
|
||||
chartView.reset();
|
||||
runOnUiThread(() -> {
|
||||
@@ -228,24 +227,15 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, double smoothedAltitude_m) {
|
||||
if (isResumed()) {
|
||||
pendingPoints.add(createPendingPoint(trackPoint));
|
||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude_m, chartByDistance, chartView.getMetricUnits());
|
||||
pendingPoints.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
if (isResumed()) {
|
||||
if (trackStatisticsUpdater != null) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused) {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused, @NonNull TrackStatistics alsoUnused) {
|
||||
if (isResumed()) {
|
||||
chartView.addChartPoints(pendingPoints);
|
||||
pendingPoints.clear();
|
||||
@@ -306,6 +296,8 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
* Returns true if the selected track is recording.
|
||||
* Needs to be synchronized because trackDataHub can be accessed by multiple threads.
|
||||
*/
|
||||
@Deprecated
|
||||
//TODO Should not be dynamic but instead set while instantiating, i.e., newFragment().
|
||||
private synchronized boolean isSelectedTrackRecording() {
|
||||
return trackDataHub != null && trackDataHub.isSelectedTrackRecording();
|
||||
}
|
||||
@@ -322,12 +314,6 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ChartPoint createPendingPoint(@NonNull TrackPoint trackPoint) {
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
return new ChartPoint(trackStatisticsUpdater, trackPoint, chartByDistance, chartView.getMetricUnits());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setChartView(ChartView view) {
|
||||
chartView = view;
|
||||
|
||||
@@ -25,9 +25,11 @@ import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.IntervalListViewBinding;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||
@@ -229,21 +231,21 @@ public class IntervalsFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused, Speed unused2, double unused3) {
|
||||
if (isResumed()) {
|
||||
viewModel.add(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics unused) {
|
||||
if (isResumed()) {
|
||||
viewModel.add(trackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused) {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint unused, @NonNull TrackStatistics alsoUnused) {
|
||||
if (isResumed()) {
|
||||
runOnUiThread(viewModel::onNewTrackPoints);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -54,6 +55,8 @@ import de.dennisguse.opentracks.viewmodels.SensorDataModel;
|
||||
*/
|
||||
public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
private static final String TAG = StatisticsRecordedFragment.class.getSimpleName();
|
||||
|
||||
private static final String TRACK_ID_KEY = "trackId";
|
||||
|
||||
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
|
||||
@@ -154,19 +157,25 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
if ((this.track == null && track != null) || (this.track != null && track != null && !this.track.getCategory().equals(track.getCategory()))) {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
||||
if (track == null) {
|
||||
Log.e(TAG, "track cannot be null");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
sensorStatistics = contentProviderUtils.getSensorStats(trackId);
|
||||
|
||||
boolean prefsChanged = this.track == null || (!this.track.getCategory().equals(track.getCategory()));
|
||||
this.track = track;
|
||||
if (prefsChanged) {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, getString(R.string.stats_rate_key));
|
||||
}
|
||||
|
||||
loadTrackDescription(track);
|
||||
updateUI();
|
||||
updateSensorUI();
|
||||
|
||||
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon, viewBinding.statsNameValue);
|
||||
((TrackRecordedActivity) getActivity()).startPostponedEnterTransitionWith(viewBinding.statsActivityTypeIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
@@ -211,17 +212,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSampledOutTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
// We don't care.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint newLastTrackPoint) {
|
||||
public void onNewTrackPointsDone(@NonNull TrackPoint newLastTrackPoint, @NonNull TrackStatistics trackStatistics) {
|
||||
if (isResumed()) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (isResumed()) {
|
||||
@@ -388,7 +379,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
|
||||
// Set time
|
||||
if (track != null && track.getTrackStatistics() != null) {
|
||||
if (track != null) {
|
||||
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(track.getTrackStatistics().getMovingTime()));
|
||||
updateTotalTime();
|
||||
}
|
||||
|
||||
@@ -192,6 +192,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return serviceStatus.getRecordingTrackPaused();
|
||||
}
|
||||
|
||||
public Track.Id getRecordingTrackId() {
|
||||
return serviceStatus.getRecordingTrackId();
|
||||
}
|
||||
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
if (trackStatisticsUpdater == null) {
|
||||
return null;
|
||||
@@ -252,7 +256,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
*/
|
||||
public Track.Id startNewTrack() {
|
||||
if (isRecording()) {
|
||||
Log.d(TAG, "Ignore startNewTrack. Already recording.");
|
||||
Log.w(TAG, "Ignore startNewTrack. Already recording.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -312,7 +316,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void resumeCurrentTrack() {
|
||||
if (!isRecording() || !isPaused()) {
|
||||
Log.d(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
|
||||
Log.w(TAG, "Ignore resumeCurrentTrack. Not recording or not paused.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -361,10 +365,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public Track.Id endCurrentTrack() {
|
||||
public void endCurrentTrack() {
|
||||
if (!isRecording()) {
|
||||
Log.d(TAG, "Ignore endCurrentTrack. Not recording.");
|
||||
return null;
|
||||
Log.w(TAG, "Ignore endCurrentTrack. Not recording.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to remember the recordingTrackId before setting it to null
|
||||
@@ -390,14 +394,12 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
ExportUtils.postWorkoutExport(this, track, new ExportServiceResultReceiver(new Handler(), this));
|
||||
|
||||
endRecording(true);
|
||||
|
||||
return trackId;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void pauseCurrentTrack() {
|
||||
if (!isRecording() || isPaused()) {
|
||||
Log.d(TAG, "Ignore pauseCurrentTrack. Not recording or paused.");
|
||||
Log.w(TAG, "Ignore pauseCurrentTrack. Not recording or paused.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -569,6 +571,12 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
serviceStatus.addListener(listener);
|
||||
}
|
||||
|
||||
//TODO Check that this is used everywhere, where addListener is called!
|
||||
//Otherwise, we keep objects in referenced and waste memory until this service instance is terminated.
|
||||
public void removeListener(@NonNull TrackRecordingServiceStatus.Listener listener) {
|
||||
serviceStatus.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ public class TrackRecordingServiceStatus {
|
||||
|
||||
private final List<Listener> listeners = new ArrayList<>();
|
||||
|
||||
public void addListener(@NonNull Listener listener) {
|
||||
void addListener(@NonNull Listener listener) {
|
||||
if (this.listeners.contains(listener)) {
|
||||
return;
|
||||
}
|
||||
@@ -28,6 +28,10 @@ public class TrackRecordingServiceStatus {
|
||||
listener.onTrackRecordingPaused(recordingTrackPaused);
|
||||
}
|
||||
|
||||
void removeListener(@NonNull Listener listener) {
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean getRecordingTrackPaused() {
|
||||
return this.recordingTrackPaused;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class AltitudeSumManager implements SensorEventListener {
|
||||
Log.w(TAG, "No pressure sensor available.");
|
||||
isConnected = false;
|
||||
} else {
|
||||
isConnected = sensorManager.registerListener(this, pressureSensor, (int) SAMPLING_RATE.toNanos());
|
||||
isConnected = sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_FASTEST);
|
||||
}
|
||||
|
||||
lastAcceptedPressureValue_hPa = Float.NaN;
|
||||
|
||||
+12
-14
@@ -24,6 +24,7 @@ import android.speech.tts.UtteranceProgressListener;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
@@ -32,7 +33,6 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceStatus;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.AnnouncementUtils;
|
||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||
@@ -58,8 +58,6 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private Track.Id recordingTrackId;
|
||||
|
||||
private final AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
@@ -142,13 +140,8 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
Log.e(TAG, "TrackRecordingService is null.");
|
||||
return;
|
||||
}
|
||||
trackRecordingService.addListener(new TrackRecordingServiceStatus.Listener() {
|
||||
@Override
|
||||
public void onTrackRecordingId(Track.Id trackId) {
|
||||
recordingTrackId = trackId;
|
||||
}
|
||||
});
|
||||
announce(trackRecordingService.getTrackStatistics());
|
||||
|
||||
announce(trackRecordingService.getRecordingTrackId(), trackRecordingService.getTrackStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +149,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
*
|
||||
* @param trackStatistics the track statistics
|
||||
*/
|
||||
private void announce(TrackStatistics trackStatistics) {
|
||||
private void announce(Track.Id trackId, TrackStatistics trackStatistics) {
|
||||
if (trackStatistics == null) {
|
||||
Log.e(TAG, "TrackStatistics is null.");
|
||||
return;
|
||||
@@ -175,13 +168,18 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
|
||||
}
|
||||
}
|
||||
|
||||
if (audioManager.getMode() == AudioManager.MODE_IN_CALL || audioManager.getMode() == AudioManager.MODE_IN_COMMUNICATION) {
|
||||
if (Arrays.asList(AudioManager.MODE_IN_CALL, AudioManager.MODE_IN_COMMUNICATION)
|
||||
.contains(audioManager.getMode())) {
|
||||
Log.i(TAG, "Speech is not allowed at this time.");
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = contentProviderUtils.getTrack(recordingTrackId);
|
||||
String category = track != null ? track.getCategory() : "";
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
if (track == null) {
|
||||
Log.i(TAG, "It doesn't exists a track with trackid = " + track);
|
||||
return;
|
||||
}
|
||||
String category = track.getCategory();
|
||||
|
||||
//TODO Querying all TrackPoints all the time is inefficient; use TrackDataHub or something else.
|
||||
TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null);
|
||||
|
||||
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.stats;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -193,6 +194,7 @@ public class TrackStatistics {
|
||||
this.movingTime = movingTime;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void addMovingTime(Duration time) {
|
||||
movingTime = movingTime.plus(time);
|
||||
}
|
||||
@@ -265,6 +267,7 @@ public class TrackStatistics {
|
||||
this.totalAltitudeGain_m = totalAltitudeGain_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void addTotalAltitudeGain(float gain_m) {
|
||||
if (totalAltitudeGain_m == null) {
|
||||
totalAltitudeGain_m = 0f;
|
||||
@@ -285,6 +288,7 @@ public class TrackStatistics {
|
||||
this.totalAltitudeLoss_m = totalAltitudeLoss_m;
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void addTotalAltitudeLoss(float loss_m) {
|
||||
if (totalAltitudeLoss_m == null) {
|
||||
totalAltitudeLoss_m = 0f;
|
||||
|
||||
@@ -106,7 +106,7 @@ public class TrackStatisticsUpdater {
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||
internalAddTrackPoint(trackPoint, minGPSDistance);
|
||||
Log.d(TAG, this.toString());
|
||||
Log.v(TAG, this.toString());
|
||||
}
|
||||
|
||||
private void internalAddTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class AnnouncementUtils {
|
||||
int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
rate = context.getString(paceId, getAnnounceTime(context, time));
|
||||
|
||||
Duration currentTime = currentDistancePerTime.toPace(isMetricUnits);
|
||||
Duration currentTime = currentDistancePerTime != null ? currentDistancePerTime.toPace(isMetricUnits) : Duration.ofMillis(0);
|
||||
currentRate = context.getString(paceId, getAnnounceTime(context, currentTime));
|
||||
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
intervalsLiveData = new MutableLiveData<>();
|
||||
distanceInterval = interval.getValue();
|
||||
distanceInterval = interval.getDistance(metricUnits);
|
||||
loadIntervalStatistics();
|
||||
}
|
||||
return intervalsLiveData;
|
||||
@@ -73,7 +73,7 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
interval = IntervalOption.OPTION_1;
|
||||
}
|
||||
|
||||
distanceInterval = interval.getValue();
|
||||
distanceInterval = interval.getDistance(metricUnits);
|
||||
loadIntervalStatistics();
|
||||
}
|
||||
}
|
||||
@@ -91,19 +91,21 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
||||
OPTION_20(20),
|
||||
OPTION_50(50);
|
||||
|
||||
private final Distance value;
|
||||
private final int multiplier;
|
||||
|
||||
IntervalOption(int value) {
|
||||
this.value = Distance.of(value);
|
||||
IntervalOption(int multiplier) {
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
public Distance getValue() {
|
||||
return value;
|
||||
public Distance getDistance(boolean metricUnits) {
|
||||
return Distance
|
||||
.one(metricUnits)
|
||||
.multipliedBy(multiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + (int) value.toM(); //TODO Somehow IntervalsFragment relies on a parsable Integer.
|
||||
return "" + multiplier; //TODO Somehow IntervalsFragment relies on a parsable Integer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,8 +217,8 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
||||
<string name="gps_location_access">\"Standortzugriff\"</string>
|
||||
<string name="gps_disabled">GPS ist nicht aktiviert. Tippen Sie hier, um zu %1$s wechseln. Drücken Sie \"Zurück\", um zurückzugehen.</string>
|
||||
<string name="gps_disabled_my_location">Der aktuelle Standort kann nicht ermittelt werden. Aktivieren Sie mehr Standortquellen unter \"%1$s\" und vergewissern Sie sich, dass der Himmel sichtbar ist. Bitte versuchen Sie es später erneut.</string>
|
||||
<string name="gps_not_found">Das Gerät sendet kein GPS-Signal.</string>
|
||||
<string name="gps_starting">GPS wird gestartet.</string>
|
||||
<string name="gps_not_found">Das Gerät empfängt kein GPS-Signal</string>
|
||||
<string name="gps_starting">GPS wird gestartet</string>
|
||||
<string name="gps_wait_for_better_signal">Warten auf besseres GPS-Signal</string>
|
||||
<string name="gps_wait_for_signal">Warten auf GPS-Signal</string>
|
||||
<!-- Image -->
|
||||
@@ -584,9 +584,11 @@ g) das tun, was Deiner Meinung nach OpenTracks hilft.</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Streckenaufzeichnung überschrieben</string>
|
||||
<string name="export_progress_summary_skip_msg">Strecke übersprungen</string>
|
||||
<string name="export_progress_summary_errors_msg">Fehler</string>
|
||||
<string name="generic_click_twice_cancel">Klicke nochmal zurück um abzubrechen.</string>
|
||||
<string name="generic_click_twice_cancel">Klicke nochmal auf zurück um abzubrechen</string>
|
||||
<string name="import_error_list_dialog_title">Dateien wurden nicht importiert</string>
|
||||
<string name="voice_speed_lap">Rundengeschwindigkeit %1$s</string>
|
||||
<string name="voice_pace_lap">Rundenzeit %1$s</string>
|
||||
<string name="toast_message_instant_track_was_exported">Letzte Aufzeichnung exportiert</string>
|
||||
<string name="export_track_already_exists_msg">Der Track %1$s existiert bereits im Zielverzeichnis.</string>
|
||||
<string name="gps_fixed_and_ready">GPS-Position bestimmt und bereit</string>
|
||||
</resources>
|
||||
@@ -600,7 +600,7 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
||||
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
|
||||
<string name="sensor_state_cadence_max">Cadencia máxima</string>
|
||||
<string name="sensor_state_cadence_avg">Cadencia media</string>
|
||||
<string name="value_float_kilometer_hour">%1$.2f kp/h</string>
|
||||
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Pistas sobrescritas</string>
|
||||
<string name="export_progress_summary_skip_msg">Pistas omitidas</string>
|
||||
<string name="export_progress_summary_errors_msg">Errores</string>
|
||||
|
||||
@@ -13,7 +13,8 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--><resources>
|
||||
-->
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="about_preference_title">À propos d\'OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> est une application de suivi sportif qui respecte totalement votre vie privée.</string>
|
||||
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
|
||||
@@ -291,7 +292,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="sensor_state_cadence_value">%1$d tr/min</string>
|
||||
<string name="sensor_state_disconnected">Déconnecté</string>
|
||||
<string name="sensor_state_heart_rate">Fréquence cardiaque</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d ppm</string>
|
||||
<string name="sensor_state_power">Puissance</string>
|
||||
<string name="sensor_state_power_value">%1$d W</string>
|
||||
<string name="sensor_not_known">Capteur %1$s est inconnu. Veuillez vérifier les paramètres.</string>
|
||||
@@ -358,7 +359,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="stats_average_speed">Vitesse moyenne</string>
|
||||
<string name="stats_altitude">Altitude</string>
|
||||
<string name="stats_fastest_pace">Allure la plus rapide</string>
|
||||
<string name="stats_gain">Gain</string>
|
||||
<string name="stats_gain">Gagner</string>
|
||||
<string name="stats_grade">Inclinaison</string>
|
||||
<string name="stats_latitude">Latitude</string>
|
||||
<string name="stats_longitude">Longitude</string>
|
||||
@@ -537,4 +538,78 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<!-- Share image -->
|
||||
<string name="share_image_subject">J aimerais partager une image avec vous</string>
|
||||
<string name="share_image_body">Je pense que cette image pourrait vous intéresser.</string>
|
||||
<string name="track_pace_notification">Vitesse : %1$s</string>
|
||||
<string name="track_speed_notification">Vitesse : %1$s</string>
|
||||
<string name="track_detail_intervals_tab">Intervalles</string>
|
||||
<string name="interval_list_empty_message">Il n\'y a pas encore d\'intervalle</string>
|
||||
<string name="aggregated_stats_empty_message">Enregistrez votre première piste pour voir les statistiques agrégées</string>
|
||||
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
|
||||
<string name="value_float_mile_hour">%1$.2f mph</string>
|
||||
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
|
||||
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
|
||||
<string name="voice_pace_lap">Temps au tour de %1$s</string>
|
||||
<string name="voice_speed_lap">Vitesse du tour de %1$s</string>
|
||||
<string name="toast_message_instant_track_was_exported">Dernière piste exportée</string>
|
||||
<string name="settings_default_export_uri_title">Répertoire d\'exportation des pistes</string>
|
||||
<string name="instant_export_enabled_title">Exportation instantanée après l\'entraînement</string>
|
||||
<string name="stats_intervals">Intervalles</string>
|
||||
<string name="stats_loss">Perte</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Empêcher la réimportation de pistes</string>
|
||||
<string name="settings_night_mode_option_night">Nuit</string>
|
||||
<string name="settings_night_mode_option_day">Jour</string>
|
||||
<string name="settings_night_mode_option_system">Système</string>
|
||||
<string name="stats_split_interval">Intervalle partagé</string>
|
||||
<string name="instant_export_enabled_summary">Exportation de la piste vers le stockage après la fin de l\'enregistrement</string>
|
||||
<string name="hold_to_pause">Tenir pour faire une pause</string>
|
||||
<string name="hold_to_stop">Maintenir pour arrêter</string>
|
||||
<string name="track_distance_notification">Distanciation : %1$s</string>
|
||||
<string name="open_track_as_KMZ">Ouvert comme KMZ</string>
|
||||
<string name="open_track_as_GPX">Ouvert en tant que GPX</string>
|
||||
<string name="open_track_as_trackfileformat">Ouvrir comme %1$s</string>
|
||||
<string name="open_track_using_dashboard">Ouvrir en utilisant OpenTracks-Dashboard</string>
|
||||
<string name="activity_type_escooter">éscooter</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circonférence de roue (millimètre)</string>
|
||||
<string name="export_error_post_workout">L\'exportation du post-travail a échoué, veuillez vérifier le répertoire d\'exportation.</string>
|
||||
<string name="export_all_do_it_for_all">le faire pour tous les conflits</string>
|
||||
<string name="export_track_errors">Pistes non exportées</string>
|
||||
<string name="import_unable_to_import_file">Impossible d\'importer le fichier : %1$s</string>
|
||||
<string name="import_no_kml_file_found">Pas de doc.fichier kml trouvé dans KMZ</string>
|
||||
<string name="import_error_info">%1$s : %2$s</string>
|
||||
<string name="import_error_list_dialog_title">Les fichiers ne sont pas importées</string>
|
||||
<string name="settings_night_mode_title">Thème de l\'Interface utilisateur</string>
|
||||
<string name="generic_completed">Compléter</string>
|
||||
<string name="sensor_state_cadence_max">Cadence maximale</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">Plein écran</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Pendant l\'enregistrement, présent en plein écran.</string>
|
||||
<string name="sensor_state_power_avg">Puissance moyenne</string>
|
||||
<string name="sensor_state_heart_rate_max">Fréquence cardiaque max</string>
|
||||
<string name="sensor_state_heart_rate_avg">Rythme cardiaque moyen</string>
|
||||
<string name="sensor_state_cadence_avg">Cadence moyenne</string>
|
||||
<string name="generic_click_twice_cancel">Cliquez sur retour pour annuler</string>
|
||||
<string name="export_progress_summary_new_msg">Nouvelles pistes exportées</string>
|
||||
<string name="export_progress_summary_skip_msg">Pistes sautées</string>
|
||||
<string name="export_progress_summary_errors_msg">Erreurs</string>
|
||||
<string name="generic_overwrite">Écraser</string>
|
||||
<string name="generic_show_errors">Afficher les erreurs</string>
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<string name="generic_skip">Sauter</string>
|
||||
<string name="generic_open_track">Voie ouverte</string>
|
||||
<string name="gps_disabled_msg">GPS désactivé</string>
|
||||
<string name="gps_fixed_and_ready">GPS fixe et prêt</string>
|
||||
<string name="import_thread_interrupted">Fil interrompu</string>
|
||||
<string name="import_unsupported_format">Format de fichier non pris en charge</string>
|
||||
<string name="import_parser_error">Erreur de l\'analyseur : %1$s</string>
|
||||
<string name="import_file_imported">Fichier %1$s importé</string>
|
||||
<string name="import_prevent_reimport">Vous avez défini empêcher la réimportation de la piste</string>
|
||||
<string name="import_progress_summary_ok_msg">Les fichiers importés</string>
|
||||
<string name="import_progress_summary_exists_msg">Il existe déjà</string>
|
||||
<string name="import_progress_summary_errors_msg">Erreur</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one" tools:ignore="ImpliedQuantity">Terminé avec une erreur</item>
|
||||
<item quantity="other">Terminé avec des erreurs %1$d</item>
|
||||
</plurals>
|
||||
<string name="settings_default_trackfileformat">Format de fichier d\'exportation / partage</string>
|
||||
<string name="description_altitude_loss">Perte d\'altitude : %1$d m (%2$d ft)</string>
|
||||
<string name="export_track_already_exists_msg">La piste %1$s existe déjà dans le répertoire de destination.</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Pistes écrasées</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">repülőgép</string>
|
||||
<string name="activity_type_atv">terepjáró</string>
|
||||
@@ -63,7 +63,7 @@ limitations under the License.
|
||||
<string name="activity_type_skate_boarding">gördeszkázás</string>
|
||||
<string name="activity_type_skating">korcsolyázás</string>
|
||||
<string name="activity_type_skiing">síelés</string>
|
||||
<string name="activity_type_sky_jumping">sky jumping</string>
|
||||
<string name="activity_type_sky_jumping">égi ugrás</string>
|
||||
<string name="activity_type_sled">szánkó</string>
|
||||
<string name="activity_type_snow_boarding">snowboard</string>
|
||||
<string name="activity_type_snow_shoeing">hótalpazás</string>
|
||||
@@ -75,7 +75,7 @@ limitations under the License.
|
||||
<string name="activity_type_trail_hiking">túrázás</string>
|
||||
<string name="activity_type_trail_running">terepfutás</string>
|
||||
<string name="activity_type_train">vonat</string>
|
||||
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string>
|
||||
<string name="activity_type_ultimate_frisbee">ultimate frizbi</string>
|
||||
<string name="activity_type_unknown">ismeretlen</string>
|
||||
<string name="activity_type_wakeboarding">wakeboard</string>
|
||||
<string name="activity_type_walking">gyaloglás</string>
|
||||
@@ -147,7 +147,7 @@ limitations under the License.
|
||||
<string name="generic_ok">OK</string>
|
||||
<string name="generic_paused">Szüneteltetett</string>
|
||||
<string name="generic_progress_title">Kis türelmet…</string>
|
||||
<string name="generic_recording">Rögzítés</string>
|
||||
<string name="generic_recording">Rögzítés…</string>
|
||||
<string name="generic_save">Mentés</string>
|
||||
<string name="generic_success_title">Sikerült</string>
|
||||
<string name="generic_tracks">Számok</string>
|
||||
@@ -277,10 +277,11 @@ limitations under the License.
|
||||
<string name="settings_stats_units_imperial">Angolszász (mérföld, láb)</string>
|
||||
<string name="settings_stats_units_metric">Metrikus (km, m)</string>
|
||||
<string name="settings_stats_units_title">Mértékegység választása</string>
|
||||
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file">Útvonal-fájl megosztása</string>
|
||||
<string name="share_track_share_file_body">Szerintem érdekelni fog ez a pálya.\n\nA pályát a Google Earth vagy más térképszoftver segítségével nézheted meg.\n\n%1$s</string>
|
||||
<string name="share_track_share_file_body">Szerintem érdekelni fog ez a pálya.
|
||||
\n
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Szeretnék megosztani veled egy pályát</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Mozgás átlagos irama</string>
|
||||
@@ -462,4 +463,146 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">vízesés</string>
|
||||
<string name="waypoint_type_water_fountain">szökőkút</string>
|
||||
<string name="waypoint_type_waypoint">útpont</string>
|
||||
</resources>
|
||||
<string name="generic_overwrite">Felülírás</string>
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<string name="generic_skip">Ugrás</string>
|
||||
<string name="about_url">A projekt weboldala: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Verzió neve: %1$s</string>
|
||||
<string name="about_version_code">Verziókód: %1$d</string>
|
||||
<string name="about_preference_title">Az OpenTracks</string>
|
||||
<string name="help_data_google_play_services_title">Az OpenTracks igényel/használja a Google Play szolgáltatást\?</string>
|
||||
<string name="help_track_export_title">Hogyan tudom exportálni/megosztani a felvett zeneszámokat\?</string>
|
||||
<string name="help_storage_pictures_content">Ezeket az alkalmazás belső tárhelyén tárolja (azaz csak az OpenTracks-en belül érhető el).</string>
|
||||
<string name="about_libraries">Könyvtárak</string>
|
||||
<string name="help_feature_title">Az OpenTracks a következő funkciókkal rendelkezik:</string>
|
||||
<string name="help_storage_pictures_title">Hogyan tárolja az OpenTracks a jelölőként készített képeket\?</string>
|
||||
<string name="help_track_map_title">Hogyan jelenítsünk meg egy pályát a térképen\?</string>
|
||||
<string name="help_gps_troubleshooting_title">Az OpenTracks nem rögzíti a helyszíneket\?</string>
|
||||
<string name="help_support_title">Hogyan tudom támogatni az OpenTracks-t\?</string>
|
||||
<string name="open_track_using_dashboard">Megnyitás az OpenTracks-Dashboard segítségével</string>
|
||||
<string name="open_track_as_KMZ">Megnyitás KMZ-ként</string>
|
||||
<string name="open_track_as_GPX">Megnyitás GPX-ként</string>
|
||||
<string name="open_track_as_trackfileformat">Megnyitás %1$s néven</string>
|
||||
<string name="activity_type_escooter">eszkoter</string>
|
||||
<string name="help_recording_content">Az OpenTracks a készülék GPS-érzékelőjét használja a hely folyamatos rögzítésére. Ha a GPS-eszköz pontatlan adatokat (pl. hely, sebesség, magasság) jelent, az OpenTracks nem tud mit tenni.</string>
|
||||
<string name="help_data_storage_title">Hogyan tárolja az OpenTracks az érzékelőadatokat\?</string>
|
||||
<string name="app_not_installed_show_on_map">Kérjük, telepítsen egy olyan alkalmazást, amely támogatja a térképadatok megjelenítését.</string>
|
||||
<string name="import_error_list_dialog_title">Nem importált fájlok</string>
|
||||
<string name="import_progress_summary_errors_msg">Hibák</string>
|
||||
<string name="sensor_state_cadence_max">Max kadencia</string>
|
||||
<string name="sensor_state_heart_rate_max">Átlagos pulzusszám</string>
|
||||
<string name="stats_intervals">Intervallumok</string>
|
||||
<string name="settings_sensor_wheel_circumference">Kerékkörfogat (mm)</string>
|
||||
<string name="stats_split_interval">Osztott intervallum</string>
|
||||
<string name="track_speed_notification">Sebesség: %1$s</string>
|
||||
<string name="track_detail_intervals_tab">Intervallum</string>
|
||||
<string name="track_detail_record_success">Most egy új szám felvétele</string>
|
||||
<string name="track_detail_resume_success">Most folytatódik a pálya</string>
|
||||
<string name="interval_list_empty_message">Még nincs intervallum</string>
|
||||
<string name="value_float_mile_hour">%1$.2f mph</string>
|
||||
<string name="value_pace_mile">%1$d:%2$02d min/mi</string>
|
||||
<string name="settings_default_export_uri_title">Pálya export könyvtár</string>
|
||||
<string name="instant_export_enabled_summary">Sáv exportálása a tárolóba a felvétel befejezése után</string>
|
||||
<string name="toast_message_instant_track_was_exported">Utolsó exportált zeneszám</string>
|
||||
<string name="hold_to_pause">Tartsa a szünethez</string>
|
||||
<string name="hold_to_stop">Tartsa lenyomva a leállításhoz</string>
|
||||
<string name="track_distance_notification">Távolság: %1$s</string>
|
||||
<string name="value_pace_kilometer">%1$d:%2$02d min/km</string>
|
||||
<string name="instant_export_enabled_title">Azonnali edzés utáni export</string>
|
||||
<string name="track_pace_notification">Tempó: %1$s</string>
|
||||
<string name="export_with_photos">fotókkal</string>
|
||||
<string name="export_error_post_workout">Az edzés utáni exportálás sikertelen, kérjük, ellenőrizze az exportkönyvtárat.</string>
|
||||
<string name="export_track_already_exists_msg">A %1$s cím már létezik a célkönyvtárban.</string>
|
||||
<string name="export_all_do_it_for_all">minden konfliktus esetén</string>
|
||||
<string name="export_track_errors">Nem exportált sávok</string>
|
||||
<string name="import_thread_interrupted">Szál megszakadt</string>
|
||||
<string name="import_unsupported_format">Nem támogatott fájlformátum</string>
|
||||
<string name="description_default_speed_or_pace">Tevékenységtípus szerint</string>
|
||||
<string name="export_without_photos">fotók nélkül</string>
|
||||
<string name="value_float_kilometer_hour">%1$.2f km/h</string>
|
||||
<string name="voice_speed_lap">%1$s körsebessége</string>
|
||||
<string name="help_gps_troubleshooting_content"> Mit tehetsz:
|
||||
\n a) ellenőrizze, hogy a GPS engedélyezve van-e,
|
||||
\n b) javítsa a GPS-vételt (pl. menjen a szabadba),
|
||||
\n c) ellenőrizze a <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> alkalmazással, és
|
||||
\n d) tiltsa le az energiatakarékosságot.
|
||||
\n
|
||||
\n Ha mindez nem segít, kérjük, jelentse a hibát. </string>
|
||||
<string name="export_progress_summary_new_msg">Új pályák exportálása</string>
|
||||
<string name="generic_completed">Befejezett</string>
|
||||
<string name="sensor_state_heart_rate_avg">Átlagos pulzusszám</string>
|
||||
<string name="sensor_state_power_avg">Átlagos teljesítmény</string>
|
||||
<string name="sensor_not_known">A %1$s érzékelő ismeretlen. Kérjük, ellenőrizze a beállításokat.</string>
|
||||
<string name="bluetooth_disabled">Kérjük, engedélyezze a Bluetooth funkciót.</string>
|
||||
<string name="settings_default_trackfileformat">Export/megosztás fájlformátum</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Felvétel közben a statisztika megjelenítése a készülék feloldása nélkül.</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">Tartsa a képernyőt bekapcsolva</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">Felvétel közben tartsa bekapcsolva a képernyőt.</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">Teljes képernyőn</string>
|
||||
<string name="sensor_state_cadence_avg">Avg Kadencia</string>
|
||||
<string name="settings_night_mode_title">UI téma</string>
|
||||
<string name="settings_night_mode_option_system">Rendszer</string>
|
||||
<string name="settings_night_mode_option_day">Nap</string>
|
||||
<string name="settings_night_mode_option_night">Éjszaka</string>
|
||||
<string name="settings_error_initial_values_restored">Sajnáljuk, de hiba történt. Minden beállítás visszaállt az alapértelmezett értékekre.</string>
|
||||
<string name="settings_sensor_connecting">Csatlakoztatva %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Megszakadt a kapcsolat %1$s</string>
|
||||
<string name="stats_loss">Veszteség</string>
|
||||
<string name="track_recording_notification_accuracy">Helyszín pontossága: %1$s</string>
|
||||
<string name="voice_pace_lap">Köridő %1$s</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Sávok felülírása</string>
|
||||
<string name="export_progress_summary_skip_msg">Kihagyott sávok</string>
|
||||
<string name="export_progress_summary_errors_msg">Hibák</string>
|
||||
<string name="generic_open_track">Nyílt pálya</string>
|
||||
<string name="generic_show_errors">Hibák megjelenítése</string>
|
||||
<string name="generic_click_twice_cancel">Kattintson vissza a törléshez</string>
|
||||
<string name="gps_disabled_msg">GPS letiltva</string>
|
||||
<string name="gps_fixed_and_ready">GPS rögzítve és készen áll</string>
|
||||
<string name="import_unable_to_import_file">Nem sikerült importálni a fájlt: %1$s</string>
|
||||
<string name="import_no_kml_file_found">Nincs doc.kml fájl található a KMZ fájlban</string>
|
||||
<string name="import_error_info">%1$s: %2$s</string>
|
||||
<string name="import_file_imported">Fájl %1$s importálva</string>
|
||||
<string name="import_prevent_reimport">Beállítottad, hogy megakadályozd az újraimportálást</string>
|
||||
<string name="import_progress_summary_ok_msg">Importált fájlok</string>
|
||||
<string name="import_progress_summary_exists_msg">Már létezik</string>
|
||||
<string name="menu_insert_gallery_img">Galéria kép beillesztése</string>
|
||||
<string name="menu_resume_track">Folytassa a pályát</string>
|
||||
<string name="import_parser_error">Parser hiba: %1$s</string>
|
||||
<string name="help_data_storage_content"> Az érzékelőadatokat rögzített helyenként tárolják. A pulzusszám-érzékelőre vonatkoztatva ez azt jelenti, hogy csak <i>egy</i> pulzusszámmérés kerül tárolásra helyenként. Ha mozdulatlanul áll vagy beltérben tartózkodik, akkor nem kerülnek rögzítésre az érzékelő adatai (de megjelenítésre kerülnek). </string>
|
||||
<string name="help_support_content"> Megteheti:
|
||||
\n a) terjesztheti az OpenTracks hírét,
|
||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">jellemzőket javasolhat</a>,
|
||||
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">hibákat jelenteni</a>,
|
||||
\n d) segíthetsz a szoftverfejlesztésben,
|
||||
\n e) vásárolhat a <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>-ban,
|
||||
\n f) adományozni, és
|
||||
\n g) tegyen bármit, ami Ön szerint segít az OpenTracks-nak. </string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">Egy hibával befejezve</item>
|
||||
<item quantity="other">%1$d hibával befejezve</item>
|
||||
</plurals>
|
||||
<string name="description_altitude_loss">Magasságveszteség: %1$d m (%2$d ft)</string>
|
||||
<string name="marker_add_photo_canceled">Törölt, nincs fotójelző hozzáadva</string>
|
||||
<string name="permission_gps_failed">Az OpenTracks engedélyt igényel a GPS használatához.</string>
|
||||
<string name="sensor_could_not_scan">Nem sikerült Bluetooth-eszközöket keresni (hiba %1$i).</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">Statisztikák megjelenítése a záróképernyőn</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Felvétel közben teljes képernyőn jelenik meg.</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">A pályák újraimportálásának megakadályozása</string>
|
||||
<string name="aggregated_stats_empty_message">Az első pálya rögzítése az összesített statisztikák megtekintéséhez</string>
|
||||
<string name="share_image_subject">Szeretnék megosztani veletek egy képet</string>
|
||||
<string name="share_image_body">Azt hiszem, ez a kép érdekelheti önöket.</string>
|
||||
<string name="help_recording_title">Hogyan rögzíti az OpenTracks az adatokat\?</string>
|
||||
<string name="help_track_export_content"> A nyomvonalak KMZ, KML vagy GPX formátumban exportálhatók a Beállításokból. KMZ/KML minden adat; GPX csak a helyszínek. A <i>megosztás ikonon</i> keresztül megosztott pályák KMZ formátumban kerülnek megosztásra. </string>
|
||||
<string name="help_track_map_content"> Használja a kis térkép ikont.
|
||||
\n a) (élő frissítések) Telepítse a <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Ez online vagy offline térképeket biztosít a <a href="https://www.openstreetmap.org">OpenStreetMap</a> segítségével.
|
||||
\n Valójában az OSMDashboard egy különálló alkalmazás, amely csak a megjelenítendő pálya(k) adataihoz fér hozzá (ideiglenesen).
|
||||
\n b) Egy normál térképalkalmazáson keresztül (pl. <a href="http://osmand.net/">OsmAnd</a> és <a href="https://maps.me/">MAPS.ME</a>). Ezeknek az alkalmazásoknak támogatniuk kell a KMZ fájlformátumot (csak a helyeket és az időbélyegeket). Valójában egy alkalmazásnak támogatnia kell a <i>ACTION_VIEW</i> MIME <i>application/vnd.google-earth.kmz</i> formátumot. </string>
|
||||
<string name="about_description">Az <i>OpenTracks</i> egy sportkövető alkalmazás, amely teljes mértékben tiszteletben tartja az Ön magánéletét.</string>
|
||||
<string name="about_history">Az OpenTracks a <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (2016-ban megszűnt) elágazása.</string>
|
||||
<string name="help_feature_content">* GPS nyomvonalak létrehozása
|
||||
\n * szívritmus-érzékelő használata (csak Bluetooth LE)
|
||||
\n * hangbejelentések (idő vagy távolság alapján)
|
||||
\n * jelölések létrehozása (képekkel együtt)
|
||||
\n * teljesen offline működik
|
||||
\n * pályák megjelenítése térképen (lásd alább)</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">avião</string>
|
||||
<string name="activity_type_atv">quadriciclos</string>
|
||||
@@ -30,7 +30,7 @@ limitations under the License.
|
||||
<string name="activity_type_driving_bus">ônibus</string>
|
||||
<string name="activity_type_driving_car">carro</string>
|
||||
<string name="activity_type_ferry">balsa</string>
|
||||
<string name="activity_type_frisbee">frisbee</string>
|
||||
<string name="activity_type_frisbee">frízebee</string>
|
||||
<string name="activity_type_gliding">asa-delta</string>
|
||||
<string name="activity_type_hang_gliding">voo livre</string>
|
||||
<string name="activity_type_helicopter">helicóptero</string>
|
||||
@@ -75,7 +75,7 @@ limitations under the License.
|
||||
<string name="activity_type_trail_hiking">caminhada em trilha</string>
|
||||
<string name="activity_type_trail_running">corrida em trilha</string>
|
||||
<string name="activity_type_train">treino</string>
|
||||
<string name="activity_type_ultimate_frisbee">ultimate frisbee</string>
|
||||
<string name="activity_type_ultimate_frisbee">frízebee final</string>
|
||||
<string name="activity_type_unknown">desconhecida</string>
|
||||
<string name="activity_type_wakeboarding">esqui aquático</string>
|
||||
<string name="activity_type_walking">caminhada</string>
|
||||
@@ -173,7 +173,7 @@ limitations under the License.
|
||||
<string name="image_stop">Parar</string>
|
||||
<string name="image_track">Rota</string>
|
||||
<!-- Import -->
|
||||
<string name="import_error">Importação: %1$d de %2$s de %3$s</string>
|
||||
<string name="import_error">importados %1$d de %2$s desde %3$s</string>
|
||||
<string name="import_no_directory">%1$s não existe</string>
|
||||
<string name="import_no_file">Nenhum arquivo para importar. Conecte o dispositivo ao computador e coloque os
|
||||
arquivos para importação em %1$s.</string>
|
||||
@@ -185,7 +185,7 @@ limitations under the License.
|
||||
<!-- Marker -->
|
||||
<string name="marker_add_error">Não foi possível inserir um marcador. Não há sinal de GPS. Tente novamente.</string>
|
||||
<string name="marker_add_success">Um marcador foi inserido</string>
|
||||
<string name="marker_add_canceled">Cancelado. Nenhum marcador foi adicionado.</string>
|
||||
<string name="marker_add_canceled">Cancelado. Nenhum marcador foi adicionado</string>
|
||||
<string name="marker_delete_all_confirm_message">Todos os marcadores desta rota serão excluídos permanentemente do dispositivo</string>
|
||||
<string name="marker_delete_one_confirm_message">O marcador será excluído permanentemente do dispositivo</string>
|
||||
<string name="marker_delete_one_confirm_title">Excluir marcador?</string>
|
||||
@@ -255,8 +255,8 @@ limitations under the License.
|
||||
<string name="settings_recording_location_frequency_summary">%1$s entre os locais gravados</string>
|
||||
<string name="settings_recording_max_recording_distance_summary">Um novo segmento será criado se a distância entre os dois locais for maior que %1$s</string>
|
||||
<string name="settings_recording_max_recording_distance_title">Distância máxima</string>
|
||||
<string name="settings_recording_min_recording_distance_title">Interv. distância grav.</string>
|
||||
<string name="settings_recording_min_recording_interval_title">Interv. tempo de grav.</string>
|
||||
<string name="settings_recording_min_recording_distance_title">Interv. distância grav</string>
|
||||
<string name="settings_recording_min_recording_interval_title">Interv. tempo de grav</string>
|
||||
<string name="settings_recording_min_required_accuracy_summary">O local será ignorado se a precisão for menor que %1$s</string>
|
||||
<string name="settings_recording_min_required_accuracy_title">Precisão do GPS</string>
|
||||
<string name="settings_recording_track_name_date_iso_8601_option">Data (ISO 8601)</string>
|
||||
@@ -269,7 +269,7 @@ limitations under the License.
|
||||
<string name="settings_reset_confirm_title">Redefinir agora?</string>
|
||||
<string name="settings_reset_done">Todas as configurações foram revertidas para os valores padrão</string>
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor">Sensor</string>
|
||||
<string name="settings_sensor">Sensores</string>
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_sensor_bluetooth_sensor">Sensor Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
@@ -278,11 +278,11 @@ limitations under the License.
|
||||
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
|
||||
<string name="settings_stats_units_metric">Métrica (km, m)</string>
|
||||
<string name="settings_stats_units_title">Unidades preferenciais</string>
|
||||
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file">Compartilhar arquivo da trilha</string>
|
||||
<string name="share_track_share_file_body">Achei que você pudesse se interessar por essa trilha.\n\nPara visualizá-la, use o Google Earth ou outro
|
||||
software de mapas.\n\n%1$s</string>
|
||||
<string name="share_track_share_file_body">Achei que você pudesse se interessar por essa trilha.
|
||||
\n
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Eu gostaria de compartilhar uma trilha com você</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Ritmo médio de movimentação</string>
|
||||
@@ -296,7 +296,7 @@ limitations under the License.
|
||||
<string name="stats_latitude">Latitude</string>
|
||||
<string name="stats_longitude">Longitude</string>
|
||||
<string name="stats_max">Máx.</string>
|
||||
<string name="stats_max_speed">Velocidade máx.</string>
|
||||
<string name="stats_max_speed">Velocidade máx</string>
|
||||
<string name="stats_min">Mín.</string>
|
||||
<string name="stats_moving_time">Tempo decorrido</string>
|
||||
<string name="stats_pace">Ritmo</string>
|
||||
@@ -464,4 +464,30 @@ limitations under the License.
|
||||
<string name="waypoint_type_waterfall">Cascata</string>
|
||||
<string name="waypoint_type_water_fountain">fonte de água</string>
|
||||
<string name="waypoint_type_waypoint">ponto de referência</string>
|
||||
</resources>
|
||||
<string name="help_track_map_title">Como mostrar uma pista num mapa\?</string>
|
||||
<string name="help_feature_content">* criar pistas GPS
|
||||
\n * utilizar um sensor do ritmo cardíaco (apenas Bluetooth LE)
|
||||
\n * anúncios de voz (por tempo ou distância)
|
||||
\n * criar marcadores (incl. imagens)
|
||||
\n * funciona completamente offline
|
||||
\n * mostrar pistas num mapa (ver abaixo)</string>
|
||||
<string name="help_track_map_content"> Utilize o pequeno ícone do mapa.
|
||||
\n a) (actualizações ao vivo) Instalar <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Fornece mapas online ou offline através de <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n Na verdade, o OSMDashboard é uma aplicação separada que só pode aceder (temporariamente) aos dados da(s) faixa(s) que vão ser mostrados.
|
||||
\n b) Através de uma aplicação de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicações precisam de suportar o formato de ficheiro KMZ (apenas locais e timestamps). De facto, uma aplicação precisa de suportar <i>ACTION_VIEW</i> com MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_gps_troubleshooting_content"> O que se pode fazer:
|
||||
\n a) verificar se o GPS está activado,
|
||||
\n b) melhorar a recepção GPS (por exemplo, ir para o exterior),
|
||||
\n c) verificar com <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, e
|
||||
\n d) desactivar a poupança de energia.
|
||||
\n
|
||||
\n Se tudo isto não ajudar, por favor, informe um erro. </string>
|
||||
<string name="help_support_content"> Pode:
|
||||
\n a) espalhar a palavra sobre OpenTracks,
|
||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">propose features</a>,
|
||||
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">reportar bugs</a>,
|
||||
\n d) ajuda no desenvolvimento de software,
|
||||
\n e) comprar em <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\n f) doar, e
|
||||
\n g) fazer o que achar que ajuda a OpenTracks. </string>
|
||||
</resources>
|
||||
@@ -462,4 +462,8 @@ limitations under the License.
|
||||
<string name="waypoint_type_water_fountain">đài phun nước</string>
|
||||
<string name="waypoint_type_waypoint">điểm tham chiếu</string>
|
||||
<string name="about_preference_title">Về OpenTracks</string>
|
||||
<string name="about_version_name">Tên phiên bản: %1$s</string>
|
||||
<string name="about_version_code">Mã phiên bản: %1$d</string>
|
||||
<string name="about_libraries">Thư viện</string>
|
||||
<string name="help_feature_title">OpenTracks có những tính năng sau đây:</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user