Compare commits

...

122 Commits

Author SHA1 Message Date
Dennis Guse 0f05100807 Release: v4.20.0 2025-06-27 21:01:11 +02:00
Fjuro d16de9789f Translated using Weblate (Czech)
Currently translated at 100.0% (166 of 166 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-27 15:51:59 +02:00
mcliquid ca809bde6b Translated using Weblate (German)
Currently translated at 100.0% (540 of 540 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/de/
2025-06-27 15:51:58 +02:00
Fjuro 692650cf99 Translated using Weblate (Czech)
Currently translated at 100.0% (540 of 540 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/cs/
2025-06-27 15:51:58 +02:00
Patrick Santa Maria 9bf77c41a0 Translated using Weblate (Corsican)
Currently translated at 100.0% (540 of 540 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/co/
2025-06-27 15:51:58 +02:00
Aindriú Mac Giolla Eoin 3986172095 Translated using Weblate (Irish)
Currently translated at 100.0% (166 of 166 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-27 15:51:58 +02:00
Ghost of Sparta 943e05f17d Translated using Weblate (Hungarian)
Currently translated at 100.0% (540 of 540 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/hu/
2025-06-27 15:51:58 +02:00
Aindriú Mac Giolla Eoin 65e7044d49 Translated using Weblate (Irish)
Currently translated at 100.0% (540 of 540 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ga/
2025-06-27 15:51:58 +02:00
Aindriú Mac Giolla Eoin b072635a32 Translated using Weblate (Irish)
Currently translated at 100.0% (165 of 165 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-27 15:51:58 +02:00
Fjuro 9237693063 Translated using Weblate (Czech)
Currently translated at 100.0% (165 of 165 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-27 15:51:58 +02:00
Dennis Guse 4999b54a97 Reworked MarkerDetail UI. 2025-06-27 15:51:51 +02:00
Dennis Guse 0dca1461e1 Cleanup: list adapters. 2025-06-27 15:51:51 +02:00
Dennis Guse 2e13054042 Cleanup: TrackPoint exposes Position. 2025-06-27 15:51:51 +02:00
Dennis Guse 3eba204b9d MarkerDetail page shows name in title. 2025-06-27 15:51:51 +02:00
Dennis Guse 8b8eaa5ea9 MarkerDetailActivity must call onDestroy(). 2025-06-27 15:51:51 +02:00
dependabot[bot] c0b6b9e5e4 Bump com.android.tools.build:gradle from 8.10.1 to 8.11.0
Bumps com.android.tools.build:gradle from 8.10.1 to 8.11.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 20:09:43 +02:00
Dennis Guse da109fb8fe Import+Export activity: removed "require twice back navigation".
onBackPressed() is not support anymore as of Android gradle plugin 8.11.0
2025-06-25 19:58:40 +02:00
Dennis Guse fca4206c86 Migrated MarkerDetailActivity to ViewPager2. 2025-06-25 19:44:38 +02:00
Dennis Guse a2531c8651 Cleanup: MarkerDetailFragment uses MenuProvider. 2025-06-25 19:44:38 +02:00
Dennis Guse 7b4ab81f5c Marker: replaced orange pushpin. 2025-06-25 19:44:38 +02:00
Dennis Guse 90446639b0 Cleanup: remove TrackUtils by inlining functionality. 2025-06-25 19:44:38 +02:00
Dennis Guse 8744724c0f Enable to edit timezone offset for recorded tracks.
Fixes #2014.
2025-06-25 19:44:38 +02:00
Dennis Guse 1113cb1764 Bugfix: always update track meta data. 2025-06-25 19:44:38 +02:00
Dennis Guse f9466b7347 Cleanup: inline TrackEdit UI into one file. 2025-06-25 19:44:38 +02:00
Dennis Guse 45be184d76 Upgrade to Gradlew 8.14.2 2025-06-25 19:43:38 +02:00
Dennis Guse 730ace0b60 Cleanup: moved colors into colors.xml 2025-06-24 10:37:52 +02:00
Dennis Guse b662a8bc42 Launcher icon was sometimes incorrectly scaled (e.g., kvaesitso).
Use same icon as OSMDashboard for launcher (w/o background).

This time also for the reproducible build.

Fixes #2108.
2025-06-23 09:28:25 +02:00
Dennis Guse a0b4f78977 Cleanup: remove support for launcher icons before SDK26.
Part of #2108.
2025-06-23 08:54:20 +02:00
Dennis Guse 6334c03dbc Edge-2-edge: adjust for virtual keyboard changes.
Part of #2073.
2025-06-22 22:28:45 +02:00
Dennis Guse 57fd2fd160 Track edit: description should be multiline. 2025-06-22 22:14:51 +02:00
Dennis Guse d76131a34f Release: v4.19.6 2025-06-22 21:41:51 +02:00
Dennis Guse 3bae0869c4 Launcher icon was sometimes incorrectly scaled (e.g., kvaesitso).
Use same icon as OSMDashboard for launcher (w/o background).

This time also for the reproducible build.

Fixes #2108.
2025-06-21 17:22:39 +02:00
Dennis Guse a164876615 Edge-2-edge for Android 10 and below requires a quirk.
Part of #2073.
2025-06-20 19:13:05 +02:00
Dennis Guse 5bbb5f789f Bugfix: apply edge-2-edge insets to everything always.
Fixes #2073.
2025-06-20 19:13:05 +02:00
Dennis Guse 6e42857023 Cleanup: properly name method (has side effect). 2025-06-20 19:13:05 +02:00
Dennis Guse b883bb3862 Track list: center recording icon. 2025-06-19 17:54:45 +02:00
Dennis Guse 9b5ef0707e Cleanup: remove unused code for track list. 2025-06-19 17:54:45 +02:00
Dennis Guse 9c1a44abb4 Release: v4.19.5 2025-06-19 10:18:37 +02:00
தமிழ்நேரம் f75ed13e52 Translated using Weblate (Tamil)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ta/
2025-06-17 09:02:20 +02:00
தமிழ்நேரம் 5483065d4d Translated using Weblate (Tamil)
Currently translated at 100.0% (164 of 164 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ta/
2025-06-17 09:02:18 +02:00
Cloud Esp c1ecebccaa Translated using Weblate (French)
Currently translated at 95.3% (514 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/fr/
2025-06-17 09:01:46 +02:00
Aindriú Mac Giolla Eoin 8f130c50ff Translated using Weblate (Irish)
Currently translated at 100.0% (164 of 164 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-14 09:02:13 +02:00
Fjuro 1da04398f8 Translated using Weblate (Czech)
Currently translated at 100.0% (164 of 164 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-14 09:02:13 +02:00
Yurt Page 8c05be048a Translated using Weblate (Russian)
Currently translated at 99.6% (537 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ru/
2025-06-14 09:02:12 +02:00
Dennis Guse 77d040d08b Launcher icon was sometimes incorrectly scaled (e.g., kvaesitso).
Use same icon as OSMDashboard for launcher (w/o background).

Fixes #2108.
2025-06-14 09:02:06 +02:00
Dennis Guse 07124ad702 Bugfix: track icon in track detail page should taken from activityType (not te localized string).
Fixes #2112.
2025-06-13 08:16:29 +02:00
Dennis Guse 9257abb9ec Cleanup: remove unused attribute. 2025-06-13 08:16:29 +02:00
Dennis Guse fc4e1583b0 Release: v4.19.4 2025-06-10 21:39:23 +02:00
Dennis Guse 4843a2386a Bugfix: KML/KMZ failed to export heading - ancient functionality got removed.
Fixes #2096.
2025-06-10 17:58:42 +02:00
Dennis Guse 65e1ade5d4 Cleanup: remove unused preference keys. 2025-06-10 16:59:06 +02:00
Dennis Guse 2e3c8c8895 Add logging for export failures.
Part of #2096.
2025-06-10 08:40:57 +02:00
Dennis Guse adef029ce4 Add logging for export failures.
Part of #2096.
2025-06-09 22:55:37 +02:00
Dennis Guse fd947ece1d Merge pull request #2111 from Megadash452/main
Enabled edge-to-edge rendering and applied navbar insets
2025-06-07 22:59:04 +02:00
Megadash452 08283c6e31 Move code thatvapplies insets to its own method 2025-06-07 15:32:03 -04:00
Megadash452 33fff17749 Enabled edge-to-edge rendering and applied navbar insets 2025-06-06 18:01:21 -04:00
Lacey Anaya 28ceac3ec4 Translated using Weblate (German)
Currently translated at 3.6% (6 of 163 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/de/
2025-06-05 04:55:13 +00:00
Aindriú Mac Giolla Eoin 76b4ae69d3 Translated using Weblate (Irish)
Currently translated at 100.0% (163 of 163 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-05 04:55:13 +00:00
Patrick Santa Maria fbad0abe40 Translated using Weblate (Corsican)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/co/
2025-06-05 04:55:12 +00:00
Fjuro 9df7b8d8dc Translated using Weblate (Czech)
Currently translated at 100.0% (163 of 163 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-05 04:55:11 +00:00
Bora Atıcı 1fef75e0ab Translated using Weblate (Turkish)
Currently translated at 11.1% (18 of 162 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/tr/
2025-06-05 04:55:11 +00:00
Bora Atıcı 99216a30e4 Translated using Weblate (Turkish)
Currently translated at 10.4% (17 of 162 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/tr/
2025-06-05 04:55:10 +00:00
Bora Atıcı a1f6f11f3f Translated using Weblate (Turkish)
Currently translated at 98.1% (529 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/tr/
2025-06-05 04:55:09 +00:00
abdelbasset jabrane 6dfed5ebef Translated using Weblate (Arabic)
Currently translated at 4.3% (7 of 162 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ar/
2025-06-05 04:55:09 +00:00
abdelbasset jabrane 60581665b4 Translated using Weblate (Arabic)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ar/
2025-06-05 04:55:08 +00:00
Aindriú Mac Giolla Eoin d07c60fe71 Translated using Weblate (Irish)
Currently translated at 100.0% (162 of 162 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-05 04:55:08 +00:00
Cloud Esp 456e985476 Translated using Weblate (French)
Currently translated at 23.4% (38 of 162 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/fr/
2025-06-05 04:55:07 +00:00
Fjuro 2189135add Translated using Weblate (Czech)
Currently translated at 100.0% (162 of 162 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-05 04:55:06 +00:00
Aindriú Mac Giolla Eoin d1322a7fb5 Translated using Weblate (Irish)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ga/
2025-06-05 04:55:06 +00:00
Lacey Anaya 84ffb8a642 Translated using Weblate (German)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/de/
2025-06-05 04:55:05 +00:00
Ghost of Sparta 9638f088fd Translated using Weblate (Hungarian)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/hu/
2025-06-05 04:55:04 +00:00
Fjuro c2cf4edad8 Translated using Weblate (Czech)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/cs/
2025-06-05 04:55:04 +00:00
Sketch6580 62519cece1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/zh_Hans/
2025-06-05 04:55:03 +00:00
abdelbasset jabrane 0e3f48b492 Translated using Weblate (Arabic)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/ar/
2025-06-05 04:55:02 +00:00
abdelbasset jabrane 9886db98a7 Translated using Weblate (Arabic)
Currently translated at 3.1% (5 of 161 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ar/
2025-06-05 04:55:02 +00:00
abdelbasset jabrane c6b27bc7ae Translated using Weblate (English)
Currently translated at 100.0% (539 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/en/
2025-06-05 04:55:01 +00:00
Aindriú Mac Giolla Eoin 6512547f07 Translated using Weblate (Irish)
Currently translated at 100.0% (161 of 161 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-05 04:55:01 +00:00
Fjuro a7f71d8902 Translated using Weblate (Czech)
Currently translated at 100.0% (161 of 161 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-05 04:54:59 +00:00
ovl-1 ce4845d1b3 Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.9% (528 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2025-06-05 04:54:59 +00:00
Simon Artus 4f68b72333 Translated using Weblate (French)
Currently translated at 95.1% (513 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/fr/
2025-06-05 04:54:58 +00:00
Cloud Esp a8a7c2488a Translated using Weblate (French)
Currently translated at 95.1% (513 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/fr/
2025-06-05 04:54:58 +00:00
Aindriú Mac Giolla Eoin 5f983ec5e6 Translated using Weblate (Irish)
Currently translated at 100.0% (160 of 160 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/ga/
2025-06-05 04:54:57 +00:00
plum7x 9253eb5340 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.8% (538 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/zh_Hant/
2025-06-05 04:54:56 +00:00
Patrick Santa Maria 7afc5cc66a Translated using Weblate (Corsican)
Currently translated at 99.8% (538 of 539 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/co/
2025-06-05 04:54:56 +00:00
Fjuro 09fe79771f Translated using Weblate (Czech)
Currently translated at 100.0% (160 of 160 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2025-06-05 04:54:55 +00:00
dependabot[bot] 6baa816156 Bump androidx.appcompat:appcompat from 1.7.0 to 1.7.1
Bumps androidx.appcompat:appcompat from 1.7.0 to 1.7.1.

---
updated-dependencies:
- dependency-name: androidx.appcompat:appcompat
  dependency-version: 1.7.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-05 06:54:48 +02:00
dependabot[bot] 0d632f8125 Bump androidx.mediarouter:mediarouter from 1.7.0 to 1.8.0
Bumps androidx.mediarouter:mediarouter from 1.7.0 to 1.8.0.

---
updated-dependencies:
- dependency-name: androidx.mediarouter:mediarouter
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-05 06:54:33 +02:00
dependabot[bot] a81a63a2ad Bump com.android.tools.build:gradle from 8.10.0 to 8.10.1
Bumps com.android.tools.build:gradle from 8.10.0 to 8.10.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-29 07:48:13 +02:00
Dennis Guse 6656c9ab92 Release: v4.19.3 2025-05-27 22:31:14 +02:00
Peter Storch af6baeab9f fix #2104 csv export altitude above 1000m 2025-05-27 22:29:57 +02:00
dependabot[bot] 9b4d3a4718 Bump org.mockito:mockito-android from 5.17.0 to 5.18.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.17.0 to 5.18.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.17.0...v5.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 06:19:44 +02:00
Morag S. 942b29aa25 Update README.md
Added example command, and some minor copyedit
2025-05-18 11:22:27 +02:00
dependabot[bot] 18e18353e7 Bump androidx.documentfile:documentfile from 1.0.1 to 1.1.0
Bumps androidx.documentfile:documentfile from 1.0.1 to 1.1.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 08:42:31 +02:00
dependabot[bot] 8c8fe9f22d Bump com.android.tools.build:gradle from 8.9.2 to 8.10.0
Bumps com.android.tools.build:gradle from 8.9.2 to 8.10.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 06:21:29 +02:00
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
193 changed files with 1668 additions and 1392 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
+35
View File
@@ -1,5 +1,40 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" module="true" />
<package name="android" withSubpackages="true" static="true" />
<package name="androidx" withSubpackages="true" static="true" />
<package name="com" withSubpackages="true" static="true" />
<package name="junit" withSubpackages="true" static="true" />
<package name="net" withSubpackages="true" static="true" />
<package name="org" withSubpackages="true" static="true" />
<package name="java" withSubpackages="true" static="true" />
<package name="javax" withSubpackages="true" static="true" />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="androidx" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
+26 -4
View File
@@ -100,8 +100,8 @@ __Only required permissions:__
### Public API
OpenTracks includes an API for starting/stopping recording by another installed application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
The API can be invoked by sending an explicit Intent to start an activity.
OpenTracks includes an API for starting/stopping recording by another installed application (e.g., [Automate](https://llamalab.com/automate/), [Tasker](https://tasker.joaoapps.com), or [Easer](https://github.com/renyuneyun/Easer)).
The API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings. Once enabled, the API can be invoked by sending an explicit Intent to start an activity.
`Package` (depends on the variant installed):
* F-Droid: `de.dennisguse.opentracks`
@@ -121,9 +121,17 @@ The API can be invoked by sending an explicit Intent to start an activity.
* **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
* **Create a marker:** `de.dennisguse.opentracks.publicapi.CreateMarker`
For testing via adb: `adb shell am start -e someParameter someValue -n "package/class"`
For testing the API using adb, the general command syntax is:
The Public API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
```shell
adb shell am start -e someParameter someValue -n "package/class"
```
Depending on the package and class, a complete command could look something like this:
```shell
adb shell am start -n "de.dennisguse.opentracks.playstore/de.dennisguse.opentracks.publicapi.StartRecording"
```
## File formats compatibility with open-source software
| | [GPX 1.1](https://www.topografix.com/GPX/1/1/) | [KML 2.3](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html) | [KMZ 2.3](https://docs.opengeospatial.org/is/12-007r2/12-007r2.html) |
@@ -135,6 +143,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 +162,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/)).
+11 -11
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.11.0'
}
}
@@ -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 6217
versionName "v4.20.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
minSdk = 26
targetSdk = 35
targetSdk = 36
testInstrumentationRunner 'de.dennisguse.opentracks.TestRunner'
testInstrumentationRunnerArguments clearPackageData: 'true'
@@ -142,15 +142,15 @@ android {
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.documentfile:documentfile:1.1.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.mediarouter:mediarouter:1.8.0'
implementation 'androidx.core:core-location-altitude:1.0.0-alpha03'
androidTestImplementation 'androidx.test:core:1.6.1'
@@ -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.18.0'
androidTestUtil 'androidx.test:orchestrator:1.5.1'
}
@@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_marker_orange_pushpin_with_shadow.svg">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient871"
x1="91.712517"
y1="266.48581"
x2="95.902588"
y2="266.72339"
gradientUnits="userSpaceOnUse"
gradientTransform="rotate(22.027833,208.46275,168.26892)"
>
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="0"
id="stop867" />
<stop
style="stop-color:#898d8d;stop-opacity:1"
offset="1"
id="stop869" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="298.98541"
inkscape:cy="561.0427"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1012"
inkscape:window-x="0"
inkscape:window-y="1080"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<path
d="m 108.52826,213.29755 a 56.035402,7.0061195 5.281088 0 0 -22.425508,2.5321 56.035402,7.0061195 5.281088 0 0 -0.233576,0.17697 l -0.206706,-0.0237 c -0.04471,0.11554 -0.07454,0.2314 -0.09612,0.34752 a 56.035402,7.0061195 5.281088 0 0 0.337963,1.58159 c 0.980726,1.70884 3.760629,3.46326 6.583579,5.2188 l -22.744347,15.50009 a 67.064653,11.556784 4.2275244 0 0 -39.967588,5.26867 67.064653,11.556784 4.2275244 0 0 -0.729154,0.57564 67.064653,11.556784 4.2275244 0 0 -2.754353,1.36358 67.064653,11.556784 4.2275244 0 0 38.908736,16.04324 l -32.176847,27.0646 52.228334,-24.74551 a 67.064653,11.556784 4.2275244 0 0 71.411147,-3.8186 67.064653,11.556784 4.2275244 0 0 0.72812,-0.57507 67.064653,11.556784 4.2275244 0 0 2.75539,-1.36415 67.064653,11.556784 4.2275244 0 0 -12.38374,-10.59818 40.033569,5.0733922 5.2502179 0 0 0.0563,-0.0369 40.033569,5.0733922 5.2502179 0 0 0.37672,-0.34325 l 22.45186,-15.30075 c 8.23597,-0.65161 17.65581,-1.47811 22.94433,-3.00957 a 56.035402,7.0061195 5.281088 0 0 2.65514,-1.03772 56.035402,7.0061195 5.281088 0 0 -46.08401,-12.26953 56.035402,7.0061195 5.281088 0 0 -41.6357,-2.54991 z"
style="fill:#333333;fill-opacity:0.49421525;stroke:#000000;stroke-width:0.138931px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.265501"
id="path839" />
<path
style="fill:url(#linearGradient871);fill-opacity:1;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.265501"
d="M 70.603317,174.30832 33.024148,288.94657 94.566672,183.19473 Z"
id="path865"
sodipodi:nodetypes="cccc" />
<ellipse
cx="144.42586"
cy="113.06232"
rx="70.318817"
ry="39.974442"
id="ellipse26"
style="fill:#d45500;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
transform="rotate(22.027833)" />
<ellipse
style="fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="ellipse30"
ry="39.974442"
rx="70.318817"
cy="105.23625"
cx="145.017"
transform="rotate(22.027833)" />
<path
id="rect28"
style="fill:#d45500;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
d="m 85.662408,24.322061 c -0.890996,8.350369 2.928035,17.133472 6.825491,25.923906 l -23.333902,57.672763 -0.730686,1.80598 0.248621,0.10059 a 42.43035,17.360788 22.027833 0 0 33.303948,30.10067 42.43035,17.360788 22.027833 0 0 44.86507,1.52585 l 0.13892,0.0562 0.24519,-0.60599 a 42.43035,17.360788 22.027833 0 0 0.59472,-1.15581 42.43035,17.360788 22.027833 0 0 0.37668,-1.24514 l 22.45185,-55.492655 c 9.28986,-2.66566 20.09069,-6.13552 24.76397,-13.17485 z" />
<ellipse
style="fill:#ff6600;fill-opacity:1;fill-rule:evenodd;stroke-width:0.264583"
id="path24"
ry="23.966745"
rx="59.409279"
cy="-10.262156"
cx="148.14029"
transform="rotate(22.027833)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

@@ -0,0 +1,15 @@
الإصدار 3.0.0: OpenTracks
الإصدار الأول من OpenTracks (أي، تفرع صلب من MyTracks)
التغييرات:
- تغيير العلامة التجارية من MyTracks إلى OpenTracks
- إضافة مستشعر معدل ضربات القلب بتقنية Bluetooth LE
- إزالة جميع الميزات التي تتطلب الوصول إلى الإنترنت
- إزالة تكامل خرائط جوجل
- تمت إزالة استخدام خدمات Google Play
- تمت إزالة مستشعرات ANT+
- تمت إزالة مستشعرات البلوتوث الكلاسيكية
- تمت إزالة بروتوبوف
- تمت إزالة تقدير السعرات الحرارية وتقدير النشاط
- تمت إزالة واجهة برمجة تطبيقات MyTracks Service API (أي لا يوجد تكامل مع تطبيقات أخرى)
@@ -0,0 +1,17 @@
v3.0.1: OpenTracks
التغييرات:
- علامات المشاركة
- إعادة هيكلة الإعدادات
- تمت إزالة زر الرجوع إلى شريط العمل
- تصدير: تمت إزالة خيار إعدادات حجم الصورة
- تصدير: KML كتنسيق قابل للتحديد الآن
إصلاحات الأخطاء:
- أصبح دليل التصدير الآن /OpenTracks/
- الوضع الأفقي لا يتعطل
- تم إصلاح تصدير KML/KMZ
المطور:
- الاختبار قابل للتنفيذ (لكن العديد منها يفشل)
- تمكين التكامل المستمر مع TravisCI (لا توجد اختبارات)
@@ -0,0 +1,14 @@
v3.1.0: OpenTracks
التغييرات:
- تمت إضافة إظهار المسار في تطبيقات الخرائط (تم اختباره مع OsmAnd وMaps.ME)
- تمت الإضافة أثناء التتبع، إظهار إحصائيات المسار دون فتح الجهاز
- تمت إزالة الوضع الأفقي لعرض تفاصيل المسار (كان به أخطاء)
- تمت إزالة سجل اقتراحات البحث
إصلاح الخلل:
- طلب إذن تخزين لاستيراد الكل/تصدير الكل
مطور:
- الترقية إلى Gradle 5.6
- عدم تصغير بنيات الإصدار
@@ -0,0 +1,22 @@
رفيق تتبع الرياضة الذي يحترم خصوصيتك.
الرياضة والأنشطة الخارجية تمنح السعادة والمهارة والثقة بالنفس.
قدّر صحتك من خلال تتبع تمارينك الرياضية.
يسجل أثناء الجري أو المشي، ويمنحك كمبيوتر للدراجات بشاشة أكبر لركوب الدراجات.
حدد المواقع المثيرة للاهتمام على طول طريقك بالصور.
احتفظ بالإحصائيات المسجلة بتفاصيل كبيرة لتحليلها.
شارك فقط البيانات التي تريد أن يحصل عليها الآخرون.
* إعلانات صوتية.
* يدعم مستشعرات Bluetooth LE: معدل ضربات القلب والسرعة والمسافة (ركوب الدراجات)، والإيقاع (ركوب الدراجات)، ومقياس الطاقة (ركوب الدراجات).
* كسب الارتفاع وفقدان الارتفاع: عبر مستشعر بارومتري.
* يظهر الارتفاع بالارتفاع EGM2008 (فوق متوسط مستوى سطح البحر)؛ يتم تصديره على هيئة WGS84.
* تصدير البيانات كمسارات إما على هيئة KMZ (بما في ذلك الصور) أو KML أو GPX.
* لا يوجد وصول إلى الإنترنت أو أذونات إضافية.
* سمة داكنة وفاتحة، مع احترام إعدادات النظام.
* لا إعلانات.
برنامج ليبر / برنامج مجاني / مفتوح المصدر
بمعنى أنه يمكنك استخدام ودراسة وتغيير ومشاركة شفرة المصدر.
مرخص من أباتشي 2.0
@@ -1 +1 @@
تعقب الرياضة التي تحترم تماما خصوصيتك.
رفيق تعقّب رياضي يحترم خصوصيتك.
+1 -1
View File
@@ -1 +1 @@
OpenTracks
المسارات المفتوحة
@@ -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
Změny:
- Export GPX nebyl kompatibilní (pořadí a obsah rozšíření TrackPointExtension)
Oprava chyb:
- Před verzí 4.18.0 nebylo možné odstranit značky bez obrázků
@@ -0,0 +1,4 @@
v4.19.1: OpenTracks
Oprava chyby:
- Exportovaným souborům GPX chyběla jedna schemaLocation
@@ -0,0 +1,4 @@
v4.19.2: OpenTracks
Oprava chyby:
- Opraven pád z důvodu problémových překladů
@@ -0,0 +1,4 @@
v4.19.3: OpenTracks
Oprava chyby:
- Opraven export CSV pro nadmořskou výšku (byla uříznuta v 1000 m)
@@ -0,0 +1,5 @@
v4.19.4: OpenTracks
Oprava chyb:
- Edge-to-edge (SDK35): použito na všechny verze Androidu (určitý pokrok, ale stále se na tom pracuje)
- KMZ s obrázky nebylo možné exportovat (záhlaví nebylo možné vypočítat; funkce odstraněna)
@@ -0,0 +1,5 @@
v4.19.5: OpenTracks
Oprava chyby:
- Ikona v launcheru byla někdy příliš malá
- Ikona trasy v podrobnostech používá lokalizovaný typ aktivity
@@ -0,0 +1,4 @@
v4.19.6: OpenTracks
Změny:
- Podpora edge-to-edge by nyní měla fungovat na všech verzích systému Android
@@ -1,10 +1,22 @@
OpenTracks – ein GPS-Tracker, der deine Daten für sich behält.
Erfasst deine Kilometer, ohne dass es jemand anderes tut.
Funktionen & Features:
* zeichnet Sport und andere Outdooraktivitäten auf
* unterstützt das Markieren interessanter Orte, inkl. Foto-Funktion
* zeichnet deine Herzfrequenz auf (Bluetooth LE)
* nutze beim Radfahren deine Trittfrequenz- und Geschwindigkeitssensoren sowie Leistungsmesser (Bluetooth LE)
* exportiert deine Daten als KML oder GPX
* funktioniert ganz ohne Internet
Sport und Aktivitäten im Freien machen Spaß und bieten Training und mehr Selbstvertrauen.
Achte auf deine Gesundheit, indem du dein Training im Blick behältst.
Die App zeichnet während dem Laufen oder Spazieren mit und kann auch als Fahrradcomputer genutzt werden.
Interessante Orte können am Weg entlang markiert und mit Fotos versehen werden.
Messdaten können zur Analyse detailreich gespeichert werden.
Du entscheidest, welche Daten du mit anderen Menschen teilst.
* Gesprochene Ansagen
* Unterstützt Bluetooth LE Sensoren: Herzschlagfrequenz, sowie Geschwindigkeit, Distanz, Kadenz und Leistungsmessung beim Fahrradfahren.
* Höhenmessung mit einem eingebauten Barometer.
* Anzeige der Höhenmessung in EGM2008 (über dem durchschnittlichen Meeresspiegel); Export als WGS84.
* Export deiner Streckendaten als KMZ (inkl. Fotos), KML oder GPX.
* Kein Internet oder zusätzliche Berechtigungen nötig.
* Dunkler und heller Modus, sowie automatische Anpassung an das System.
* Keine Werbung.
Freie Software / Open Source
Das bedeutet, dass du den Quellcode nutzen, inspizieren, verändern und teilen darfst.
Lizensiert unter Apache 2.0
@@ -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,5 @@
v4.19.3: OpenTracks
Bugfix:
- Fix CSV export for altitude (was cut at 1000m)
@@ -0,0 +1,5 @@
v4.19.4: OpenTracks
Bugfix:
- Edge-to-edge (SDK35): applied to all version of Android (some progress, but still work in progress)
- KMZ with pictures could not be exported (heading not computable; feature removed)
@@ -0,0 +1,5 @@
v4.19.5: OpenTracks
Bugfix:
- Launcher icon was sometimes shown too small
- Track icon on detail page using localized activity type
@@ -0,0 +1,4 @@
v4.19.6: OpenTracks
Changes:
- Edge-2-edge should now be working on all Android versions
@@ -0,0 +1,8 @@
v4.20.0: OpenTracks
Changes:
- Allow to change timezone offset of tracks
- Simplified Marker UI
Bugfix:
- Edge-2-edge: react to virtual keyboard
@@ -0,0 +1,4 @@
v4.19.2 : OpenTracks
Correction de bugs :
- Correction d'un crash dû à des traductions défectueuses
@@ -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,7 @@
v4.19.0: OpenTracks
Athruithe:
- níor chomhlíon easpórtáil GPX (Ordú agus inneachar TrackPointExtension)
Ceartúchán Bug:
- níorbh fhéidir marcóirí réamh-v4.18.0 gan pictiúir a scriosadh
@@ -0,0 +1,4 @@
v4.19.1: OpenTracks
Ceartú fabht:
- Chaill comhaid GPX onnmhairithe suíomh scéime amháin
@@ -0,0 +1,4 @@
v4.19.2: OpenTracks
Ceartú fabht:
- Deisíodh tuairteáil mar gheall ar aistriúcháin lochtacha
@@ -0,0 +1,4 @@
v4.19.3: OpenTracks
Ceartú fabht:
- Deisíodh easpórtáil CSV le haghaidh airde (gearradh ag 1000m é)
@@ -0,0 +1,5 @@
v4.19.4: OpenTracks
Ceartú fabht:
- Imeall go himeall (SDK35): curtha i bhfeidhm ar gach leagan de Android (roinnt dul chun cinn, ach obair ar siúl fós)
- Níorbh fhéidir KMZ le pictiúir a easpórtáil (níorbh fhéidir an ceannteideal a ríomh; baineadh an ghné)
@@ -0,0 +1,5 @@
v4.19.5: OpenTracks
Ceartú fabht:
- Taispeánadh deilbhín an lainseálaí róbheag uaireanta
- Deilbhín an rianaithe ar an leathanach sonraí ag baint úsáide as cineál gníomhaíochta logánaithe
@@ -0,0 +1,4 @@
v4.19.6: OpenTracks
Athruithe:
- Ba chóir go mbeadh Edge-2-edge ag obair anois ar gach leagan de Android
@@ -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,4 @@
V4.17.8: Opentracks
மாற்றங்கள்:
- விளக்கப்படங்கள்: காட்டப்பட்ட தரவை உள்ளமைக்க முடியும்
@@ -0,0 +1,11 @@
v4.18.0: ஓபன்ட்ராக்ச்
மாற்றங்கள்:
- சி.பி.எச் முடக்கப்பட்ட எச்சரிக்கையை முடக்கலாம்
பிழைத்திருத்த:
- பாட்டம்அப்ப்பார் உள்ளடக்கத்தை ஒன்றுடன் ஒன்று
டெவலப்பர்:
- ஆண்ட்ராய்டு இருப்பிடத்திற்கு பதிலாக பதிவு நிலையைப் பயன்படுத்தவும்
- மறுசீரமைக்கப்பட்ட சென்சார் திரட்டிகள்
@@ -0,0 +1,7 @@
V4.19.0: Opentracks
மாற்றங்கள்:
- சிபிஎக்ச் ஏற்றுமதி இணக்கமாக இல்லை (டிராக் பாயிண்ட் எக்ச்டென்சன் ஆர்டர் மற்றும் உள்ளடக்கம்)
பிழைத்திருத்த:
- முன்-V4.18.0 படங்கள் இல்லாத குறிப்பான்களை நீக்க முடியவில்லை
@@ -0,0 +1,4 @@
V4.19.1: Opentracks
பிழைத்திருத்த:
- ஏற்றுமதி செய்யப்பட்ட சிபிஎக்ச் கோப்புகள் ஒரு திட்டத்தை தவறவிட்டன
@@ -0,0 +1,4 @@
V4.19.2: Opentracks
பிழைத்திருத்த:
- தவறான மொழிபெயர்ப்புகள் காரணமாக விபத்தை சரிசெய்யவும்
@@ -0,0 +1,4 @@
V4.19.3: Opentracks
பிழைத்திருத்த:
- உயரத்திற்கு காபிம ஏற்றுமதியை சரிசெய்யவும் (1000 மீ.
@@ -0,0 +1,5 @@
V4.19.4: Opentracks
பிழைத்திருத்த:
-எட்ச்-டு-எட்ச் (SDK35): ஆண்ட்ராய்டு இன் அனைத்து பதிப்பிற்கும் பயன்படுத்தப்பட்டது (சில முன்னேற்றம், ஆனால் இன்னும் முன்னேற்றத்தில் உள்ளது)
- படங்களுடன் KMZ ஏற்றுமதி செய்ய முடியவில்லை (தலைப்பு கணக்கிட முடியாதது; நற்பொருத்தம் அகற்றப்பட்டது)
@@ -0,0 +1,7 @@
v3.2.1: OpenTracks
Değişiklikler:
- Ayarlar artık tek bir sayfada
Gelişim:
- Androidx Compat'a taşınan ayarlar (parçaları kullanır)
@@ -0,0 +1,10 @@
v4.17.5: Opentracks
Değişiklikler:
- Tekrarlanabilir yapının: işe yarayıp yaramadığını görelim.
Bugfix:
- Tekrarlanabilir yapı PlayStore simgesini kullanır.
Geliştirici:
- RELEASE_BUILD.sh eklendi
@@ -0,0 +1,11 @@
v4.18.0: Opentracks
Değişiklikler:
- GPS devre dışı bırakılmış uyarısı devre dışı bırakılabilir
Bugfix:
- Üst üste binen BottomAppBar düzeltildi
Gelişim:
- Android konumu yerine kayıt konumunu kullanır
- Refactored sensör toplayıcıları
@@ -0,0 +1,7 @@
v4.19.0: Opentracks
Değişiklikler:
- GPX dışa aktarma uyumlu değildi (TrackPointExtension sıra ve içerik)
Bugfix:
- pre-v4.18.0 öncesi resimsiz işaretçilerin silinmemesi düzeltildi
@@ -0,0 +1,4 @@
v4.19.2: Opentracks
Bugfix:
- Hatalı çeviriler nedeniyle çökme sorunu düzeltildi
@@ -0,0 +1,11 @@
v4.18.0: OpenTracks
更改:
- GPS 禁用警告可被禁用
错误修复:
- BottomAppBar 重叠内容
开发相关:
- 使用记录位置代替 Android 位置
- 重构了传感器聚合器
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+1 -2
View File
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -763,8 +763,8 @@ public class CustomContentProviderUtilsTest {
TrackPoint trackPoint = contentProviderUtils.createTrackPoint(cursorMock);
// then
assertEquals(longitude, trackPoint.getLongitude(), 0.01);
assertEquals(latitude, trackPoint.getLatitude(), 0.01);
assertEquals(latitude, trackPoint.getPosition().latitude(), 0.01);
assertEquals(longitude, trackPoint.getPosition().longitude(), 0.01);
assertEquals(time, trackPoint.getTime().toEpochMilli());
assertEquals(speed, trackPoint.getSpeed().toMPS(), 0.01);
assertEquals(HeartRate.of(75f), trackPoint.getHeartRate());
@@ -18,6 +18,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
@@ -45,6 +46,7 @@ import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.TimezoneRule;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -87,6 +89,9 @@ public class ExportImportTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
//For csv_export_only() as we the timezone is hardcoded in the expectation.
@Rule
public TimezoneRule timezoneRule = new TimezoneRule(TimeZone.getTimeZone("Europe/Berlin"));
@@ -144,7 +149,7 @@ public class ExportImportTest {
Distance sensorDistance = Distance.of(10); // recording distance interval
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 13, 15, 10, 1f);
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 1f);
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 1", "Marker 1 desc", "Marker 1 category", null, null));
// A sensor-only TrackPoint
@@ -155,11 +160,10 @@ public class ExportImportTest {
mockSensorData(trackPointCreator, 15f, null, 67f, 3f, 50f, null);
trackPointCreator.setClock("2020-02-02T02:02:15Z");
mockSensorData(trackPointCreator, null, null, 68f, 3f, 50f, null);
trackPointCreator.setClock("2020-02-02T02:02:16Z");
mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3.1234567, 14.0014567, 10, 13, 15, 1020.25, 0f);
contentProviderUtils.insertMarker(new Marker(trackId, service.getLastStoredTrackPointWithLocation(), "Marker 2", "Marker 2 desc", "Marker 2 category", null, null));
trackPointCreator.setClock("2020-02-02T02:02:18Z");
@@ -169,14 +173,14 @@ public class ExportImportTest {
trackPointCreator.setClock("2020-02-02T02:03:20Z");
service.resumeTrack(trackId);
sendLocation(trackPointCreator, "2020-02-02T02:03:21Z", 3, 14.002, 10, 13, 15, 10, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:21Z", 3.1234567, 14.0024567, 10, 13, 15, 999.123, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:22Z", 3, 16, 10, 13, 15, 10, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:22Z", 3.1234567, 16, 10, 13, 15, 999.123, 0f);
trackPointCreator.setClock("2020-02-02T02:03:30Z");
service.getTrackRecordingManager().onIdle();
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3, 16.001, 10, 27, 15, 10, 0f);
sendLocation(trackPointCreator, "2020-02-02T02:03:50Z", 3.1234567, 16.001, 10, 27, 15, 999.123, 0f);
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet(trackPointCreator);
trackPointCreator.setClock("2020-02-02T02:04:00Z");
@@ -209,16 +213,16 @@ public class ExportImportTest {
assertEquals(Duration.ofSeconds(26), trackStatistics.getMovingTime()); //TODO Likely too low
// Distance
assertEquals(222125.53125, trackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low
assertEquals(222049.34375, trackStatistics.getTotalDistance().toM(), 0.01); //TODO Too low
// Speed
assertEquals(8543.29, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(3966.52, trackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8543.28, trackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(8540.359, trackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(3965.166, trackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8540.359, trackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(10, trackStatistics.getMinAltitude(), 0.01);
assertEquals(10, trackStatistics.getMaxAltitude(), 0.01);
assertEquals(999.122, trackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.25, trackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, trackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, trackStatistics.getTotalAltitudeLoss(), 0.01);
@@ -229,8 +233,8 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:02:03Z"),
3d, 14d, Distance.of(10),
Altitude.WGS84.of(10), null,
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
null,
Speed.of(15)))
.setAltitudeLoss(1f)
@@ -251,8 +255,8 @@ public class ExportImportTest {
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,
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.25), null,
null,
Speed.of(5)))
.setSensorDistance(Distance.of(2))
@@ -266,16 +270,16 @@ public class ExportImportTest {
new TrackPoint(TrackPoint.Type.TRACKPOINT,
new Position(
Instant.parse("2020-02-02T02:03:21Z"),
3d, 14.002d, Distance.of(10),
Altitude.WGS84.of(10), null,
3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), 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,
3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
@@ -286,8 +290,8 @@ public class ExportImportTest {
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,
3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.1229858398438), null,
null, Speed.of(15)))
.setAltitudeLoss(0f)
.setAltitudeGain(0f),
@@ -312,7 +316,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
@@ -334,7 +338,26 @@ public class ExportImportTest {
// Time
assertEquals(track.getZoneOffset(), importedTrack.getZoneOffset());
assertEquals(track.getTrackStatistics(), importedTrackStatistics);
assertEquals(Instant.parse("2020-02-02T02:02:02Z"), importedTrackStatistics.getStartTime());
assertEquals(Instant.parse("2020-02-02T02:04:00Z"), importedTrackStatistics.getStopTime());
assertEquals(Duration.ofSeconds(56), importedTrackStatistics.getTotalTime());
assertEquals(Duration.ofSeconds(26), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(222049.421, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(8540.362, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(3965.168, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(8540.362, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(999.122, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.25, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
// 4. markers
assertMarkers();
@@ -358,7 +381,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 +406,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
@@ -406,8 +429,8 @@ public class ExportImportTest {
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,
3.123456, 14.001456d, Distance.of(10),
Altitude.WGS84.of(1020.2), null,
null,
Speed.of(15)))
.setAltitudeLoss(1f)
@@ -415,8 +438,8 @@ public class ExportImportTest {
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,
3.123456, 14.001456, Distance.of(10),
Altitude.WGS84.of(1020.2), null,
null,
Speed.of(5)))
.setAltitudeLoss(1f)
@@ -428,8 +451,8 @@ public class ExportImportTest {
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,
3.123456, 14.002456, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
@@ -437,8 +460,8 @@ public class ExportImportTest {
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,
3.123456, 16d, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null,
null,
Speed.of(15)))
.setAltitudeLoss(0f)
@@ -446,8 +469,8 @@ public class ExportImportTest {
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,
3.123456, 16.001, Distance.of(10),
Altitude.WGS84.of(999.0999755859375), null,
null,
Speed.of(10)))
.setAltitudeLoss(0f)
@@ -467,16 +490,16 @@ public class ExportImportTest {
assertEquals(Duration.ofSeconds(107), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(222347.85, importedTrackStatistics.getTotalDistance().toM(), 0.01);
assertEquals(222271.734, importedTrackStatistics.getTotalDistance().toM(), 0.01);
// Speed
assertEquals(2078.01, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(2078.01, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(2078.01, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getMaxSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getAverageSpeed().toMPS(), 0.01);
assertEquals(2077.305, importedTrackStatistics.getAverageMovingSpeed().toMPS(), 0.01);
// Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(999.099, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(1020.2, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
@@ -503,7 +526,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
@@ -614,7 +637,7 @@ public class ExportImportTest {
}
}
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, float altitude, Float altitudeGain) {
private void sendLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, float verticalAccuracy, float speed, double altitude, Float altitudeGain) {
Location location = new Location("mock");
location.setLatitude(latitude);
location.setLongitude(longitude);
@@ -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
@@ -20,8 +20,8 @@ public class TrackPointAssert {
Assert.assertEquals("has location,", expected.hasLocation(), actual.hasLocation());
if (expected.hasLocation()) {
Assert.assertEquals("latitude", expected.getLatitude(), actual.getLatitude(), 0.001);
Assert.assertEquals("longitude", expected.getLongitude(), actual.getLongitude(), 0.001);
Assert.assertEquals("latitude", expected.getPosition().latitude(), actual.getPosition().latitude(), 0.001);
Assert.assertEquals("longitude", expected.getPosition().longitude(), actual.getPosition().longitude(), 0.001);
}
Assert.assertEquals("has altitude", expected.hasAltitude(), actual.hasAltitude());
+5 -5
View File
@@ -1,13 +1,13 @@
#time,trackpoint_type,latitude,longitude,altitude,accuracy_horizontal,accuracy_vertical,speed,altitude_gain,altitude_loss,sensor_distance,heartrate,cadence,power
"2020-02-02T03:02:02+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
"2020-02-02T03:02:03+01:00","TRACKPOINT",3,14,10,10,,54,1,1,,,,
"2020-02-02T03:02:03+01:00","TRACKPOINT",3.123456,14.001456,1020.2,10,,54,1,1,,,,
"2020-02-02T03:02:04+01:00","TRACKPOINT",,,,,,54,1,1,10,66,3,50
"2020-02-02T03:02:15+01:00","TRACKPOINT",,,,,,,,,,68,3,50
"2020-02-02T03:02:17+01:00","TRACKPOINT",3,14.001,10,10,,18,0,0,2,69,3,50
"2020-02-02T03:02:17+01:00","TRACKPOINT",3.123456,14.001456,1020.2,10,,18,0,0,2,69,3,50
"2020-02-02T03:02:18+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
"2020-02-02T03:03:20+01:00","SEGMENT_START_MANUAL",,,,,,,,,,,,
"2020-02-02T03:03:21+01:00","TRACKPOINT",3,14.002,10,10,,54,0,0,,,,
"2020-02-02T03:03:22+01:00","SEGMENT_START_AUTOMATIC",3,16,10,10,,54,0,0,,,,
"2020-02-02T03:03:21+01:00","TRACKPOINT",3.123456,14.002456,999.1,10,,54,0,0,,,,
"2020-02-02T03:03:22+01:00","SEGMENT_START_AUTOMATIC",3.123456,16,999.1,10,,54,0,0,,,,
"2020-02-02T03:03:30+01:00","IDLE",,,,,,,0,0,,,,
"2020-02-02T03:03:50+01:00","TRACKPOINT",3,16.001,10,10,,54,0,0,,,,
"2020-02-02T03:03:50+01:00","TRACKPOINT",3.123456,16.001,999.1,10,,54,0,0,,,,
"2020-02-02T03:04:00+01:00","SEGMENT_END_MANUAL",,,,,,,,,,,,
1 #time trackpoint_type latitude longitude altitude accuracy_horizontal accuracy_vertical speed altitude_gain altitude_loss sensor_distance heartrate cadence power
2 2020-02-02T03:02:02+01:00 SEGMENT_START_MANUAL
3 2020-02-02T03:02:03+01:00 TRACKPOINT 3 3.123456 14 14.001456 10 1020.2 10 54 1 1
4 2020-02-02T03:02:04+01:00 TRACKPOINT 54 1 1 10 66 3 50
5 2020-02-02T03:02:15+01:00 TRACKPOINT 68 3 50
6 2020-02-02T03:02:17+01:00 TRACKPOINT 3 3.123456 14.001 14.001456 10 1020.2 10 18 0 0 2 69 3 50
7 2020-02-02T03:02:18+01:00 SEGMENT_END_MANUAL
8 2020-02-02T03:03:20+01:00 SEGMENT_START_MANUAL
9 2020-02-02T03:03:21+01:00 TRACKPOINT 3 3.123456 14.002 14.002456 10 999.1 10 54 0 0
10 2020-02-02T03:03:22+01:00 SEGMENT_START_AUTOMATIC 3 3.123456 16 10 999.1 10 54 0 0
11 2020-02-02T03:03:30+01:00 IDLE 0 0
12 2020-02-02T03:03:50+01:00 TRACKPOINT 3 3.123456 16.001 10 999.1 10 54 0 0
13 2020-02-02T03:04:00+01:00 SEGMENT_END_MANUAL
+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>
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.databinding.AboutBinding;
@@ -29,7 +30,8 @@ public class AboutActivity extends AbstractActivity {
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
}
protected View getRootView() {
@NonNull
protected View createRootView() {
viewBinding = AboutBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -19,7 +19,13 @@ package de.dennisguse.opentracks;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewGroupCompat;
import androidx.core.view.WindowInsetsCompat;
import de.dennisguse.opentracks.services.announcement.TTSManager;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -35,12 +41,27 @@ public abstract class AbstractActivity extends AppCompatActivity {
setTheme(R.style.OpenTracksThemeOled);
}
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
// Set volume control stream for text to speech
setVolumeControlStream(TTSManager.AUDIO_STREAM);
setContentView(getRootView());
View rootView = createRootView();
setContentView(rootView);
edge2edgeApplyInsets(rootView);
}
private void edge2edgeApplyInsets(View rootView) {
ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.ime());
view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
return WindowInsetsCompat.CONSUMED;
});
// For Android 10 and below.
ViewGroupCompat.installCompatInsetsDispatch(rootView);
}
@Override
@@ -49,5 +70,6 @@ public abstract class AbstractActivity extends AppCompatActivity {
return true;
}
protected abstract View getRootView();
@NonNull
protected abstract View createRootView();
}
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.dennisguse.opentracks.databinding.HelpBinding;
@@ -19,8 +20,9 @@ public class HelpActivity extends AbstractActivity {
ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
helpBinding = HelpBinding.inflate(getLayoutInflater());
return helpBinding.getRoot();
}
@@ -7,6 +7,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -28,8 +30,9 @@ public class ShowErrorActivity extends AbstractActivity {
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = ActivityShowErrorBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -23,12 +23,14 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import java.time.ZoneOffset;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackEditBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.TrackUtils;
import de.dennisguse.opentracks.ui.ZoneOffsetAdapter;
/**
* An activity that let's the user see and edit the user editable track meta data such as track name, activity type, and track description.
@@ -68,19 +70,18 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
return;
}
viewBinding.fields.trackEditName.setText(track.getName());
viewBinding.fields.trackEditActivityType.setText(track.getActivityTypeLocalized());
viewBinding.trackEditName.setText(track.getName());
viewBinding.trackEditActivityTypeLocalized.setText(track.getActivityTypeLocalized());
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> {
String localizedActivityType = (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position);
viewBinding.trackEditActivityTypeLocalized.setAdapter(adapter);
viewBinding.trackEditActivityTypeLocalized.setOnItemClickListener((parent, view, position, id) -> {
String localizedActivityType = (String) viewBinding.trackEditActivityTypeLocalized.getAdapter().getItem(position);
setActivityTypeIcon(ActivityType.findByLocalizedString(this, localizedActivityType));
});
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
viewBinding.trackEditActivityTypeLocalized.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
String localizedActivityType = viewBinding.fields.trackEditActivityType.getText().toString();
String localizedActivityType = viewBinding.trackEditActivityTypeLocalized.getText().toString();
setActivityTypeIcon(ActivityType.findByLocalizedString(this, localizedActivityType));
}
});
@@ -92,16 +93,23 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
if (activityType == null) {
activityType = track.getActivityType();
}
setActivityTypeIcon(activityType);
viewBinding.fields.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), this, viewBinding.fields.trackEditActivityType.getText().toString()));
viewBinding.trackEditActivityTypeIcon.setOnClickListener(v -> ChooseActivityTypeDialogFragment.showDialog(getSupportFragmentManager(), this, viewBinding.trackEditActivityTypeLocalized.getText().toString()));
viewBinding.fields.trackEditDescription.setText(track.getDescription());
viewBinding.trackEditDescription.setText(track.getDescription());
final ArrayAdapter<ZoneOffset> zoneOffsetAdapter = new ZoneOffsetAdapter(this, android.R.layout.simple_dropdown_item_1line);
viewBinding.trackEditTimeOffset.setText(track.getZoneOffset().toString());
viewBinding.trackEditTimeOffset.setAdapter(zoneOffsetAdapter);
viewBinding.trackEditTimeOffset.setOnItemClickListener((parent, view, position, id) -> track.setZoneOffset(zoneOffsetAdapter.getItem(position)));
viewBinding.trackEditSave.setOnClickListener(v -> {
TrackUtils.updateTrack(TrackEditActivity.this, track, viewBinding.fields.trackEditName.getText().toString(),
viewBinding.fields.trackEditActivityType.getText().toString(), viewBinding.fields.trackEditDescription.getText().toString(),
contentProviderUtils);
track.setName(viewBinding.trackEditName.getText().toString());
track.setDescription(viewBinding.trackEditDescription.getText().toString());
track.setActivityTypeLocalizedAndUpdateActivityType(this, viewBinding.trackEditActivityTypeLocalized.getText().toString());
contentProviderUtils.updateTrack(track);
finish();
});
@@ -115,20 +123,21 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
outState.putSerializable(ICON_VALUE_KEY, activityType);
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = TrackEditBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
private void setActivityTypeIcon(ActivityType activityType) {
this.activityType = activityType;
viewBinding.fields.trackEditActivityTypeIcon.setImageResource(activityType.getIconDrawableId());
viewBinding.trackEditActivityTypeIcon.setImageResource(activityType.getIconDrawableId());
}
@Override
public void onChooseActivityTypeDone(ActivityType activityType) {
setActivityTypeIcon(activityType);
viewBinding.fields.trackEditActivityType.setText(getString(activityType.getLocalizedStringId()));
viewBinding.trackEditActivityTypeLocalized.setText(getString(activityType.getLocalizedStringId()));
}
}
@@ -30,6 +30,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
@@ -242,8 +243,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
adapter = null;
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = TrackListBinding.inflate(getLayoutInflater());
viewBinding.trackListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> {
@@ -34,7 +34,6 @@ import com.google.android.material.tabs.TabLayoutMediator;
import de.dennisguse.opentracks.chart.ChartFragment;
import de.dennisguse.opentracks.chart.TrackDataHubInterface;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackRecordedBinding;
@@ -67,8 +66,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
// The following are setFrequency in onCreate.
private ContentProviderUtils contentProviderUtils;
private TrackDataHub trackDataHub;
private TrackRecordedBinding viewBinding;
@@ -85,8 +82,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this);
handleIntent(getIntent());
trackDataHub = new TrackDataHub(this);
@@ -101,7 +96,6 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindCallback);
Track track = contentProviderUtils.getTrack(trackId);
viewBinding.bottomAppBarLayout.bottomAppBar.replaceMenu(R.menu.track_detail);
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
@@ -142,8 +136,9 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
outState.putInt(CURRENT_TAB_TAG_KEY, viewBinding.trackDetailActivityViewPager.getCurrentItem());
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = TrackRecordedBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -49,7 +49,6 @@ import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
import de.dennisguse.opentracks.ui.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.TrackUtils;
/**
* An activity to show the track detail, record a new track or resumes an existing one.
@@ -250,8 +249,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
trackRecordingServiceConnection = null;
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = TrackRecordingBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -326,7 +326,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
public void onChooseActivityTypeDone(ActivityType activityType) {
Track track = contentProviderUtils.getTrack(trackId);
String activityTypeLocalized = getString(activityType.getLocalizedStringId());
TrackUtils.updateTrack(this, track, null, activityTypeLocalized, null, contentProviderUtils);
track.setActivityTypeLocalizedAndUpdateActivityType(this, activityTypeLocalized);
contentProviderUtils.updateTrack(track);
}
private class CustomFragmentPagerAdapter extends FragmentStateAdapter {
@@ -7,6 +7,8 @@ import android.util.Pair;
import android.view.View;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
@@ -20,7 +22,6 @@ import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragm
import de.dennisguse.opentracks.util.ExportUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackUtils;
public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller {
@@ -32,8 +33,6 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
private Track.Id trackId;
private boolean isDiscarding = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -105,13 +104,16 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
}
private void storeTrackMetaData(ContentProviderUtils contentProviderUtils, Track track) {
TrackUtils.updateTrack(TrackStoppedActivity.this, track, viewBinding.trackEditName.getText().toString(),
viewBinding.trackEditActivityType.getText().toString(), viewBinding.trackEditDescription.getText().toString(),
contentProviderUtils);
track.setName(viewBinding.trackEditName.getText().toString());
track.setDescription(viewBinding.trackEditDescription.getText().toString());
track.setActivityTypeLocalizedAndUpdateActivityType(this, viewBinding.trackEditActivityType.getText().toString());
contentProviderUtils.updateTrack(track);
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = TrackStoppedBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -141,7 +143,6 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
@Override
protected void onDeleteConfirmed() {
isDiscarding = true;
viewBinding.loadingLayout.loadingText.setText(getString(R.string.track_discarding));
viewBinding.contentLinearLayout.setVisibility(View.GONE);
viewBinding.loadingLayout.loadingIndeterminate.setVisibility(View.VISIBLE);
@@ -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)));
}
}
@@ -777,8 +784,8 @@ public class ContentProviderUtils {
values.put(TrackPointsColumns.TYPE, trackPoint.getType().type_db);
if (trackPoint.hasLocation()) {
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getPosition().latitude() * 1E6));
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getPosition().longitude() * 1E6));
}
values.put(TrackPointsColumns.TIME, trackPoint.getTime().toEpochMilli());
if (trackPoint.hasAltitude()) {
@@ -129,10 +129,12 @@ public enum ActivityType {
.orElse(ActivityType.UNKNOWN);
}
@NonNull
public static ActivityType findByLocalizedString(Context context, String localizedActivityType) {
return findByLocalizedString(context.getResources(), localizedActivityType);
}
@NonNull
public static ActivityType findByLocalizedString(Resources resources, String localizedActivityType) {
return Arrays.stream(ActivityType.values())
.filter(it -> resources.getString(it.getLocalizedStringId()).equals(localizedActivityType))
@@ -132,14 +132,6 @@ public final class Marker {
return position;
}
public double getLatitude() {
return position.latitude();
}
public double getLongitude() {
return position.longitude();
}
public boolean hasAccuracy() {
return position.hasHorizontalAccuracy();
}
@@ -16,6 +16,7 @@
package de.dennisguse.opentracks.data.models;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
@@ -48,7 +49,7 @@ public class Track {
private ActivityType activityType;
private final ZoneOffset zoneOffset;
private ZoneOffset zoneOffset;
private TrackStatistics trackStatistics = new TrackStatistics();
@@ -58,7 +59,7 @@ public class Track {
}
public Track(@NonNull ZoneOffset zoneOffset) {
this.zoneOffset = zoneOffset;
setZoneOffset(zoneOffset);
}
/**
@@ -113,10 +114,19 @@ public class Track {
this.activityType = activityType;
}
public void setActivityTypeLocalizedAndUpdateActivityType(Context context, String activityTypeLocalized) {
setActivityTypeLocalized(activityTypeLocalized);
setActivityType(ActivityType.findByLocalizedString(context, activityTypeLocalized));
}
public ZoneOffset getZoneOffset() {
return zoneOffset;
}
public void setZoneOffset(@NonNull ZoneOffset zoneOffset) {
this.zoneOffset = zoneOffset;
}
public OffsetDateTime getStartTime() {
return trackStatistics
.getStartTime().atOffset(zoneOffset);
@@ -147,18 +147,6 @@ public class TrackPoint {
return position.hasLocation();
}
//TODO Should be double
@Deprecated //Use getPosition()
public Double getLatitude() {
return position.latitude();
}
//TODO Should be double
@Deprecated //Use getPosition()
public Double getLongitude() {
return position.longitude();
}
@NonNull
public Position getPosition() {
return position;
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
@@ -33,7 +32,6 @@ import androidx.fragment.app.Fragment;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
@@ -185,13 +183,7 @@ public class StatisticsRecordedFragment extends Fragment {
}
// Set activity type
{
Context context = getContext();
String localizedActivityType = track.getActivityTypeLocalized();
int iconDrawableId = ActivityType.findByLocalizedString(context, localizedActivityType)
.getIconDrawableId();
viewBinding.statsActivityTypeIcon.setImageDrawable(ContextCompat.getDrawable(getContext(), iconDrawableId));
}
viewBinding.statsActivityTypeIcon.setImageDrawable(ContextCompat.getDrawable(getContext(), track.getActivityType().getIconDrawableId()));
// Set time and start datetime
{
@@ -47,8 +47,9 @@ public class IntroductionActivity extends AbstractActivity {
viewBinding.introductionViewPager.setUserInputEnabled(false);
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = IntroductionBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -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
@@ -60,7 +60,6 @@ public class CSVTrackExporter implements TrackExporter {
ALTITUDE_FORMAT.setGroupingUsed(false);
COORDINATE_FORMAT.setMaximumFractionDigits(6);
COORDINATE_FORMAT.setMaximumIntegerDigits(3);
COORDINATE_FORMAT.setGroupingUsed(false);
SPEED_FORMAT.setMaximumFractionDigits(2);
@@ -92,19 +91,19 @@ public class CSVTrackExporter implements TrackExporter {
List<Column> columns = List.of(
new Column("time", null),
new Column("trackpoint_type", t -> quote(t.getType().name())),
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""),
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getPosition().latitude()) : ""),
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getPosition().longitude()) : ""),
new Column("altitude", t -> t.hasAltitude() ? ALTITUDE_FORMAT.format(t.getAltitude().toM()) : ""),
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""),
new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""),
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""),
new Column("altitude_gain", t -> t.hasAltitudeGain() ? ALTITUDE_FORMAT.format(t.getAltitudeGain()) : ""),
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? ALTITUDE_FORMAT.format(t.getAltitudeLoss()) : ""),
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""),
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""),
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""),
new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : ""));
new Column("power", t -> t.hasPower() ? POWER_FORMAT.format(t.getPower().getW()) : ""));
try {
prepare(outputStream);
@@ -136,7 +135,7 @@ public class CSVTrackExporter implements TrackExporter {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) throw new InterruptedException();
writeTrackPoint(columns, trackPointIterator.next());
writeTrackPoint(columns, trackPointIterator.next());
}
}
}
@@ -21,7 +21,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -95,8 +94,6 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
private int trackExportSkippedCount;
private int trackExportTotalCount;
boolean doubleBackToCancel = false;
private ExportActivityBinding viewBinding;
private ArrayList<String> trackErrors = new ArrayList<>();
@@ -214,19 +211,6 @@ public class ExportActivity extends AppCompatActivity implements ExportService.E
exportTasks.clear();
}
@Override
public void onBackPressed() {
if (doubleBackToCancel || getTotalDone() == trackExportTotalCount) {
super.onBackPressed();
return;
}
this.doubleBackToCancel = true;
Toast.makeText(this, getString(R.string.generic_click_twice_cancel), Toast.LENGTH_SHORT).show();
new Handler().postDelayed(() -> doubleBackToCancel = false, 2000);
}
private void createExportTasks(boolean allInOneFile) {
List<Track> tracks = contentProviderUtils.getTracks();
exportTasks = new ArrayList<>();
@@ -7,6 +7,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
@@ -17,6 +18,8 @@ import de.dennisguse.opentracks.util.ExportUtils;
public class ExportService extends JobIntentService {
private static final String TAG = ExportService.class.getSimpleName();
private static final int JOB_ID = 1;
private static final String EXTRA_RECEIVER = "extra_receiver";
@@ -55,6 +58,9 @@ public class ExportService extends JobIntentService {
ExportUtils.exportTrack(this, directoryFile, exportTask);
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_SUCCESS, bundle);
} catch (Exception e) {
Log.e(TAG, "Export failed: " + e);
e.printStackTrace(); //TODO Remove
bundle.putString(ExportServiceResultReceiver.EXTRA_EXPORT_ERROR_MESSAGE, e.getMessage());
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
}
@@ -36,6 +36,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -102,7 +103,6 @@ public class GPXTrackExporter implements TrackExporter {
try {
prepare(outputStream);
writeHeader();
for (Track track : tracks) {
writeMarkers(track);
}
@@ -200,17 +200,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 +228,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,8 +253,8 @@ public class GPXTrackExporter implements TrackExporter {
}
}
public void writeMarker(ZoneOffset zoneOffset, Marker marker) {
printWriter.println("<wpt " + formatLocation(marker.getLatitude(), marker.getLongitude()) + ">");
private void writeMarker(ZoneOffset zoneOffset, Marker marker) {
printWriter.println("<wpt " + formatLocation(marker.getPosition()) + ">");
if (marker.hasAltitude()) {
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(marker.getAltitude().toM()) + "</ele>");
}
@@ -265,7 +265,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,22 +297,22 @@ 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()) + ">");
printWriter.println("<trkpt " + formatLocation(trackPoint.getPosition()) + ">");
if (trackPoint.hasAltitude()) {
printWriter.println("<ele>" + ALTITUDE_FORMAT.format(trackPoint.getAltitude().toM()) + "</ele>");
@@ -321,51 +321,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>");
}
}
@@ -385,7 +396,7 @@ public class GPXTrackExporter implements TrackExporter {
.orElse(null);
}
private String formatLocation(double latitude, double longitude) {
return "lat=\"" + COORDINATE_FORMAT.format(latitude) + "\" lon=\"" + COORDINATE_FORMAT.format(longitude) + "\"";
private String formatLocation(Position position) {
return "lat=\"" + COORDINATE_FORMAT.format(position.latitude()) + "\" lon=\"" + COORDINATE_FORMAT.format(position.longitude()) + "\"";
}
}
@@ -33,7 +33,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -107,6 +106,7 @@ public class KMLTrackExporter implements TrackExporter {
this.contentProviderUtils = contentProviderUtils;
}
@Override
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
try {
prepare(outputStream);
@@ -273,8 +273,7 @@ public class KMLTrackExporter implements TrackExporter {
private void writeMarker(Marker marker, ZoneOffset zoneOffset) {
boolean existsPhoto = MarkerUtils.buildInternalPhotoFile(context, marker.getTrackId(), marker.getPhotoUrl()) != null;
if (marker.hasPhoto() && exportPhotos && existsPhoto) {
float heading = getHeading(marker.getTrackId(), marker.getPosition());
writePhotoOverlay(marker, heading, zoneOffset);
writePhotoOverlay(marker, zoneOffset);
} else {
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getPosition(), marker.getTime(), zoneOffset);
}
@@ -428,15 +427,14 @@ public class KMLTrackExporter implements TrackExporter {
}
}
private void writePhotoOverlay(Marker marker, float heading, ZoneOffset zoneOffset) {
private void writePhotoOverlay(Marker marker, ZoneOffset zoneOffset) {
printWriter.println("<PhotoOverlay>");
printWriter.println("<name>" + StringUtils.formatCData(marker.getName()) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(marker.getDescription()) + "</description>");
printWriter.print("<Camera>");
printWriter.print("<longitude>" + marker.getLongitude() + "</longitude>");
printWriter.print("<latitude>" + marker.getLatitude() + "</latitude>");
printWriter.print("<longitude>" + marker.getPosition().longitude() + "</longitude>");
printWriter.print("<latitude>" + marker.getPosition().latitude() + "</latitude>");
printWriter.print("<altitude>20</altitude>");
printWriter.print("<heading>" + heading + "</heading>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getTime()) + "</when></TimeStamp>");
@@ -444,7 +442,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>");
@@ -467,26 +465,6 @@ public class KMLTrackExporter implements TrackExporter {
return StringUtils.formatDateTimeIso8601(instant, zoneOffset);
}
/**
* TODO: check if this is a useful feature (likely not).
* Gets the heading to a location.
*/
private float getHeading(Track.Id trackId, Position position) {
TrackPoint.Id trackPointId = contentProviderUtils.getTrackPointId(trackId, position);
if (trackPointId == null) {
return position.bearing();
}
TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
if (viewLocation != null) {
Optional<Float> bearing = viewLocation.bearingTo(position);
if (bearing.isPresent()) {
return bearing.get();
}
}
return position.bearing();
}
private static String getCoordinates(Position position, String separator) {
String result = position.longitude() + separator + position.latitude();
if (position.hasAltitude()) {
@@ -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;
}
@@ -20,9 +20,7 @@ import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
@@ -56,8 +54,6 @@ public class ImportActivity extends FragmentActivity {
private ImportActivityBinding viewBinding;
boolean doubleBackToCancel = false;
private ArrayList<Uri> documentUris = new ArrayList<>();
private boolean isDirectory;
@@ -122,21 +118,6 @@ public class ImportActivity extends FragmentActivity {
outState.putBoolean(BUNDLE_IS_DIRECTORY, isDirectory);
}
@Override
public void onBackPressed() {
if (doubleBackToCancel || (summary != null && getTotalDone() == summary.getTotalCount())) {
super.onBackPressed();
viewModel.cancel();
getViewModelStore().clear();
return;
}
this.doubleBackToCancel = true;
Toast.makeText(this, getString(R.string.generic_click_twice_cancel), Toast.LENGTH_SHORT).show();
new Handler().postDelayed(() -> doubleBackToCancel=false, 2000);
}
private void initViews() {
viewBinding.importProgressDone.setText("0");
viewBinding.importProgressTotal.setText("0");
@@ -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
@@ -178,10 +178,10 @@ public class TrackImporter {
if (current.hasLocation()) {
Instant time = current.getTime();
if (current.getLatitude() == 100) {
if (current.getPosition().latitude() == 100) {
//TODO Remove by 31st December 2021.
trackPoints.set(i, new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, time));
} else if (current.getLatitude() == 200) {
} else if (current.getPosition().latitude() == 200) {
//TODO Remove by 31st December 2021.
trackPoints.set(i, new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, time));
//TODO Delete location
@@ -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);
@@ -10,7 +10,6 @@ import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.TrackUtils;
public class StartRecording extends AbstractAPIActivity {
@@ -42,12 +41,12 @@ public class StartRecording extends AbstractAPIActivity {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Track track = contentProviderUtils.getTrack(trackId);
TrackUtils.updateTrack(this, track,
bundle.getString(EXTRA_TRACK_NAME, null),
bundle.getString(EXTRA_TRACK_ACTIVITY_TYPE_LOCALIZED, null),
ActivityType.findBy(bundle.getString(EXTRA_TRACK_ACTIVITY_TYPE_ID, null)),
bundle.getString(EXTRA_TRACK_DESCRIPTION, null),
contentProviderUtils);
track.setName(bundle.getString(EXTRA_TRACK_NAME, ""));
track.setDescription(bundle.getString(EXTRA_TRACK_DESCRIPTION, ""));
track.setActivityType(ActivityType.findBy(bundle.getString(EXTRA_TRACK_ACTIVITY_TYPE_ID, null)));
track.setActivityTypeLocalized(bundle.getString(EXTRA_TRACK_ACTIVITY_TYPE_LOCALIZED, ""));
contentProviderUtils.updateTrack(track);
}
private void startDashboardAPI(@NonNull Track.Id trackId, @NonNull Bundle bundle) {
@@ -72,8 +72,9 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = SettingsBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -116,8 +116,9 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
recordingLayoutFieldsVisible = null;
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = ActivitySettingsCustomLayoutBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -108,8 +108,9 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
adapter.reloadLayouts();
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
PreferencesUtils.getCustomLayout();
viewBinding = ActivitySettingsCustomLayoutListBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
@@ -35,7 +35,6 @@ import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.util.ActivityUtils;
import de.dennisguse.opentracks.ui.util.ListItemUtils;
import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.StringUtils;
public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ActionMode.Callback {
@@ -164,7 +163,6 @@ public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private final TrackListItemBinding viewBinding;
private final View view;
private Track.Id trackId;
@@ -172,10 +170,9 @@ public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
super(itemView);
viewBinding = TrackListItemBinding.bind(itemView);
view = itemView;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
viewBinding.getRoot().setOnClickListener(this);
viewBinding.getRoot().setOnLongClickListener(this);
}
@@ -229,18 +226,12 @@ public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
viewBinding.trackListItemTime.setText(null);
}
//TODO Check if this is needed or a leftover from the MarkerList migration
String category = activityType == null ? activityTypeLocalized : null;
String categoryDescription = StringUtils.getCategoryDescription(category, description);
viewBinding.trackListItemCategoryDescription.setText(categoryDescription);
viewBinding.trackListItemCategoryDescription.setVisibility("".equals(categoryDescription) ? View.GONE : View.VISIBLE);
setSelected(selection.get((int) getId()));
}
public void setSelected(boolean isSelected) {
selection.put((int) getId(), isSelected);
view.setActivated(isSelected);
viewBinding.getRoot().setActivated(isSelected);
}
public long getId() {
@@ -251,7 +242,7 @@ public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
@Override
public void onClick(View v) {
if (selectionMode) {
setSelected(!view.isActivated());
setSelected(!viewBinding.getRoot().isActivated());
actionMode.invalidate();
return;
}
@@ -267,14 +258,14 @@ public class TrackListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
ActivityOptions activityOptions = ActivityOptions.makeSceneTransitionAnimation(
context,
new Pair<>(view.findViewById(R.id.track_list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
new Pair<>(viewBinding.getRoot().findViewById(R.id.track_list_item_icon), TrackRecordedActivity.VIEW_TRACK_ICON));
context.startActivity(newIntent, activityOptions.toBundle());
}
}
@Override
public boolean onLongClick(View v) {
setSelected(!view.isActivated());
setSelected(!viewBinding.getRoot().isActivated());
if (!selectionMode) {
actionMode = context.startSupportActionMode(TrackListAdapter.this);
} else {
@@ -0,0 +1,19 @@
package de.dennisguse.opentracks.ui;
import android.content.Context;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import java.time.ZoneOffset;
import java.util.stream.IntStream;
public class ZoneOffsetAdapter extends ArrayAdapter<ZoneOffset> {
public ZoneOffsetAdapter(@NonNull Context context, int resource) {
super(context, resource, IntStream.range(-12, +12)
.boxed()
.map(ZoneOffset::ofHours)
.toList());
}
}
@@ -83,8 +83,9 @@ public class AggregatedStatisticsActivity extends AbstractActivity implements Fi
outState.putBoolean(STATE_ARE_FILTERS_APPLIED, areFiltersApplied);
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = AggregatedStatsBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@@ -24,17 +24,15 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.tables.MarkerColumns;
import de.dennisguse.opentracks.databinding.MarkerDetailActivityBinding;
import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller;
@@ -53,7 +51,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
private MarkerDetailActivityBinding viewBinding;
private List<Marker.Id> markerIds;
private Cursor cursor;
@Override
protected void onCreate(Bundle bundle) {
@@ -69,40 +67,29 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Marker marker = contentProviderUtils.getMarker(markerId);
markerIds = new ArrayList<>();
//TODO only load used data: ID + name
cursor = contentProviderUtils.getMarkerCursor(marker.getTrackId(), null, -1);
if (cursor == null) {
finish();
}
int markerIndex = -1;
//TODO Load only markerIds, not the whole marker
try (Cursor cursor = contentProviderUtils.getMarkerCursor(marker.getTrackId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
Marker currentMarker = contentProviderUtils.createMarker(cursor);
markerIds.add(currentMarker.getId());
if (markerId.equals(currentMarker.getId())) {
markerIndex = markerIds.size() - 1;
}
cursor.moveToNext();
if (cursor != null && cursor.moveToFirst()) {
while (markerIndex == -1 && cursor.moveToNext()) {
Marker.Id currentId = new Marker.Id(cursor.getLong(cursor.getColumnIndexOrThrow(MarkerColumns._ID)));
if (markerId.equals(currentId)) {
markerIndex = cursor.getPosition();
}
}
}
final MarkerDetailPagerAdapter markerAdapter = new MarkerDetailPagerAdapter(getSupportFragmentManager());
final MarkerDetailPagerAdapter markerAdapter = new MarkerDetailPagerAdapter(this);
viewBinding.makerDetailActivityViewPager.setAdapter(markerAdapter);
viewBinding.makerDetailActivityViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
viewBinding.makerDetailActivityViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
viewBinding.markerDetailToolbar.setTitle(markerAdapter.getPageTitle(position));
// viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(markerAdapter.getPageTitle(position)); TOOD
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
viewBinding.makerDetailActivityViewPager.setCurrentItem(markerIndex == -1 ? 0 : markerIndex);
@@ -110,43 +97,49 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
}
@NonNull
@Override
protected View getRootView() {
protected View createRootView() {
viewBinding = MarkerDetailActivityBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@Override
protected void onDestroy() {
super.onDestroy();
viewBinding = null;
if (cursor != null) cursor.close();
cursor = null;
}
@Override
public void onMarkerDeleted() {
runOnUiThread(this::finish);
}
/**
* Marker detail pager adapter.
*
* @author Jimmy Shih
*/
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
private class MarkerDetailPagerAdapter extends FragmentStateAdapter {
MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
MarkerDetailPagerAdapter(@NonNull FragmentActivity fa) {
super(fa);
}
@Override
@NonNull
public Fragment getItem(int position) {
return MarkerDetailFragment.newInstance(markerIds.get(position));
public Fragment createFragment(int position) {
cursor.moveToPosition(position);
return MarkerDetailFragment.newInstance(new Marker.Id(cursor.getLong(cursor.getColumnIndexOrThrow(MarkerColumns._ID))));
}
@Override
public int getItemCount() {
return cursor.getCount();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return getString(R.string.marker_title, position + 1, getCount());
}
@Override
public int getCount() {
return markerIds.size();
cursor.moveToPosition(position);
return getString(R.string.marker_detail_title, position + 1, getItemCount(), cursor.getString(cursor.getColumnIndexOrThrow(MarkerColumns.NAME)));
}
}
}

Some files were not shown because too many files have changed in this diff Show More