Compare commits

...

29 Commits

Author SHA1 Message Date
Dennis Guse 648c106f7f Release: v4.19.2 2025-05-05 09:23:40 +02:00
Dennis Guse bb86ab927c Merge pull request #2098 from quazgar/fix-values-de-crash
Fix crash in overeager translations
2025-05-05 08:19:26 +02:00
Quazgar 9150318ef9 Bugfix: Fixed overeager translations to French, Gaelic, Tamil.
See #2097
2025-05-05 00:26:21 +02:00
Quazgar b98a0a0283 Bugfix: Fixed overeager translation to German. 2025-05-04 23:54:37 +02:00
Dennis Guse 71f1049642 Release: v4.19.1 2025-04-23 22:25:57 +02:00
dependabot[bot] 6185154af9 Bump com.android.tools.build:gradle from 8.9.1 to 8.9.2
Bumps com.android.tools.build:gradle from 8.9.1 to 8.9.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-version: 8.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 07:21:22 +02:00
Dennis Guse b4ccdcf4a2 Bugfix: GPX exporter missed schemaLocation for TrackStatsExtension/v1
Fixes #2093.
2025-04-21 22:15:17 +02:00
Dennis Guse 681b987927 Cleanup: export/import renaming. 2025-04-21 22:05:28 +02:00
Dennis Guse ed163b2083 Target SDK 36. 2025-04-10 07:54:44 +02:00
dependabot[bot] 77ccbe7004 Bump androidx.gridlayout:gridlayout from 1.0.0 to 1.1.0
Bumps androidx.gridlayout:gridlayout from 1.0.0 to 1.1.0.

---
updated-dependencies:
- dependency-name: androidx.gridlayout:gridlayout
  dependency-version: 1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 07:33:53 +02:00
dependabot[bot] 247a5f342a Bump androidx.core:core from 1.15.0 to 1.16.0
Bumps androidx.core:core from 1.15.0 to 1.16.0.

