Compare commits

...

49 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
75 changed files with 753 additions and 719 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
+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
+10 -10
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.4.1'
classpath 'com.android.tools.build:gradle:8.5.1'
}
}
@@ -64,8 +64,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 5788
versionName "v4.12.4"
versionCode 5837
versionName "v4.13.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -127,7 +127,7 @@ 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'
@@ -137,12 +137,12 @@ dependencies {
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 '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.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,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.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
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -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;
@@ -65,7 +64,6 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
import de.dennisguse.opentracks.sensors.sensorData.Raw;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
@@ -141,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");
@@ -156,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();
@@ -596,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.getSensorManager().onChanged(new Raw<>(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,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();
}
}
@@ -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;
}
}
@@ -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;
@@ -55,7 +55,7 @@ public abstract class Aggregator<Input, 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.
@@ -1,12 +1,10 @@
package de.dennisguse.opentracks.sensors.sensorData;
import android.location.Location;
import androidx.annotation.NonNull;
import java.util.Optional;
import de.dennisguse.opentracks.data.models.Position;
public class AggregatorGPS extends Aggregator<Location, Optional<Location>> {
public class AggregatorGPS extends Aggregator<Position, Position> {
public AggregatorGPS(String sensorAddress) {
@@ -14,8 +12,8 @@ public class AggregatorGPS extends Aggregator<Location, Optional<Location>> {
}
@Override
protected void computeValue(Raw<Location> current) {
value = Optional.of(current.value());
protected void computeValue(Raw<Position> current) {
value = current.value();
}
@Override
@@ -25,7 +23,7 @@ public class AggregatorGPS extends Aggregator<Location, Optional<Location>> {
@NonNull
@Override
protected Optional<Location> getNoneValue() {
return Optional.empty();
protected Position getNoneValue() {
return Position.empty();
}
}
@@ -1,6 +1,5 @@
package de.dennisguse.opentracks.sensors.sensorData;
import android.location.Location;
import android.util.Log;
import android.util.Pair;
@@ -11,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;
@@ -127,8 +127,8 @@ public final class SensorDataSet {
this.barometer.add((Raw<AtmosphericPressure>) data);
return;
}
if (value instanceof Location) {
this.gps.add((Raw<Location>) data);
if (value instanceof Position) {
this.gps.add((Raw<Position>) data);
return;
}
@@ -152,8 +152,7 @@ public final class SensorDataSet {
public void fillTrackPoint(TrackPoint trackPoint) {
if (gps != null && gps.hasValue()) {
gps.getValue()
.ifPresent(trackPoint::setLocation);
trackPoint.setPosition(gps.getValue());
}
if (getHeartRate() != null) {
@@ -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");
@@ -247,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) {
@@ -308,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() {
@@ -269,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() {
@@ -18,6 +18,7 @@ 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;
@@ -138,7 +139,7 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
return;
}
listener.onChange(new Raw<>(location));
listener.onChange(new Raw<>(Position.of(location)));
}
@Override
@@ -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 {
@@ -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();
}
@@ -107,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;
@@ -119,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 {
@@ -26,6 +26,7 @@ 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}.
@@ -56,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();
@@ -87,9 +83,6 @@ public class TrackStatisticsUpdater {
trackPoints.stream().forEachOrdered(this::addTrackPoint);
}
/**
*
*/
public void addTrackPoint(TrackPoint trackPoint) {
if (trackPoint.isSegmentManualStart()) {
reset(trackPoint);
@@ -151,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()) {
@@ -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;
@@ -25,7 +24,6 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.List;
@@ -95,7 +93,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
}
};
private MenuItem insertMarkerMenuItem;
private MenuItem searchMenuItem;
private String searchQuery;
@@ -117,9 +114,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
setSupportActionBar(viewBinding.markerListToolbar);
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> {
finish();
});
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> finish());
}
@Override
@@ -153,6 +148,14 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@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();
}
@@ -162,9 +165,6 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
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);
}
@@ -236,35 +236,10 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
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);
@@ -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) {
@@ -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>
+3 -2
View File
@@ -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>
@@ -608,4 +608,5 @@ limitations under the License.
<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>
+5
View File
@@ -641,4 +641,9 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="settings_announcements_unit">Distanz/Geschwindigkeits Einheit</string>
<string name="power_x_watt">Leistung {x, plural, =1 {1 Watt} other {# Watt} }</string>
<string name="power">Leistung</string>
<string name="voiceDistance">{n, plural, =1 {1 } anderes {{n,number,#.0}} }</string>
<string name="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>
+9 -2
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,
@@ -657,4 +657,11 @@ limitations under the License.
<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>
+5 -5
View File
@@ -16,7 +16,7 @@ limitations under the License.
--><resources>
<string name="about_preference_title">À propos d\'OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> est une application de suivi sportif qui respecte totalement votre vie privée.</string>
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Nom de la version : %1$s</string>
<string name="about_version_code">Code de version : %1$d</string>
<string name="about_history">OpenTracks est une bifurcation de <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (abandonné en 2016).</string>
@@ -30,10 +30,10 @@ limitations under the License.
\n* Fonctionne complètement hors ligne
\n* Affichage des traces sur une carte (voir ci-dessous)</string>
<string name="help_track_map_title">Comment afficher un tracé sur une carte \?</string>
<string name="help_track_map_content"> Utilisez la petite icône en forme de carte.
\na) (Actualisation en direct) Installez <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Il fournit des cartes en ligne ou hors ligne grâce à <a href="https://www.openstreetmap.org ">OpenStreetMap</a>.
\nOSMDashboard est en réalité une application séparée qui n\'accéder (temporairement) qu\'aux données des tracés qui vont être affichées.
\nb) Via une application cartographique standard (p. ex. <a href="http://osmand.net/">OsmAnd</a> ou <a href="https://maps.me/">MAPS.ME</a> ). L’application doit prendre en charge le format de fichier KMZ (uniquement les lieux et les horodatages). Plus précisément, l\'application doit prendre en charge <i>ACTION_VIEW</i> avec le type MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_track_map_content"> \u0020Utilisez la petite icône en forme de carte.
\n \u0020 \u0020a) \u0020 \u0020(Actualisation en direct) Installez <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \u0020 \u0020Il fournit des cartes en ligne ou hors ligne grâce à <a href="https://www.openstreetmap.org ">OpenStreetMap</a>.
\n \u0020 \u0020OSMDashboard est en réalité une application séparée qui n\'accéder (temporairement) qu\'aux données des tracés qui vont être affichées. \u0020
\n \u0020 \u0020b) \u0020 Via une application cartographique standard (p. ex. <a href="http://osmand.net/">OsmAnd</a> ou <a href="https://maps.me/">MAPS.ME</a> ). \u0020 L’application doit prendre en charge le format de fichier KMZ (uniquement les lieux et les horodatages). \u0020 Plus précisément, l\'application doit prendre en charge <i>ACTION_VIEW</i> avec le type MIME <i>application/vnd.google-earth.kmz</i>. \u0020</string>
<string name="help_storage_pictures_title">Comment OpenTracks stocke-t-il les photos qui ont été prises en tant que repère \?</string>
<string name="help_storage_pictures_content">Elles sont enregistrées dans le stockage interne de l\'application (c\'est-à-dire uniquement accessible dans OpenTracks).</string>
<string name="help_track_export_title">Comment puis-je exporter/partager des tracés enregistrées \?</string>
+10 -2
View File
@@ -175,7 +175,7 @@ limitations under the License.
<string name="settings_chart_by_distance">Etter avstand</string>
<string name="settings_chart_by_time">Etter tid</string>
<string name="settings_recording_default_activity_title">Forvalgt aktivitetstype</string>
<string name="settings_recording_location_frequency_summary">%1$s mellom registrerte posisjoner</string>
<string name="settings_recording_location_frequency_summary">%1$s mellom posisjonskontroller</string>
<string name="settings_recording_max_recording_distance_summary">Det blir opprettet et nytt segment hvis avstanden mellom to posisjoner er større enn %1$s</string>
<string name="settings_recording_max_recording_distance_title">Maks. avstand</string>
<string name="settings_recording_min_recording_distance_title">Avstandsintervall for opptak</string>
@@ -479,7 +479,7 @@ limitations under the License.
<string name="settings_sensors_title">Sensorer</string>
<string name="settings_announcements_statistics_title">Statistikkmeldinger</string>
<string name="settings_announcements_summary">Tid, distanse, talehastighet</string>
<string name="settings_import_export_title">Import/eksport</string>
<string name="settings_import_export_title">Import og eksport</string>
<string name="settings_import_export_summary">Import, eksport, auto-eksport</string>
<string name="settings_units_category">Enheter</string>
<string name="settings_activity_defaults_category">Aktivitetens forvalg</string>
@@ -589,4 +589,12 @@ limitations under the License.
<string name="settings_announcements_lap_power">Runde-kraft</string>
<string name="power_x_watt">Kraft {x, plural, =1 {1 watt} other {# watt} }</string>
<string name="power">Kraft</string>
<string name="settings_theme_switch_restart">Temaendringer kan kreve en manuell omstart.</string>
<string name="value_internal_sensor">Intern sensor</string>
<string name="sensor_state_pressure">Barometer</string>
<string name="sensor_state_power_unit">W</string>
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="settings_sensor_bluetooth_service_filter_title">Filtrer Bluetooth-enheter</string>
<string name="settings_sensor_bluetooth_service_filter_off">Vis alle Bluetooth-enheter som kan oppdages</string>
<string name="permission_recording_failed">OpenTracks krever tillatelse for å få tilgang til posisjonen din samt enheter i nærheten.</string>
</resources>
+1
View File
@@ -609,4 +609,5 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="power">Power</string>
<string name="settings_announcements_lap_power">Lap power</string>
<string name="power_x_watt">Power {x, plural, =1 {1 watt} other {# watt} }</string>
<string name="create_marker_error">Kan geen markering maken. Probeer het opnieuw.</string>
</resources>
+1 -1
View File
@@ -30,7 +30,7 @@ limitations under the License.
<string name="activity_type_driving_bus">prowadzenie autobusu</string>
<string name="activity_type_driving_car">prowadzenie samochodu</string>
<string name="activity_type_ferry">prom</string>
<string name="activity_type_frisbee">fisbee</string>
<string name="activity_type_frisbee">frizbee</string>
<string name="activity_type_gliding">szybownictwo</string>
<string name="activity_type_hang_gliding">lotniarstwo</string>
<string name="activity_type_helicopter">helikopter</string>
+64 -8
View File
@@ -151,7 +151,7 @@ limitations under the License.
<string name="menu_aggregated_statistics">Toplu istatistikler</string>
<string name="menu_delete">Sil</string>
<string name="menu_edit">Düzenle</string>
<string name="menu_export_all">Tümünü dışa aktar</string>
<string name="menu_export_all">Tümünü dışa aktar (her parça başına bir dosya)</string>
<string name="menu_help">Yardım</string>
<string name="menu_import_all">Tümünü içe aktar</string>
<string name="menu_insert_marker">İşaretçi ekle</string>
@@ -330,7 +330,7 @@ limitations under the License.
\n Aslında OSMDashboard, sadece gösterilecek olan parçaların verilerine (geçici olarak) erişebilen ayrı bir uygulamadır.
\n b) Normal bir harita uygulaması aracılığıyla (ör. &lt;a href=\"http://osmand.net/\"&gt;OsmAnd&lt;/a&gt; ve &lt;a href=\"https://maps.me/\"&gt;MAPS.ME&lt; /a&gt;). Bu uygulamaların KMZ dosya biçimini (yalnızca konumlar ve zaman damgaları) desteklemesi gerekir. Aslında, bir uygulamanın MIME &lt;i&gt;application/vnd.google-earth.kmz&lt;/i&gt; ile &lt;i&gt;ACTION_VIEW&lt;/i&gt; özelliğini desteklemesi gerekir. &lt;/i&gt; "</string>
<string name="help_storage_pictures_title">OpenTracks, işaretçi olarak çekilmiş resimleri nasıl saklar\?</string>
<string name="about_url">Proje web sayfası: <a href="%1$s">%1$s</a></string>
<string name="about_url">Proje ağ sayfası: <a href="%1$s">%1$s</a></string>
<string name="help_track_export_title">Kayıtlı parkurları nasıl dışa aktarabilirim/paylaşabilirim\?</string>
<string name="about_version_code">Sürüm kodu: %1$d</string>
<string name="generic_choose_an_option">Bir seçenek belirleyin</string>
@@ -343,7 +343,7 @@ limitations under the License.
<string name="export_without_photos">fotoğrafsız</string>
<string name="description_speed_source_gps">GPS</string>
<string name="export_error_post_workout">Antrenman sonrası dışa aktarma başarısız oldu, lütfen dışa aktarma dizinini kontrol edin.</string>
<string name="export_progress_summary_new_msg">Yeni parçalar dışa aktarıldı</string>
<string name="export_progress_summary_new_msg">Yeni dosyalar dışa aktarıldı</string>
<string name="generic_today">Bugün</string>
<string name="generic_click_twice_cancel">İptal etmek için tekrar geri düğmesine tıklayın</string>
<string name="generic_yesterday">Dün</string>
@@ -415,9 +415,9 @@ limitations under the License.
<string name="help_recording_content">OpenTracks, konumu devamlı olarak kaydetmek için cihazın GPS sensörünü kullanır. GPS cihazı yanlış veriler bildirirse (ör. konum, hız, yükseklik) OpenTracks bu konuda bir şey yapamaz.</string>
<string name="help_data_storage_title">OpenTracks sensör verilerini nasıl depolar\?</string>
<string name="export_dir_not_writable">" (yazılabilir değil!)"</string>
<string name="export_track_errors">Parçalar dışa aktarılmadı</string>
<string name="export_progress_summary_overwrite_msg">Parçaların üzerine yazıldı</string>
<string name="export_progress_summary_skip_msg">Parçalar atlandı</string>
<string name="export_track_errors">Dosyalar dışa aktarılmadı</string>
<string name="export_progress_summary_overwrite_msg">Dosyaların üzerine yazıldı</string>
<string name="export_progress_summary_skip_msg">Dosyalar atlandı</string>
<string name="export_progress_summary_errors_msg">Hatalar</string>
<string name="generic_open_track">Parçayı Aç</string>
<string name="generic_overwrite">Üzerine yaz</string>
@@ -435,7 +435,7 @@ limitations under the License.
<string name="settings_ui_title">Kullanıcı Arayüzü</string>
<string name="settings_default_trackfileformat">Dosya biçimini dışa aktarma/paylaşma</string>
<string name="settings_export_filename_title">Dosya adının biçimi</string>
<string name="settings_import_export_title">İçe/Dışa Aktar</string>
<string name="settings_import_export_title">İçe ve Dışa Aktar</string>
<string name="settings_import_export_summary">İçe, Dışa, Otomatik Dışa Aktar</string>
<string name="settings_units_category">Birimler</string>
<string name="settings_activity_defaults_category">Aktivitenin Varsayılanları</string>
@@ -496,7 +496,7 @@ limitations under the License.
<string name="instant_export_enabled_summary">Kayıt tamamlandıktan sonra parçayı depolamaya aktar</string>
<string name="value_float_mile_hour">%1$.1f mi/s</string>
<string name="settings_announcements_totaltime_frequency">Zaman aralığı</string>
<string name="settings_announcements_distance_frequency">Mesafe aralığı</string>
<string name="settings_announcements_distance_frequency">Tur uzunluğu</string>
<string name="stats_sensors_heart_rate">Kalp atış hızı</string>
<string name="stats_clock">Saat</string>
<string name="stats_sensors_cadence">Kadans</string>
@@ -544,4 +544,60 @@ limitations under the License.
<string name="voice_per_nautical_mile">deniz mili başına</string>
<string name="settings_public_api_package_title">Genel API için paket adı</string>
<string name="show_on_dashboard_not_installed">OpenTracks Gösterge Paneli API\'sini destekleyen hiçbir uygulama yüklü değil.</string>
<string name="settings_sensor_bluetooth_service_filter_title">Bluetooth Cihazlarını Filtrele</string>
<string name="settings_stats_units_imperial_meter">İngiliz Ölçü Birimi (mi, m)</string>
<string name="settings_api_summary">Diğer uygulamalar için uzaktan kontrol ve istatistik Uygulama Programlama Arayüzü (API)</string>
<string name="generic_from">\'den beri</string>
<string name="image_discard">Çıkar</string>
<string name="import_thread_interrupted">İzlek kesintiye uğradı</string>
<string name="settings_theme_switch_restart">Tema değişiklikleri elle yeniden başlatmayı gerektirebilir.</string>
<string name="settings_ui_dynamic_colors_summary">Kullanıcı tanımlı Dinamik Renkleri kullan (sadece Android 12+; elle yeniden başlatmayı gerektirir)</string>
<string name="settings_night_mode_option_night_oled">Gece (OLED dostu)</string>
<string name="settings_announcements_lap_heart_rate">Tur kalp atışı</string>
<string name="settings_announcements_unit">Mesafe/hız birimi</string>
<string name="settings_sensor_heart_rate_max">Alanlar: en yüksek Kalp atış hızı (atım/dk)</string>
<string name="track_discarding">Çıkarılan Parçalar</string>
<string name="unit_knots">kn</string>
<string name="unit_nautical_mile">NM</string>
<string name="value_internal_sensor">Dâhilî duyarga (sensör)</string>
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
<string name="voiceSpeedKilometersPerHourPlural">{n, plural, =1 {1 kilometre/saat} other {{n,number,#.0} kilometre/saat} }</string>
<string name="voiceSpeedMilesPerHourPlural">{n, plural, =1 {1 mil/saat} other {{n,number,#.0} mil/saat} }</string>
<string name="voice_on_device_speaker_title">Cihazın hoparlörünü kullan</string>
<string name="voiceSpeedMKnotsPlural">{n, plural, =1 {1 not} other {{n,number,#.0} not} }</string>
<string name="voiceDistanceKilometersPlural">{n, plural, =1 {1 kilometre} other {{n,number,#.0} kilometre} }</string>
<string name="voiceDistanceMilesPlural">{n, plural, =1 {1 mil} other {{n,number,#.0} mil} }</string>
<string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 deniz mili} other {{n,number,#.0} deniz mili} }</string>
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
<string name="introduction_osm_dashboard">OpenTracks uygulaması harita işlevselliği sunmaz. Kayıtlarınızı bir haritada görüntülemek için lütfen OSMDashboard benzeri bir uygulama yükleyiniz.</string>
<string name="settings_recording_min_sampling_interval_title">Örnekleme zaman aralığı</string>
<string name="settings_recording_location_distance_summary">kayıtlı konumlar arasındaki mesafe: %1$s</string>
<string name="no_compatible_camera_installed">Uyumlu kamera uygulaması yüklü değil</string>
<string name="settings_sensor_bluetooth_service_filter_off">Tüm keşfedilebilir Bluetooth cihazlarını göster</string>
<string name="sensor_state_cadence_unit">devir/dk</string>
<string name="sensor_state_heart_rate_unit">atım/dk</string>
<string name="voiceHoursPlural">{n, plural, =1 {1 saat} other {{n,number} saat} }</string>
<string name="no_compatible_gallery_installed">Uyumlu galeri uygulaması yüklü değil</string>
<string name="permission_recording_failed">OpenTracks, konumunuzun yanı sıra yakındaki cihazlara erişmek için de izin gerektirir.</string>
<string name="generic_to">\'e</string>
<string name="settings_recording_min_required_accuracy_summary">Eğer hassasiyet oranı %1$s\'dan büyükse Konum göz ardı edilir</string>
<string name="settings_recording_idle_timeout_title">Boşta Durma Eşiği</string>
<string name="value_int_seconds">%1$d s (tavsiye edilen)</string>
<string name="sensor_state_power_max">En Yüksek Güç</string>
<string name="sensor_state_power_unit">W</string>
<string name="settings_announcements_idle_category_title">Boşta Durma Bildirimleri</string>
<string name="settings_announcements_idle_title">Boşta Durma</string>
<string name="settings_recording_title">Kaydediliyor</string>
<string name="settings_ui_dynamic_colors_title">Dinamik Renkler</string>
<string name="voiceMinutesPlural">{n, plural, =1 {1 dakika} other {{n,number} dakika} }</string>
<string name="voiceSecondsPlural">{n, plural, =1 {1 saniye} other {{n,number} saniye} }</string>
<string name="voiceIdle">Boşta durma hâline geçiliyor.</string>
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
<string name="sensor_state_pressure">Basınçölçer</string>
<string name="menu_export_all_one_file">Tümünü dışa aktar (tüm parçaları tek dosyada topla)</string>
<string name="track_delete_number_of_tracks">%1$d parçalar</string>
<string name="settings_announcements_lap_power">Tur gücü</string>
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
<string name="power_x_watt">Power {x, plural, =1 {1 vat(watt)} other {# vat(watt)} }</string>
<string name="power">Güç</string>
</resources>
-1
View File
@@ -201,7 +201,6 @@ limitations under the License.
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">顯示錯誤</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">已完成,但有一個錯誤</item>
<item quantity="other">已完成,但有 %1$d 個錯誤</item>
</plurals>
<string name="generic_completed">完成</string>
+1
View File
@@ -258,6 +258,7 @@ limitations under the License.
<string name="import_progress_summary_exists_msg">Already exists</string>
<string name="import_progress_summary_errors_msg">Errors</string>
<!-- Marker -->
<string name="create_marker_error">Unable to create a marker. Try again.</string>
<string name="marker_add_error">Unable to insert a marker. No GPS signal. Try again.</string>
<string name="marker_add_success">A marker was inserted</string>
<string name="marker_add_canceled">Canceled, no marker added</string>