Compare commits

..

107 Commits

Author SHA1 Message Date
Dennis Guse 117281469d Release: v4.13.0 2024-08-04 14:12:11 +02:00
Fjuro 3782a22684 Translated using Weblate (Czech)
Currently translated at 99.8% (531 of 532 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/
2024-08-03 20:09:21 +02:00
Stephan Paternotte aed2025a5a Translated using Weblate (Dutch)
Currently translated at 99.8% (531 of 532 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/nl/
2024-08-02 12:54:31 +02:00
gallegonovato 780be5d1a9 Translated using Weblate (Spanish)
Currently translated at 99.8% (531 of 532 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/es/
2024-08-02 12:54:31 +02:00
tct123 a2d9b10381 Translated using Weblate (German)
Currently translated at 99.4% (528 of 531 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/
2024-07-30 15:24:21 +02:00
Jake 5950c73785 Translated using Weblate (Polish)
Currently translated at 78.9% (419 of 531 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/pl/
2024-07-30 15:24:21 +02:00
Jake 99a9248b27 Translated using Weblate (English (en_001))
Currently translated at 47.8% (254 of 531 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_001/
2024-07-30 15:24:21 +02:00
Aliberk Sandıkçı 6bcf1c0818 Translated using Weblate (Turkish)
Currently translated at 7.0% (10 of 141 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/tr/
2024-07-30 15:24:21 +02:00
Aliberk Sandıkçı 96dd057287 Translated using Weblate (Turkish)
Currently translated at 96.9% (515 of 531 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/
2024-07-30 15:24:21 +02:00
Stephan Paternotte 84aeebea4a Translated using Weblate (Dutch)
Currently translated at 100.0% (141 of 141 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2024-07-30 15:24:21 +02:00
Fjuro cfb3ff9985 Translated using Weblate (Czech)
Currently translated at 100.0% (141 of 141 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2024-07-30 15:24:21 +02:00
Peter Storch 389c4bed96 fix #1957 open MarkerEditActivity to receive external marker (#1960)
* fix #1957 add CreateMarkerActivity to receive external marker
2024-07-30 15:24:18 +02:00
m-gail d8acf7df22 Center icons in marker-list and track-list search bar 2024-07-25 09:41:39 +02:00
dependabot[bot] 53ea905713 Bump com.android.tools.build:gradle from 8.5.0 to 8.5.1
Bumps com.android.tools.build:gradle from 8.5.0 to 8.5.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>
2024-07-16 20:02:19 +02:00
Dennis Guse 8cceadd93e Reverted: Cleanup using AndroidStudio analyze (some)
d8eed2ccc9
2024-07-12 07:41:21 +02:00
Dennis Guse d8eed2ccc9 Cleanup using AndroidStudio analyze. 2024-07-11 07:38:45 +02:00
Dennis Guse f5190aef06 Cleanup using AndroidStudio analyze. 2024-07-11 07:33:43 +02:00
Dennis Guse 9834c60f1c Bugfix: settings for default activity type now the dropdown.
Fixes #1953.
2024-07-11 07:13:01 +02:00
Dennis Guse 6eeb9a0eb2 Bugfix: kickscooter icon needed tinting. 2024-07-11 07:09:20 +02:00
Dennis Guse ea23e25a47 Bugfix: label for off trail hiking was missing the "off trail". 2024-07-11 07:06:15 +02:00
Dennis Guse 53e97a5e4c Bugfix: back arrow in setting is now working.
Fixes #1932.
2024-07-11 07:03:43 +02:00
dependabot[bot] f1f9153ec6 Bump reactivecircus/android-emulator-runner from 2.31.0 to 2.32.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.31.0 to 2.32.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.31.0...v2.32.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>
2024-07-09 06:41:08 +02:00
Dennis Guse e442558eed Introduced Position.
Internal value object replacing android.location.Location.

Follow-up to #1898.
2024-07-06 14:51:45 +02:00
Dennis Guse 382acadc6a Release: v4.12.5 2024-07-06 13:33:08 +02:00
Dennis Guse 1ed807054b Bugfix: moving time was not computed.
Introduced in 6be9d2477c (first released in v4.9.0)

Fixes #1756.
2024-07-05 23:42:25 +02:00
Dennis Guse c96635c46b Upgrade to Gradlew 8.8 2024-07-05 07:29:50 +02:00
Michel CABILLOT 9b2c93f74f Translated using Weblate (French)
Currently translated at 92.4% (491 of 531 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/
2024-06-27 06:51:38 +02:00
Fjuro e7a86b6aad Translated using Weblate (Czech)
Currently translated at 99.8% (530 of 531 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/
2024-06-27 06:51:38 +02:00
dependabot[bot] b86f8c9a20 Bump androidx.test:rules from 1.6.0 to 1.6.1
Bumps androidx.test:rules from 1.6.0 to 1.6.1.

---
updated-dependencies:
- dependency-name: androidx.test:rules
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 06:51:33 +02:00
dependabot[bot] e9130f3402 Bump androidx.test:core from 1.6.0 to 1.6.1
Bumps androidx.test:core from 1.6.0 to 1.6.1.

---
updated-dependencies:
- dependency-name: androidx.test:core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 06:47:48 +02:00
dependabot[bot] a08ec97c29 Bump androidx.test:runner from 1.6.0 to 1.6.1
Bumps androidx.test:runner from 1.6.0 to 1.6.1.

---
updated-dependencies:
- dependency-name: androidx.test:runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 06:47:40 +02:00
dependabot[bot] a6340d13fa Bump androidx.test.ext:junit from 1.2.0 to 1.2.1
Bumps androidx.test.ext:junit from 1.2.0 to 1.2.1.

---
updated-dependencies:
- dependency-name: androidx.test.ext:junit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 06:45:44 +02:00
dependabot[bot] dbbf6abcd6 Bump androidx.test.espresso:espresso-core from 3.6.0 to 3.6.1
Bumps androidx.test.espresso:espresso-core from 3.6.0 to 3.6.1.

---
updated-dependencies:
- dependency-name: androidx.test.espresso:espresso-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 06:45:28 +02:00
dependabot[bot] 683bf2d280 Bump androidx.test.espresso:espresso-core from 3.5.1 to 3.6.0
Bumps androidx.test.espresso:espresso-core from 3.5.1 to 3.6.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-26 07:07:30 +02:00
dependabot[bot] 6c2377a8e8 Bump androidx.test:core from 1.5.0 to 1.6.0
Bumps androidx.test:core from 1.5.0 to 1.6.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 06:39:36 +02:00
dependabot[bot] b6661ccafd Bump androidx.test:rules from 1.5.0 to 1.6.0
Bumps androidx.test:rules from 1.5.0 to 1.6.0.

---
updated-dependencies:
- dependency-name: androidx.test:rules
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 06:39:29 +02:00
dependabot[bot] 932c142691 Bump androidx.test:orchestrator from 1.4.2 to 1.5.0
Bumps androidx.test:orchestrator from 1.4.2 to 1.5.0.

---
updated-dependencies:
- dependency-name: androidx.test:orchestrator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 06:38:29 +02:00
dependabot[bot] 57918249bc Bump androidx.test.ext:junit from 1.1.5 to 1.2.0
Bumps androidx.test.ext:junit from 1.1.5 to 1.2.0.

---
updated-dependencies:
- dependency-name: androidx.test.ext:junit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 06:38:16 +02:00
dependabot[bot] 3405eca556 Bump androidx.test:runner from 1.5.2 to 1.6.0
Bumps androidx.test:runner from 1.5.2 to 1.6.0.

---
updated-dependencies:
- dependency-name: androidx.test:runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 06:37:55 +02:00
ovl-1 a932adde3f Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.9% (515 of 531 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/
2024-06-21 11:09:19 +02:00
ovl-1 a03dfd81ac Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.2% (506 of 531 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/
2024-06-14 05:09:49 +00:00
Stephan Paternotte 7550525462 Translated using Weblate (Dutch)
Currently translated at 100.0% (140 of 140 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2024-06-14 05:09:49 +00:00
Fjuro 5d70381b23 Translated using Weblate (Czech)
Currently translated at 100.0% (140 of 140 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2024-06-14 05:09:48 +00:00
dependabot[bot] 96a4cbf70d Bump com.android.tools.build:gradle from 8.4.2 to 8.5.0
Bumps com.android.tools.build:gradle from 8.4.2 to 8.5.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-14 07:09:44 +02:00
dependabot[bot] 959aadbb62 Bump com.android.tools.build:gradle from 8.4.1 to 8.4.2
Bumps com.android.tools.build:gradle from 8.4.1 to 8.4.2.

---
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>
2024-06-11 06:07:51 +02:00
OMTDesign fa59752c04 Update README_TESTED_SENSORS.md
I have thoroughly tested the CYCPLUS H1 heart rate monitor in Opentracks, and I it works flawlessly. It tracks accurately and it pairs quickly with the app.
2024-06-07 10:06:19 +02:00
dependabot[bot] 5305ec3206 Bump androidx.appcompat:appcompat from 1.6.1 to 1.7.0
Bumps androidx.appcompat:appcompat from 1.6.1 to 1.7.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-30 08:04:55 +02:00
dependabot[bot] 584339b7eb Bump reactivecircus/android-emulator-runner from 2.30.1 to 2.31.0
Bumps [reactivecircus/android-emulator-runner](https://github.com/reactivecircus/android-emulator-runner) from 2.30.1 to 2.31.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.30.1...v2.31.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>
2024-05-27 06:45:21 +02:00
Dennis Guse 151c3a79de Predictive back: for everything except import/export.
Fixes #1708.
2024-05-25 23:46:24 +02:00
Dennis Guse a90c7deb32 Release: v4.12.4 2024-05-25 22:03:52 +02:00
ovl-1 c3601e41cd Translated using Weblate (Norwegian Bokmål)
Currently translated at 94.9% (504 of 531 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/
2024-05-25 21:50:11 +02:00
Ghost of Sparta b3d98340dc Translated using Weblate (Hungarian)
Currently translated at 98.4% (523 of 531 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/
2024-05-25 21:50:11 +02:00
josé m ebbd9c1535 Translated using Weblate (Galician)
Currently translated at 99.6% (529 of 531 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/gl/
2024-05-25 21:50:11 +02:00
ken-der-guru 14fc605c45 Translated using Weblate (German)
Currently translated at 98.4% (523 of 531 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/
2024-05-25 21:50:11 +02:00
Stephan Paternotte c5ca6644f4 Translated using Weblate (Dutch)
Currently translated at 99.8% (530 of 531 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/nl/
2024-05-25 21:50:11 +02:00
Ghost of Sparta e15c729c00 Translated using Weblate (Hungarian)
Currently translated at 98.1% (521 of 531 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/
2024-05-25 21:50:11 +02:00
Fjuro 93c89690c9 Translated using Weblate (Czech)
Currently translated at 99.8% (530 of 531 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/
2024-05-25 21:50:11 +02:00
Luna f3f05512c9 Translated using Weblate (Chinese (Traditional))
Currently translated at 96.4% (512 of 531 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/
2024-05-25 21:50:11 +02:00
Luna d2fc2598d1 Translated using Weblate (Japanese)
Currently translated at 48.3% (257 of 531 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/ja/
2024-05-25 21:50:11 +02:00
Luna fd2d2c5820 Translated using Weblate (Finnish)
Currently translated at 88.5% (470 of 531 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/fi/
2024-05-25 21:50:11 +02:00
Luna 01f10bce24 Translated using Weblate (Basque)
Currently translated at 78.7% (418 of 531 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/eu/
2024-05-25 21:50:11 +02:00
Luna fd963b0fdb Translated using Weblate (German)
Currently translated at 97.1% (516 of 531 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/
2024-05-25 21:50:11 +02:00
Luna 0947405563 Translated using Weblate (English)
Currently translated at 99.8% (530 of 531 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/
2024-05-25 21:50:11 +02:00
Dennis Guse f595bc4d1e Bugfix: delete markers from list was broken.
Fixes #1915.
2024-05-25 21:50:05 +02:00
dependabot[bot] 4b26583c27 ---
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>
2024-05-21 06:30:56 +02:00
Quentin 734d4e08e9 Translated using Weblate (French)
Currently translated at 93.1% (491 of 527 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/
2024-05-13 07:10:12 +02:00
josé m 6615034314 Translated using Weblate (Galician)
Currently translated at 99.8% (526 of 527 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/gl/
2024-05-13 07:10:12 +02:00
Stephan Paternotte 039495d107 Translated using Weblate (Dutch)
Currently translated at 100.0% (139 of 139 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2024-05-13 07:10:12 +02:00
Stephan Paternotte 1d926d72a7 Translated using Weblate (Dutch)
Currently translated at 99.8% (526 of 527 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/nl/
2024-05-13 07:10:12 +02:00
josé m d1af0fa072 Translated using Weblate (Galician)
Currently translated at 99.4% (524 of 527 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/gl/
Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
2024-05-13 07:10:12 +02:00
gallegonovato ef802d1948 Translated using Weblate (Spanish)
Currently translated at 99.4% (524 of 527 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/es/
Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
2024-05-13 07:10:12 +02:00
Fjuro 322e91bc22 Translated using Weblate (Czech)
Currently translated at 99.8% (526 of 527 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (139 of 139 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/cs/
Translation: OpenTracks/App store metadata files
Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
2024-05-13 07:10:12 +02:00
dependabot[bot] 055f2beb1e Bump org.mockito:mockito-android from 5.11.0 to 5.12.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.11.0 to 5.12.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.11.0...v5.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 07:09:53 +02:00
Peter Hercek 6cfc05af82 support for lap cycling power in the announcer
Implements #1806
2024-05-12 15:32:23 +02:00
dependabot[bot] 70a2040142 Bump com.google.android.material:material from 1.11.0 to 1.12.0
Bumps [com.google.android.material:material](https://github.com/material-components/material-components-android) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.11.0...1.12.0)

---
updated-dependencies:
- dependency-name: com.google.android.material:material
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-03 12:43:02 +02:00
dependabot[bot] 181e1a503c Bump androidx.core:core from 1.13.0 to 1.13.1
Bumps androidx.core:core from 1.13.0 to 1.13.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-02 07:30:35 +02:00
Dennis Guse b08302237f READMEs: added reference to NoTrustIssues. 2024-05-01 20:02:43 +02:00
Dennis Guse 881f68c580 Upgrade to Gradlew 8.7.0 2024-05-01 19:52:58 +02:00
dm1cael a8a5aa4268 Announcements: add toggle to distance/speed unit announcement
Fixes #1805.
2024-05-01 19:23:26 +02:00
dependabot[bot] 9c8b9ca05d Bump com.android.tools.build:gradle from 8.3.2 to 8.4.0
Bumps com.android.tools.build:gradle from 8.3.2 to 8.4.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-01 09:21:00 +02:00
Dennis Guse b70241c967 Upgrade to Gradlew 8.7.0 2024-05-01 09:11:12 +02:00
Dennis Guse a6f3c05e82 ExifTool now supports OpenTracks's GPX. 2024-04-29 07:53:25 +02:00
Stephan Paternotte f9640b0fb1 Translated using Weblate (Dutch)
Currently translated at 100.0% (138 of 138 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2024-04-28 11:09:07 +02:00
Fjuro 80a48563d3 Translated using Weblate (Czech)
Currently translated at 100.0% (138 of 138 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2024-04-28 11:09:07 +02:00
Dennis Guse 9e496f97c1 Release: v4.12.3 2024-04-28 11:02:07 +02:00
Dennis Guse 455c2fcf75 Bugfix: idle time should start also if the first trackpoint with a location is stored.
Identified in #1898.
2024-04-28 10:50:56 +02:00
Dennis Guse e590e4e07e Bugfix: overestimated distance.
A combination of idle trackpoint and stale GPS data, resulted in recording lat=0.0 and lng=0.0; and thus inflating distance as well as GPS-based speed.

Fixes #1898.

Introduced in dfdbc373ff
2024-04-28 10:50:56 +02:00
dependabot[bot] 36474e8541 Bump androidx.core:core from 1.12.0 to 1.13.0
Bumps androidx.core:core from 1.12.0 to 1.13.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-18 06:44:20 +02:00
Dennis Guse be7a43823a Release: v4.12.2. 2024-04-14 13:00:48 +02:00
Cat 64326590e6 Translated using Weblate (Danish)
Currently translated at 3.6% (5 of 137 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/da/
2024-04-13 15:02:25 +02:00
Cat 82aa23f0bb Translated using Weblate (Danish)
Currently translated at 90.8% (475 of 523 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/da/
2024-04-13 15:02:24 +02:00
Dennis Guse 13eb5c30a2 Bugfix: if sensors are running, do not try to start again.
Fixes #1887.
2024-04-12 06:51:05 +02:00
dependabot[bot] 7bbdd55a93 Bump com.android.tools.build:gradle from 8.3.1 to 8.3.2
Bumps com.android.tools.build:gradle from 8.3.1 to 8.3.2.

---
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>
2024-04-10 07:35:14 +02:00
Stephan Paternotte 1617a7377b Translated using Weblate (Dutch)
Currently translated at 100.0% (137 of 137 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2024-04-08 18:18:07 +02:00
Fjuro af25d158e1 Translated using Weblate (Czech)
Currently translated at 100.0% (137 of 137 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2024-04-08 18:18:07 +02:00
ken-der-guru 47525a260e Translated using Weblate (German)
Currently translated at 98.6% (516 of 523 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/
2024-04-08 18:18:07 +02:00
josé m 2984a8328a Translated using Weblate (Galician)
Currently translated at 100.0% (523 of 523 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/gl/
2024-04-08 18:18:07 +02:00
josé m c7a9a1a7b5 Translated using Weblate (Galician)
Currently translated at 16.1% (22 of 136 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/gl/
2024-04-08 18:18:07 +02:00
ken-der-guru b56eaa740f Translated using Weblate (German)
Currently translated at 97.1% (508 of 523 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/
2024-04-08 18:18:06 +02:00
Ghost of Sparta 214f4adb70 Translated using Weblate (Hungarian)
Currently translated at 100.0% (523 of 523 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/
2024-04-08 18:18:06 +02:00
gallegonovato bc1c3eabca Translated using Weblate (Spanish)
Currently translated at 100.0% (523 of 523 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/es/
2024-04-08 18:18:06 +02:00
Tea-NT cd1ba6d1ae Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (136 of 136 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/zh_Hans/
2024-04-08 18:18:06 +02:00
Tea-NT 0c543e6083 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (523 of 523 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/
2024-04-08 18:18:06 +02:00
Stephan Paternotte a28bf53245 Translated using Weblate (Dutch)
Currently translated at 100.0% (136 of 136 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/nl/
2024-04-08 18:18:06 +02:00
Fjuro 6961f3ee50 Translated using Weblate (Czech)
Currently translated at 100.0% (136 of 136 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/cs/
2024-04-08 18:18:06 +02:00
Dennis Guse 52624b5cfe PublicAPI: add test for setting icon via parameter.
Follow-up to #1818.
2024-04-08 18:17:54 +02:00
Dennis Guse dfdbc373ff GPS: event-based using Raw.
Part of #1424.
2024-04-08 17:48:29 +02:00
113 changed files with 1382 additions and 1052 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
java-version: '17'
- name: run tests
uses: reactivecircus/android-emulator-runner@v2.30.1
uses: reactivecircus/android-emulator-runner@v2.32.0
with:
arch: x86_64
api-level: 34
+3 -1
View File
@@ -70,7 +70,7 @@ hosted.weblate.org</a>.
* export automatically after each recording (e.g., to sync via [Nextcloud](https://nextcloud.com/))
* avoid duplication: each exported file contain a random unique identifier (i.e., `opentracks:trackid`)
* __Altitude:__
* gain/loss via barometric sensor (if present)
* gain/loss via barometric sensor (internal if present or via Bluetooth's Environmental Sensing Service)
* shown in EGM2008 (above mean sea level); exported as WGS84
* __Bluetooth LE sensors:__
* heart rate
@@ -133,6 +133,8 @@ The Public API is disabled by default to protect the user's privacy, but it can
| [OsmAnd](https://github.com/osmandapp/OsmAnd) | ? | [no](https://github.com/osmandapp/OsmAnd/issues/15271) | [no](https://github.com/osmandapp/OsmAnd/issues/15271) |
| [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 |
## Dashboard API (incl. map)
+10 -1
View File
@@ -12,6 +12,7 @@
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
* Garmin HRM-Dual (Reference: 010-12883-00)
* CYCPLUS H1
## 0x1816: Cycling Cadence and Speed Service
@@ -60,7 +61,15 @@ We do not support type of movement (i.e., walking vs running).
* Stryd (incl. power)
* Wahoo Tickr X (Cadence appears to be reported in SPM rather than RPM)
## Tested Barometric Sensor Smartphones
## Barometer (gain/loss)
### 0x2A6D: Environmental Sensing Service
Sadly, Bluetooth sensors providing _air pressure_ via Environmental Sensing Service seem to be very rare.
* NoTrustIssues: https://github.com/OpenTracksApp/NoTrustIssues
An Android app that exposes the internal barometer via Environmental Sensing Service.
### Internal: tested Barometric Sensor Smartphones
OpenTracks derives the altitude gain and altitude loss using the internal barometric sensor of a device (if present).
However, barometric sensor seem to be prone to a lot of noise.
+13 -13
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.3.1'
classpath 'com.android.tools.build:gradle:8.5.1'
}
}
@@ -64,8 +64,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 5730
versionName "v4.12.1"
versionCode 5837
versionName "v4.13.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -127,22 +127,22 @@ android {
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core:1.12.0'
implementation 'androidx.core:core:1.13.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.mediarouter:mediarouter:1.7.0'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'org.mockito:mockito-android:5.11.0'
androidTestImplementation 'androidx.test:core:1.6.1'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test:rules:1.6.1'
androidTestImplementation 'androidx.test:runner:1.6.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
androidTestImplementation 'org.mockito:mockito-android:5.12.0'
androidTestUtil 'androidx.test:orchestrator:1.4.2'
androidTestUtil 'androidx.test:orchestrator:1.5.0'
}
@@ -0,0 +1,8 @@
v4.12.0: OpenTracks
Změny:
- Tmavý motiv pro obrazovky OLED (vyžaduje ruční restart)
- Změna uspořádání nastavení (GPS je senzor)
Vývojáři:
- Přidány skripty pro nahrání Fastlane na Obchod Play (překlady)
@@ -0,0 +1,5 @@
v4.12.1: OpenTracks
Oprava chyb:
- Android 14: nahlášení chybějících oprávnění (namísto nuceného pádu se stacktrace)
- POST_NOTIFICATION je volitelné
@@ -0,0 +1,4 @@
v4.12.2.: OpenTracks
Oprava chyvy:
- Umožnění spuštění senzorů před začátkem záznamu
@@ -0,0 +1,8 @@
v4.12.3: OpenTracks
Opraveny chyby:
- Používání funkcionality IDLE a GPS ukládalo souřadnice 0.0/0.0 a tím pádem přeceňovalo všechny statistiky (vzdálenost >10 000 km). (Vzniklo ve v4.12.0)
- Nečinnost: také spustit, pokud byla obdržena pouze jedna lokace.
Ohledně chyby přeceňování vzdálenosti: uložená data nebudou v OpenTracks opravena.
Doporučení: exportujte záznamy do KML/KMZ a nahraďte neplatné souřadnice.
@@ -0,0 +1,8 @@
v4.12.4: OpenTracks
Změny:
- Oznámení: jednotky lze vynechat
- Oznámení: přidán výkon pro jízdu na kole
Oprava chyby:
- Pád při odstranění značky ze seznamu
@@ -0,0 +1,8 @@
v4.12.5: OpenTracks
Oprava chyby:
- Pohyb je nyní opět počítán správně.
Byl rozbitý od verze 4.9.0 z důvodu představení funkce přestávky. Viz #1756.
Změny:
- Podpora prediktivního zadávání
@@ -0,0 +1,15 @@
v3.0.0: OpenTracks
Første udgivelse af OpenTracks (dvs. hard fork fra MyTracks)
Ændringer:
- Omdøbt fra MyTracks til OpenTracks
- Tilføjet Bluetooth LE-pulssensor
- Fjernet alle funktioner, der kræver internetadgang
- Fjernet Google Maps-integration
- Fjernet brug af Google Play-tjenester
- ANT+ sensorer fjernet
- Fjernet klassiske Bluetooth-sensorer
- Fjernet Protobuf
- Fjernet kalorieberegning og aktivitetsberegning
- Fjernet MyTracks Service-API (dvs. ingen integration med andre apps)
@@ -1,11 +1,22 @@
OpenTracks er et program til sporing af sport, der fuldt ud respekterer dit privatliv.
En sporingsmakker, der respekterer dit privatliv.
Funktioner:
* Sport tracking: Spor dine sports- og udendørsaktiviteter
* Billeder og markører: Markér interessante steder under sporing
* Højdeforøgelse og -tab: via barometrisk sensor
* Højde vist i EGM2008 (over middelhavets overflade); eksporteret som WGS84
* Stemmemeddelelser
* Understøtter Bluetooth LE-sensorer: puls, hastighed og distance (cykling), kadence (cykling) og effektmåler (cykling)
* Eksport af data: Eksportér spor enten som KMZ (inkl. fotos), KML eller GPX
* Ingen internetadgang: Internet bruges ikke
Sports- og udendørsaktiviteter giver glæde, dygtighed og selvtillid.
Værdsæt dit helbred ved at holde styr på din træning.
Registrerer, mens du løber eller går, og giver dig en cykelcomputer med en større skærm til cykling.
Markér interessante steder undervejs med billeder.
Opbevar registrerede statistikker i detaljer til analyse.
Del kun de data, du ønsker, at andre skal have.
* Stemmemeddelelser.
* Understøtter Bluetooth LE-sensorer: puls, hastighed og distance (cykling), kadence (cykling) og effektmåler (cykling).
* Højdestigning og -fald: via barometrisk sensor.
* Højde vist i EGM2008 (over havets overflade); eksporteret som WGS84.
* Eksport af data i form af spor, enten som KMZ (inkl. fotos), KML eller GPX.
* Ingen internetadgang eller ekstra tilladelser.
* Mørkt og lyst tema, der respekterer systemindstillinger.
* Ingen reklamer.
Libre Software / Fri Software / Open Source
hvilket betyder, at du kan bruge, studere, ændre og dele kildekoden.
Licenseret Apache 2.0
@@ -1 +1 @@
En sportstracker, der respekterer dit privatliv fuldt ud.
En sportstracker, der respekterer dit privatliv.
@@ -0,0 +1,4 @@
v4.12.2.: OpenTracks
Bugfix:
- Allow to start sensors before recording
@@ -0,0 +1,8 @@
v4.12.3: OpenTracks
Bugfix:
- Using GPS and IDLE functionality stored coordinates 0.0/0.0 and thus overestimating all statistics (distance >10.000 km). (Introduced in v4.12.0)
- Idle: also trigger if only one location was received.
Regarding the overestimating distance bug: the stored data will not be fixed within OpenTracks.
Recommendation: export the as KML/KMZ and replace the invalid coordinates.
@@ -0,0 +1,8 @@
v4.12.4: OpenTracks
Changes:
- Announcements: units can be omitted
- Announcements: added lap cycling power
Bugfix:
- Delete markers from list crashed
@@ -0,0 +1,8 @@
v4.12.5: OpenTracks
Bugfix:
- Moving is now computed correctly again.
Was broken since v4.9.0 due to the introduction of idle functionality. See #1756.
Changes:
- Support for Predictive Back
@@ -0,0 +1,15 @@
v4.13.0: OpenTracks
Changes:
- Markers can be created via Public API
This allows to create Markers at any location and after recording.
Drawback: Markers are not shown in diagrams anymore.
NOTE: OSMDashboard supports this API after version: v4.1.0
Bugfix:
- Default activity setting should have been a dropdown
- Back arrow not working in settings
- Label for off trail hiking now shows "off trail hiking"
Developer:
- Use Position instead of Android's Location
@@ -0,0 +1,8 @@
v4.12.0: OpenTracks
Cambios:
- decorado escuro amigable con OLED (hai que reiniciar)
- nova orde nos axustes (GPS é un sensor)
Desenvolvemento:
- Engadidos scripts Fastlane para subir á PlayStore (traducións)
@@ -0,0 +1,8 @@
v4.12.0: OpenTracks
Veranderingen:
- OLED-vriendelijk donker thema (handmatige herstart vereist)
- Re-order instellingen (GPS is een sensor)
Ontwikkelaar:
- Scripts toegevoegd voor Fastlane om PlayStore te uploaden (vertalingen)
@@ -0,0 +1,5 @@
v4.12.1: OpenTracks
Bugfix:
- Android 14: ontbrekende rechten melden (in plaats van geforceerde chrash met stacktrace)
- POST_NOTIFICATION is optioneel
@@ -0,0 +1,4 @@
v4.12.2 .: OpenTracks
Bugfix:
- Toestaan dat sensoren voor de opname worden gestart
@@ -0,0 +1,8 @@
v4.12.3: OpenTracks
Bugfix:
- Gebruikmakend van GPS- en IDLE-functionaliteit opgeslagen coördinaten 0.0 / 0.0 en overschat zo alle statistieken (afstand >10.000 km). (Ingevoerd in v4.12.0)
- Idle: ook trigger als er maar één locatie is ontvangen.
Met betrekking tot de bug rond de overschatting van afstand: de opgeslagen gegevens worden niet binnen OpenTracks gecorrigeerd.
Aanbeveling: exporteer de als KML / KMZ en vervang de ongeldige coördinaten.
@@ -0,0 +1,8 @@
v4.12.4: OpenTracks
Veranderingen:
- Aankondigingen: eenheden kunnen worden weggelaten
- Aankondigingen: extra wielrenkracht
Bugfix:
- Markeringen verwijderen uit lijst gecrasht
@@ -0,0 +1,8 @@
v4.12.5: OpenTracks
Foutcorrectie:
- Verplaatsing wordt weer correct berekend.
Was defect sinds v4.9.0 t.g.v. de introductie van inactieve functionaliteit. Zie # 1756.
Veranderingen:
- Ondersteuning voor Predictive Back
@@ -0,0 +1,4 @@
v4.11.1: OpenTracks
Hata Düzeltme:
- PublicAPI durdurulurken çöküyor
@@ -0,0 +1,5 @@
v4.11.2: OpenTracks
Hata Düzeltme:
- Ekran kapandığında kayıt durduruluyor (v4.11.1 versiyonundan beri)
(geri dönülüyor da3dd29b8e44624a8222caea9067955f252ef5f8)
@@ -0,0 +1,5 @@
v4.12.1: OpenTracks
Hata Ayıklama:
- Android 14: eksik izinleri bildir (hata yerine)
- POST_NOTIFICATION opsiyonel
@@ -0,0 +1,4 @@
v4.12.2.: OpenTracks
Hata Ayıklama:
- Sensörleri kayıttan önce başlatmaya izin ver
@@ -0,0 +1,8 @@
v4.12.4: OpenTracks
Değişiklikler:
- Duyurular: birimler ihmal edilebilir
- Duyurular: tur bisikleti gücü eklendi
Hata Ayıklama:
- İşaretçileri çöken listesinden sil
@@ -0,0 +1,8 @@
v4.12.5: OpenTracks
Hata Ayıklama:
- Hareketler artık yeniden doğru bir şekilde hesaplanıyor.
boşta durulan vakit özelliğinin getirilmesinden dolayı v4.9.0'den beri bozuktu. #1756'ya bakınız
Değişiklikler:
- Tahmine Dayalı Geri Dönüş Desteği eklendi
@@ -0,0 +1,10 @@
(versiyonAdı): OpenTracks
Değişiklikler:
-
Hata Ayıklama:
-
Geliştirici:
-
@@ -0,0 +1 @@
OpenTracks
@@ -0,0 +1,8 @@
v4.12.0: OpenTracks
更改:
- OLED 友好型暗色主题(需要手动重启)
- 重新排序设置(GPS 是一个传感器)
开发人员:
- 为 PlayStore 上传添加了 Fastlane 脚本(翻译)
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+10 -10
View File
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -88,9 +88,9 @@ public class TestDataUtil {
stats.setTotalDistance(Distance.of(0));
stats.setTotalTime(Duration.ofMillis(0));
List<Marker> markers = List.of(
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, stats, trackPoints.get(1), null),
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, stats, trackPoints.get(4), null),
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, stats, trackPoints.get(5), null)
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, trackPoints.get(1), null),
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, trackPoints.get(4), null),
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, trackPoints.get(5), null)
);
return new TrackData(track, trackPoints, markers);
@@ -152,7 +152,7 @@ public class TestDataUtil {
stats.setTotalDistance(Distance.of(0));
stats.setTotalTime(Duration.ofMillis(0));
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, stats, trackPoint, photoUrl);
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, trackPoint, photoUrl);
}
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
@@ -9,7 +9,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import androidx.preference.PreferenceManager;
@@ -140,7 +139,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);
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
contentProviderUtils.insertMarker(new Marker("Marker 1", "Marker 1 desc", "Marker 1 category", null, trackId, service.getLastStoredTrackPointWithLocation(), ""));
// A sensor-only TrackPoint
trackPointCreator.setClock("2020-02-02T02:02:04Z");
@@ -155,7 +154,7 @@ public class ExportImportTest {
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);
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
contentProviderUtils.insertMarker(new Marker("Marker 2", "Marker 2 desc", "Marker 2 category", null, trackId, service.getLastStoredTrackPointWithLocation(), ""));
trackPointCreator.setClock("2020-02-02T02:02:18Z");
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet();
@@ -595,15 +594,13 @@ public class ExportImportTest {
location.setLatitude(latitude);
location.setLongitude(longitude);
location.setAccuracy(accuracy);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
location.setVerticalAccuracyMeters(verticalAccuracy);
}
location.setVerticalAccuracyMeters(verticalAccuracy);
location.setSpeed(speed);
location.setAltitude(altitude);
mockAltitudeChange(trackPointCreator, altitudeGain);
trackPointCreator.setClock(time);
trackPointCreator.onChange(location);
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
}
}
@@ -166,7 +166,7 @@ public class KMLTrackImporterTest {
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
// 2. markers
assertEquals(0, contentProviderUtils.getMarkers(importTrackId).size());
assertEquals(1, contentProviderUtils.getMarkers(importTrackId).size());
// 3. trackpoints
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
@@ -1,18 +1,24 @@
package de.dennisguse.opentracks.publicapi;
import android.content.Context;
import android.content.Intent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.IntentUtils;
@@ -35,12 +41,23 @@ public class PublicApiTest {
@Test
public void StartStopTest() throws InterruptedException {
PreferencesUtils.setBoolean(R.string.publicapi_enabled_key, true);
context.startActivity(IntentUtils.newIntent(context, StartRecording.class));
Intent startIntent = IntentUtils.newIntent(context, StartRecording.class);
startIntent.putExtra("TRACK_NAME", "trackName");
startIntent.putExtra("TRACK_CATEGORY", "activityTypeLocalized");
startIntent.putExtra("TRACK_ICON", "airplane");
startIntent.putExtra("TRACK_DESCRIPTION", "description");
context.startActivity(startIntent);
Thread.sleep(5000);
context.startActivity(IntentUtils.newIntent(context, StopRecording.class));
List<Track> tracks = new ContentProviderUtils(context).getTracks();
Assert.assertEquals(1, tracks.size());
Track track = tracks.get(0);
Assert.assertEquals("trackName", track.getName());
Assert.assertEquals("activityTypeLocalized", track.getActivityTypeLocalized());
Assert.assertEquals("airplane", track.getActivityType().getId());
}
@Test
@@ -1,152 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceMarkerTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
private TrackRecordingService service;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@AfterClass
public static void finalTearDown() {
if (Looper.myLooper() != null) Looper.myLooper().quit();
}
private TrackRecordingService startService() throws TimeoutException {
Intent startIntent = new Intent(context, TrackRecordingService.class);
return ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
.getService();
}
@Before
public void setUp() throws TimeoutException {
contentProviderUtils = new ContentProviderUtils(context);
service = startService();
tearDown();
}
@After
public void tearDown() {
// Ensure that the database is empty after every test
contentProviderUtils.deleteAllTracks(context);
}
@MediumTest
@Test
public void notRecording_testInsertMarker() {
// given
assertFalse(service.isRecording());
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNull(markerId);
}
@MediumTest
@Test
public void recording_noGPSfix_cannotCreateMarker() {
// given
service.startNewTrack();
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNull(markerId);
}
@MediumTest
@Test
public void recording_GPSfix_createsMarker() {
// given
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock("2020-02-02T02:02:02Z");
Track.Id trackId = service.startNewTrack();
service.stopUpdateRecordingData();
assertTrue(service.isRecording());
trackPointCreator.onNewTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
.setLatitude(10)
.setLongitude(10)
);
// when
Marker.Id markerId = service.insertMarker(null, null, null, null);
// then
assertNotEquals(new Marker.Id(-1L), markerId);
Marker wpt = contentProviderUtils.getMarker(markerId);
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
assertEquals(trackId, wpt.getTrackId());
assertEquals(0.0, wpt.getLength().toM(), 0.01);
assertNotNull(wpt.getLocation());
trackPointCreator.setClock("2020-02-02T02:02:04Z");
service.endCurrentTrack();
}
}
@@ -10,6 +10,7 @@ import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.rule.ServiceTestRule;
import org.junit.AfterClass;
@@ -20,12 +21,14 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TestUtil;
import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
@@ -54,6 +57,9 @@ public class TrackRecordingServiceRecordingTest {
@Rule
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
@Rule
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils;
@@ -127,6 +133,47 @@ public class TrackRecordingServiceRecordingTest {
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
/**
* Test that an IDLE event, doesn't store invalid GPS-provided data.
*/
@MediumTest
@Test
public void recording_startIdle() throws InterruptedException {
// given
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
String startTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
String gps1 = "2020-02-02T02:02:03Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
String gps2 = "2020-02-02T02:02:04Z";
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 15);
// when
String idleTime = "2020-02-02T02:02:17Z";
trackPointCreator.setClock("2020-02-02T02:02:17Z");
Thread.sleep(Duration.ofSeconds(15).toMillis());
// then
new TrackPointAssert().assertEquals(List.of(
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15)),
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps2))
.setLatitude(45)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15)),
new TrackPoint(TrackPoint.Type.IDLE, Instant.parse(idleTime))
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
}
@MediumTest
@Test
public void testRecording_startPauseResume() {
@@ -415,6 +462,10 @@ public class TrackRecordingServiceRecordingTest {
.setAltitudeGain(0f)
.setAltitudeLoss(0f),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setLatitude(45.00002)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSpeed(Speed.of(15))
.setAltitudeGain(0f)
.setAltitudeLoss(0f)
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
@@ -667,6 +718,9 @@ public class TrackRecordingServiceRecordingTest {
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(0)),
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.parse(stopTime))
.setLatitude(45.001)
.setLongitude(35)
.setHorizontalAccuracy(Distance.of(1))
.setSensorDistance(Distance.of(11))
.setSpeed(Speed.of(5))
.setSensorDistance(Distance.of(0))
@@ -1,126 +0,0 @@
package de.dennisguse.opentracks.services.handlers;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.location.Location;
import android.os.Handler;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
public class GPSManagerTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private TrackPointCreator trackPointCreator;
@InjectMocks
private GPSManager locationHandler;
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
@Before
public void setUp() {
// Let's use default values.
PreferencesUtils.clear();
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
locationHandler.start(context, new Handler());
}
/**
* When a valid location changed in LocationHandler -> newTrackPoint service method is called.
*/
@Test
public void testOnLocationChanged_okay() {
// when
locationHandler.onLocationChanged(createLocation(45f, 35f, 3, 5, System.currentTimeMillis()));
// then
verify(trackPointCreator, times(1)).onChange(any(Location.class));
}
/**
* When location changed in LocationHandler with bad location -> newTrackPoint service method is not called.
*/
@Test
public void testOnLocationChanged_badLocation() {
// given
float latitude = 91f;
// when
// bad latitude
locationHandler.onLocationChanged(createLocation(latitude, 35f, 3, 5, System.currentTimeMillis()));
// then
verify(trackPointCreator, times(0)).onChange(any(Location.class));
}
/**
* When location changed in LocationHandler with poor accuracy -> newTrackPoint service method is not called.
*/
@Test
public void testOnLocationChanged_poorAccuracy() {
// given
Distance prefAccuracy = PreferencesUtils.getThresholdHorizontalAccuracy();
// when
locationHandler.onLocationChanged(createLocation(45f, 35f, (float) (prefAccuracy.toM() + 1), 5, System.currentTimeMillis()));
// then
// no newTrackPoint called
verify(trackPointCreator, times(0)).onChange(any(Location.class));
}
@Test
public void testOnLocationChanged_movingInaccurate() {
// when
locationHandler.onLocationChanged(createLocation(45.0, 35.0, 5, 15, System.currentTimeMillis()));
locationHandler.onLocationChanged(createLocation(45.1, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
locationHandler.onLocationChanged(createLocation(45.2, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
locationHandler.onLocationChanged(createLocation(45.3, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
locationHandler.onLocationChanged(createLocation(99.0, 35.0, Long.MAX_VALUE, 15, System.currentTimeMillis()));
// then
verify(trackPointCreator, times(1)).onChange(any(Location.class));
}
/**
* Creates a location with parameters and returns the Location object.
*/
private static Location createLocation(double latitude, double longitude, float accuracy, long speed, long time) {
Location location = new Location("gps");
location.setLongitude(longitude);
location.setLatitude(latitude);
location.setAccuracy(accuracy);
location.setSpeed(speed);
location.setTime(time);
location.setBearing(3.0f);
return location;
}
}
@@ -1,7 +1,9 @@
package de.dennisguse.opentracks.stats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -231,7 +233,7 @@ public class TrackStatisticsUpdaterTest {
));
// then
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(35), subject.getTrackStatistics().getMovingTime());
}
@Test
@@ -261,10 +263,67 @@ public class TrackStatisticsUpdaterTest {
));
// then
assertEquals(Duration.ofSeconds(45), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime());
assertEquals(Distance.of(1040), subject.getTrackStatistics().getTotalDistance());
}
@Test
public void addTrackPoint_idle_remain_idle() {
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
// when
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
subject.addTrackPoint(
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(10))
.setSensorDistance(Distance.of(10)));
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)));
// then
assertTrue(subject.getTrackStatistics().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getTotalTime());
assertEquals(Distance.of(10), subject.getTrackStatistics().getTotalDistance());
// when
subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40))
.setSensorDistance(Distance.of(0)));
// then
assertTrue(subject.getTrackStatistics().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getTotalTime());
assertEquals(Distance.of(10), subject.getTrackStatistics().getTotalDistance());
// when
subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45))
.setSensorDistance(Distance.of(1)));
// then
assertTrue(subject.getTrackStatistics().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(45), subject.getTrackStatistics().getTotalTime());
assertEquals(Distance.of(11), subject.getTrackStatistics().getTotalDistance());
// when
subject.addTrackPoint(
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(50))
.setSensorDistance(Distance.of(10)));
// then
assertFalse(subject.getTrackStatistics().isIdle());
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(50), subject.getTrackStatistics().getTotalTime());
assertEquals(Distance.of(21), subject.getTrackStatistics().getTotalDistance());
// when
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60)));
// then
assertFalse(subject.getTrackStatistics().isIdle());
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime());
assertEquals(Duration.ofSeconds(60), subject.getTrackStatistics().getTotalTime());
assertEquals(Distance.of(21), subject.getTrackStatistics().getTotalDistance());
}
@Test
public void copy_constructor() {
// given
@@ -38,7 +38,6 @@ import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
@@ -138,10 +137,10 @@ public class StringUtilsTest {
ArrayList<String> shortMonths = Arrays.stream(Month.values()).map(m -> m.getDisplayName(TextStyle.SHORT, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
LocalDate dayName = today.minus(2, ChronoUnit.DAYS);
LocalDate thisYear = today.minus(15, ChronoUnit.DAYS);
LocalDate aYearAgo = today.minus(400, ChronoUnit.DAYS);
LocalDate yesterday = today.minusDays(1);
LocalDate dayName = today.minusDays(2);
LocalDate thisYear = today.minusDays(15);
LocalDate aYearAgo = today.minusDays(400);
int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
+14 -33
View File
@@ -314,7 +314,7 @@ limitations under the License.
</activity>
<activity
android:name=".ui.markers.MarkerDetailActivity"
android:name=".publicapi.ShowMarkerActivity"
android:exported="true">
<intent-filter>
<action android:name="de.dennisguse.opentracks.MarkerDetails" />
@@ -322,43 +322,24 @@ limitations under the License.
</intent-filter>
</activity>
<activity
android:name=".publicapi.CreateMarkerActivity"
android:exported="true">
<intent-filter>
<action android:name="de.dennisguse.opentracks.CreateMarker" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ui.markers.MarkerDetailActivity" />
<activity android:name=".ui.markers.MarkerEditActivity" />
<activity android:name=".ui.markers.MarkerListActivity" />
<activity
android:name=".ui.markers.MarkerListActivity"
android:exported="true"
android:label="@string/menu_markers"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity
android:name=".TrackListActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity android:name=".TrackListActivity" />
<activity android:name=".TrackRecordedActivity" />
<activity android:name=".TrackStoppedActivity" />
<activity
android:name=".TrackRecordingActivity"
android:parentActivityName=".TrackListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".TrackListActivity" />
</activity>
<activity android:name=".TrackRecordingActivity" />
<activity android:name=".TrackEditActivity" />
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -26,7 +25,6 @@ import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -34,7 +32,6 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -129,7 +126,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
};
// Menu items
private MenuItem searchMenuItem;
private String searchQuery;
@@ -249,6 +245,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
@Override
protected View getRootView() {
viewBinding = TrackListBinding.inflate(getLayoutInflater());
viewBinding.trackListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> {
searchQuery = viewBinding.trackListSearchView.getEditText().getText().toString();
viewBinding.trackListSearchView.hide();
loadData();
return true;
});
return viewBinding.getRoot();
}
@@ -256,9 +260,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_list, menu);
searchMenuItem = menu.findItem(R.id.track_list_search);
ActivityUtils.configureSearchWidget(this, searchMenuItem);
return super.onCreateOptionsMenu(menu);
}
@@ -280,13 +281,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
return true;
}
if (item.getItemId() == R.id.track_list_search) {
SearchView searchView = (SearchView) searchMenuItem.getActionView();
searchView.setIconified(false);
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
return true;
}
if (item.getItemId() == R.id.track_list_help) {
startActivity(IntentUtils.newIntent(this, HelpActivity.class));
return true;
@@ -295,42 +289,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
return super.onOptionsItemSelected(item);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public void onBackPressed() {
SearchView searchView = (SearchView) searchMenuItem.getActionView();
if (!searchView.isIconified()) {
searchView.setIconified(true);
}
if (searchQuery != null) {
searchQuery = null;
loadData();
return;
}
super.onBackPressed();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
searchQuery = intent.getStringExtra(SearchManager.QUERY);
} else {
searchQuery = null;
}
}
private void loadData() {
viewBinding.trackListToolbar.setText(searchQuery);
viewBinding.trackListToolbar.setTitle(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.app_name)));
Cursor tracks = new ContentProviderUtils(this).searchTracks(searchQuery);
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.TrackDataHub;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
@@ -269,9 +270,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
if (item.getItemId() == R.id.track_detail_insert_marker) {
TrackPoint trackPoint = trackRecordingServiceConnection.getTrackRecordingService().getLastStoredTrackPointWithLocation();
if (trackPoint == null) {
return true;
}
Intent intent = IntentUtils
.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId)
.putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.getLocation());
startActivity(intent);
return true;
}
@@ -110,15 +110,6 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
contentProviderUtils);
}
@Override
public void onBackPressed() {
if (isDiscarding) {
return;
}
super.onBackPressed();
resumeTrackAndFinish();
}
@Override
protected View getRootView() {
viewBinding = TrackStoppedBinding.inflate(getLayoutInflater());
@@ -41,9 +41,9 @@ import androidx.core.view.GestureDetectorCompat;
import java.text.NumberFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Iterator;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Marker;
@@ -152,31 +152,6 @@ public class ChartView extends View {
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
// Check if the y event is within markerHeight of the marker center
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
int minDistance = Integer.MAX_VALUE;
Marker nearestMarker = null;
synchronized (markers) {
for (Marker marker : markers) {
int distance = Math.abs(getX(getMarkerXValue(marker)) - (int) event.getX() - getScrollX());
if (distance < minDistance) {
minDistance = distance;
nearestMarker = marker;
}
}
}
if (nearestMarker != null && minDistance < markerWidth) {
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestMarker.getId());
getContext().startActivity(intent);
return true;
}
}
return false;
}
});
private final ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@@ -589,7 +564,6 @@ public class ChartView extends View {
clipToGraphArea(canvas);
drawDataSeries(canvas);
drawMarker(canvas);
drawGrid(canvas);
canvas.restore();
@@ -630,30 +604,6 @@ public class ChartView extends View {
}
}
private void drawMarker(Canvas canvas) {
synchronized (markers) {
for (Marker marker : markers) {
double xValue = getMarkerXValue(marker);
double markerIconSizeInXaxisUnits = maxX*markerWidth/effectiveWidth / zoomLevel;
if (xValue > maxX + markerIconSizeInXaxisUnits * (1-MARKER_X_ANCHOR)) {
continue; // there is no chance that this marker will be visible
}
canvas.save();
float x = getX(getMarkerXValue(marker));
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
// if marker is not near the end of the track then draw it normally
if (xValue < maxX - markerIconSizeInXaxisUnits*(1-MARKER_X_ANCHOR)) {
canvas.translate(x - (markerWidth * MARKER_X_ANCHOR), topBorder + spacer);
} else { // marker at the end needs to be drawn mirrored so that it is more visible
canvas.translate(x + (markerWidth * MARKER_X_ANCHOR), topBorder + spacer);
canvas.scale(-1, 1);
}
markerPin.draw(canvas);
canvas.restore();
}
}
}
/**
* Draws the grid.
*
@@ -679,12 +629,13 @@ public class ChartView extends View {
private record TitlePosition(
int line, // line number (starts at 1, top to bottom numbering)
int xPos // x position in points (starts at 0, left to right indexing)
) {};
) {}
private record TitleDimensions(
int lineCount, // number of lines the titles will take
int lineHeight, // height of a line (all lines have the same height)
List<TitlePosition> titlePositions // positions of visible titles (the order corresponds to seriesList)
) {};
) {}
/**
* Draws series titles.
@@ -711,7 +662,7 @@ public class ChartView extends View {
private TitleDimensions getTitleDimensions() {
int lineCnt = 1;
int lineHeight = 0;
List<TitlePosition> tps = new ArrayList<TitlePosition>();
List<TitlePosition> tps = new ArrayList<>();
int xPosInLine = spacer;
for (ChartValueSeries chartValueSeries : seriesList) {
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
@@ -1042,14 +993,6 @@ public class ChartView extends View {
return topBorder + yAxisOffset + (int) ((1 - percentage) * rangeHeight);
}
private double getMarkerXValue(Marker marker) {
if (chartByDistance) {
return marker.getLength().toKM_Miles(unitSystem);
} else {
return marker.getDuration().toMillis();
}
}
/**
* Gets a paint's Rect for a string.
*
@@ -377,8 +377,6 @@ public class ContentProviderUtils {
int categoryIndex = cursor.getColumnIndexOrThrow(MarkerColumns.CATEGORY);
int iconIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ICON);
int trackIdIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TRACKID);
int lengthIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LENGTH);
int durationIndex = cursor.getColumnIndexOrThrow(MarkerColumns.DURATION);
int longitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LONGITUDE);
int latitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LATITUDE);
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
@@ -419,13 +417,6 @@ public class ContentProviderUtils {
if (!cursor.isNull(iconIndex)) {
marker.setIcon(cursor.getString(iconIndex));
}
if (!cursor.isNull(lengthIndex)) {
marker.setLength(Distance.of(cursor.getFloat(lengthIndex)));
}
if (!cursor.isNull(durationIndex)) {
marker.setDuration(Duration.ofMillis(cursor.getLong(durationIndex)));
}
if (!cursor.isNull(photoUrlIndex)) {
marker.setPhotoUrl(cursor.getString(photoUrlIndex));
}
@@ -542,9 +533,6 @@ public class ContentProviderUtils {
values.put(MarkerColumns.CATEGORY, marker.getCategory());
values.put(MarkerColumns.ICON, marker.getIcon());
values.put(MarkerColumns.TRACKID, marker.getTrackId().id());
values.put(MarkerColumns.LENGTH, marker.getLength().toM());
values.put(MarkerColumns.DURATION, marker.getDuration().toMillis());
values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6));
values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6));
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
@@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 37;
private static final int DATABASE_VERSION = 38;
private final Context context;
@@ -81,6 +81,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 35 -> upgradeFrom34to35(db);
case 36 -> upgradeFrom35to36(db);
case 37 -> upgradeFrom36to37(db);
case 38 -> upgradeFrom37to38(db);
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
}
}
@@ -105,6 +106,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 34 -> downgradeFrom35to34(db);
case 35 -> downgradeFrom36to35(db);
case 36 -> downgradeFrom37to36(db);
case 37 -> downgradeFrom38to37(db);
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
}
@@ -644,4 +646,28 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.endTransaction();
}
private void upgradeFrom37to38(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("ALTER TABLE markers RENAME TO markers_old");
db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
db.execSQL("INSERT INTO markers SELECT _id, name, description, category, icon, trackid, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers_old");
db.execSQL("DROP TABLE markers_old");
db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom38to37(SQLiteDatabase db) {
db.beginTransaction();
db.execSQL("ALTER TABLE markers ADD COLUMN length FLOAT");
db.execSQL("ALTER TABLE markers ADD COLUMN duration INTEGER");
db.setTransactionSuccessful();
db.endTransaction();
}
}
@@ -45,7 +45,7 @@ public enum ActivityType {
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_mtb_24dp, true),
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true),
PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true),
PARA_GLIDING("para gliding", R.string.activity_type_para_gliding, ActivityIcon.ICON_UNKNOWN, true),
@@ -27,8 +27,6 @@ import androidx.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
*
@@ -53,10 +51,6 @@ public final class Marker {
private Altitude altitude;
private Float bearing;
//TODO It is the distance from the track starting point; rename to something more meaningful
private Distance length;
private Duration duration;
@Deprecated //TODO Make an URI instead of String
private String photoUrl = "";
@@ -65,7 +59,6 @@ public final class Marker {
this.time = time;
}
@Deprecated //TODO Marker cannot be created without length AND duration!
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
this.trackId = trackId;
@@ -75,20 +68,15 @@ public final class Marker {
throw new RuntimeException("Marker requires a trackpoint with a location.");
setTrackPoint(trackPoint);
this.length = Distance.of(0); //TODO Not cool!
this.duration = Duration.ofMillis(0); //TODO Not cool!
}
@Deprecated
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) {
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackPoint trackPoint, String photoUrl) {
this(trackId, trackPoint);
this.name = name;
this.description = description;
this.category = category;
this.icon = icon;
this.length = statistics.getTotalDistance();
this.duration = statistics.getTotalTime();
this.photoUrl = photoUrl;
}
@@ -234,22 +222,6 @@ public final class Marker {
this.bearing = bearing;
}
public Distance getLength() {
return length;
}
public void setLength(Distance length) {
this.length = length;
}
public Duration getDuration() {
return duration;
}
public void setDuration(@NonNull Duration duration) {
this.duration = duration;
}
public String getPhotoUrl() {
return photoUrl;
}
@@ -263,7 +235,7 @@ public final class Marker {
}
public boolean hasPhoto() {
return photoUrl != null && !"".equals(photoUrl);
return photoUrl != null && !photoUrl.isEmpty();
}
public record Id(long id) implements Parcelable {
@@ -0,0 +1,88 @@
package de.dennisguse.opentracks.data.models;
import android.location.Location;
import java.time.Instant;
//TODO Use everywhere instead of android.location.Location.
public record Position(
Instant time,
Double latitude,
Double longitude,
Distance horizontalAccuracy,
Altitude altitude,
Distance verticalAccuracy,
Float bearing,
Speed speed
) {
public static Position empty() {
return new Position(
null,
null,
null,
null,
null,
null,
null,
null
);
}
public static Position of(Location location) {
return new Position(
Instant.ofEpochMilli(location.getTime()),
location.getLatitude(),
location.getLongitude(),
location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null,
location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null,
location.hasVerticalAccuracy() ? Distance.of(location.getVerticalAccuracyMeters()) : null,
location.hasBearing() ? location.getBearing() : null,
location.hasSpeed() ? Speed.of(location.getSpeed()) : null
);
}
public boolean hasLocation() {
return latitude != null && longitude != null;
}
public boolean hasHorizontalAccuracy() {
return horizontalAccuracy != null;
}
public boolean hasAltitude() {
return altitude != null;
}
public boolean hasBearing() {
return bearing != null;
}
public boolean hasSpeed() {
return speed != null;
}
public Location toLocation() {
if (!hasLocation()) {
throw new RuntimeException("Cannot convert to Location.");
}
Location location = new Location("");
location.setTime(time.toEpochMilli());
location.setLatitude(latitude);
location.setLongitude(longitude);
if (hasBearing()) {
location.setBearing(bearing);
}
if (hasHorizontalAccuracy()) {
location.setAccuracy((float) horizontalAccuracy.toM());
}
if (hasAltitude()) {
location.setAltitude(altitude.toM());
}
if (hasSpeed()) {
location.setSpeed((float) speed.toMPS());
}
return location;
}
}
@@ -130,6 +130,16 @@ public class Track {
this.trackStatistics = trackStatistics;
}
@Override
public String toString() {
return "Track{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
", activityTypeLocalized='" + activityTypeLocalized + '\'' +
", activityType=" + activityType +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.data.models;
import android.location.Location;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,6 +25,7 @@ import androidx.annotation.VisibleForTesting;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
/**
* Sensor and/or location information for a specific point in time.
@@ -42,6 +42,7 @@ public class TrackPoint {
@NonNull
private final Instant time;
//TODO We may use Position for these items
private Double latitude;
private Double longitude;
private Distance horizontalAccuracy;
@@ -135,6 +136,10 @@ public class TrackPoint {
return this;
}
public boolean isIdleTriggered() {
return type == Type.IDLE;
}
public boolean isSegmentManualStart() {
return type == Type.SEGMENT_START_MANUAL;
}
@@ -181,42 +186,40 @@ public class TrackPoint {
return this;
}
//TODO Better return null, if no location is present aka latitude == null etc.
@NonNull
public Location getLocation() {
Location location = new Location("");
location.setTime(time.toEpochMilli());
if (hasLocation()) {
location.setLatitude(latitude);
location.setLongitude(longitude);
}
if (hasBearing()) {
location.setBearing(bearing);
}
if (hasHorizontalAccuracy()) {
location.setAccuracy((float) horizontalAccuracy.toM());
}
if (hasAltitude()) {
location.setAltitude(altitude.toM());
}
if (hasSpeed()) {
location.setSpeed((float) speed.toMPS());
}
return location;
public Position getPosition() {
return new Position(
time,
latitude,
longitude,
horizontalAccuracy,
altitude,
verticalAccuracy,
bearing,
speed
);
}
public TrackPoint setLocation(@NonNull Location location) {
this.latitude = location.getLatitude();
this.longitude = location.getLongitude();
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.verticalAccuracy = location.hasVerticalAccuracy() ? Distance.of(location.getVerticalAccuracyMeters()) : null;
}
public TrackPoint setPosition(Position location) {
this.latitude = location.latitude();
this.longitude = location.longitude();
this.altitude = location.altitude();
this.speed = location.speed();
this.horizontalAccuracy = location.horizontalAccuracy();
this.verticalAccuracy = location.verticalAccuracy();
//TODO Should we copy the bearing?
return this;
}
@Deprecated
@NonNull
public Location getLocation() {
return getPosition().toLocation();
}
@Deprecated
public TrackPoint setLocation(@NonNull Location location) {
setPosition(Position.of(location));
return this;
}
@@ -288,6 +291,7 @@ public class TrackPoint {
this.speed = speed;
return this;
}
public boolean hasBearing() {
return bearing != null;
}
@@ -348,13 +352,20 @@ public class TrackPoint {
return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy);
}
public float bearingTo(@NonNull TrackPoint dest) {
public Optional<Float> bearingTo(@NonNull TrackPoint dest) {
if (!dest.hasLocation() || !hasLocation()) {
return Optional.empty();
}
return bearingTo(dest.getLocation());
}
//TODO Bearing requires a location; what do we do if we don't have any?
public float bearingTo(@NonNull Location dest) {
return getLocation().bearingTo(dest);
@Deprecated
public Optional<Float> bearingTo(@NonNull Location dest) {
if (!hasLocation()) {
return Optional.empty();
}
return Optional.of(getLocation().bearingTo(dest));
}
// Sensor data
@@ -41,10 +41,6 @@ public interface MarkerColumns extends BaseColumns {
String CATEGORY = "category"; // marker category
String ICON = "icon"; // marker icon
String TRACKID = "trackid"; // track id
String LENGTH = "length"; // length of the track (without smoothing)
String DURATION = "duration"; // total duration of the track from the beginning until now
String LONGITUDE = "longitude"; // longitude
String LATITUDE = "latitude"; // latitude
String TIME = "time"; // time
@@ -61,8 +57,6 @@ public interface MarkerColumns extends BaseColumns {
+ CATEGORY + " TEXT, "
+ ICON + " TEXT, "
+ TRACKID + " INTEGER NOT NULL, "
+ LENGTH + " FLOAT, "
+ DURATION + " INTEGER, "
+ LONGITUDE + " INTEGER, "
+ LATITUDE + " INTEGER, "
+ TIME + " INTEGER, "
@@ -310,7 +310,7 @@ public class GPXTrackExporter implements TrackExporter {
}
public Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
Distance cumulativeDistance = null;
Distance cumulativeDistance;
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
@@ -33,6 +33,7 @@ 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;
@@ -274,7 +275,7 @@ public class KMLTrackExporter implements TrackExporter {
float heading = getHeading(marker.getTrackId(), marker.getLocation());
writePhotoOverlay(marker, heading, zoneOffset);
} else {
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), zoneOffset);
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), marker.getTime(), zoneOffset);
}
}
@@ -368,7 +369,7 @@ public class KMLTrackExporter implements TrackExporter {
@VisibleForTesting
void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) {
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getLocation()) + "</when>");
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getTime()) + "</when>");
trackpointTypeList.add(trackPoint.getType());
@@ -411,12 +412,12 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.println("</SimpleArrayData>");
}
private void writePlacemark(String name, String activityType, String description, Location location, ZoneOffset zoneOffset) {
private void writePlacemark(String name, String activityType, String description, Location location, Instant time, ZoneOffset zoneOffset) {
if (location != null) {
printWriter.println("<Placemark>");
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, location) + "</when></TimeStamp>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, time) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
writeTypeLocalized(activityType);
printWriter.println("<Point>");
@@ -437,7 +438,7 @@ public class KMLTrackExporter implements TrackExporter {
printWriter.print("<heading>" + heading + "</heading>");
printWriter.print("<tilt>90</tilt>");
printWriter.println("</Camera>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getLocation()) + "</when></TimeStamp>");
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getTime()) + "</when></TimeStamp>");
printWriter.println("<styleUrl>#" + MARKER_STYLE + "</styleUrl>");
writeTypeLocalized(marker.getCategory());
@@ -460,14 +461,13 @@ public class KMLTrackExporter implements TrackExporter {
/**
* Returns the formatted time of the location; either absolute or relative depending exportTrackDetail.
*
* @param location the location
*/
private String getTime(ZoneOffset zoneOffset, Location location) {
return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()), zoneOffset);
private String getTime(ZoneOffset zoneOffset, Instant instant) {
return StringUtils.formatDateTimeIso8601(instant, zoneOffset);
}
/**
* TODO: check if this is a useful feature (likely not).
* Gets the heading to a location.
*
* @param trackId the track id containing the location
@@ -480,7 +480,10 @@ public class KMLTrackExporter implements TrackExporter {
}
TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
if (viewLocation != null) {
return viewLocation.bearingTo(location);
Optional<Float> bearing = viewLocation.bearingTo(location);
if (bearing.isPresent()) {
return bearing.get();
}
}
return location.getBearing();
@@ -495,7 +498,7 @@ public class KMLTrackExporter implements TrackExporter {
}
private void writeTypeLocalized(String localizedValue) {
if (localizedValue == null || localizedValue.equals("")) {
if (localizedValue == null || localizedValue.isEmpty()) {
return;
}
printWriter.println("<ExtendedData>");
@@ -414,7 +414,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
Float value = null;
if (content != null) {
content = content.trim();
if (!"".equals(content)) {
if (!content.isEmpty()) {
try {
value = Float.parseFloat(content);
} catch (NumberFormatException e) {
@@ -16,7 +16,6 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -26,7 +25,6 @@ import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
import de.dennisguse.opentracks.util.FileUtils;
@@ -158,7 +156,7 @@ public class TrackImporter {
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
// Store Markers
matchMarkers2TrackPoints(trackId);
updateMarkers(trackId);
for (Marker marker : markers)
marker.setTrackId(trackId); //TODO Should happen in bulkInsertMarkers
@@ -206,7 +204,8 @@ public class TrackImporter {
}
if (!current.hasBearing()) {
current.setBearing(previous.bearingTo(current));
previous.bearingTo(current)
.ifPresent(current::setBearing);
}
if (current.getType().equals(TrackPoint.Type.TRACKPOINT) && distanceToPrevious.greaterThan(maxRecordingDistance)) {
@@ -217,55 +216,16 @@ public class TrackImporter {
}
/**
* NOTE: Modifies content of markers (incl. removal).
* NOTE: Modifies content of markers.
*/
private void matchMarkers2TrackPoints(Track.Id trackId) {
List<TrackPoint> trackPointsWithLocation = trackPoints.stream()
.filter(TrackPoint::hasLocation)
.collect(Collectors.toList());
List<Marker> todoMarkers = new LinkedList<>(markers);
List<Marker> doneMarkers = new LinkedList<>();
for (final TrackPoint trackPoint : trackPointsWithLocation) {
if (todoMarkers.isEmpty()) {
break;
private void updateMarkers(Track.Id trackId) {
markers.forEach(marker -> {
if (marker.hasPhoto()) {
marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl()));
}
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
updater.addTrackPoint(trackPoint);
List<Marker> matchedMarkers = todoMarkers.stream()
.filter(it -> trackPoint.getLatitude() == it.getLatitude()
&& trackPoint.getLongitude() == it.getLongitude()
&& trackPoint.getTime().equals(it.getTime())
)
.collect(Collectors.toList());
TrackStatistics statistics = updater.getTrackStatistics();
for (Marker marker : matchedMarkers) {
if (marker.hasPhoto()) {
marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl()));
}
marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why?
marker.setLength(statistics.getTotalDistance());
marker.setDuration(statistics.getTotalTime());
marker.setTrackPoint(trackPoint);
}
todoMarkers.removeAll(matchedMarkers);
doneMarkers.addAll(matchedMarkers);
}
if (todoMarkers.isEmpty()) {
Log.w(TAG, "Some markers could not be attached to TrackPoints; those are not imported.");
}
markers.clear();
markers.addAll(doneMarkers);
marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why?
});
}
/**
@@ -0,0 +1,39 @@
package de.dennisguse.opentracks.publicapi;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* Public API to creates a Marker for a given track with a given location
*/
public class CreateMarkerActivity extends AppCompatActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_LOCATION = "location";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Track.Id trackId = new Track.Id(getIntent().getLongExtra(EXTRA_TRACK_ID, 0L));
Location location = getIntent().getParcelableExtra(EXTRA_LOCATION);
TrackRecordingServiceConnection.execute(this, (service, self) -> {
Intent intent = IntentUtils
.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId)
.putExtra(MarkerEditActivity.EXTRA_LOCATION, location);
startActivity(intent);
finish();
});
}
}
@@ -0,0 +1,57 @@
package de.dennisguse.opentracks.publicapi;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
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 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.databinding.MarkerDetailActivityBinding;
import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import de.dennisguse.opentracks.ui.markers.MarkerDetailActivity;
import de.dennisguse.opentracks.ui.markers.MarkerDetailFragment;
import de.dennisguse.opentracks.util.IntentUtils;
/**
* Public api to show an existing marker
*/
public class ShowMarkerActivity extends AppCompatActivity {
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = ShowMarkerActivity.class.getSimpleName();
private MarkerDetailActivityBinding viewBinding;
private List<Marker.Id> markerIds;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
Marker.Id markerId = new Marker.Id(getIntent().getLongExtra(EXTRA_MARKER_ID, 0));
if (markerId.id() == 0) {
throw new IllegalStateException("No valid markerId provided");
}
Intent intent = IntentUtils.newIntent(this, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
finish();
}
}
@@ -20,9 +20,7 @@ public class GainManager implements SensorConnector {
private final SensorManager.SensorDataChangedObserver listener;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
connect();
};
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> connect();
private Context context;
private Handler handler;
@@ -30,7 +30,6 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
@Override
public void onConnect(Aggregator<?, ?> aggregator) {
sensorDataSet.add(aggregator);
observer.onChange(new SensorDataSet(sensorDataSet));
}
@Override
@@ -42,13 +41,11 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
@Override
public void onDisconnect(Aggregator<?, ?> aggregator) {
sensorDataSet.add(aggregator);
observer.onChange(new SensorDataSet(sensorDataSet));
}
@Override
public void onRemove(Aggregator<?, ?> aggregator) {
sensorDataSet.remove(aggregator);
observer.onChange(new SensorDataSet(sensorDataSet));
}
};
@@ -67,7 +64,7 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
throw new RuntimeException("SensorManager cannot be started twice; stop first.");
}
gpsManager = new GPSManager(observer); //TODO Pass listener
gpsManager = new GPSManager(observer, listener);
altitudeSumManager = new GainManager(listener);
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, listener);
@@ -104,7 +101,6 @@ public class SensorManager implements SharedPreferences.OnSharedPreferenceChange
sensorDataSet.reset();
}
@Deprecated
@VisibleForTesting
public void onChanged(Raw<?> data) {
listener.onChange(data);
@@ -6,7 +6,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
public abstract class Aggregator<Input extends Record, Output> {
public abstract class Aggregator<Input, Output> {
protected Raw<Input> previous;
@@ -44,7 +44,7 @@ public abstract class Aggregator<Input extends Record, Output> {
public Output getValue() {
if (!hasValue()) {
return null;
return null; //TODO Check if this is a good idea!
}
if (isRecent()) {
return value;
@@ -55,7 +55,7 @@ public abstract class Aggregator<Input extends Record, Output> {
/**
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
*/
public void reset() {};
public void reset() {}
/**
* Is the data recent considering the current time.
@@ -0,0 +1,29 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.Position;
public class AggregatorGPS extends Aggregator<Position, Position> {
public AggregatorGPS(String sensorAddress) {
super(sensorAddress);
}
@Override
protected void computeValue(Raw<Position> current) {
value = current.value();
}
@Override
public void reset() {
value = null;
}
@NonNull
@Override
protected Position getNoneValue() {
return Position.empty();
}
}
@@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import java.time.Instant;
public record Raw<T extends Record>(
public record Raw<T>(
@NonNull T value,
@NonNull Instant time
@@ -10,6 +10,7 @@ import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
@@ -40,16 +41,20 @@ public final class SensorDataSet {
@VisibleForTesting
public AggregatorBarometer barometer;
public AggregatorGPS gps;
public SensorDataSet() {
}
public SensorDataSet(SensorDataSet toCopy) {
//TODO This is not a copy constructor anymore, but it should be - aggregators are no value objects
this.heartRate = toCopy.heartRate;
this.cyclingCadence = toCopy.cyclingCadence;
this.cyclingDistanceSpeed = toCopy.cyclingDistanceSpeed;
this.cyclingPower = toCopy.cyclingPower;
this.runningDistanceSpeedCadence = toCopy.runningDistanceSpeedCadence;
this.barometer = toCopy.barometer;
this.gps = toCopy.gps;
}
public Pair<HeartRate, String> getHeartRate() {
@@ -93,7 +98,7 @@ public final class SensorDataSet {
}
public void update(@NonNull Raw<?> data) {
Record value = data.value();
Object value = data.value();
if (value instanceof HeartRate) {
this.heartRate.add((Raw<HeartRate>) data);
@@ -122,6 +127,10 @@ public final class SensorDataSet {
this.barometer.add((Raw<AtmosphericPressure>) data);
return;
}
if (value instanceof Position) {
this.gps.add((Raw<Position>) data);
return;
}
throw new UnsupportedOperationException(data.getClass().getCanonicalName() + " " + data.value().getClass().getCanonicalName());
}
@@ -138,9 +147,14 @@ public final class SensorDataSet {
this.cyclingPower = null;
this.runningDistanceSpeedCadence = null;
this.barometer = null;
this.gps = null;
}
public void fillTrackPoint(TrackPoint trackPoint) {
if (gps != null && gps.hasValue()) {
trackPoint.setPosition(gps.getValue());
}
if (getHeartRate() != null) {
trackPoint.setHeartRate(getHeartRate().first);
}
@@ -180,6 +194,7 @@ public final class SensorDataSet {
if (cyclingPower != null) cyclingPower.reset();
if (runningDistanceSpeedCadence != null) runningDistanceSpeedCadence.reset();
if (barometer != null) barometer.reset();
if (gps != null) gps.reset();
}
private void set(@NonNull Aggregator<?, ?> type, @Nullable Aggregator<?, ?> sensorData) {
@@ -209,6 +224,10 @@ public final class SensorDataSet {
barometer = (AggregatorBarometer) sensorData;
return;
}
if (type instanceof AggregatorGPS) {
gps = (AggregatorGPS) sensorData;
return;
}
throw new UnsupportedOperationException(type.getClass().getCanonicalName());
}
@@ -1,10 +1,8 @@
package de.dennisguse.opentracks.services;
import android.content.ContentUris;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
@@ -19,7 +17,6 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
@@ -138,31 +135,6 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
return new Pair<>(track, current);
}
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (name == null) {
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(trackId);
if (nextMarkerNumber == null) {
nextMarkerNumber = 1;
}
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
}
if (lastStoredTrackPointWithLocation == null) {
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
return null;
}
category = category != null ? category : "";
description = description != null ? description : "";
String icon = context.getString(R.string.marker_icon_url);
photoUrl = photoUrl != null ? photoUrl : "";
// Insert marker
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPointWithLocation, photoUrl);
Uri uri = contentProviderUtils.insertMarker(marker);
return new Marker.Id(ContentUris.parseId(uri));
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public void onIdle() {
Log.d(TAG, "Becoming idle");
@@ -197,6 +169,9 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
if (trackPoint.hasLocation() && lastStoredTrackPointWithLocation == null) {
insertTrackPoint(trackPoint, true);
handler.removeCallbacks(ON_IDLE);
handler.postDelayed(ON_IDLE, idleDuration.toMillis());
return true;
}
@@ -244,7 +219,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
}
TrackStatistics getTrackStatistics() {
return trackStatisticsUpdater.getTrackStatistics();
return trackStatisticsUpdater == null ? null : trackStatisticsUpdater.getTrackStatistics();
}
private void insertTrackPoint(@NonNull TrackPoint trackPoint, boolean storeLastTrackPointIfUseful) {
@@ -305,6 +280,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
}
}
public TrackPoint getLastStoredTrackPointWithLocation() {
return lastStoredTrackPointWithLocation;
}
public interface IdleObserver {
void onIdle();
}
@@ -37,7 +37,6 @@ import java.io.StringWriter;
import java.time.Duration;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
@@ -57,6 +56,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null);
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
public TrackPoint getLastStoredTrackPointWithLocation() {
return trackRecordingManager.getLastStoredTrackPointWithLocation();
}
public class Binder extends android.os.Binder {
private Binder() {
@@ -205,6 +208,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
private synchronized void startSensors() {
if (isSensorStarted()) {
Log.i(TAG, "sensors already started; skipping");
return;
}
Log.i(TAG, "startSensors");
wakeLock = SystemUtils.acquireWakeLock(this, wakeLock);
@@ -268,14 +272,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
gpsStatusObservable.postValue(gpsStatusValue);
}
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
if (!isRecording()) {
return null;
}
return trackRecordingManager.insertMarker(name, category, description, photoUrl);
}
@Deprecated
@VisibleForTesting
public TrackPointCreator getTrackPointCreator() {
@@ -4,9 +4,11 @@ import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapPower;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceUnit;
import android.content.Context;
import android.icu.text.MessageFormat;
@@ -22,11 +24,13 @@ import java.util.Map;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
import de.dennisguse.opentracks.util.StringUtils;
class VoiceAnnouncementUtils {
@@ -44,34 +48,37 @@ class VoiceAnnouncementUtils {
Speed averageMovingSpeed = trackStatistics.getAverageMovingSpeed();
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
int perUnitStringId;
int distanceId;
int speedId;
String unitDistanceTTS;
String unitSpeedTTS;
switch (unitSystem) {
case METRIC -> {
perUnitStringId = R.string.voice_per_kilometer;
distanceId = R.string.voiceDistanceKilometersPlural;
speedId = R.string.voiceSpeedKilometersPerHourPlural;
unitDistanceTTS = "kilometer";
unitSpeedTTS = "kilometer per hour";
int perUnitStringId = R.string.empty;
int distanceId = R.string.voiceDistance;
int speedId = R.string.voiceSpeed;
String unitDistanceTTS = StringUtils.EMPTY;
String unitSpeedTTS = StringUtils.EMPTY;
if(shouldVoiceAnnounceUnit()) {
switch (unitSystem) {
case METRIC -> {
perUnitStringId = R.string.voice_per_kilometer;
distanceId = R.string.voiceDistanceKilometersPlural;
speedId = R.string.voiceSpeedKilometersPerHourPlural;
unitDistanceTTS = "kilometer";
unitSpeedTTS = "kilometer per hour";
}
case IMPERIAL_FEET, IMPERIAL_METER -> {
perUnitStringId = R.string.voice_per_mile;
distanceId = R.string.voiceDistanceMilesPlural;
speedId = R.string.voiceSpeedMilesPerHourPlural;
unitDistanceTTS = "mile";
unitSpeedTTS = "mile per hour";
}
case NAUTICAL_IMPERIAL -> {
perUnitStringId = R.string.voice_per_nautical_mile;
distanceId = R.string.voiceDistanceNauticalMilesPlural;
speedId = R.string.voiceSpeedMKnotsPlural;
unitDistanceTTS = "nautical mile";
unitSpeedTTS = "knots";
}
default -> throw new RuntimeException("Not implemented");
}
case IMPERIAL_FEET, IMPERIAL_METER -> {
perUnitStringId = R.string.voice_per_mile;
distanceId = R.string.voiceDistanceMilesPlural;
speedId = R.string.voiceSpeedMilesPerHourPlural;
unitDistanceTTS = "mile";
unitSpeedTTS = "mile per hour";
}
case NAUTICAL_IMPERIAL -> {
perUnitStringId = R.string.voice_per_nautical_mile;
distanceId = R.string.voiceDistanceNauticalMilesPlural;
speedId = R.string.voiceSpeedMKnotsPlural;
unitDistanceTTS = "nautical mile";
unitSpeedTTS = "knots";
}
default -> throw new RuntimeException("Not implemented");
}
double distanceInUnit = totalDistance.toKM_Miles(unitSystem);
@@ -154,6 +161,19 @@ class VoiceAnnouncementUtils {
builder.append(".");
}
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.hasAveragePower()) {
int currentPower = Math.round(currentInterval.getAveragePower().getW());
if(shouldVoiceAnnounceUnit()) {
String template = context.getResources().getString(R.string.power_x_watt);
builder.append(" ")
.append(MessageFormat.format(template, Map.of("x", currentPower)));
} else {
builder.append(" ").append(context.getString(R.string.power)).append(" ");
builder.append(String.valueOf(currentPower));
}
builder.append(".");
}
return builder;
}
@@ -18,8 +18,12 @@ import java.time.Duration;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.SensorConnector;
import de.dennisguse.opentracks.sensors.SensorManager;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorGPS;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
@@ -32,6 +36,8 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
private static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
private TrackPointCreator trackPointCreator;
private SensorManager.SensorDataChangedObserver listener;
private Context context;
private Handler handler;
@@ -40,8 +46,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
private Duration gpsInterval;
private Distance thresholdHorizontalAccuracy;
public GPSManager(TrackPointCreator trackPointCreator) {
public GPSManager(TrackPointCreator trackPointCreator, SensorManager.SensorDataChangedObserver listener) {
this.trackPointCreator = trackPointCreator;
this.listener = listener;
}
public void start(@NonNull Context context, @NonNull Handler handler) {
@@ -52,6 +59,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
gpsStatusManager = new GpsStatusManager(context, this, handler);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
listener.onConnect(new AggregatorGPS("internal"));
registerLocationListener();
gpsStatusManager.start();
}
@@ -72,6 +82,9 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
gpsStatusManager.stop();
gpsStatusManager = null;
listener.onDisconnect(new AggregatorGPS("internal"));
listener = null;
trackPointCreator = null;
}
@@ -126,7 +139,7 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
return;
}
trackPointCreator.onChange(location);
listener.onChange(new Raw<>(Position.of(location)));
}
@Override
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks.services.handlers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
@@ -69,10 +68,6 @@ public class TrackPointCreator implements SharedPreferences.OnSharedPreferenceCh
this.context = null;
}
public synchronized void onChange(@NonNull Location location) {
onNewTrackPoint(new TrackPoint(location, createNow()));
}
/**
* Got a new TrackPoint from Bluetooth only; contains no GPS location.
*/
@@ -229,7 +229,7 @@ public class PreferencesUtils {
//TODO Check if actually needed or can be superseeded by a flexible default in getUnit()
public static void applyDefaultUnit() {
if (getString(R.string.stats_units_key, "").equals("")) {
if (getString(R.string.stats_units_key, "").isEmpty()) {
if (!Locale.US.equals(Locale.getDefault())) {
setUnit(UnitSystem.METRIC);
} else {
@@ -333,6 +333,10 @@ public class PreferencesUtils {
return getBoolean(R.string.voice_announce_idle_key, true);
}
public static boolean shouldVoiceAnnounceUnit() {
return getBoolean(R.string.voice_announce_unit_key, true);
}
public static Duration getVoiceAnnouncementFrequency() {
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
@@ -430,6 +434,10 @@ public class PreferencesUtils {
return getBoolean(R.string.voice_announce_lap_heart_rate_key, false);
}
public static boolean shouldVoiceAnnounceLapPower() {
return getBoolean(R.string.voice_announce_lap_power_key, false);
}
@VisibleForTesting
public static void setVoiceAnnounceLapHeartRate(boolean value) {
setBoolean(R.string.voice_announce_lap_heart_rate_key, value);
@@ -42,8 +42,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
fragment = new MainSettingsFragment();
}
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
setSupportActionBar(viewBinding.topAppBar);
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).commit();
}
@@ -26,6 +26,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -60,6 +61,7 @@ public class TrackStatistics {
private Float totalAltitudeLoss_m = null;
// The average heart rate seen on this track
private HeartRate avgHeartRate = null;
private Power avgPower = null;
private boolean isIdle;
@@ -83,6 +85,7 @@ public class TrackStatistics {
totalAltitudeGain_m = other.totalAltitudeGain_m;
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
avgHeartRate = other.avgHeartRate;
avgPower = other.avgPower;
isIdle = other.isIdle;
}
@@ -104,6 +107,7 @@ public class TrackStatistics {
*
* @param other another statistics data object
*/
//TODO Should be refactored to append only [mainly due to isIdle] (NOTE: This requires to use a custom value object for AggregatedStatistics; this is anyhow recommended).
public void merge(TrackStatistics other) {
if (startTime == null) {
startTime = other.startTime;
@@ -116,6 +120,8 @@ public class TrackStatistics {
stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime;
}
isIdle = other.isIdle; //TODO This implicitly assumes append mode.
if (avgHeartRate == null) {
avgHeartRate = other.avgHeartRate;
} else {
@@ -129,6 +135,19 @@ public class TrackStatistics {
}
}
if (avgPower == null) {
avgPower = other.avgPower;
} else {
if (other.avgPower != null) {
// Using total time as weights for the averaging.
// Important to do this before total time is updated
avgPower = Power.of(
(totalTime.getSeconds() * avgPower.getW() + other.totalTime.getSeconds() * other.avgPower.getW())
/ (totalTime.getSeconds() + other.totalTime.getSeconds())
);
}
}
totalDistance = totalDistance.plus(other.totalDistance);
totalTime = totalTime.plus(other.totalTime);
movingTime = movingTime.plus(other.movingTime);
@@ -270,6 +289,15 @@ public class TrackStatistics {
return avgHeartRate;
}
public boolean hasPower() {
return avgPower != null;
}
@Nullable
public Power getAveragePower() {
return avgPower;
}
/**
* Gets the average speed.
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
@@ -333,6 +361,12 @@ public class TrackStatistics {
}
}
public void setAveragePower(Power power) {
if (power != null) {
avgPower = power;
}
}
public boolean hasTotalAltitudeGain() {
return totalAltitudeGain_m != null;
}
@@ -23,8 +23,10 @@ import java.util.List;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.settings.PreferencesUtils;
/**
* Updater for {@link TrackStatistics}.
@@ -43,6 +45,8 @@ public class TrackStatisticsUpdater {
private float averageHeartRateBPM;
private Duration totalHeartRateDuration = Duration.ZERO;
private float averagePowerW;
private Duration totalPowerDuration = Duration.ZERO;
// The current segment's statistics
private final TrackStatistics currentSegment;
@@ -53,11 +57,6 @@ public class TrackStatisticsUpdater {
this(new TrackStatistics());
}
/**
* Creates a new{@link TrackStatisticsUpdater} with a {@link TrackStatisticsUpdater} already existed.
*
* @param trackStatistics a {@link TrackStatisticsUpdater}
*/
public TrackStatisticsUpdater(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
this.currentSegment = new TrackStatistics();
@@ -84,9 +83,6 @@ public class TrackStatisticsUpdater {
trackPoints.stream().forEachOrdered(this::addTrackPoint);
}
/**
*
*/
public void addTrackPoint(TrackPoint trackPoint) {
if (trackPoint.isSegmentManualStart()) {
reset(trackPoint);
@@ -125,6 +121,17 @@ public class TrackStatisticsUpdater {
currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
}
// Update power
if (trackPoint.hasPower() && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalPowerDuration.plus(trackPointDuration);
averagePowerW = (totalPowerDuration.toMillis() * averagePowerW + trackPointDuration.toMillis() * trackPoint.getPower().getW()) / newTotalDuration.toMillis();
totalPowerDuration = newTotalDuration;
currentSegment.setAveragePower(Power.of(averagePowerW));
}
{
// Update total distance
Distance movingDistance = null;
@@ -137,18 +144,23 @@ public class TrackStatisticsUpdater {
movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
}
if (movingDistance != null) {
currentSegment.setIdle(false);
currentSegment.addTotalDistance(movingDistance);
}
if (!currentSegment.isIdle() && !trackPoint.isSegmentManualStart()) {
if (lastTrackPoint != null) {
if (!currentSegment.isIdle()) {
if (!trackPoint.isSegmentManualStart() && lastTrackPoint != null) {
currentSegment.addMovingTime(trackPoint, lastTrackPoint);
}
}
if (trackPoint.getType() == TrackPoint.Type.IDLE) {
if (trackPoint.isIdleTriggered()) {
currentSegment.setIdle(true);
} else if (currentSegment.isIdle()) {
// Shall we switch to non-idle?
if (movingDistance != null
&& movingDistance.greaterOrEqualThan(PreferencesUtils.getRecordingDistanceInterval())) {
currentSegment.setIdle(false);
}
}
if (trackPoint.hasSpeed()) {
@@ -9,6 +9,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -103,6 +104,7 @@ public class IntervalStatistics {
private Float gain_m;
private Float loss_m;
private HeartRate avgHeartRate;
private Power avgPower;
public Interval() {
}
@@ -118,6 +120,7 @@ public class IntervalStatistics {
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
avgPower = i.avgPower;
}
public Interval(Interval i) {
@@ -126,6 +129,7 @@ public class IntervalStatistics {
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
avgPower = i.avgPower;
}
public Distance getDistance() {
@@ -160,12 +164,21 @@ public class IntervalStatistics {
return avgHeartRate;
}
public boolean hasAveragePower() {
return avgPower != null;
}
public Power getAveragePower() {
return avgPower;
}
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
distance = distance.plus(trackStatistics.getTotalDistance());
time = time.plus(trackStatistics.getTotalTime());
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
avgPower = trackStatistics.getAveragePower();
if (lastTrackPoint == null) {
return;
}
@@ -183,6 +196,7 @@ public class IntervalStatistics {
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
avgPower = trackStatistics.getAveragePower();
}
}
}
@@ -59,15 +59,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID);
Marker.Id markerId = null;
if (bundleMarkerId instanceof Marker.Id cast) {
markerId = cast;
}
if (bundleMarkerId instanceof Long cast) {
//Incoming Intent via Dashboard API.
markerId = new Marker.Id(cast);
}
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
if (markerId == null) {
Log.d(TAG, "invalid marker id");
finish();
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -36,19 +37,19 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.PickVisualMediaRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import java.io.FileDescriptor;
import java.io.IOException;
import java.time.Instant;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.MarkerEditBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
/**
* An activity to add/edit a marker.
@@ -59,13 +60,14 @@ public class MarkerEditActivity extends AbstractActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
public static final String EXTRA_LOCATION = "location";
private static final String CAMERA_PHOTO_URI_KEY = "camera_photo_uri_key";
private static final String NEW_MARKER_ID = "new_marker_id";
private static final String TAG = MarkerEditActivity.class.getSimpleName();
private Track.Id trackId;
private Location location;
private Marker.Id markerId;
private Marker marker;
private MenuItem insertPhotoMenuItem;
@@ -93,16 +95,16 @@ public class MarkerEditActivity extends AbstractActivity {
super.onCreate(savedInstanceState);
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
@Nullable Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
final boolean isNewMarker = markerId == null;
location = getIntent().getParcelableExtra(EXTRA_LOCATION);
markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
if ((trackId == null || location == null) && markerId == null) {
throw new IllegalStateException("TrackId and Location must be provided or an existing markerId");
}
if (savedInstanceState != null) {
cameraPhotoUri = Uri.parse(savedInstanceState.getString(CAMERA_PHOTO_URI_KEY, ""));
Marker.Id newMarkerId = savedInstanceState.getParcelable(NEW_MARKER_ID);
if (newMarkerId != null) {
markerId = newMarkerId;
}
}
boolean isNewMarker = markerId == null;
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
@@ -157,16 +159,8 @@ public class MarkerEditActivity extends AbstractActivity {
}
});
if (markerId == null) {
TrackRecordingServiceConnection.execute(this, (service, self) -> {
Marker.Id newMarkerId = createNewMarker(service);
if (newMarkerId == null) {
finish();
} else {
loadMarkerData(newMarkerId);
}
});
if (isNewMarker) {
createNewMarker().observe(this, this::loadMarkerData);
} else {
loadMarkerData(markerId);
}
@@ -174,35 +168,27 @@ public class MarkerEditActivity extends AbstractActivity {
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
}
private Marker.Id createNewMarker(TrackRecordingService trackRecordingService) {
try {
Marker.Id marker = trackRecordingService.insertMarker("", "", "", null);
if (marker == null) {
Toast.makeText(this, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return null;
}
return marker;
} catch (IllegalStateException e) {
Log.e(TAG, "Unable to add marker.", e);
return null;
}
private LiveData<Marker> createNewMarker() {
TrackPoint trackPoint = new TrackPoint(location, Instant.now());
return viewModel.createNewMarker(trackId, trackPoint);
}
private void loadMarkerData(Marker.Id markerId) {
viewModel.getMarkerData(markerId).observe(this, data -> {
marker = data;
viewBinding.markerEditName.setText(marker.getName());
viewBinding.markerEditMarkerType.setText(marker.getCategory());
viewBinding.markerEditDescription.setText(marker.getDescription());
if (marker.hasPhoto()) {
setMarkerImageView(marker.getPhotoURI());
} else {
viewBinding.markerEditPhoto.setImageDrawable(null);
}
viewModel.getMarkerData(markerId).observe(this, this::loadMarkerData);
}
hideAndShowOptions();
});
private void loadMarkerData(Marker data) {
marker = data;
viewBinding.markerEditName.setText(marker.getName());
viewBinding.markerEditMarkerType.setText(marker.getCategory());
viewBinding.markerEditDescription.setText(marker.getDescription());
if (marker.hasPhoto()) {
setMarkerImageView(marker.getPhotoURI());
} else {
viewBinding.markerEditPhoto.setImageDrawable(null);
}
hideAndShowOptions();
}
@Override
@@ -210,6 +196,8 @@ public class MarkerEditActivity extends AbstractActivity {
super.onDestroy();
trackId = null;
location = null;
markerId = null;
viewBinding = null;
viewModel = null;
takePictureFromGallery = null;
@@ -222,8 +210,6 @@ public class MarkerEditActivity extends AbstractActivity {
if (cameraPhotoUri != null) {
outState.putString(CAMERA_PHOTO_URI_KEY, cameraPhotoUri.toString());
}
outState.putParcelable(NEW_MARKER_ID, marker.getId());
}
@Override
@@ -20,6 +20,8 @@ import java.util.NoSuchElementException;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Marker;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.util.FileUtils;
public class MarkerEditViewModel extends AndroidViewModel {
@@ -122,22 +124,40 @@ public class MarkerEditViewModel extends AndroidViewModel {
marker.setName(name);
marker.setCategory(category);
marker.setDescription(description);
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
if (marker.getId() == null) {
new ContentProviderUtils(getApplication()).insertMarker(marker);
} else {
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
}
if (photoOriginalUri != null && (!marker.hasPhoto() || !photoOriginalUri.equals(marker.getPhotoURI()))) {
deletePhoto(photoOriginalUri);
}
}
public LiveData<Marker> createNewMarker(Track.Id trackId, TrackPoint trackPoint) {
Integer nextMarkerNumber = new ContentProviderUtils(getApplication()).getNextMarkerNumber(trackId);
if (nextMarkerNumber == null) {
nextMarkerNumber = 1;
}
String name = getApplication().getString(R.string.marker_name_format, nextMarkerNumber + 1);
String icon = getApplication().getString(R.string.marker_icon_url);
Marker marker = new Marker(name, "", "", icon, trackId, trackPoint, "");
if (markerData == null) {
markerData = new MutableLiveData<>();
}
markerData.postValue(marker);
return markerData;
}
public void onCancel(boolean isNewMarker) {
Marker marker = getMarker();
if (isNewMarker) {
// it's new marker -> clean all photos.
deletePhoto(marker);
deletePhoto(photoOriginalUri);
new ContentProviderUtils(getApplication()).deleteMarker(getApplication(), marker.getId());
} else if (photoOriginalUri == null || (marker.hasPhoto() && !marker.getPhotoURI().equals(photoOriginalUri))) {
// it's an edit marker -> delete photo if it was empty or it was changed (leaving the original in that case).
deletePhoto(marker);
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.ui.markers;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
@@ -24,7 +23,7 @@ import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.List;
@@ -48,7 +47,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
*
* @author Leif Hendrik Wilden
*/
public class MarkerListActivity extends AbstractActivity {
public class MarkerListActivity extends AbstractActivity implements DeleteMarkerDialogFragment.DeleteMarkerCaller {
public static final String EXTRA_TRACK_ID = "track_id";
@@ -94,7 +93,6 @@ public class MarkerListActivity extends AbstractActivity {
}
};
private MenuItem insertMarkerMenuItem;
private MenuItem searchMenuItem;
private String searchQuery;
@@ -116,9 +114,7 @@ public class MarkerListActivity extends AbstractActivity {
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
setSupportActionBar(viewBinding.markerListToolbar);
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> {
finish();
});
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> finish());
}
@Override
@@ -152,6 +148,14 @@ public class MarkerListActivity extends AbstractActivity {
@Override
protected View getRootView() {
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
viewBinding.markerListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> {
searchQuery = viewBinding.markerListSearchView.getEditText().getText().toString();
viewBinding.markerListSearchView.hide();
loadData();
return true;
});
return viewBinding.getRoot();
}
@@ -161,9 +165,6 @@ public class MarkerListActivity extends AbstractActivity {
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
searchMenuItem = menu.findItem(R.id.marker_list_search);
ActivityUtils.configureSearchWidget(this, searchMenuItem);
return super.onCreateOptionsMenu(menu);
}
@@ -235,34 +236,10 @@ public class MarkerListActivity extends AbstractActivity {
return false;
}
@Override
public void onBackPressed() {
SearchView searchView = (SearchView) searchMenuItem.getActionView();
if (!searchView.isIconified()) {
searchView.setIconified(true);
}
if (searchQuery != null) {
searchQuery = null;
loadData();
return;
}
super.onBackPressed();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
searchQuery = intent.getStringExtra(SearchManager.QUERY);
} else {
searchQuery = null;
}
}
@UiThread
private void loadData() {
viewBinding.markerListToolbar.setText(searchQuery);
viewBinding.markerListToolbar.setTitle(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.menu_markers)));
List<Marker> markers = contentProviderUtils.searchMarkers(trackId, searchQuery);
@@ -281,4 +258,9 @@ public class MarkerListActivity extends AbstractActivity {
private void onRecordingStatusChanged(RecordingStatus status) {
recordingStatus = status;
}
@Override
public void onMarkerDeleted() {
runOnUiThread(this::loadData);
}
}
@@ -2,18 +2,13 @@ package de.dennisguse.opentracks.ui.util;
import static android.content.Context.VIBRATOR_SERVICE;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import java.time.Duration;
@@ -21,19 +16,6 @@ public class ActivityUtils {
private static final String TAG = ActivityUtils.class.getSimpleName();
public static SearchView configureSearchWidget(Activity activity, final MenuItem menuItem) {
final SearchView searchView = (SearchView) menuItem.getActionView();
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
if (searchManager != null) {
//NOTE: Use searchManager.getSearchableInfo(new ComponentName(activity, SearchActivity.class)) if another activity should handle the search
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
} else {
Log.w(TAG, "Could not retrieve SearchManager.");
}
searchView.setSubmitButtonEnabled(true);
return searchView;
}
public static void vibrate(@NonNull Context context, Duration duration) {
final Vibrator vibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -57,6 +57,7 @@ import de.dennisguse.opentracks.settings.UnitSystem;
public class StringUtils {
private static final String TAG = StringUtils.class.getSimpleName();
public static final String EMPTY = "";
private StringUtils() {
}
@@ -1,6 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="5.8208"
android:viewportHeight="5.8208">
<path
+13 -3
View File
@@ -22,14 +22,24 @@ limitations under the License.
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
<com.google.android.material.search.SearchBar
android:id="@+id/marker_list_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/ic_marker_show_24dp"
app:title="@string/menu_markers" />
app:layout_behavior="@string/searchbar_scrolling_view_behavior"
app:menu="@menu/marker_list" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.search.SearchView
android:id="@+id/marker_list_search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/menu_search"
app:layout_anchor="@id/marker_list_toolbar">
<!-- Search suggestions/results go here (ScrollView, RecyclerView, etc.). -->
</com.google.android.material.search.SearchView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -30,7 +30,7 @@ limitations under the License.
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="?attr/textInputOutlinedStyle"
style="?attr/textInputOutlinedExposedDropdownMenuStyle"
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:layout_marginEnd="8dp">
+12 -3
View File
@@ -22,15 +22,24 @@ limitations under the License.
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
<com.google.android.material.search.SearchBar
android:id="@+id/track_list_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/ic_logo_color_24dp"
app:title="@string/app_name"
app:layout_behavior="@string/searchbar_scrolling_view_behavior"
app:menu="@menu/track_list" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.search.SearchView
android:id="@+id/track_list_search_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/menu_search"
app:layout_anchor="@id/track_list_toolbar">
<!-- Search suggestions/results go here (ScrollView, RecyclerView, etc.). -->
</com.google.android.material.search.SearchView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+1 -6
View File
@@ -17,12 +17,7 @@ limitations under the License.
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/marker_list_insert_marker"
android:icon="@drawable/ic_marker_show_24dp"
android:icon="@drawable/ic_marker_add_24dp"
android:title="@string/menu_insert_marker"
app:showAsAction="ifRoom" />
<item
android:id="@+id/marker_list_search"
android:title="@string/menu_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom" />
</menu>
+2 -7
View File
@@ -15,12 +15,6 @@ limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- TODO SearchAction is only working if it is shown as action. However, only two slots are available. See #757. -->
<item
android:id="@+id/track_list_search"
android:title="@string/menu_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom" />
<item
android:id="@+id/track_list_markers"
android:icon="@drawable/ic_marker_show_24dp"
@@ -29,7 +23,8 @@ limitations under the License.
<item
android:id="@+id/track_list_settings"
android:icon="@drawable/ic_settings_24dp"
android:title="@string/menu_settings" />
android:title="@string/menu_settings"
app:showAsAction="ifRoom" />
<item
android:id="@+id/track_list_help"
android:icon="@drawable/ic_help_24dp"
+6 -6
View File
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_type_airplane">aeroplane</string>
<string name="activity_type_atv">ATV</string>
<string name="activity_type_biking">biking</string>
<string name="activity_type_atv">ATV 4-Wheeler</string>
<string name="activity_type_biking">A bike ride</string>
<string name="activity_type_blimp">airship</string>
<string name="activity_type_boat">boat</string>
<string name="activity_type_boat">Boating</string>
<string name="activity_type_climbing">climbing</string>
<string name="activity_type_commercial_airplane">commercial aeroplane</string>
<string name="activity_type_commercial_airplane">commercial flight</string>
<string name="activity_type_cross_country_skiing">Cross-country skiing</string>
<string name="activity_type_cycling">cycling</string>
<string name="activity_type_dirt_bike">dirt bike</string>
<string name="activity_type_donkey_back_riding">donkey riding</string>
<string name="activity_type_donkey_back_riding">Donkey ride</string>
<string name="activity_type_driving">driving</string>
<string name="activity_type_driving_bus">driving bus</string>
<string name="activity_type_driving_car">driving car</string>
<string name="activity_type_driving_car">Car driving</string>
<string name="activity_type_ferry">ferry</string>
<string name="activity_type_frisbee">frisbee</string>
<string name="activity_type_gliding">gliding</string>
+12 -4
View File
@@ -37,12 +37,12 @@ limitations under the License.
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky na cestě\?</string>
<string name="help_storage_pictures_content">Jsou uloženy v interním úložišti aplikace (tj. přístupné pouze v rámci OpenTracks).</string>
<string name="help_track_export_title">Jak mohu exportovat/sdílet zaznamenané stopy\?</string>
<string name="help_track_export_content"> \u0020Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. \u0020KMZ a KML obsahuje všechna data, GPX pouze polohu. \u0020Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. \u0020</string>
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze polohu. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
<string name="help_data_google_play_services_title">Vyžaduje/používá OpenTracks službu Google Play\?</string>
<string name="help_recording_title">Jak OpenTracks zaznamenává data?</string>
<string name="help_recording_content">OpenTracks používá GPS senzor zařízení k nepřetržitému zaznamenávání polohy. Pokud zařízení GPS hlásí nepřesná data (např. polohu, rychlost, nadmořskou výšku), OpenTracks s tím nemůže nic udělat.</string>
<string name="help_data_storage_title">Jak OpenTracks ukládá data ze senzorů\?</string>
<string name="help_data_storage_content"> \u0020Data senzoru se ukládají na zaznamenané místo. \u0020Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i> jedno </i> měření srdeční frekvence. \u0020Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). \u0020</string>
<string name="help_data_storage_content"> Data senzorů se ukládají na zaznamenané místo. Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i>jedno</i> měření srdeční frekvence. Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). </string>
<string name="help_gps_troubleshooting_title">OpenTracks nezaznamenává místa?</string>
<string name="help_gps_troubleshooting_content"> \u0020Co můžete udělat:
\n \u0020a) zkontrolujte zda je zapnuta GPS,
@@ -419,14 +419,14 @@ limitations under the License.
<item quantity="other">Ukončeno s %1$d chybami</item>
</plurals>
<string name="import_progress_summary_errors_msg">Chyba</string>
<string name="settings_prevent_reimport_tracks_title">Zabraňte opětovnému importu aktivit</string>
<string name="settings_prevent_reimport_tracks_title">Zabránit opětov. importu stop</string>
<string name="export_progress_summary_errors_msg">Chyba</string>
<string name="export_progress_summary_skip_msg">Soubory přeskočeny</string>
<string name="export_progress_summary_overwrite_msg">Soubory přepsány</string>
<string name="export_progress_summary_new_msg">Nové soubory exportovány</string>
<string name="import_progress_summary_exists_msg">Existovat</string>
<string name="import_progress_summary_ok_msg">Importované soubory</string>
<string name="generic_open_track">Otevřená trať</string>
<string name="generic_open_track">Otevřít stopu</string>
<string name="sensor_state_power_avg">Prům. výkon</string>
<string name="sensor_state_heart_rate_max">Maximální srdeční frekvence</string>
<string name="sensor_state_heart_rate_avg">Avg srdeční frekvence</string>
@@ -601,4 +601,12 @@ limitations under the License.
<string name="settings_recording_location_distance_summary">%1$s mezi zaznamenanými polohami</string>
<string name="settings_theme_switch_restart">Změna motivu může vyžadovat ruční restart.</string>
<string name="settings_night_mode_option_night_oled">Noční (OLED)</string>
<string name="settings_announcements_unit">Jednotka vzdálenosti/rychlosti</string>
<string name="voiceSpeed">{n, plural, =1 {1} few {{n,number}} other {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } few {{n,number}} other {{n,number,#.0}} }</string>
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
<string name="power">Výkon</string>
<string name="settings_announcements_lap_power">Výkon kola</string>
<string name="power_x_watt">Výkon {x, plural, =1 {1 watt} few {# watty} other {# wattů} }</string>
<string name="create_marker_error">Nepodařilo se vytvořit značku. Zkuste to znovu.</string>
</resources>
+1 -1
View File
@@ -16,7 +16,7 @@ limitations under the License.
--><resources>
<string name="about_preference_title">Om OpenTracks</string>
<string name="about_description"><i> OpenTracks </i> er en sportstracker, der fuldt ud respekterer dit privatliv.</string>
<string name="about_url">Projekthjemmeside: <a href="%1$s">%1$s</a></string>
<string name="about_url">Projektets hjemmeside: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Versionsnavn: %1$s</string>
<string name="about_version_code">Versionskode: %1$d</string>
<string name="about_history">OpenTracks er en forgrening fra <a href="https://code.google.com/archive/p/mytracks/"> <i> Googles MyTracks </i> </a> (afviklet i 2016).</string>
+38 -10
View File
@@ -23,13 +23,12 @@ limitations under the License.
<string name="about_libraries">Bibliotheken</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks hat die folgenden Funktionen:</string>
<string name="help_feature_content">
* GPS-Tracks erstellen\n
* einen Herzfrequenzsensor verwenden (nur Bluetooth LE)\n
* Sprachansagen (nach Zeit oder Entfernung)\n
* Markierungen erstellen (inkl. Bilder)\n
* arbeitet vollständig offline\n
* Tracks auf einer Karte anzeigen (siehe unten)</string>
<string name="help_feature_content">* GPS-Tracks erstellen
\n* einen Herzfrequenzsensor verwenden (nur Bluetooth LE)
\n* Sprachansagen (nach Zeit oder Entfernung)
\n* Markierungen erstellen (inkl. Bilder)
\n* arbeitet vollständig offline
\n* Tracks auf einer Karte anzeigen (siehe unten)</string>
<string name="help_track_map_title">Wie wird ein Track auf einer Karte angezeigt?</string>
<string name="help_track_map_content"> Verwende das kleine Kartensymbol.
\n a) (Live-Aktualisierungen) Installiere <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. OSMDashboard stellt Online- oder Offline-Karten über <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
@@ -243,7 +242,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="settings_recording_keep_screen_on_while_recording_title">Bildschirm eingeschaltet lassen</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Lasse während der Aufnahme den Bildschirm eingeschaltet.</string>
<string name="settings_recording_default_activity_title">Standardaktivität</string>
<string name="settings_recording_location_frequency_summary">Entfernungs-/Zeitintervall %1$s zwischen zwei Standortaufzeichnungen</string>
<string name="settings_recording_location_frequency_summary">%1$s zwischen Standort Überprüfungen</string>
<string name="settings_recording_max_recording_distance_summary">Ein neues Segment wird erstellt, wenn die Entfernung zwischen zwei Standorten mehr als %1$s beträgt</string>
<string name="settings_recording_max_recording_distance_title">Max. Entfernung</string>
<string name="settings_recording_min_recording_distance_title">Aufzeichnungsintervall</string>
@@ -490,7 +489,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="menu_select_layout">Wähle Layout</string>
<string name="generic_choose_an_option">Wähle eine Option</string>
<string name="settings_gps_sensor">GPS</string>
<string name="settings_import_export_title">Import/Export</string>
<string name="settings_import_export_title">Import und Export</string>
<string name="settings_sensors_title">Sensoren</string>
<string name="settings_sensors_summary">Fahrradsensoren, Radumfang und Lauf-Sensoren</string>
<string name="settings_announcements_statistics_title">Sprachansagen</string>
@@ -532,7 +531,7 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="export_dir_not_writable">" (nicht schreibbar)"</string>
<string name="stats_clock">Uhr</string>
<string name="image_discard">Verwerfen</string>
<string name="settings_announcements_distance_frequency">Entfernungsintervall</string>
<string name="settings_announcements_distance_frequency">Runden Länge</string>
<string name="settings_public_api_enabled_title">Öffentliche API</string>
<string name="track_discarding">Verwerfe Track</string>
<string name="average_heart_rate">Durchschnittlicher Puls</string>
@@ -618,4 +617,33 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="settings_recording_min_required_accuracy_summary">Ort wird ignoriert wenn seine Genauigkeit über %1$s liegt</string>
<string name="settings_sensor_bluetooth_service_filter_title">Bluetooth Geräte filtern</string>
<string name="settings_sensor_bluetooth_service_filter_off">Alle verfügbaren Bluetooth Geräte anzeigen</string>
<string name="settings_theme_switch_restart">Themenänderungen können einen manuellen Neustart erfordern.</string>
<string name="settings_recording_idle_timeout_title">Stillstand Schwelle</string>
<string name="settings_night_mode_option_night_oled">Nacht (OLED freundlich)</string>
<string name="sensor_state_pressure">Barometer</string>
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="settings_announcements_idle_category_title">Stillstand Benachrichtigung</string>
<string name="settings_announcements_idle_title">Stillstand</string>
<string name="settings_ui_dynamic_colors_title">Dynamische Farben</string>
<string name="value_internal_sensor">Interner Sensor</string>
<string name="permission_recording_failed">OpenTracks benötigt die Berechtigubg, auf Ihren Standort sowie nahe gelegene Geräte zuzugreifen.</string>
<string name="sensor_state_power_max">Maximale Leistung</string>
<string name="sensor_state_power_unit">W</string>
<string name="settings_recording_title">Aufnahme</string>
<string name="settings_ui_dynamic_colors_summary">Verwenden Sie benutzerdefinierte dynamische Farben (nur Android 12+; erfordert einen manuellen Neustart)</string>
<string name="value_int_seconds">%1$d s (empfohlen)</string>
<string name="sensor_state_cadence_unit">Drehzahl</string>
<string name="sensor_state_heart_rate_unit">bpm</string>
<string name="activity_type_kickscooter">Roller</string>
<string name="settings_recording_location_distance_summary">%1$s zwischen aufgezeichneten Orten</string>
<string name="voiceSpeedMKnotsPlural">{n, plural, =1 {1 Knoten} other {{n,number,#.0} Knoten} }</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
<string name="settings_announcements_unit">Distanz/Geschwindigkeits Einheit</string>
<string name="power_x_watt">Leistung {x, plural, =1 {1 Watt} other {# Watt} }</string>
<string name="power">Leistung</string>
<string name="voiceDistance">{n, plural, =1 {1 } anderes {{n,number,#.0}} }</string>
<string name="settings_recording_min_sampling_interval_title">Abtastzeitintervall</string>
<string name="voiceIdle">Untätig werden.</string>
<string name="voice_announce_lap_power_key">StimmeAnkündigungLapPower</string>
<string name="settings_announcements_lap_power">Rundenleistung</string>
</resources>
+13 -3
View File
@@ -37,12 +37,12 @@ limitations under the License.
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\?</string>
<string name="help_storage_pictures_content">Estos se almacenan en el almacenamiento interno de la aplicación (es decir, solo se puede acceder a ellos dentro de OpenTracks).</string>
<string name="help_track_export_title">¿Cómo puedo exportar/compartir recorridos grabados\?</string>
<string name="help_track_export_content"> \u0020Los Tracks se pueden exportar desde la Configuración como KMZ, KML o GPX. \u0020KMZ/KML todos los datos; solo ubicaciones GPX. \u0020Las pistas compartidas a través del <i>icono de compartir</i> se comparten como KMZ. \u0020</string>
<string name="help_track_export_content"> Las pistas se pueden exportar desde la configuración como KMZ, KML o GPX. KMZ/KML todos los datos; Ubicaciones solo GPX. Las pistas compartidas a través del <i>icono de compartir</i> se comparten como KMZ. </string>
<string name="help_data_google_play_services_title">¿OpenTracks requiere/usa los servicios de Google Play\?</string>
<string name="help_recording_title">¿Cómo registra OpenTracks los datos?</string>
<string name="help_recording_content">OpenTracks utiliza el sensor GPS del dispositivo para registrar la ubicación de forma continua. Si el dispositivo GPS informa datos inexactos (por ejemplo, ubicación, velocidad, elevación), OpenTracks no puede hacer nada al respecto.</string>
<string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string>
<string name="help_data_storage_content"> \u0020Los datos del sensor se almacenan por ubicación registrada. \u0020En términos de un sensor de frecuencia cardiaca, esto significa que sólo se almacena <i>una</i> medición de frecuencia cardiaca por ubicación. \u0020Si está parado o se encuentra en interiores, no se registrará ningún dato del sensor (pero se mostrará). \u0020</string>
<string name="help_data_storage_content"> Los datos del sensor se almacenan por ubicación registrada. En términos de un sensor de frecuencia cardíaca, esto significa que solo se almacena <i>una</i> medición de frecuencia cardíaca por ubicación. Si está parado o está en interiores, no se registrarán los datos del sensor (pero se mostrarán). </string>
<string name="help_gps_troubleshooting_title">¿OpenTracks no registra ubicaciones\?</string>
<string name="help_gps_troubleshooting_content"> Lo que puedes hacer:
\na) compruebe si el GPS está habilitado,
@@ -520,7 +520,7 @@ limitations under the License.
<string name="settings_gps_sensor">GPS</string>
<string name="settings_announcements_statistics_title">Avisos de voz</string>
<string name="settings_units_category">Unidades</string>
<string name="settings_import_export_title">Importar/Exportar</string>
<string name="settings_import_export_title">Importar y exportar</string>
<string name="stats_custom_layout_default_layout">Diseño predeterminado</string>
<string name="custom_layout_select_layout">Seleccionar diseño</string>
<string name="custom_layout_list_edit_already_exists">El nombre de este diseño ya existe</string>
@@ -654,4 +654,14 @@ limitations under the License.
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="settings_recording_min_sampling_interval_title">Intervalo de muestreo</string>
<string name="settings_recording_location_distance_summary">%1$s entre lugares registrados</string>
<string name="settings_theme_switch_restart">Los cambios del tema pueden requerir un reinicio manual.</string>
<string name="settings_night_mode_option_night_oled">Oscuro (compatible con OLED)</string>
<string name="settings_announcements_unit">Unidad de distancia/velocidad</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
<string name="settings_announcements_lap_power">Potencia por vuelta</string>
<string name="power_x_watt">Potencia {x, plural, =1 {1 vatio} otros {# vatios} }</string>
<string name="create_marker_error">No se ha podido crear un marcador. Inténtelo de nuevo.</string>
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
<string name="power">Potencia</string>
</resources>
+6 -7
View File
@@ -23,13 +23,12 @@ limitations under the License.
<string name="about_libraries">Liburutegiak</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks ek ezaugarri hauek ditu:</string>
<string name="help_feature_content">
* GPS ibilbideak sortu\n
* erabili bihotz-maiztasuneko sentsorea (Bluetooth LE soilik)\n
* ahots iragarpenak (denboraren edo distantziaren arabera)\n
* Sortu markatzaileak (irudiak barne)\n
* guztiz lineaz kanpo funtzionatzen du\n
* Erakutsi ibilbideak mapa batean (ikusi azpian)</string>
<string name="help_feature_content">* GPS ibilbideak sortu
\n * erabili bihotz-maiztasuneko sentsorea (Bluetooth LE soilik)
\n * ahots iragarpenak (denboraren edo distantziaren arabera)
\n * Sortu markatzaileak (irudiak barne)
\n * guztiz lineaz kanpo funtzionatzen du
\n * Erakutsi ibilbideak mapa batean (ikusi azpian)</string>
<string name="help_track_map_title">Nola erakutsi pista batean mapa batean?</string>
<string name="help_track_map_content"> Erabili maparen ikono txikia.
\n a) (zuzeneko eguneratzeak) Instalatu <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Lineako edo lineaz kanpoko mapak eskaintzen ditu <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
+6 -7
View File
@@ -23,13 +23,12 @@ limitations under the License.
<string name="about_libraries">Kirjastot</string>
<!-- Help -->
<string name="help_feature_title">OpenTracksissa on seuraavat ominaisuudet:</string>
<string name="help_feature_content">
* luo GPS-reittejä\n
* käytä sykeanturia (vain Bluetooth LE)\n
* ääni-ilmoitukset (ajan tai etäisyyden perusteella)\n
* luo merkkejä (mukaan lukien kuvia)\n
* toimii täysin ilman verkkoyhteyttä\n
* näytä jäljet kartalla (ks. alla)</string>
<string name="help_feature_content">* luo GPS-reittejä
\n * käytä sykeanturia (vain Bluetooth LE)
\n * ääni-ilmoitukset (ajan tai etäisyyden perusteella)
\n * luo merkkejä (mukaan lukien kuvia)
\n * toimii täysin ilman verkkoyhteyttä
\n * näytä jäljet kartalla (ks. alla)</string>
<string name="help_track_map_title">Miten näyttää rata kartalla\?</string>
<string name="help_track_map_content"> Käytä pientä karttakuvaketta.
\n a) (live-päivityksiä) Asenna <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Se tarjoaa online-tai offline-karttoja kautta <a href="https://www.openstreetmap.org">OpenStreetMap</a>.

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