---
updated-dependencies:
- dependency-name: androidx.core:core
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 07:33:41 +02:00
dependabot[bot] 7b9b1d3c95 Bump org.mockito:mockito-android from 5.16.1 to 5.17.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.16.1 to 5.17.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.16.1...v5.17.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-version: 5.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 07:09:59 +02:00
dependabot[bot] 44fe4f8818 Bump reactivecircus/android-emulator-runner from 2.33.0 to 2.34.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.33.0 to 2.34.0.
- [Release notes](https://github.com/reactivecircus/android-emulator-runner/releases)
- [Changelog](https://github.com/ReactiveCircus/android-emulator-runner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/reactivecircus/android-emulator-runner/compare/v2.33.0...v2.34.0)

---
updated-dependencies:
- dependency-name: reactivecircus/android-emulator-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 07:19:03 +02:00
Dennis Guse 1f55729b71 README: add wanderer 2025-03-28 23:01:56 +01:00
Dennis Guse 8592375edc Bugfix: settings for recording UI configuration are now edge-2-edge friendly. 2025-03-26 22:04:33 +01:00
dependabot[bot] 2c1ffd5fa1 Bump com.android.tools.build:gradle from 8.9.0 to 8.9.1
Bumps com.android.tools.build:gradle from 8.9.0 to 8.9.1.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 06:28:53 +01:00
Dennis Guse 2672602a71 Bugfix: diagram x-line was hidden on some devices.
Fixes #2075.
2025-03-17 17:08:21 +01:00
Aindriú Mac Giolla Eoin 9e0553d5d6 Translated using Weblate (Irish)
Currently translated at 100.0% (159 of 159 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-03-17 05:43:18 +00:00
Poesty Li 5b7bc4ea24 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (159 of 159 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/zh_Hans/
2025-03-17 05:43:17 +00:00
Fjuro f4a477b99d Translated using Weblate (Czech)
Currently translated at 100.0% (159 of 159 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-03-17 05:43:16 +00:00
Stephan Paternotte c9a2b31866 Translated using Weblate (Dutch)
Currently translated at 100.0% (159 of 159 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2025-03-17 05:43:16 +00:00
dependabot[bot] 4412a6a698 Bump org.mockito:mockito-android from 5.16.0 to 5.16.1
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.16.0 to 5.16.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.16.0...v5.16.1)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-android
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 06:43:10 +01:00
Dennis Guse 92da9bd598 Release: v4.19.0 2025-03-16 11:15:50 +01:00
Dennis Guse 3df45c54d6 GPX: export TrackPointExtension correctly (order and rest of the data under <extension> directly).
Importing old (incorrect) and new (correct) format supported.

Part of #2074.
2025-03-16 11:06:21 +01:00
Dennis Guse c9f7ef810e GPX: export correct URL for Cluetrust.
Part of #2074.
2025-03-16 11:06:20 +01:00
Dennis Guse ebd4030869 GPX: correct URL for Cluetrust.
Part of #2074.
2025-03-16 08:59:37 +01:00
Dennis Guse 074484393e Bugfix: Markers without photo could not be deleted (created pre v4.18.0).
Introduced in 1b33184835

Fixes #2071.
2025-03-15 20:36:00 +01:00
Dennis Guse 08a231c64f README: added information for backups.
Fixes #2072.
2025-03-13 06:42:23 +01:00
Dennis Guse 07cbda5faa Translations: TA had invalid placeholders. 2025-03-08 18:12:21 +01:00
34 changed files with 422 additions and 156 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.33.0
uses: reactivecircus/android-emulator-runner@v2.34.0
with:
arch: x86_64
api-level: 34
+14
View File
@@ -135,6 +135,7 @@ The Public API is disabled by default to protect the user's privacy, but it can
| [FitTrackee](https://github.com/SamR1/FitTrackee) | yes | n/a | n/a |
| [SportsTracker](https://github.com/ssaring/sportstracker) | yes, [single tracks only](https://github.com/ssaring/sportstracker/issues/260) | no | no |
| [ExifTool](https://exiftool.org) | [yes](https://exiftool.org/forum/index.php?topic=15972.0) | no | no |
| [Wanderer](https://wanderer.to/) | yes | yes | yes |
## Dashboard API (incl. map)
@@ -153,6 +154,19 @@ slow and does not provide updates while recording.
The developer documentation is in [README_API.md](README_API.md).
## Backup
OpenTracks stores the data in the app-internal space:
`/data/data/de.dennisguse.opentracks[|.playstore]`
There is a SQLite database `database.db` that contains the tracks as well as the markers.
Pictures attached to markers are stored as separate files.
For backup and recovery, the best approach is to export as one KMZ file.
This will contain all the tracks, pictures and the data as similar as possible as stored internally.
In difference to backing up the database directly, a KMZ allows to be imported into a different (
e.g., newer) version of OpenTracks.
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
+8 -8
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.9.0'
classpath 'com.android.tools.build:gradle:8.9.2'
}
}
@@ -33,7 +33,7 @@ android {
targetCompatibility JavaVersion.VERSION_17
}
compileSdk = 35
compileSdk = 36
buildFeatures {
viewBinding = true
@@ -46,13 +46,13 @@ android {
defaultConfig {
applicationId 'de.dennisguse.opentracks'
versionCode 6095
versionName "v4.18.0"
versionCode 6124
versionName "v4.19.2"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdk = 26
targetSdk = 35
targetSdk = 36
testInstrumentationRunner 'de.dennisguse.opentracks.TestRunner'
testInstrumentationRunnerArguments clearPackageData: 'true'
@@ -145,10 +145,10 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.gridlayout:gridlayout:1.1.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.core:core:1.15.0'
implementation 'androidx.core:core:1.16.0'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.mediarouter:mediarouter:1.7.0'
implementation 'androidx.core:core-location-altitude:1.0.0-alpha03'
@@ -158,7 +158,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.6.1'
androidTestImplementation 'androidx.test:runner:1.6.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
androidTestImplementation 'org.mockito:mockito-android:5.16.0'
androidTestImplementation 'org.mockito:mockito-android:5.17.0'
androidTestUtil 'androidx.test:orchestrator:1.5.1'
}
@@ -0,0 +1,11 @@
v4.18.0: OpenTracks
Změny:
- Varování o zakázané poloze lze vypnout
Oprava chyby:
- BottomAppBar překrýval obsah
Vývojář:
- Použití zaznamenané pozice namísto polohy systému Android
- Předělány agregátory senzorů
@@ -0,0 +1,7 @@
v4.19.0: OpenTracks
Changes:
- GPX export was not compliant (TrackPointExtension order and content)
Bugfix:
- pre-v4.18.0 markers without pictures could not be deleted
@@ -0,0 +1,4 @@
v4.19.1: OpenTracks
Bugfix:
- Exported GPX files missed one schemaLocation
@@ -0,0 +1,5 @@
v4.19.2: OpenTracks
Bugfix:
- Fix crash due to faulty translations
@@ -0,0 +1,11 @@
v4.18.0: OpenTracks
Athruithe:
- Is féidir rabhadh faoi mhíchumas GPS a dhíchumasú
Ceartúchán Bug:
- Ábhar forluiteach BottomAppBar
Forbróir:
- Úsáid taifead Seasamh in ionad Android suíomh
- Comhbhailitheoirí braite athfhachtóirithe
@@ -0,0 +1,11 @@
v4.18.0: OpenTracks
Wijzigingen:
- GPS uitgeschakeld waarschuwing kan worden uitgeschakeld
Bugfix:
- BottomAppBar overlapte inhoud
Ontwikkelaar:
- Gebruik recordpositie in plaats van Android-locatie
- Gerefactoreerde sensor-aggregators
@@ -0,0 +1,11 @@
v4.18.0: OpenTracks
更改:
- GPS 禁用警告可被禁用
错误修复:
- BottomAppBar 重叠内容
开发相关:
- 使用记录位置代替 Android 位置
- 重构了传感器聚合器
@@ -312,7 +312,7 @@ public class ExportImportTest {
contentProviderUtils.deleteTrack(context, trackId);
// 2. import
KmzTrackImporter importer = new KmzTrackImporter(context, trackImporter);
KMZTrackImporter importer = new KMZTrackImporter(context, trackImporter);
importTrackId = importer.importFile(tmpFileUri).get(0);
// then
@@ -358,7 +358,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
importTrackId = importer.importFile(inputStream).get(0);
// then
@@ -383,7 +383,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
importTrackId = importer.importFile(inputStream).get(0);
// then
@@ -503,7 +503,7 @@ public class ExportImportTest {
// 2. import
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
importTrackId = importer.importFile(inputStream).get(0);
// then
@@ -68,7 +68,7 @@ public class GPXTrackImporterTest {
@Test
public void gpx_with_pause_resume() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_pause_resume);
// when
@@ -132,7 +132,7 @@ public class GPXTrackImporterTest {
@Test
public void gpx_without_speed() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_without_speed);
// when
@@ -187,7 +187,7 @@ public class GPXTrackImporterTest {
@Test
public void gpx_speed_no_namespace() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_with_speed_no_namespace);
// when
@@ -238,11 +238,86 @@ public class GPXTrackImporterTest {
), importedTrackPoints);
}
/**
* until v4.18.0: some extensions where incorrectly added to gpxtpx:TrackPointExtension
* We only need to check the trackpoints.
*/
@LargeTest
@Test
public void gpx_legacy_trackpointextension() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_trackpointextensions_incorrect);
// when
// 1. import
importTrackId = importer.importFile(inputStream).get(0);
// then: We only need to check the trackpoints.
List<TrackPoint> actual = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
TrackPointAssert a = new TrackPointAssert()
.setDelta(0.05); // speed is not fully
a.assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3d, 14d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(1f)
.setAltitudeGain(1f),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:17Z"),
3d, 14.001, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(5)))
.setAltitudeLoss(1f)
.setAltitudeGain(1f)
.setSensorDistance(Distance.of(12))
.setHeartRate(69)
.setPower(50f)
.setCadence(3f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2020-02-02T02:03:21Z"),
3d, 14.002, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
.setAltitudeGain(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC,
new Position(
Instant.parse("2020-02-02T02:03:22Z"),
3d, 16d, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
.setAltitudeGain(0f),
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:50Z"),
3d, 16.001, Distance.of(10),
Altitude.WGS84.of(10), null,
null,
Speed.of(10)))
.setAltitudeLoss(0f)
.setAltitudeGain(0f)
.setSpeed(Speed.of(15))
), actual);
}
@LargeTest
@Test
public void importExportTest_timezone() throws IOException {
// given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new GPXTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
@@ -62,7 +62,7 @@ public class KMLTrackImporterTest {
@Test
public void kml22_order_location_and_when() throws IOException {
// given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_order_location_and_when);
// when
@@ -118,7 +118,7 @@ public class KMLTrackImporterTest {
@Test
public void kml22_time_decreases() throws IOException {
// given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_time_decreases);
// when
@@ -159,7 +159,7 @@ public class KMLTrackImporterTest {
@Test
public void kml22_with_statistics_marker() throws IOException {
// given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_kml22_statistics_marker);
// when
@@ -243,7 +243,7 @@ public class KMLTrackImporterTest {
@Test(expected = ImportParserException.class)
public void kml_without_locations() throws IOException {
// given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_without_locations);
// when
@@ -254,7 +254,7 @@ public class KMLTrackImporterTest {
@Test(expected = ImportParserException.class)
public void kml_when_locations_different() throws IOException {
// given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter importer = new XMLImporter(new KMLTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_when_locations_different);
// when
+1 -1
View File
@@ -10,7 +10,7 @@ xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
xmlns:cluetrust="http://www.cluetrust.com/Schemas/"
xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 http://www.cluetrust.com/Schemas http://www.cluetrust.com/Schemas/gpxdata10.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 https://www8.garmin.com/xmlschemas/TrackStatsExtension.xsd http://www.cluetrust.com/Schemas https://www.cluetrust.com/Schemas/gpxdata10.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
<wpt lat="3" lon="14">
<ele>10</ele>
<time>2021-01-07T21:51:59.179+01:15</time>
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<gpx
version="1.1"
creator="OpenTracks (Debug)"
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:topografix="http://www.topografix.com/GPX/Private/TopoGrafix/0/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1"
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v2"
xmlns:gpxtrkx="http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
xmlns:cluetrust="http://www.cluetrust.com/Schemas/"
xmlns:pwr="http://www.garmin.com/xmlschemas/PowerExtension/v1"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 http://www.cluetrust.com/Schemas https://www.cluetrust.com/Schemas/gpxdata10.xsd http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
<wpt lat="3" lon="14">
<ele>10</ele>
<time>2020-02-02T03:02:03+01:00</time>
<name><![CDATA[Marker 1]]></name>
<desc><![CDATA[Marker 1 desc]]></desc>
<type><![CDATA[Marker 1 category]]></type>
</wpt>
<wpt lat="3" lon="14.001">
<ele>10</ele>
<time>2020-02-02T03:02:17+01:00</time>
<name><![CDATA[Marker 2]]></name>
<desc><![CDATA[Marker 2 desc]]></desc>
<type><![CDATA[Marker 2 category]]></type>
</wpt>
<trk>
<name><![CDATA[2020-02-02T03:02+01]]></name>
<desc><![CDATA[the description]]></desc>
<type><![CDATA[mountain biking]]></type>
<extensions>
<topografix:color>c0c0c0</topografix:color>
<opentracks:trackid>75c581d0-eef5-4aa2-91a3-66d2c6de4f6d</opentracks:trackid>
<opentracks:typeTranslated><![CDATA[the activity type]]></opentracks:typeTranslated>
<gpxtrkx:TrackStatsExtension>
<gpxtrkx:Distance>222125.53125</gpxtrkx:Distance>
<gpxtrkx:TimerTime>56</gpxtrkx:TimerTime>
<gpxtrkx:MovingTime>26</gpxtrkx:MovingTime>
<gpxtrkx:StoppedTime>30</gpxtrkx:StoppedTime>
<gpxtrkx:MaxSpeed>8543.2900390625</gpxtrkx:MaxSpeed>
<gpxtrkx:Ascent>2.0</gpxtrkx:Ascent>
<gpxtrkx:Descent>2.0</gpxtrkx:Descent>
</gpxtrkx:TrackStatsExtension>
</extensions>
<trkseg>
<trkpt lat="3" lon="14">
<ele>10</ele>
<time>2020-02-02T03:02:03+01:00</time>
<extensions><gpxtpx:TrackPointExtension>
<gpxtpx:speed>15</gpxtpx:speed>
<opentracks:gain>1</opentracks:gain>
<opentracks:loss>1</opentracks:loss>
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
</trkpt>
<trkpt lat="3" lon="14.001">
<ele>10</ele>
<time>2020-02-02T03:02:17+01:00</time>
<extensions><gpxtpx:TrackPointExtension>
<gpxtpx:speed>5</gpxtpx:speed>
<gpxtpx:hr>69</gpxtpx:hr>
<gpxtpx:cad>3</gpxtpx:cad>
<pwr:PowerInWatts>50</pwr:PowerInWatts>
<opentracks:gain>1</opentracks:gain>
<opentracks:loss>1</opentracks:loss>
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal><opentracks:distance>12</opentracks:distance>
<cluetrust:distance>12</cluetrust:distance>
</gpxtpx:TrackPointExtension></extensions>
</trkpt>
</trkseg>
<trkseg>
<trkpt lat="3" lon="14.002">
<ele>10</ele>
<time>2020-02-02T03:03:21+01:00</time>
<extensions><gpxtpx:TrackPointExtension>
<gpxtpx:speed>15</gpxtpx:speed>
<opentracks:gain>0</opentracks:gain>
<opentracks:loss>0</opentracks:loss>
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
</trkpt>
</trkseg>
<trkseg>
<trkpt lat="3" lon="16">
<ele>10</ele>
<time>2020-02-02T03:03:22+01:00</time>
<extensions><gpxtpx:TrackPointExtension>
<gpxtpx:speed>15</gpxtpx:speed>
<opentracks:gain>0</opentracks:gain>
<opentracks:loss>0</opentracks:loss>
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
</trkpt>
<trkpt lat="3" lon="16.001">
<ele>10</ele>
<time>2020-02-02T03:03:50+01:00</time>
<extensions><gpxtpx:TrackPointExtension>
<gpxtpx:speed>15</gpxtpx:speed>
<opentracks:gain>0</opentracks:gain>
<opentracks:loss>0</opentracks:loss>
<opentracks:accuracy_horizontal>10</opentracks:accuracy_horizontal></gpxtpx:TrackPointExtension></extensions>
</trkpt>
</trkseg>
</trk>
</gpx>
@@ -434,7 +434,14 @@ public class ContentProviderUtils {
marker.setIcon(cursor.getString(iconIndex));
}
if (!cursor.isNull(photoUrlIndex)) {
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
String photoUrl = cursor.getString(photoUrlIndex);
if (photoUrl.isEmpty()) {
// Before v4.18.0: a marker without a picture as URL ""
// TODO Data should be migrated.
marker.setPhotoUrl(null);
} else {
marker.setPhotoUrl(Uri.parse(cursor.getString(photoUrlIndex)));
}
}
@@ -15,7 +15,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.CSVTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.KMZTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
/**
@@ -33,7 +33,7 @@ public enum TrackFileFormat {
@Override
public String getMimeType() {
return MIME_KML;
return "application/vnd.google-earth.kml+xml";
}
public String getExtension() {
@@ -49,12 +49,12 @@ public enum TrackFileFormat {
@Override
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
return new KMZTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
}
@Override
public String getMimeType() {
return MIME_KMZ;
return "application/vnd.google-earth.kmz";
}
public String getExtension() {
@@ -74,12 +74,12 @@ public enum TrackFileFormat {
@Override
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
return new KMZTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
}
@Override
public String getMimeType() {
return MIME_KMZ;
return KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getMimeType();
}
public String getExtension() {
@@ -126,17 +126,13 @@ public enum TrackFileFormat {
}
};
private static final String MIME_KMZ = "application/vnd.google-earth.kmz";
private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
private final String preferenceId;
TrackFileFormat(String preferenceId) {
this.preferenceId = preferenceId;
}
public static Map<String, String> toPreferenceIdLabelMap(final Resources resources, final TrackFileFormat ... trackFileFormats) {
public static Map<String, String> toPreferenceIdLabelMap(final Resources resources, final TrackFileFormat... trackFileFormats) {
Map<String, String> preferenceIdLabelMap = new LinkedHashMap<>(trackFileFormats.length);
for (TrackFileFormat trackFileFormat : trackFileFormats) {
String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case
@@ -102,7 +102,6 @@ public class GPXTrackExporter implements TrackExporter {
try {
prepare(outputStream);
writeHeader();
for (Track track : tracks) {
writeMarkers(track);
}
@@ -200,17 +199,17 @@ public class GPXTrackExporter implements TrackExporter {
}
}
public void prepare(OutputStream outputStream) {
private void prepare(OutputStream outputStream) {
this.printWriter = new PrintWriter(outputStream);
}
public void close() {
private void close() {
printWriter.flush();
printWriter = null;
}
public void writeHeader() {
private void writeHeader() {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
printWriter.println("<gpx");
printWriter.println("version=\"1.1\"");
@@ -228,12 +227,12 @@ public class GPXTrackExporter implements TrackExporter {
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
+ " http://www.garmin.com/xmlschemas/PowerExtension/v1 https://www8.garmin.com/xmlschemas/PowerExtensionv1.xsd"
+ " http://www.garmin.com/xmlschemas/TrackStatsExtension/v1"
+ " http://www.cluetrust.com/Schemas http://www.cluetrust.com/Schemas/gpxdata10.xsd"
+ " http://www.garmin.com/xmlschemas/TrackStatsExtension/v1 https://www8.garmin.com/xmlschemas/TrackStatsExtension.xsd"
+ " http://www.cluetrust.com/Schemas https://www.cluetrust.com/Schemas/gpxdata10.xsd"
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
}
public void writeFooter() {
private void writeFooter() {
printWriter.println("</gpx>");
}
@@ -253,7 +252,7 @@ public class GPXTrackExporter implements TrackExporter {
}
}
public void writeMarker(ZoneOffset zoneOffset, Marker marker) {
private void writeMarker(ZoneOffset zoneOffset, Marker marker) {
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
if (marker.hasAltitude()) {
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
@@ -265,7 +264,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("</wpt>");
}
public void writeBeginTrack(Track track) {
private void writeBeginTrack(Track track) {
printWriter.println("<trk>");
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
@@ -297,19 +296,19 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("</extensions>");
}
public void writeEndTrack() {
private void writeEndTrack() {
printWriter.println("</trk>");
}
public void writeOpenSegment() {
private void writeOpenSegment() {
printWriter.println("<trkseg>");
}
public void writeCloseSegment() {
private void writeCloseSegment() {
printWriter.println("</trkseg>");
}
public Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
private Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
Distance cumulativeDistance;
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
@@ -321,51 +320,62 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime(), zoneOffset) + "</time>");
{
String trackPointExtensionContent = "";
if (trackPoint.hasSpeed()) {
trackPointExtensionContent += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n";
}
String trackPointExtensionV2Content = "";
if (trackPoint.hasHeartRate()) {
trackPointExtensionContent += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate().getBPM()) + "</gpxtpx:hr>\n";
trackPointExtensionV2Content += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate().getBPM()) + "</gpxtpx:hr>\n";
}
if (trackPoint.hasCadence()) {
trackPointExtensionContent += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence().getRPM()) + "</gpxtpx:cad>\n";
trackPointExtensionV2Content += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence().getRPM()) + "</gpxtpx:cad>\n";
}
if (trackPoint.hasSpeed()) {
trackPointExtensionV2Content += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n";
}
String extensionContent = "";
if (trackPoint.hasPower()) {
trackPointExtensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower().getW()) + "</pwr:PowerInWatts>\n";
extensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower().getW()) + "</pwr:PowerInWatts>\n";
}
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null);
if (cumulativeGain != null) {
trackPointExtensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
extensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
}
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null);
if (cumulativeLoss != null) {
trackPointExtensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
extensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
}
if (trackPoint.hasHorizontalAccuracy()) {
trackPointExtensionContent += ("<opentracks:accuracy_horizontal>" + DISTANCE_FORMAT.format(trackPoint.getHorizontalAccuracy().toM()) + "</opentracks:accuracy_horizontal>");
extensionContent += ("<opentracks:accuracy_horizontal>" + DISTANCE_FORMAT.format(trackPoint.getHorizontalAccuracy().toM()) + "</opentracks:accuracy_horizontal>");
}
if (trackPoint.hasVerticalAccuracy()) {
trackPointExtensionContent += ("<opentracks:accuracy_vertical>" + DISTANCE_FORMAT.format(trackPoint.getVerticalAccuracy().toM()) + "</opentracks:accuracy_vertical>");
extensionContent += ("<opentracks:accuracy_vertical>" + DISTANCE_FORMAT.format(trackPoint.getVerticalAccuracy().toM()) + "</opentracks:accuracy_vertical>");
}
cumulativeDistance = Distance.ofOrNull(cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null));
if (cumulativeDistance != null) {
trackPointExtensionContent += ("<opentracks:distance>" + DISTANCE_FORMAT.format(cumulativeDistance.toM()) + "</opentracks:distance>\n");
trackPointExtensionContent += ("<cluetrust:distance>" + DISTANCE_FORMAT.format(trackDistance.plus(cumulativeDistance).toM()) + "</cluetrust:distance>\n");
extensionContent += ("<opentracks:distance>" + DISTANCE_FORMAT.format(cumulativeDistance.toM()) + "</opentracks:distance>\n");
extensionContent += ("<cluetrust:distance>" + DISTANCE_FORMAT.format(trackDistance.plus(cumulativeDistance).toM()) + "</cluetrust:distance>\n");
}
if (!trackPointExtensionContent.isEmpty()) {
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
printWriter.print(trackPointExtensionContent);
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
if (!extensionContent.isEmpty() || !trackPointExtensionV2Content.isEmpty()) {
printWriter.println("<extensions>");
if (!trackPointExtensionV2Content.isEmpty()) {
printWriter.println("<gpxtpx:TrackPointExtension>");
printWriter.print(trackPointExtensionV2Content);
printWriter.println("</gpxtpx:TrackPointExtension>");
}
if (!extensionContent.isEmpty()) {
printWriter.print(extensionContent);
}
printWriter.println("</extensions>");
}
}
@@ -107,6 +107,7 @@ public class KMLTrackExporter implements TrackExporter {
this.contentProviderUtils = contentProviderUtils;
}
@Override
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
try {
prepare(outputStream);
@@ -444,7 +445,7 @@ public class KMLTrackExporter implements TrackExporter {
writeTypeLocalized(marker.getCategory());
if (exportPhotos) {
printWriter.println("<Icon><href>" + KmzTrackExporter.buildKmzImageFilePath(marker) + "</href></Icon>");
printWriter.println("<Icon><href>" + KMZTrackExporter.buildKmzImageFilePath(marker) + "</href></Icon>");
}
printWriter.print("<ViewVolume>");
@@ -43,12 +43,12 @@ import de.dennisguse.opentracks.util.FileUtils;
*
* @author Jimmy Shih
*/
public class KmzTrackExporter implements TrackExporter {
public class KMZTrackExporter implements TrackExporter {
private static final String KMZ_IMAGES_DIR = "images";
public static final String KMZ_KML_FILE = "doc.kml";
private static final String TAG = KmzTrackExporter.class.getSimpleName();
private static final String TAG = KMZTrackExporter.class.getSimpleName();
private static final int BUFFER_SIZE = 4096;
private final ContentProviderUtils contentProviderUtils;
@@ -57,7 +57,7 @@ public class KmzTrackExporter implements TrackExporter {
private final boolean exportPhotos;
private final Context context;
public KmzTrackExporter(Context context, ContentProviderUtils contentProviderUtils, KMLTrackExporter trackExporter, boolean exportPhotos) {
public KMZTrackExporter(Context context, ContentProviderUtils contentProviderUtils, KMLTrackExporter trackExporter, boolean exportPhotos) {
this.context = context;
this.contentProviderUtils = contentProviderUtils;
this.fileTrackExporter = trackExporter;
@@ -52,9 +52,9 @@ import de.dennisguse.opentracks.util.StringUtils;
*
* @author Jimmy Shih
*/
public class GpxTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
public class GPXTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
private static final String TAG = GpxTrackImporter.class.getSimpleName();
private static final String TAG = GPXTrackImporter.class.getSimpleName();
private static final String TAG_DESCRIPTION = "desc";
private static final String TAG_ALTITUDE = "ele";
@@ -123,7 +123,7 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
private final TrackImporter trackImporter;
public GpxTrackImporter(Context context, TrackImporter trackImporter) {
public GPXTrackImporter(Context context, TrackImporter trackImporter) {
this.context = context;
this.trackImporter = trackImporter;
}
@@ -54,17 +54,16 @@ public class ImportService extends JobIntentService {
String fileExtension = FileUtils.getExtension(file);
try {
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance();
Distance recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval();
boolean preventReimport = PreferencesUtils.getPreventReimportTracks();
TrackImporter trackImporter = new TrackImporter(this, new ContentProviderUtils(this), maxRecordingDistance, preventReimport);
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
trackIds.addAll(new XMLImporter(new GpxTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
trackIds.addAll(new XMLImporter(new GPXTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
trackIds.addAll(new XMLImporter(new KmlTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
trackIds.addAll(new XMLImporter(new KMLTrackImporter(this, trackImporter)).importFile(this, file.getUri()));
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
trackIds.addAll(new KmzTrackImporter(this, trackImporter).importFile(file.getUri()));
trackIds.addAll(new KMZTrackImporter(this, trackImporter).importFile(file.getUri()));
} else {
Log.d(TAG, "Unsupported file format.");
sendResult(ImportServiceResultReceiver.RESULT_CODE_ERROR, null, file, getString(R.string.import_unsupported_format));
@@ -47,9 +47,9 @@ import de.dennisguse.opentracks.util.StringUtils;
*
* @author Jimmy Shih
*/
public class KmlTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
public class KMLTrackImporter extends DefaultHandler implements XMLImporter.TrackParser {
private static final String TAG = KmlTrackImporter.class.getSimpleName();
private static final String TAG = KMLTrackImporter.class.getSimpleName();
private static final String MARKER_STYLE = "#" + KMLTrackExporter.MARKER_STYLE;
@@ -128,7 +128,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
private final TrackImporter trackImporter;
public KmlTrackImporter(Context context, TrackImporter trackImporter) {
public KMLTrackImporter(Context context, TrackImporter trackImporter) {
this.context = context;
this.trackImporter = trackImporter;
}
@@ -44,9 +44,9 @@ import de.dennisguse.opentracks.util.FileUtils;
*
* @author Jimmy Shih
*/
public class KmzTrackImporter {
public class KMZTrackImporter {
private static final String TAG = KmzTrackImporter.class.getSimpleName();
private static final String TAG = KMZTrackImporter.class.getSimpleName();
private static final String KML_FILE_EXTENSION = ".kml";
@@ -55,7 +55,7 @@ public class KmzTrackImporter {
private final Context context;
private final TrackImporter trackImporter;
public KmzTrackImporter(Context context, TrackImporter trackImporter) {
public KMZTrackImporter(Context context, TrackImporter trackImporter) {
this.context = context;
this.trackImporter = trackImporter;
}
@@ -211,7 +211,7 @@ public class KmzTrackImporter {
}
private List<Track.Id> parseKml(ZipInputStream zipInputStream) throws IOException {
XMLImporter kmlFileTrackImporter = new XMLImporter(new KmlTrackImporter(context, trackImporter));
XMLImporter kmlFileTrackImporter = new XMLImporter(new KMLTrackImporter(context, trackImporter));
InputStream nonClosableInputStream = new FilterInputStream(zipInputStream) {
@Override
@@ -233,7 +233,7 @@ public class TrackImporter {
* @param externalPhotoUrl the file name
*/
private Uri getInternalPhotoUrl(@NonNull Track.Id trackId, @NonNull Uri externalPhotoUrl) {
String importFileName = KmzTrackImporter.importNameForFilename(externalPhotoUrl.toString());
String importFileName = KMZTrackImporter.importNameForFilename(externalPhotoUrl.toString());
File file = MarkerUtils.buildInternalPhotoFile(context, trackId, Uri.parse(importFileName));
if (file != null) {
return FileUtils.getUriForFile(context, file);
@@ -47,7 +47,7 @@ public class XMLImporter {
return parser.getImportTrackIds();
} catch (SAXException | ParserConfigurationException | ParsingException e) {
Log.e(TAG, "Unable to import file", e);
if (parser.getImportTrackIds().size() > 0) {
if (!parser.getImportTrackIds().isEmpty()) {
parser.cleanImport();
}
throw new ImportParserException(e);
@@ -3,24 +3,14 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/export_activity_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/custom_layout_list_title" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="60dp">
android:paddingBottom="?attr/actionBarSize">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/rows_options_layout"
@@ -50,16 +40,16 @@
android:id="@+id/recycler_view_visible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/recycler_view_not_visible" />
app:layout_constraintBottom_toTopOf="@id/recycler_view_not_visible"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_not_visible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/recycler_view_visible"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/recycler_view_visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,16 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/export_activity_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/custom_layout_list_title" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:paddingTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:paddingBottom="60dp">
android:fitsSystemWindows="true"
android:orientation="vertical">
<LinearLayout
android:id="@+id/custom_layout_list_add_linear_layout"
@@ -18,25 +30,24 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:layout_marginBottom="50dp"
android:visibility="gone"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/custom_layout_list_input_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
app:errorEnabled="true"
android:layout_margin="16dp">
android:layout_margin="16dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/custom_layout_list_edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapSentences"
android:hint="@string/custom_layout_list_edit_name"
android:imeOptions="actionDone"
android:hint="@string/custom_layout_list_edit_name" />
android:inputType="textCapSentences" />
</com.google.android.material.textfield.TextInputLayout>
@@ -50,9 +61,9 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/custom_layout_list_cancel_button"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.TextButton"
android:text="@android:string/cancel" />
<com.google.android.material.button.MaterialButton
+1 -1
View File
@@ -21,7 +21,6 @@ limitations under the License.
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="90dp"
android:fitsSystemWindows="true">
<com.google.android.material.tabs.TabLayout
@@ -32,6 +31,7 @@ limitations under the License.
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/track_detail_activity_view_pager"
android:layout_width="match_parent"
android:paddingBottom="?attr/actionBarSize"
android:layout_height="0dp"
android:layout_weight="1" />
+1 -1
View File
@@ -9,7 +9,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="90dp"
android:fitsSystemWindows="true">
<com.google.android.material.tabs.TabLayout
@@ -20,6 +19,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/track_recording_coordinator_layout"
android:layout_width="match_parent"
android:paddingBottom="?attr/actionBarSize"
android:layout_height="0dp"
android:layout_weight="1">
+1 -1
View File
@@ -637,7 +637,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="settings_announcements_unit">Distanz/Geschwindigkeits Einheit</string>
<string name="power_x_watt">Leistung {x, plural, =1 {1 Watt} other {# Watt} }</string>
<string name="power">Leistung</string>
<string name="voiceDistance">{n, plural, =1 {1 } anderes {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
<string name="settings_recording_min_sampling_interval_title">Abtastzeitintervall</string>
<string name="voiceIdle">Untätig werden.</string>
<string name="voice_announce_lap_power_key">StimmeAnkündigungLapPower</string>
+3 -3
View File
@@ -657,11 +657,11 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="permission_recording_failed">OpenTracks a besoin de la permission d\'accéder à votre localisation ainsi qu\'aux appareils à proximité.</string>
<string name="create_marker_error">Impossible de créer un marqueur. Veuillez réessayer.</string>
<string name="power">Puissance</string>
<string name="power_x_watt">Puissance {x, plural, =1 {1 watt} autres {# watt} }</string>
<string name="power_x_watt">Puissance {x, plural, =1 {1 watt} other {# watt} }</string>
<string name="settings_announcements_lap_power">Puissance par tour</string>
<string name="activity_type_kickscooter">trottinette</string>
<string name="voiceSpeed">{n, plural, =1 {1} autres {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } autres {{n,number,#.0}} }</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
<string name="settings_locale_system_default">Paramètres par défaut du système</string>
<string name="settings_locale_title">Langue</string>
<string name="stats_gain_loss">Gain et perte</string>
+12 -12
View File
@@ -507,11 +507,11 @@
<string name="settings_announcements_unit">Aonad faid/luas</string>
<string name="value_internal_sensor">Braiteoir inmheánach</string>
<string name="voiceIdle">Ag éirí díomhaoin.</string>
<string name="voiceSecondsPlural">{n, iolra, =1 {1 soicind} eile {{ n,uimhir} soicind} }</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, iolra, =1 {1 chiliméadar in aghaidh na huaire} eile {{ n,uimhir, #.0} ciliméadar san uair} }</string>
<string name="voiceSpeedMKnotsPlural">{n, iolra, =1 {1 snaidhm} eile {{ n,uimhir, #.0} snaidhm} }</string>
<string name="voiceDistanceNauticalMilesPlural">{n, iolra, =1 {1 mhuirmhíle} eile {{ n,uimhir, #.0} muirmhíle} }</string>
<string name="voiceDistance">{n, iolra, =1 {1 } eile {{ n,uimhir, #.0}} }</string>
<string name="voiceSecondsPlural">{n, plural, =1 {1 soicind} other {{ n,number} soicind} }</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, plural, =1 {1 chiliméadar in aghaidh na huaire} other {{ n,number, #.0} ciliméadar san uair} }</string>
<string name="voiceSpeedMKnotsPlural">{n, plural, =1 {1 snaidhm} other {{ n,number, #.0} snaidhm} }</string>
<string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 mhuirmhíle} other {{ n,number, #.0} muirmhíle} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{ n,number, #.0}} }</string>
<string name="settings_recording_min_sampling_interval_title">Eatramh ama samplála</string>
<string name="sensor_state_cadence_unit">rpm</string>
<string name="sensor_state_heart_rate_unit">bpm</string>
@@ -527,20 +527,20 @@
<string name="settings_api_summary">Cianrialú agus staitisticí API le haghaidh aipeanna eile</string>
<string name="create_marker_error">Ní féidir marcóir a chruthú. Déan iarracht arís.</string>
<string name="sensor_state_power_unit">W</string>
<string name="voiceMinutesPlural">{n, iolra, =1 {1 nóiméad} eile {{ n,uimhir} nóiméad} }</string>
<string name="voiceDistanceKilometersPlural">{n, iolra, =1 {1 chiliméadar} eile {{ n,uimhir, #.0} ciliméadar} }</string>
<string name="voiceMinutesPlural">{n, plural, =1 {1 nóiméad} other {{ n,number} nóiméad} }</string>
<string name="voiceDistanceKilometersPlural">{n, plural, =1 {1 chiliméadar} other {{ n,number, #.0} ciliméadar} }</string>
<string name="sensor_state_power_max">Cumhacht Max</string>
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="voiceHoursPlural">{n, iolra, =1 {1 uair} eile {{ n,uimhir} uair an chloig} }</string>
<string name="voiceDistanceMilesPlural">{n, iolra, =1 {1 mhíle} eile {{ n,uimhir, #.0} míle} }</string>
<string name="voiceHoursPlural">{n, plural, =1 {1 uair} other {{ n,number} uair an chloig} }</string>
<string name="voiceDistanceMilesPlural">{n, plural, =1 {1 mhíle} other {{ n,number, #.0} míle} }</string>
<string name="no_compatible_camera_installed">Níl feidhmchlár ceamara comhoiriúnach suiteáilte</string>
<string name="voiceSpeedMilesPerHourPlural">{n, iolra, =1 {1 mhíle san uair} eile {{ n,uimhir, #.0} míle san uair} }</string>
<string name="voiceSpeed">{n, iolra, =1 {1} eile {{ n,uimhir, #.0}} }</string>
<string name="voiceSpeedMilesPerHourPlural">{n, plural, =1 {1 mhíle san uair} other {{ n,number, #.0} míle san uair} }</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{ n,number, #.0}} }</string>
<string name="voice_on_device_speaker_title">Úsáid cainteoir an ghléis</string>
<string name="no_compatible_gallery_installed">Níl feidhmchlár gailearaí íomhá comhoiriúnach suiteáilte</string>
<string name="settings_recording_location_distance_summary">%1$s idir suíomhanna taifeadta</string>
<string name="settings_announcements_lap_power">Cumhacht glúine</string>
<string name="power_x_watt">Cumhacht {x, iolra, = 1 {1 vata} eile {# vata} }</string>
<string name="power_x_watt">Cumhacht {x, plural, = 1 {1 vata} other {# vata} }</string>
<string name="empty">" "</string>
<string name="settings_locale_system_default">Réamhshocrú córais</string>
<string name="settings_locale_title">Teanga</string>
+22 -22
View File
@@ -65,7 +65,7 @@
<string name="settings_recording_location_distance_summary">பதிவுசெய்யப்பட்ட இடங்களுக்கு இடையில் %1$s</string>
<string name="voice_announce_lap_power_key">VoiceAnnouncelappower</string>
<string name="settings_announcements_lap_power">மடியில் ஆற்றல்</string>
<string name="power_x_watt">ஆற்றல் {x, பன்மை, = 1 {1 watt} பிற {# வாட்ச்}}</string>
<string name="power_x_watt">ஆற்றல் {x, plural, = 1 {1 watt} other {# வாட்ச்}}</string>
<string name="power">விசை</string>
<string name="help_feature_content">* சி.பி.எச் தடங்களை உருவாக்கவும்\n * இதய துடிப்பு சென்சார் பயன்படுத்தவும் (புளூடூத் லு மட்டும்)\n * குரல் அறிவிப்புகள் (நேரம் அல்லது தூரத்தின் அடிப்படையில்)\n * குறிப்பான்களை உருவாக்கு (படங்கள் உள்ளிட்டவை)\n * முற்றிலும் ஆஃப்லைனில் வேலை செய்கிறது\n * வரைபடத்தில் தடங்களைக் காண்பி (கீழே காண்க)</string>
<string name="help_track_map_title">வரைபடத்தில் ஒரு பாதையை எவ்வாறு காண்பிப்பது?</string>
@@ -110,16 +110,16 @@
<string name="activity_type_workout">பயிற்சி</string>
<string name="description_activity_type">செயல்பாட்டு வகை: %1$s</string>
<string name="description_average_moving_pace_in_minute">சராசரி நகரும் வேகம்: %1$s min/km ( %2$s min/mi)</string>
<string name="description_average_moving_speed">சராசரி நகரும் வேகம்: %1 $ .2f கிமீ/மணி ( %2 $ .1f மைல்/எச்)</string>
<string name="description_average_moving_speed">சராசரி நகரும் வேகம்: %1$.2f கிமீ/மணி ( %2$.1f மைல்/எச்)</string>
<string name="description_average_pace_in_minute">சராசரி வேகம்: %1$s min/km ( %2$s min/mi)</string>
<string name="description_average_speed">சராசரி வேகம்: %1 $ .2f கிமீ/மணி ( %2 $ .1f Mi/h)</string>
<string name="description_average_speed">சராசரி வேகம்: %1$.2f கிமீ/மணி ( %2$.1f Mi/h)</string>
<string name="description_altitude_gain">உயர்வு ஆதாயம்: %1$d மீ ( %2$d அடி)</string>
<string name="description_altitude_loss">உயர இழப்பு: %1$d மீ ( %2$d அடி)</string>
<string name="description_altitude_imperial">உயரம் (அடி)</string>
<string name="description_altitude_metric">உயரம் (மீ)</string>
<string name="description_fastest_pace_in_minute">வேகமான வேகம்: %1$s min/km ( %2$s min/mi)</string>
<string name="description_max_altitude">அதிகபட்ச உயர்வு: %1$d மீ ( %2$d அடி)</string>
<string name="description_max_speed">அதிகபட்ச வேகம்: %1 $ .2f கிமீ/மணி ( %2 $ .1f Mi/h)</string>
<string name="description_max_speed">அதிகபட்ச வேகம்: %1$.2f கிமீ/மணி ( %2$.1f Mi/h)</string>
<string name="description_moving_time">நகரும் நேரம்: %1$s</string>
<string name="description_pace_imperial">விரைவு (நிமிடம்/மை)</string>
<string name="description_pace_nautical">விரைவு (நிமிடம்/என்.எம்)</string>
@@ -134,7 +134,7 @@
<string name="description_speed_nautical">விரைவு (முடிச்சுகள்)</string>
<string name="description_speed_metric">விரைவு (கிமீ/மணிநேரம்)</string>
<string name="description_time">நேரம்</string>
<string name="description_total_distance">மொத்த தூரம்: %1 $ .2f கி.மீ ( %2 $ .1f மை)</string>
<string name="description_total_distance">மொத்த தூரம்: %1$.2f கி.மீ ( %2$.1f மை)</string>
<string name="description_total_time">மொத்த நேரம்: %1$s</string>
<string name="description_speed_source_gps">உலக இடம் காட்டும் அமைப்பு</string>
<string name="export_with_photos">புகைப்படங்களுடன்</string>
@@ -251,7 +251,7 @@
<string name="sensor_state_power_avg">ஏ.வி.சி ஆற்றல்</string>
<string name="sensor_state_power_max">அதிகபட்ச ஆற்றல்</string>
<string name="sensor_state_power_unit">W</string>
<string name="sensor_could_not_scan">ஊடலை சாதனங்களுக்கு ச்கேன் செய்ய முடியவில்லை (பிழை %1 $ i).</string>
<string name="sensor_could_not_scan">ஊடலை சாதனங்களுக்கு ச்கேன் செய்ய முடியவில்லை (பிழை %1$i).</string>
<string name="bluetooth_disabled">ப்ளூடூத்தை இயக்கவும்.</string>
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="settings_defaults_title">இயல்புநிலை அலகுகள் மற்றும் செயல்பாடு</string>
@@ -394,16 +394,16 @@
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="value_integer_feet_recommended">%1$d அடி (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_integer_kilometer_hour">%1$d கிமீ/மணி</string>
<string name="value_float_kilometer_hour">%1 $ .1f கிமீ/மணி</string>
<string name="value_float_kilometer_hour">%1$.1f கிமீ/மணி</string>
<string name="value_integer_meter_excellent_gps">%1$d மீ (சிறந்த சி.பி.எச்)</string>
<string name="value_integer_meter_poor_gps">%1$d மீ (மோசமான சி.பி.எச்)</string>
<string name="value_integer_meter_recommended">%1$d மீ (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_integer_mile_hour">%1$d Mi/h</string>
<string name="value_integer_knots">%1$d முடிச்சுகள்</string>
<string name="value_float_mile_hour">%1 $ .1f Mi/h</string>
<string name="value_float_knots">%1 $ .1f முடிச்சுகள்</string>
<string name="value_float_mile_hour_recommended">%1 $ .1f Mi/h (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_float_knots_recommended">%1 $ .1f முடிச்சுகள் (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_float_mile_hour">%1$.1f Mi/h</string>
<string name="value_float_knots">%1$.1f முடிச்சுகள்</string>
<string name="value_float_mile_hour_recommended">%1$.1f Mi/h (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_float_knots_recommended">%1$.1f முடிச்சுகள் (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_integer_second">%1$d கள்</string>
<string name="value_none">எதுவுமில்லை</string>
<string name="value_off">அணை</string>
@@ -411,13 +411,13 @@
<string name="value_smallest_recommended">சிறியது (பரிந்துரைக்கப்படுகிறது)</string>
<string name="value_int_seconds">%1$d கள் (பரிந்துரைக்கப்படுகிறது)</string>
<string name="voiceIdle">சும்மா ஆக.</string>
<string name="voiceHoursPlural">{n, பன்மை, = 1 {1 hour} பிற {{n, எண்} மணிநேரம்}}</string>
<string name="voiceMinutesPlural">{n, பன்மை, = 1 {1 minute} பிற {{n, எண்} நிமிடங்கள்}}</string>
<string name="voiceSecondsPlural">{n, பன்மை, = 1 {1 second} பிற {{n, எண்} விநாடிகள்}}</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, பன்மை, = 1 {1 kilometer per hour} பிற {{n, எண்,#. 0} ஒரு மணி நேரத்திற்கு கிலோமீட்டர்}}</string>
<string name="voiceSpeedMKnotsPlural">{n, பன்மை, = 1 {1 knot} பிற {{n, எண்,#. 0} முடிச்சுகள்}}</string>
<string name="voiceSpeedMilesPerHourPlural">{n, பன்மை, = 1 {1 mile per hour} பிற {{n, எண்,#. 0} ஒரு மணி நேரத்திற்கு மைல்கள்}}</string>
<string name="voiceSpeed">{n, பன்மை, = 1 {1} பிற {{n, எண்,#. 0}}}</string>
<string name="voiceHoursPlural">{n, plural, = 1 {1 hour} other {{n,number} மணிநேரம்}}</string>
<string name="voiceMinutesPlural">{n, plural, = 1 {1 minute} other {{n,number} நிமிடங்கள்}}</string>
<string name="voiceSecondsPlural">{n, plural, = 1 {1 second} other {{n,number} விநாடிகள்}}</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, plural, = 1 {1 kilometer per hour} other {{n,number,#. 0} ஒரு மணி நேரத்திற்கு கிலோமீட்டர்}}</string>
<string name="voiceSpeedMKnotsPlural">{n, plural, = 1 {1 knot} other {{n,number,#. 0} முடிச்சுகள்}}</string>
<string name="voiceSpeedMilesPerHourPlural">{n, plural, = 1 {1 mile per hour} other {{n,number,#. 0} ஒரு மணி நேரத்திற்கு மைல்கள்}}</string>
<string name="voiceSpeed">{n, plural, = 1 {1} other {{n,number,#. 0}}}</string>
<string name="voice_on_device_speaker_title">சாதனத்தின் ச்பீக்கரைப் பயன்படுத்தவும்</string>
<string name="voice_per_kilometer">ஒரு கிலோமீட்டருக்கு</string>
<string name="voice_per_nautical_mile">ஒரு கடல் மைலுக்கு</string>
@@ -426,10 +426,10 @@
<string name="lap_speed">மடியில் விரைவு</string>
<string name="speed">சராசரி நகரும் விரைவு</string>
<string name="total_distance">மொத்த தூரம்</string>
<string name="voiceDistanceKilometersPlural">{n, பன்மை, = 1 {1 kilometer} பிற {{n, எண்,#. 0} கிலோமீட்டர்}}</string>
<string name="voiceDistanceMilesPlural">{n, பன்மை, = 1 {1 mile} பிற {{n, எண்,#. 0} மைல்கள்}}</string>
<string name="voiceDistanceNauticalMilesPlural">{n, பன்மை, = 1 {1 nautical mile} பிற {{n, எண்,#. 0} கடல் மைல்கள்}}</string>
<string name="voiceDistance">{n, பன்மை, = 1 {1 } பிற {{n, எண்,#. 0}}}</string>
<string name="voiceDistanceKilometersPlural">{n, plural, = 1 {1 kilometer} other {{n,number,#. 0} கிலோமீட்டர்}}</string>
<string name="voiceDistanceMilesPlural">{n, plural, = 1 {1 mile} other {{n,number,#. 0} மைல்கள்}}</string>
<string name="voiceDistanceNauticalMilesPlural">{n, plural, = 1 {1 nautical mile} other {{n,number,#. 0} கடல் மைல்கள்}}</string>
<string name="voiceDistance">{n, plural, = 1 {1 } other {{n,number,#. 0}}}</string>
<string name="average_heart_rate">சராசரி இதய துடிப்பு</string>
<string name="current_heart_rate">தற்போதைய இதய துடிப்பு</string>
<string name="lap_heart_rate">மடியில் இதய துடிப்பு</string>