Compare commits

..

52 Commits

Author SHA1 Message Date
Dennis Guse 5594a125d0 Release: v3.20.0 2021-08-22 22:08:23 +02:00
Román Martínez 13380afef0 TrackRecordingManager only updates track's statistics every time a new track point is inserted. Fixes #916. 2021-08-22 20:01:38 +02:00
Román Martínez 9c4c8131f5 Recording stats layout customizable.
Users can select the order, visibility and importance (primary or secondary) of all stats for recording layout from the Settings.
Also, users can select the number of columns per row in the recording layout: 1, 2 or 3.
Finally, show altitude/coordinate from settings have been removed (not needed anymore).
Fixes #659.
Fixes #302.
Fixes #820.
2021-08-21 19:40:31 +02:00
dependabot[bot] 7f10ae5c3f Bump mockito-android from 3.10.0 to 3.12.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 3.10.0 to 3.12.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v3.10.0...v3.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-20 07:37:15 +02:00
dependabot[bot] 1fa45a3393 Bump gradle from 7.0.0 to 7.0.1
Bumps gradle from 7.0.0 to 7.0.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>
2021-08-19 10:11:22 +02:00
Dennis Guse f1b0c83196 Merge branch 'translations' 2021-08-16 22:17:29 +02:00
Dennis Guse f0f7bca54b Translations: fix fr. 2021-08-16 22:16:58 +02:00
mondstern 8f39bf139c Translated using Weblate (Slovenian)
Currently translated at 71.5% (292 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sl/
2021-08-16 22:15:27 +02:00
mondstern 85585969bd Translated using Weblate (Russian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ru/
2021-08-16 22:15:27 +02:00
mondstern 976e7be370 Translated using Weblate (French)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-08-16 22:15:27 +02:00
Jaroslav Lichtblau 48dc64d4d7 Translated using Weblate (Czech)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-08-16 22:15:27 +02:00
Pablo 16e69e850b Translated using Weblate (Spanish (Latin America))
Currently translated at 98.2% (401 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es_419/
2021-08-16 22:15:27 +02:00
FTno 5ece301ac0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.5% (70 of 71 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/nb_NO/
2021-08-16 22:15:27 +02:00
☆Verdulo a5e500cdb1 Translated using Weblate (Esperanto)
Currently translated at 97.1% (69 of 71 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/eo/
2021-08-16 22:15:27 +02:00
Román Martínez 8433ed82fe Release: v3.19.1 2021-08-15 20:19:08 +02:00
Dennis Guse 7c7defa233 Merge pull request #912 from OpenTracksApp/raceCondition
Bugfix: LocationHandler should ignore locations after stopping.
2021-08-15 15:53:30 +02:00
Dennis Guse 1f84e44a8d Bugfix: LocationHandler should ignore locations after stopping. 2021-08-15 14:30:57 +02:00
Dennis Guse 858a8f20a8 Merge pull request #910 from OpenTracksApp/translations
Translations
2021-08-14 20:36:20 +02:00
Dennis Guse 6761c41dbf Translations: removed invalid plurals. 2021-08-14 19:46:40 +02:00
FTno 59e55be214 Translated using Weblate (Norwegian Bokmål)
Currently translated at 98.5% (70 of 71 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/nb_NO/
2021-08-09 20:06:09 +02:00
☆Verdulo 1b68cba1ba Translated using Weblate (Esperanto)
Currently translated at 97.1% (69 of 71 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/app-store-metadata-files/eo/
2021-08-09 20:06:08 +02:00
mondstern 32f47340a9 Translated using Weblate (Latvian)
Currently translated at 99.0% (404 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/lv/
2021-08-09 20:06:07 +02:00
mondstern e25814c674 Translated using Weblate (Lithuanian)
Currently translated at 98.2% (401 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/lt/
2021-08-09 20:06:05 +02:00
FTno 522baa5193 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-08-09 11:54:21 +02:00
mondstern b597b56b84 Translated using Weblate (Romanian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ro/
2021-08-07 19:37:23 +02:00
mondstern d956e8d3f0 Translated using Weblate (Romanian)
Currently translated at 99.0% (404 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ro/
2021-08-07 19:37:23 +02:00
mondstern 274781ccfa Translated using Weblate (Romanian)
Currently translated at 83.3% (340 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ro/
2021-08-07 19:37:23 +02:00
mondstern e7e3ef23ff Translated using Weblate (Lithuanian)
Currently translated at 97.5% (398 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/lt/
2021-08-07 19:37:23 +02:00
Quang Trung 3289fa27e4 Translated using Weblate (Vietnamese)
Currently translated at 88.7% (362 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/vi/
2021-08-07 19:37:23 +02:00
mondstern 96fc074a4b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/uk/
2021-08-07 19:37:23 +02:00
mondstern eaab09747c Translated using Weblate (Swedish)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/sv/
2021-08-07 19:37:23 +02:00
mondstern bdb2a9dbc6 Translated using Weblate (Russian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/ru/
2021-08-07 19:37:23 +02:00
mondstern 7e26571124 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pt_BR/
2021-08-07 19:37:23 +02:00
mondstern 33b23a6e8f Translated using Weblate (Polish)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/pl/
2021-08-07 19:37:23 +02:00
mondstern 584adc7c57 Translated using Weblate (Dutch)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nl/
2021-08-07 19:37:23 +02:00
mondstern 667da76c4f Translated using Weblate (Italian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/it/
2021-08-07 19:37:23 +02:00
mondstern df6a3a7f04 Translated using Weblate (Hungarian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/hu/
2021-08-07 19:37:23 +02:00
mondstern b254f5e40b Translated using Weblate (Galician)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/gl/
2021-08-07 19:37:23 +02:00
mondstern 7a9c3efb3b Translated using Weblate (French)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fr/
2021-08-07 19:37:23 +02:00
mondstern 53dd7ada1f Translated using Weblate (Finnish)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/fi/
2021-08-07 19:37:23 +02:00
mondstern 6a5edcbdef Translated using Weblate (Basque)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/eu/
2021-08-07 19:37:23 +02:00
mondstern 6e954261bb Translated using Weblate (Estonian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/et/
2021-08-07 19:37:23 +02:00
mondstern 277061ad27 Translated using Weblate (Spanish)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/es/
2021-08-07 19:37:23 +02:00
mondstern c03f9fc792 Translated using Weblate (Danish)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/da/
2021-08-07 19:37:23 +02:00
mondstern 657b82b57a Translated using Weblate (Czech)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/cs/
2021-08-07 19:37:23 +02:00
mondstern 4608edec56 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/bg/
2021-08-07 19:37:23 +02:00
FTno b086c30696 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-08-07 19:37:23 +02:00
mondstern 7eca033a8e Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (408 of 408 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://weblate.bubu1.eu/projects/OpenTracks/opentracks/nb_NO/
2021-08-07 19:37:23 +02:00
Dennis Guse 6f3efd9ff2 Bugfix: reset TrackRecordingManager properly.
Fixes #901.
2021-08-07 19:37:14 +02:00
A Rabbit 7a2741e508 Fix: README 2021-08-06 16:42:22 +02:00
Román Martínez 017eee8b68 Bugfix: generated fields for include and merge tags. Fixes #902. 2021-08-05 18:01:34 +02:00
Dennis Guse 024d7f8652 PlayStore: describe metadata upload. 2021-08-04 17:11:29 +02:00
119 changed files with 3102 additions and 1226 deletions
+8 -8
View File
@@ -51,16 +51,16 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
## Screenshots
<div>
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot1.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot2.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot3.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot4.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot1.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot2.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot3.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot4.png">
</div>
<div>
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot5.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot6.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot7.png">
<img width="23%" src="fastlane/metadata/android/en-US/phoneScreenshots/screenshot8.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot5.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot6.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot7.png">
<img width="23%" src="fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot8.png">
</div>
## Features
+4 -3
View File
@@ -28,9 +28,10 @@ Requirement: `gem install fastlane`
(requires keystore)
2. Upload signed apk to [Google Play Console](https://play.google.com/apps/publish)
3. Update store meta data (e.g., description and screenshots)
`fastlane supply --version_code VERSION_CODE --skip_upload_apk`
(use the version code from the manifest)
(requires API key)
`fastlane metadata`
(requires API key configured in `Appfile`)
Supported languages: https://support.google.com/googleplay/android-developer/table/4419860
## Translations (localization)
+4 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.0'
classpath 'com.android.tools.build:gradle:7.0.1'
}
}
@@ -50,8 +50,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 4358
versionName "v3.19.0"
versionCode 4410
versionName "v3.20.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -98,7 +98,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.mockito:mockito-android:3.10.0'
androidTestImplementation 'org.mockito:mockito-android:3.12.0'
androidTestUtil 'androidx.test:orchestrator:1.4.0'
}
+21 -2
View File
@@ -43,7 +43,6 @@ class TrackPoint {
- sensor_power
}
class Marker {
- id
- trackId
@@ -58,6 +57,26 @@ class Marker {
- photoUrl
}
class DataField {
- key
- title
- isVisible
- isPrimary
- isWide
}
class StatisticData {
- value
- unit
- description
}
StatisticData o-- DataField
class Layout {
- profile
}
Layout "1" o-- "many" DataField
note left of TrackPoint
As of OpenTracks version 3.15.0, all times are using device time.
Before that TrackPoint.time contained GPS time (determined by GPS hardware).
@@ -77,4 +96,4 @@ Trackpoints of Type.SEGMENT_START_MANUAL or Type.SEGMENT_END_MANUAL do not conta
Tracks recorded prior to OpenTracks version 3.15.0, do neither begin with a Type.SEGMENT_START_MANUAL nor end with a Type.SEGMENT_END_MANUAL.
end note
@enduml
@enduml
+3
View File
@@ -0,0 +1,3 @@
README.md
Appfile
report.xml
+6 -18
View File
@@ -16,23 +16,11 @@
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
# sh "your_script.sh"
# You can also use other beta testing services here
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
desc "Upload metadata"
lane :metadata do
upload_to_play_store(
skip_upload_apk: true,
skip_upload_changelogs: true
)
end
end
@@ -0,0 +1,8 @@
v3.19.1: OpenTracks
Changes:
- Updated translations.
Bugfix:
- Export All Tracks bug fixed.
- Resume Track bug fixed.
@@ -0,0 +1,9 @@
v3.20.0: OpenTracks
Changes:
- While recording: statistics UI is now configurable
Bugfix:
- Changing track name/ category while recording was ignored (introduced in v3.19.0)

Before

Width:  |  Height:  |  Size: 303 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Before

Width:  |  Height:  |  Size: 574 KiB

After

Width:  |  Height:  |  Size: 574 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 456 KiB

Before

Width:  |  Height:  |  Size: 630 KiB

After

Width:  |  Height:  |  Size: 630 KiB

Before

Width:  |  Height:  |  Size: 416 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Before

Width:  |  Height:  |  Size: 508 KiB

After

Width:  |  Height:  |  Size: 508 KiB

Before

Width:  |  Height:  |  Size: 798 KiB

After

Width:  |  Height:  |  Size: 798 KiB

@@ -4,8 +4,8 @@ Eblaĵoj:
• registri vian sportan kaj alian aktivecon eksterdome
• fotoj kaj markoj: marki interesajn lokojn dum registri aktivecon
• registri ŝanĝon de altitudo uzante barometron
• montri altitudon en EGM2008 ()super averaĝa marnivelo); elporti ilin kiel WGS84
• montri altitudon en EGM2008 (super averaĝa marnivelo); elporti ilin kiel WGS84
• voĉaj anoncoj
• subteno por sentiloj BLE por registri vian pulson; rapidon, veturigitan distancon, pedal-rapidon, povumon (biciklado)
• subteno por sentiloj BLE por registri vian pulson, rapidon, veturigitan distancon, pedal-rapidon, povumon (biciklado)
• elporti datumojn al dosieroj KMZ (kun fotoj), KML aŭ GPX
• funkcias malkonekte: interreta permeso ne estas uzata
@@ -1,9 +1,11 @@
OpenTracks er en sportsporingsapp som respekterer ditt personvern.
Funksjoner:
* Sportssoring: Spor dine sports- og utendørsaktiviteter
* Sportsporing: Registrer dine sports- og utendørsaktiviteter
* Bilder og markører: Marker interessante steder under sporing
* Høydeendringer: via barometer
* Total stigning og nedstigning: via barometersensor
* Høyde vist i EGM2008 (over havnivå); eksportert som WGS84
* Stemmekunngjøringer
* Støtter Blåtann LE-sensorer: pulse, hastighet og distanse (sykling), takt (sykling), og effektmeter (sykling)
* Eksport av data: Enten som KMZ (inkl. bilder), KML, eller GPX
* Ingen tilgang til Internett: Ingenting lastes opp noensteds hen
* Talemeldinger
* Støtter Blåtann LE-sensorer: puls, hastighet og distanse (sykling), kadens (sykling) og wattmåler (sykling)
* Eksport av data: Enten som KMZ (inkl. bilder), KML eller GPX
* Ingen internettilgang: Internett brukes ikke
@@ -1,191 +0,0 @@
package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.is;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.test.espresso.DataInteraction;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/**
* Espresso test that checks the unit for activity category selected.
* Also it checks that unit is properly when user change from a pace activity to an speed one.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoCategoryUnitTest {
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public TestRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
private final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
@Rule
public TestRule mLocaleRule = new PreferenceMetricUnitRule(context, true);
@Test
public void changeBetweenCategoryUpdateProperlyUnitTest() {
// Start record.
ViewInteraction appCompatImageButton = onView(
allOf(withId(R.id.controller_record), withContentDescription("Record"),
childAtPosition(
allOf(withId(R.id.controller_container),
childAtPosition(
withId(R.id.controller_fragment),
0)),
0),
isDisplayed()));
appCompatImageButton.perform(click());
{
// Change to running activity (pace activity).
ViewInteraction appCompatImageView = onView(
allOf(withId(R.id.stats_activity_type_icon),
childAtPosition(
childAtPosition(
withClassName(is("android.widget.ScrollView")),
0),
7)));
appCompatImageView.perform(scrollTo(), click());
DataInteraction imageView = onData(anything())
.inAdapterView(allOf(withId(R.id.choose_activity_type_grid_view),
childAtPosition(
withClassName(is("android.widget.LinearLayout")),
0)))
.atPosition(1);
imageView.perform(click());
}
// Pace (min/km) in all fields.
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_speed_unit)));
textView.perform(waitFor(2000));
textView.check(matches(withText("min/km")));
}
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_average_speed_unit)));
textView.check(matches(withText("min/km")));
}
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_max_speed_unit)));
textView.check(matches(withText("min/km")));
}
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_moving_speed_unit)));
textView.check(matches(withText("min/km")));
}
{
// Change to biking activity (speed activity).
ViewInteraction appCompatImageView = onView(
allOf(withId(R.id.stats_activity_type_icon),
childAtPosition(
childAtPosition(
withClassName(is("android.widget.ScrollView")),
0),
7)));
appCompatImageView.perform(scrollTo(), click());
DataInteraction imageView = onData(anything())
.inAdapterView(allOf(withId(R.id.choose_activity_type_grid_view),
childAtPosition(
withClassName(is("android.widget.LinearLayout")),
0)))
.atPosition(9);
imageView.perform(click());
}
// Speed (km/h) in all fields.
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_speed_unit)));
textView.perform(waitFor(2000));
textView.check(matches(withText("km/h")));
}
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_average_speed_unit)));
textView.check(matches(withText("km/h")));
}
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_max_speed_unit)));
textView.check(matches(withText("km/h")));
}
{
ViewInteraction textView = onView(allOf(withId(R.id.stats_moving_speed_unit)));
textView.check(matches(withText("km/h")));
}
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
private static ViewAction waitFor(final long duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Wait for milliseconds.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
uiController.loopMainThreadForAtLeast(duration_ms);
}
};
}
}
@@ -0,0 +1,138 @@
package de.dennisguse.opentracks;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.runner.AndroidJUnit4;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.*;
import static androidx.test.espresso.matcher.ViewMatchers.*;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.util.PreferencesUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.CoreMatchers.is;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoCustomLayoutTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Rule
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.ACCESS_FINE_LOCATION");
@Test
public void customLayoutTest() {
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
trackControllerRecordButton.perform(click());
// Get custom layout preferences and check all data fields are showed.
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
// stop recording
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
trackControllerStopButton.perform(veryLongTouch(1600));
}
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
private final int expectedCount;
public RecyclerViewItemCountAssertion(int expectedCount) {
this.expectedCount = expectedCount;
}
@Override
public void check(View view, NoMatchingViewException noViewFoundException) {
if (noViewFoundException != null) {
throw noViewFoundException;
}
RecyclerView recyclerView = (RecyclerView) view;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
assertThat(adapter.getItemCount(), is(expectedCount));
}
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
private static ViewAction veryLongTouch(final int duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Perform long touch.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
// Get view absolute position
int[] location = new int[2];
view.getLocationOnScreen(location);
// Offset coordinates by view position
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
// Send down event, pause, and send up
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
uiController.loopMainThreadForAtLeast(duration_ms);
MotionEvents.sendUp(uiController, down, coordinates);
}
};
}
}
@@ -0,0 +1,261 @@
package de.dennisguse.opentracks;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.MotionEvents;
import androidx.test.espresso.util.HumanReadables;
import androidx.test.espresso.util.TreeIterables;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.hamcrest.core.IsInstanceOf;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.TimeoutException;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoEditTrackRecordingTest {
@Rule
public ActivityTestRule<TrackListActivity> mActivityTestRule = new ActivityTestRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION");
@LargeTest
@Test
public void espressoEditTrackRecordingTest() {
{
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
trackControllerRecordButton.perform(click());
}
{
// TrackRecordingActivity
ViewInteraction trackControllerRecordButton = onView(withId(R.id.controller_record));
ViewInteraction trackControllerStopButton = onView(withId(R.id.controller_stop));
// wait; stay recording
trackControllerRecordButton.perform(waitFor(15000));
// open menu
ViewInteraction overflowMenuButton = onView(
allOf(withContentDescription("More options"),
childAtPosition(
childAtPosition(
withId(R.id.toolbar),
2),
2),
isDisplayed()));
overflowMenuButton.perform(click());
// click on edit
ViewInteraction appCompatTextView = onView(
allOf(withId(R.id.title), withText("Edit"),
childAtPosition(
childAtPosition(
withId(R.id.content),
0),
0),
isDisplayed()));
appCompatTextView.perform(click());
// change name for "New Name"
ViewInteraction textInputEditText = onView(
allOf(withId(R.id.track_edit_name),
childAtPosition(
childAtPosition(
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
0),
1)));
textInputEditText.perform(scrollTo(), replaceText("New Name"));
ViewInteraction textInputEditText2 = onView(
allOf(withId(R.id.track_edit_name), withText("New Name"),
childAtPosition(
childAtPosition(
withClassName(is("com.google.android.material.textfield.TextInputLayout")),
0),
1),
isDisplayed()));
textInputEditText2.perform(closeSoftKeyboard());
// save edition
ViewInteraction appCompatButton = onView(
allOf(withId(R.id.track_edit_save),
childAtPosition(
childAtPosition(
withClassName(is("android.widget.LinearLayout")),
3),
1),
isDisplayed()));
appCompatButton.perform(click());
//pause
trackControllerRecordButton.perform(veryLongTouch(1600));
// wait; stay paused
trackControllerRecordButton.perform(waitFor(1000));
// resume
trackControllerRecordButton.perform(click());
// wait; stay recording
trackControllerRecordButton.perform(waitFor(1000));
// stop;
trackControllerStopButton.perform(veryLongTouch(1600));
// check track's name is "New Name"
ViewInteraction textView = onView(
allOf(withId(R.id.stats_name_value),
withParent(withParent(IsInstanceOf.<View>instanceOf(android.widget.ScrollView.class))),
isDisplayed()));
textView.check(matches(withText("New Name")));
}
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
public static ViewAction waitId(final int viewId, final long millis) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
}
@Override
public void perform(final UiController uiController, final View view) {
uiController.loopMainThreadUntilIdle();
final long startTime = System.currentTimeMillis();
final long endTime = startTime + millis;
final Matcher<View> viewMatcher = withId(viewId);
do {
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
// found view with required ID
if (viewMatcher.matches(child)) {
return;
}
}
uiController.loopMainThreadForAtLeast(50);
}
while (System.currentTimeMillis() < endTime);
// timeout happens
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new TimeoutException())
.build();
}
};
}
private static ViewAction waitFor(final long duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Wait for milliseconds.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
uiController.loopMainThreadForAtLeast(duration_ms);
}
};
}
private static ViewAction veryLongTouch(final int duration_ms) {
return new ViewAction() {
@Override
public String getDescription() {
return "Perform long touch.";
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, final View view) {
// Get view absolute position
int[] location = new int[2];
view.getLocationOnScreen(location);
// Offset coordinates by view position
float[] coordinates = new float[]{location[0] + 1, location[1] + 1};
// Send down event, pause, and send up
MotionEvent down = MotionEvents.sendDown(uiController, coordinates, new float[]{1f, 1f}).down;
uiController.loopMainThreadForAtLeast(duration_ms);
MotionEvents.sendUp(uiController, down, coordinates);
}
};
}
}
@@ -10,9 +10,14 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class PreferencesUtilsTest {
@@ -64,4 +69,131 @@ public class PreferencesUtilsTest {
// then
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
}
}
@Test
public void testGetCustomLayout_default() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
// then
assertTrue(layout.getFields().size() > 0);
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
}
@Test
public void testGetCustomLayout_1() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
"run;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getProfile(), "run");
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
assertTrue(layout.getFields().get(0).isPrimary());
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(layout.getFields().get(1).isVisible());
assertFalse(layout.getFields().get(1).isPrimary());
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
assertFalse(layout.getFields().get(2).isVisible());
assertTrue(layout.getFields().get(2).isPrimary());
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(layout.getFields().get(3).isVisible());
assertFalse(layout.getFields().get(3).isPrimary());
}
@Test
public void testGetCustomLayout_coordinatesIsWide() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(
context.getString(R.string.stats_custom_layout_fields_key),
"walking;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout(sharedPreferences, context);
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getProfile(), "walking");
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(layout.getFields().get(0).isVisible());
assertTrue(layout.getFields().get(0).isPrimary());
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(layout.getFields().get(1).isVisible());
assertFalse(layout.getFields().get(1).isPrimary());
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
assertFalse(layout.getFields().get(2).isVisible());
assertTrue(layout.getFields().get(2).isPrimary());
assertTrue(layout.getFields().get(2).isWide());
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(layout.getFields().get(3).isVisible());
assertFalse(layout.getFields().get(3).isPrimary());
}
@Test
public void testSetCustomLayout() {
// given
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
Layout layoutSrc = new Layout("road cycling");
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
// when
PreferencesUtils.setCustomLayout(sharedPreferences, context, layoutSrc);
// then
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
assertNotNull(csv);
assertEquals(csv,
"road cycling;"
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
Layout layoutDst = PreferencesUtils.getCustomLayout(sharedPreferences, context);
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
}
}
}
@@ -0,0 +1,68 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
@RunWith(AndroidJUnit4.class)
public class StatisticsUtilsTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
@Test
public void testEmptyValue() {
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_distance_key)));
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_total_time_key)));
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_speed_key)));
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_moving_time_key)));
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_speed_key)));
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_max_speed_key)));
assertEquals(context.getString(R.string.stats_empty_value_float), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_moving_speed_key)));
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_altitude_key)));
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_gain_key)));
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_loss_key)));
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_pace_key)));
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_moving_pace_key)));
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_average_pace_key)));
assertEquals(context.getString(R.string.stats_empty_value_time), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_fastest_pace_key)));
assertEquals(context.getString(R.string.stats_empty_value_coordinates), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_coordinates_key)));
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_heart_rate_key)));
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_cadence_key)));
assertEquals(context.getString(R.string.stats_empty_value_integer), StatisticsUtils.emptyValue(context, context.getString(R.string.stats_custom_layout_power_key)));
}
@Test
public void testFilterVisible() {
// given
Layout layout = new Layout("profile");
layout.addField(new DataField("key1", "title1", true, true, false));
layout.addField(new DataField("key2", "title2", false, true, false));
layout.addField(new DataField("key3", "title3", true, true, false));
layout.addField(new DataField("key4", "title4", false, true, false));
layout.addField(new DataField("key5", "title5", true, true, false));
// when
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
// then
assertEquals(resultTrue.getFields().size(), 3);
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key1")));
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key3")));
assertTrue(resultTrue.getFields().stream().anyMatch(f -> f.getKey().equals("key5")));
assertEquals(resultFalse.getFields().size(), 2);
assertTrue(resultFalse.getFields().stream().anyMatch(f -> f.getKey().equals("key2")));
assertTrue(resultFalse.getFields().stream().anyMatch(f -> f.getKey().equals("key4")));
}
}
@@ -0,0 +1,195 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class StatisticDataBuilderTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private Track trackMock;
@Mock
private TrackStatistics trackStatisticsMock;
@Mock
private TrackRecordingService.RecordingData recordingDataMock;
@Mock
private SensorStatistics sensorStatisticsMock;
@Test
public void testFromRecordingData() {
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
// given
Layout layout = new Layout(context.getString(R.string.default_activity_default));
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
// then
assertEquals(statisticDataList.size(), 3);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
}
@Test
public void testFromRecordingData_allFields() {
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
// given
Layout layout = new Layout(context.getString(R.string.default_activity_default));
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false);
layout.addField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, true);
// then
assertEquals(statisticDataList.size(), 18);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_max_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_fastest_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_altitude))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_gain))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_loss))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_coordinates))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))));
}
@Test
public void testFromSensorStatistics_onlyHeartRate() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
when(sensorStatisticsMock.hasPower()).thenReturn(false);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
// then
assertEquals(statisticDataList.size(), 2);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
}
@Test
public void testFromSensorStatistics_onlyCadence() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
when(sensorStatisticsMock.hasPower()).thenReturn(false);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
// then
assertEquals(statisticDataList.size(), 2);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
}
@Test
public void testFromSensorStatistics_onlyPower() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
// then
assertEquals(statisticDataList.size(), 1);
}
@Test
public void testFromSensorStatistics() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(200f);
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(150f);
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(90f);
when(sensorStatisticsMock.getMaxCadence()).thenReturn(110f);
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(300f);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
// then
assertEquals(statisticDataList.size(), 5);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
}
}
+1
View File
@@ -316,6 +316,7 @@ limitations under the License.
<activity android:name=".io.file.exporter.ExportActivity" />
<activity android:name=".settings.SettingsActivity" />
<activity android:name=".settings.SettingsCustomLayoutActivity" />
<activity
android:name=".ShowOnMapProxyActivity$KML"
@@ -0,0 +1,16 @@
package de.dennisguse.opentracks.adapters;
enum CustomLayoutFieldType {
SHORT(0),
WIDE(1);
private final int value;
CustomLayoutFieldType(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
@@ -1,91 +0,0 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
public class SensorsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<SensorDataModel> sensorDataList;
private final Context context;
public SensorsAdapter(Context context) {
this.context = context;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.sensor_item, parent, false);
return new SensorsAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
SensorsAdapter.ViewHolder viewHolder = (SensorsAdapter.ViewHolder) holder;
SensorDataModel sensorDataModel = sensorDataList.get(position);
viewHolder.setData(sensorDataModel);
}
@Override
public int getItemCount() {
if (sensorDataList == null) {
return 0;
} else {
return sensorDataList.size();
}
}
public List<SensorDataModel> swapData(List<SensorDataModel> data) {
if (sensorDataList == data) {
return null;
}
sensorDataList = data;
if (data != null) {
this.notifyDataSetChanged();
}
return data;
}
private class ViewHolder extends RecyclerView.ViewHolder {
private final TextView label;
private final TextView sensorValue;
private final TextView value;
private final TextView unit;
public ViewHolder(@NonNull View itemView) {
super(itemView);
label = itemView.findViewById(R.id.stats_sensor_label);
sensorValue = itemView.findViewById(R.id.stats_sensor_sensor_value);
value = itemView.findViewById(R.id.stats_sensor_value);
unit = itemView.findViewById(R.id.stats_sensor_unit);
}
public void setData(SensorDataModel sensorDataModel) {
String sensorName = sensorDataModel.getSensorName();
String sensorValue = sensorDataModel.hasValue() ? sensorDataModel.getSensorValue() : context.getString(R.string.value_unknown);
this.label.setText(context.getString(sensorDataModel.getLabelId()));
if (sensorName == null) {
this.sensorValue.setVisibility(View.GONE);
} else {
this.sensorValue.setText(sensorName);
}
this.value.setText(sensorValue);
this.unit.setText(context.getString(sensorDataModel.getUnitId()));
}
}
}
@@ -0,0 +1,118 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Optional;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.util.StatisticsUtils;
public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Layout layout;
private final Context context;
private final SettingsCustomLayoutItemClickListener itemClickListener;
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
this.context = context;
this.itemClickListener = itemClickListener;
this.layout = layout;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
return new SettingsCustomLayoutAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
SettingsCustomLayoutAdapter.ViewHolder viewHolder = (SettingsCustomLayoutAdapter.ViewHolder) holder;
DataField field = layout.getFields().get(position);
viewHolder.itemView.setTag(field.getKey());
viewHolder.title.setText(field.getTitle());
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel) : R.style.StatsLabelHidden);
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue) : R.style.StatsValueHidden);
viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
}
@Override
public int getItemCount() {
if (layout == null) {
return 0;
} else {
return layout.getFields().size();
}
}
@Override
public int getItemViewType(int position) {
return layout.getFields().get(position).isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
}
public boolean isItemWide(int position) {
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
}
public DataField getItem(int position) {
return layout.getFields().get(position);
}
public void swapValues(Layout data) {
this.layout = data;
if (this.layout != null) {
this.notifyDataSetChanged();
}
}
public Layout move(int fromPosition, int toPosition) {
layout.moveField(fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
return layout;
}
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView title;
final TextView value;
final TextView unit;
final ImageView statusIcon;
final ImageView moveIcon;
public ViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.stats_description_main);
value = itemView.findViewById(R.id.stats_value);
unit = itemView.findViewById(R.id.stats_unit);
statusIcon = itemView.findViewById(R.id.stats_icon_show_status);
moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
String statTitle = (String) view.getTag();
Optional<DataField> optionalField = layout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
}
}
public interface SettingsCustomLayoutItemClickListener {
void onSettingsCustomLayoutItemClicked(@NonNull DataField field);
}
}
@@ -0,0 +1,140 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.viewmodels.StatisticData;
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<StatisticData> statisticDataList;
private final Context context;
public StatisticsAdapter(Context context) {
this.context = context;
this.statisticDataList = new ArrayList<>();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_item, parent, false);
return new StatisticsAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
StatisticData statisticData = statisticDataList.get(position);
viewHolder.setData(statisticData);
}
@Override
public int getItemCount() {
if (statisticDataList == null) {
return 0;
} else {
return statisticDataList.size();
}
}
@Override
public int getItemViewType(int position) {
return statisticDataList.get(position).getField().isWide() ? CustomLayoutFieldType.WIDE.value() : CustomLayoutFieldType.SHORT.value();
}
public boolean isItemWide(int position) {
return getItemViewType(position) == CustomLayoutFieldType.WIDE.value();
}
public List<StatisticData> swapData(List<StatisticData> data) {
if (statisticDataList == data) {
return null;
}
statisticDataList = data;
if (data != null) {
this.notifyDataSetChanged();
}
return data;
}
public static class WithRecordedLayout extends StatisticsAdapter {
public WithRecordedLayout(Context context) {
super(context);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_recorded_item, parent, false);
return new StatisticsAdapter.ViewRecordedHolder(view);
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
final TextView value;
final TextView unit;
final TextView descMain;
final TextView descSecondary;
public ViewHolder(@NonNull View itemView) {
super(itemView);
value = itemView.findViewById(R.id.stats_value);
unit = itemView.findViewById(R.id.stats_unit);
descMain = itemView.findViewById(R.id.stats_description_main);
descSecondary = itemView.findViewById(R.id.stats_description_secondary);
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryValue : R.style.StatsSecondaryValue);
this.unit.setText(statisticData.getUnit());
this.descMain.setText(statisticData.getField().getTitle());
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.StatsPrimaryLabel : R.style.StatsSecondaryLabel);
if (statisticData.hasDescription()) {
this.descSecondary.setVisibility(View.VISIBLE);
this.descSecondary.setText(statisticData.getDescription());
} else {
this.descSecondary.setVisibility(View.GONE);
}
}
}
private class ViewRecordedHolder extends ViewHolder {
public ViewRecordedHolder(@NonNull View itemView) {
super(itemView);
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
this.unit.setText(statisticData.getUnit());
this.descMain.setText(statisticData.getField().getTitle());
if (statisticData.hasDescription()) {
this.descSecondary.setVisibility(View.VISIBLE);
this.descSecondary.setText(statisticData.getDescription());
} else {
this.descSecondary.setVisibility(View.GONE);
}
}
}
}
@@ -0,0 +1,101 @@
package de.dennisguse.opentracks.content.data;
import android.content.Context;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.CsvConstants;
public class DataField {
public static final String YES_VALUE = "1";
public static final String NOT_VALUE = "0";
private final String key;
private final String title;
private boolean isVisible;
private boolean isPrimary;
private final boolean isWide;
public DataField(String key, String title, boolean isVisible, boolean isPrimary, boolean isWide) {
this.key = key;
this.title = title;
this.isVisible = isVisible;
this.isPrimary = isPrimary;
this.isWide = isWide;
}
public String getKey() {
return key;
}
public String getTitle() {
return title;
}
public boolean isVisible() {
return isVisible;
}
public void toggleVisibility() {
isVisible = !isVisible;
}
public boolean isPrimary() {
return isPrimary;
}
public void togglePrimary() {
isPrimary = !isPrimary;
}
public boolean isWide() {
return isWide;
}
public String toCsv() {
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
}
public static String getTitleByKey(Context context, String key) {
if (key.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
return context.getString(R.string.stats_total_time);
} else if (key.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
return context.getString(R.string.stats_moving_time);
} else if (key.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
return context.getString(R.string.stats_distance);
} else if (key.equals(context.getString(R.string.stats_custom_layout_speed_key))) {
return context.getString(R.string.stats_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
return context.getString(R.string.stats_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return context.getString(R.string.stats_average_moving_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
return context.getString(R.string.stats_average_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
return context.getString(R.string.stats_max_speed);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return context.getString(R.string.stats_average_moving_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
return context.getString(R.string.stats_average_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return context.getString(R.string.stats_fastest_pace);
} else if (key.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
return context.getString(R.string.stats_altitude);
} else if (key.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
return context.getString(R.string.stats_gain);
} else if (key.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
return context.getString(R.string.stats_loss);
} else if (key.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
return context.getString(R.string.stats_coordinates);
} else if (key.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
return context.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
return context.getString(R.string.stats_sensors_cadence);
} else if (key.equals(context.getString(R.string.stats_custom_layout_power_key))) {
return context.getString(R.string.stats_sensors_power);
} else {
throw new RuntimeException("It doesn't exists a field with key: " + key);
}
}
}
@@ -0,0 +1,42 @@
package de.dennisguse.opentracks.content.data;
import java.util.ArrayList;
import java.util.List;
public class Layout {
private final String profile;
private final List<DataField> dataFields = new ArrayList<>();
public Layout(String profile) {
this.profile = profile;
}
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
dataFields.add(new DataField(key, title, visible, primary, isWide));
}
public void addField(DataField dataField) {
dataFields.add(dataField);
}
public void addFields(List<DataField> dataFields) {
this.dataFields.addAll(dataFields);
}
public void removeField(DataField dataField) {
dataFields.remove(dataField);
}
public List<DataField> getFields() {
return new ArrayList<>(dataFields);
}
public void moveField(int from, int to) {
DataField dataFieldToMove = dataFields.remove(from);
dataFields.add(to, dataFieldToMove);
}
public String getProfile() {
return profile;
}
}
@@ -310,11 +310,11 @@ public class TrackPoint {
}
@Nullable
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
public Distance distanceToPrevious(TrackPoint previous) {
if (hasSensorDistance()) {
return getSensorDistance();
}
if (!(hasLocation() && previous.hasLocation())) {
if (previous == null || !(hasLocation() && previous.hasLocation())) {
return null;
}
@@ -290,6 +290,31 @@ public class ContentProviderUtils {
return values;
}
public void updateTrackStatistics(@NonNull Track.Id trackId, @NonNull TrackStatistics trackStatistics) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(trackStatistics), TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
}
private ContentValues createContentValues(TrackStatistics trackStatistics) {
ContentValues values = new ContentValues();
if (trackStatistics.getStartTime() != null) {
values.put(TracksColumns.STARTTIME, trackStatistics.getStartTime().toEpochMilli());
}
if (trackStatistics.getStopTime() != null) {
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
}
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
values.put(TracksColumns.ALTITUDE_LOSS, trackStatistics.getTotalAltitudeLoss());
return values;
}
public Marker createMarker(Cursor cursor) {
int idIndex = cursor.getColumnIndexOrThrow(MarkerColumns._ID);
int nameIndex = cursor.getColumnIndexOrThrow(MarkerColumns.NAME);
@@ -254,9 +254,9 @@ public class MarkerDetailFragment extends Fragment {
private void setLocation(boolean addShadow) {
String value = "[" + getString(R.string.stats_latitude) + " "
+ StringUtils.formatCoordinate(marker.getLatitude()) + ", "
+ StringUtils.formatCoordinate(getContext(), marker.getLatitude()) + ", "
+ getString(R.string.stats_longitude) + " "
+ StringUtils.formatCoordinate(marker.getLongitude()) + "]";
+ StringUtils.formatCoordinate(getContext(), marker.getLongitude()) + "]";
ListItemUtils.setTextView(getActivity(), viewBinding.markerDetailMarkerLocation, value, addShadow);
}
@@ -31,12 +31,11 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.adapters.SensorsAdapter;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.StatisticsRecordedBinding;
@@ -45,7 +44,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticDataBuilder;
/**
* A fragment to display track statistics to the user for a recorded {@link Track}.
@@ -69,7 +69,7 @@ public class StatisticsRecordedFragment extends Fragment {
}
private SensorStatistics sensorStatistics;
private SensorsAdapter sensorsAdapter;
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
private Track.Id trackId;
@Nullable // Lazily loaded.
@@ -114,7 +114,7 @@ public class StatisticsRecordedFragment extends Fragment {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
sensorsAdapter = new SensorsAdapter(getContext());
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
}
@Override
@@ -238,10 +238,6 @@ public class StatisticsRecordedFragment extends Fragment {
// Set altitude gain and loss
{
// Make altitude visible?
boolean show = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
Float altitudeGain_m = trackStatistics.getTotalAltitudeGain();
Float altitudeLoss_m = trackStatistics.getTotalAltitudeLoss();
@@ -254,6 +250,9 @@ public class StatisticsRecordedFragment extends Fragment {
parts = StringUtils.getAltitudeParts(getContext(), altitudeLoss_m, preferenceMetricUnits);
viewBinding.statsAltitudeLossValue.setText(parts.first);
viewBinding.statsAltitudeLossUnit.setText(parts.second);
boolean show = altitudeGain_m != null && altitudeLoss_m != null;
viewBinding.statsAltitudeGroup.setVisibility(show ? View.VISIBLE : View.GONE);
}
}
@@ -262,18 +261,7 @@ public class StatisticsRecordedFragment extends Fragment {
return;
}
List<SensorDataModel> sensorDataList = new ArrayList<>();
if (sensorStatistics.hasHeartRate()) {
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_max, R.string.sensor_unit_beats_per_minute, sensorStatistics.getMaxHeartRate()));
sensorDataList.add(new SensorDataModel(R.string.sensor_state_heart_rate_avg, R.string.sensor_unit_beats_per_minute, sensorStatistics.getAvgHeartRate()));
}
if (sensorStatistics.hasCadence()) {
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_max, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getMaxCadence()));
sensorDataList.add(new SensorDataModel(R.string.sensor_state_cadence_avg, R.string.sensor_unit_rounds_per_minute, sensorStatistics.getAvgCadence()));
}
if (sensorStatistics.hasPower()) {
sensorDataList.add(new SensorDataModel(R.string.sensor_state_power_avg, R.string.sensor_unit_power, sensorStatistics.getAvgPower()));
}
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
if (sensorDataList.size() > 0) {
sensorsAdapter.swapData(sensorDataList);
}
@@ -3,7 +3,6 @@ package de.dennisguse.opentracks.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -11,28 +10,25 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordingActivity;
import de.dennisguse.opentracks.adapters.SensorsAdapter;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.SensorDataModel;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
/**
* A fragment to display track statistics to the user for a currently recording {@link Track}.
@@ -51,15 +47,16 @@ public class StatisticsRecordingFragment extends Fragment {
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackRecordingService.RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
private TrackPoint latestTrackPoint;
private Layout layout;
private StatisticsRecordingBinding viewBinding;
private SensorsAdapter sensorsAdapter;
private StatisticsAdapter statisticsAdapter;
private GridLayoutManager gridLayoutManager;
private StatisticsDataModel viewModel;
private LiveData<List<StatisticData>> statisticsLiveData;
private SharedPreferences sharedPreferences;
private boolean preferenceMetricUnits;
private boolean preferenceReportSpeed;
private boolean preferenceShowAltitude;
private boolean preferenceShowCoordinate;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
@@ -69,19 +66,13 @@ public class StatisticsRecordingFragment extends Fragment {
preferenceMetricUnits = PreferencesUtils.isMetricUnits(sharedPreferences, getContext());
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key) && recordingData != null) {
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_fields_key, key)) {
updateUInecessary = true;
preferenceReportSpeed = PreferencesUtils.isReportSpeed(sharedPreferences, getContext(), recordingData.getTrackCategory());
layout = PreferencesUtils.getCustomLayout(sharedPreferences, getContext());
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_grade_altitude_key, key)) {
updateUInecessary = true;
preferenceShowAltitude = PreferencesUtils.isShowStatsAltitude(sharedPreferences, getContext());
}
if (PreferencesUtils.isKey(getContext(), R.string.stats_show_coordinate_key, key)) {
updateUInecessary = true;
preferenceShowCoordinate = PreferencesUtils.isStatsShowCoordinate(sharedPreferences, getContext());
if (PreferencesUtils.isKey(getContext(), R.string.stats_custom_layout_columns_key, key)) {
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns(sharedPreferences, getContext()));
}
if (key != null && updateUInecessary && isResumed()) {
@@ -109,17 +100,28 @@ public class StatisticsRecordingFragment extends Fragment {
sharedPreferences = PreferencesUtils.getSharedPreferences(getContext());
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
sensorsAdapter = new SensorsAdapter(getContext());
statisticsAdapter = new StatisticsAdapter(getContext());
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
viewBinding.statsActivityTypeIcon.setOnClickListener(v -> ((TrackRecordingActivity) getActivity()).chooseActivityType(recordingData.getTrackCategory()));
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
sensorsRecyclerView.setAdapter(sensorsAdapter);
RecyclerView recyclerView = viewBinding.statsRecyclerView;
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
final int numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, getContext());
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (statisticsAdapter.isItemWide(position)) {
return numColumns;
}
return 1;
}
});
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setAdapter(statisticsAdapter);
return viewBinding.getRoot();
}
@@ -131,13 +133,16 @@ public class StatisticsRecordingFragment extends Fragment {
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(sharedPreferences, null);
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
statisticsLiveData = viewModel.getStatsData();
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
trackRecordingServiceConnection.startConnection(getContext());
}
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
@@ -158,160 +163,16 @@ public class StatisticsRecordingFragment extends Fragment {
super.onDestroy();
trackRecordingServiceConnection = null;
sharedPreferences = null;
viewModel = null;
statisticsLiveData.removeObservers(getActivity());
}
private void updateUI() {
if (isResumed()) {
updateStats();
setLocationValues();
updateSensorDataUI();
viewModel.update(recordingData, layout, preferenceMetricUnits);
}
}
/**
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
* Also sets altitude gain and loss.
*/
private void updateSensorDataUI() {
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
if (sensorDataSet != null) {
List<SensorDataModel> sensorDataList = new ArrayList<>();
if (sensorDataSet.getHeartRate() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getHeartRate()));
}
if (sensorDataSet.getCyclingCadence() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingCadence()));
}
if (sensorDataSet.getCyclingPower() != null) {
sensorDataList.add(new SensorDataModel(sensorDataSet.getCyclingPower()));
}
sensorsAdapter.swapData(sensorDataList);
}
{
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), recordingData.getTrackStatistics().getTotalAltitudeGain(), preferenceMetricUnits);
viewBinding.statsAltitudeGainValue.setText(parts.first);
viewBinding.statsAltitudeGainUnit.setText(parts.second);
}
{
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), recordingData.getTrackStatistics().getTotalAltitudeLoss(), preferenceMetricUnits);
viewBinding.statsAltitudeLossValue.setText(parts.first);
viewBinding.statsAltitudeLossUnit.setText(parts.second);
}
}
private void updateStats() {
// Set activity type
String trackIconValue = TrackIconUtils.getIconValue(getContext(), recordingData.getTrackCategory());
viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue));
// Set time
viewBinding.statsMovingTimeValue.setText(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()));
updateTotalTime();
// Set average speed/pace
{
Speed speed = recordingData.getTrackStatistics().getAverageSpeed();
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
viewBinding.statsAverageSpeedValue.setText(parts.first);
viewBinding.statsAverageSpeedUnit.setText(parts.second);
}
// Set max speed/pace
{
Speed speed = recordingData.getTrackStatistics().getMaxSpeed();
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
viewBinding.statsMaxSpeedValue.setText(parts.first);
viewBinding.statsMaxSpeedUnit.setText(parts.second);
}
// Set moving speed/pace
{
Speed speed = recordingData.getTrackStatistics().getAverageMovingSpeed();
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
viewBinding.statsMovingSpeedValue.setText(parts.first);
viewBinding.statsMovingSpeedUnit.setText(parts.second);
}
// Set altitude gain and loss
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
}
private void updateTotalTime() {
Duration totalTime = recordingData.getTrackStatistics().getTotalTime();
viewBinding.statsTotalTimeValue.setText(StringUtils.formatElapsedTime(totalTime));
}
private void setLocationValues() {
// Set speed/pace
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) {
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
if (data.hasValue() && data.isRecent()) {
speed = data.getValue().getSpeed();
}
}
setSpeed(speed);
// Set distance
{
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), recordingData.getTrackStatistics().getTotalDistance(), preferenceMetricUnits);
viewBinding.statsDistanceValue.setText(parts.first);
viewBinding.statsDistanceUnit.setText(parts.second);
}
// Set altitude
viewBinding.statsAltitudeGroup.setVisibility(preferenceShowAltitude ? View.VISIBLE : View.GONE);
if (preferenceShowAltitude) {
// Current altitude
Float altitude = null;
int labelId = R.string.value_unknown;
if (latestTrackPoint != null && latestTrackPoint.hasAltitude()) {
altitude = (float) latestTrackPoint.getAltitude().toM();
labelId = latestTrackPoint.getAltitude().getLabelId();
}
Pair<String, String> parts = StringUtils.getAltitudeParts(getContext(), altitude, preferenceMetricUnits);
viewBinding.statsAltitudeCurrentValue.setText(parts.first);
viewBinding.statsAltitudeCurrentUnit.setText(parts.second);
viewBinding.statsAltitudeCurrentLabelEgm.setText(labelId);
}
// Set coordinate
viewBinding.statsCoordinateGroup.setVisibility(preferenceShowCoordinate ? View.VISIBLE : View.GONE);
if (preferenceShowCoordinate) {
String latitudeText = getContext().getString(R.string.value_unknown);
String longitudeText = getContext().getString(R.string.value_unknown);
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
latitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLatitude());
longitudeText = StringUtils.formatCoordinate(latestTrackPoint.getLongitude());
}
viewBinding.statsLatitudeValue.setText(latitudeText);
viewBinding.statsLongitudeValue.setText(longitudeText);
}
}
private void setSpeed(Speed speed) {
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
viewBinding.statsSpeedValue.setText(parts.first);
viewBinding.statsSpeedUnit.setText(parts.second);
}
private void onRecordingDataChanged(TrackRecordingService.RecordingData recordingData) {
String oldCategory = this.recordingData.getTrackCategory();
String newCategory = recordingData.getTrackCategory();
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
import de.dennisguse.opentracks.databinding.ToolbarBinding;
import de.dennisguse.opentracks.io.file.ErrorListDialog;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.ExportUtils;
@@ -134,6 +135,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding = ExportActivityBinding.inflate(getLayoutInflater());
ToolbarBinding mergedBinding = ToolbarBinding.bind(viewBinding.getRoot());
setContentView(viewBinding.getRoot());
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
@@ -144,7 +146,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
DocumentFile documentFile = DocumentFile.fromTreeUri(this, directoryUri);
String directoryDisplayName = FileUtils.getPath(documentFile);
viewBinding.toolbar.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
mergedBinding.toolbar.setTitle(getString(R.string.export_progress_message, directoryDisplayName));
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
@@ -111,6 +111,11 @@ class TrackRecordingManager {
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
Track track = getTrack(); //Get copy
if (track == null) {
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
return null;
}
track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
return new Pair<>(track, current);
@@ -157,26 +162,28 @@ class TrackRecordingManager {
}
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
insertTrackPointIfNewer(track, lastTrackPoint);
if (distanceToLastTrackLocation != null) {
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
insertTrackPointIfNewer(track, lastTrackPoint);
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(track, trackPoint);
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
insertTrackPoint(track, trackPoint);
isIdle = false;
lastTrackPoint = trackPoint;
return;
}
isIdle = false;
lastTrackPoint = trackPoint;
return;
}
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPointIfNewer(track, lastTrackPoint);
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
insertTrackPointIfNewer(track, lastTrackPoint);
insertTrackPoint(track, trackPoint);
insertTrackPoint(track, trackPoint);
isIdle = false;
isIdle = false;
lastTrackPoint = trackPoint;
return;
lastTrackPoint = trackPoint;
return;
}
}
if (!isIdle && !trackPoint.isMoving()) {
@@ -267,7 +274,7 @@ class TrackRecordingManager {
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
track.setTrackStatistics(trackStatisticsUpdater.getTrackStatistics());
contentProviderUtils.updateTrack(track);
contentProviderUtils.updateTrackStatistics(track.getId(), track.getTrackStatistics());
} catch (SQLiteException e) {
/*
* Insert failed, most likely because of SqlLite error code 5 (SQLite_BUSY).
@@ -450,6 +450,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
Pair<Track, Pair<TrackPoint, SensorDataSet>> data = trackRecordingManager.get(handlerServer);
if (data == null) {
Log.w(TAG, "Requesting data if not recording is taking place, should not be done.");
return;
}
TrackPoint trackPoint = data.second.first;
egm2008CorrectionManager.correctAltitude(this, trackPoint);
@@ -44,6 +44,10 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
gpsStatus.start();
}
private boolean isStarted() {
return locationManager != null;
}
@SuppressWarnings({"MissingPermission"})
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
public void onStop() {
@@ -96,6 +100,11 @@ public class LocationHandler implements LocationListener, GpsStatus.GpsStatusLis
*/
@Override
public void onLocationChanged(@NonNull Location location) {
if (!isStarted()) {
Log.w(TAG, "Location is ignored; not started.");
return;
}
TrackPoint trackPoint = new TrackPoint(location, handlerServer.createNow());
boolean isAccurate = trackPoint.fulfillsAccuracy(thresholdHorizontalAccuracy);
boolean isValid = LocationUtils.isValidLocation(location);
@@ -72,6 +72,10 @@ public class TrackPointCreator {
}
public void resetSensorData() {
if (remoteSensorManager == null || altitudeSumManager == null) {
Log.d(TAG, "No recording running and no reset necessary.");
return;
}
remoteSensorManager.reset();
altitudeSumManager.reset();
}
@@ -101,6 +105,16 @@ public class TrackPointCreator {
// }
// serviceExecutor = null;
if (remoteSensorManager != null) {
remoteSensorManager.stop();
remoteSensorManager = null;
}
if (altitudeSumManager != null) {
altitudeSumManager.stop(context);
altitudeSumManager = null;
}
this.context = null;
}
@@ -156,6 +170,7 @@ public class TrackPointCreator {
}
//TODO Limit visibility
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public Instant createNow() {
return Instant.now(clock);
}
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.settings;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;
import androidx.fragment.app.FragmentActivity;
@@ -35,15 +36,23 @@ public class ResetDialogPreference extends DialogPreference {
@Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
FragmentActivity activity = getActivity();
if (!positiveResult) {
return;
}
FragmentActivity activity = getActivity();
String preferenceKey = getArguments().getString(PreferenceDialogFragmentCompat.ARG_KEY);
if (preferenceKey.equals(getString(R.string.settings_reset_key))) {
PreferencesUtils.resetPreferences(activity, true);
Toast.makeText(activity, R.string.settings_reset_done, Toast.LENGTH_SHORT).show();
} else if (preferenceKey.equals(getString(R.string.settings_layout_reset_key))) {
PreferencesUtils.resetCustomLayoutPreferences(activity);
Toast.makeText(activity, R.string.settings_layout_reset_done, Toast.LENGTH_SHORT).show();
}
if (activity instanceof ResetCallback) {
((ResetCallback) activity).onReset();
}
if (activity instanceof ResetCallback) {
((ResetCallback) activity).onReset();
}
}
}
@@ -208,6 +208,13 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
}
);
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
startActivity(intent);
return true;
});
announcementsFrequency = findPreference(getString(R.string.voice_frequency_key));
announcementsSpeed = findPreference(getString(R.string.voice_speed_rate_key));
updateVoiceAnnouncements();
@@ -0,0 +1,170 @@
package de.dennisguse.opentracks.settings;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.stream.IntStream;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutAdapter;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StatisticsUtils;
public class SettingsCustomLayoutActivity extends AbstractActivity implements SettingsCustomLayoutAdapter.SettingsCustomLayoutItemClickListener {
private ActivitySettingsCustomLayoutBinding viewBinding;
private GridLayoutManager gridLayoutManager;
private SettingsCustomLayoutAdapter adapterFieldsVisible;
private SettingsCustomLayoutAdapter adapterFieldsHidden;
private Layout layoutFieldsVisible;
private Layout layoutFieldsHidden;
private SharedPreferences sharedPreferences;
private int numColumns;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
// Recycler view with visible stats.
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(sharedPreferences, this), true);
adapterFieldsVisible = new SettingsCustomLayoutAdapter(this, this, layoutFieldsVisible);
numColumns = PreferencesUtils.getLayoutColumns(sharedPreferences, this);
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
gridLayoutManager = new GridLayoutManager(this, numColumns);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (adapterFieldsVisible.isItemWide(position)) {
return numColumns;
}
return 1;
}
});
recyclerViewVisible.setLayoutManager(gridLayoutManager);
recyclerViewVisible.setAdapter(adapterFieldsVisible);
ItemTouchHelper.SimpleCallback simpleCallback = new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END, 0) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
layoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleCallback);
itemTouchHelper.attachToRecyclerView(recyclerViewVisible);
// Spinner with items per row.
ArrayAdapter<Integer> spinnerAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item,
IntStream.of(getResources().getIntArray(R.array.stats_custom_layout_fields_columns_per_row)).boxed().toArray(Integer[]::new));
viewBinding.spinnerOptions.setAdapter(spinnerAdapter);
viewBinding.spinnerOptions.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
numColumns = position + 1;
gridLayoutManager.setSpanCount(numColumns);
PreferencesUtils.setLayoutColumns(sharedPreferences, SettingsCustomLayoutActivity.this, position + 1);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
viewBinding.spinnerOptions.setSelection(PreferencesUtils.getLayoutColumns(sharedPreferences, SettingsCustomLayoutActivity.this) - 1);
// Recycler view with not visible stats.
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(sharedPreferences, this), false);
adapterFieldsHidden = new SettingsCustomLayoutAdapter(this, this, layoutFieldsHidden);
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
}
@Override
protected void onPause() {
super.onPause();
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
Layout newLayout = new Layout(layoutFieldsVisible.getProfile());
newLayout.addFields(layoutFieldsVisible.getFields());
newLayout.addFields(layoutFieldsHidden.getFields());
PreferencesUtils.setCustomLayout(sharedPreferences, this, newLayout);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
sharedPreferences = null;
layoutFieldsVisible = null;
}
@Override
protected void setupActionBarBack(Toolbar toolbar) {
super.setupActionBarBack(toolbar);
toolbar.setTitle(R.string.menu_settings);
}
@Override
protected View getRootView() {
viewBinding = ActivitySettingsCustomLayoutBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@Override
public void onSettingsCustomLayoutItemClicked(@NonNull DataField field) {
if (field.isVisible()) {
new AlertDialog.Builder(this)
.setTitle(R.string.generic_choose_an_option)
.setItems(new String[]{getString(field.isPrimary() ? R.string.field_set_secondary : R.string.field_set_primary), getString(R.string.field_remove_from_layout)}, (dialog, which) -> {
if (which == 0) {
field.togglePrimary();
} else {
layoutFieldsVisible.removeField(field);
field.toggleVisibility();
layoutFieldsHidden.addField(field);
}
adapterFieldsVisible.swapValues(layoutFieldsVisible);
adapterFieldsHidden.swapValues(layoutFieldsHidden);
})
.create()
.show();
} else {
layoutFieldsHidden.removeField(field);
field.toggleVisibility();
layoutFieldsVisible.addField(field);
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
adapterFieldsVisible.swapValues(layoutFieldsVisible);
adapterFieldsHidden.swapValues(layoutFieldsHidden);
}
}
}
@@ -0,0 +1,10 @@
package de.dennisguse.opentracks.util;
public final class CsvConstants {
private CsvConstants() {
}
public static final String LINE_SEPARATOR = ";";
public static final String ITEM_SEPARATOR = ",";
}
@@ -0,0 +1,41 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.SharedPreferences;
import de.dennisguse.opentracks.R;
public class PreferencesOpenHelper {
private final Context context;
private final SharedPreferences sharedPreferences;
private static final int PREFERENCES_VERSION = 1;
private PreferencesOpenHelper(Context context, SharedPreferences sharedPreferences) {
this.context = context;
this.sharedPreferences = sharedPreferences;
}
public static PreferencesOpenHelper newInstance(Context context, SharedPreferences sharedPreferences) {
return new PreferencesOpenHelper(context, sharedPreferences);
}
public void checkForUpgrade() {
int lastVersion = PreferencesUtils.getInt(sharedPreferences, context, R.string.prefs_last_version_key, 0);
if (PREFERENCES_VERSION > lastVersion) {
onUpgrade();
}
}
private void onUpgrade() {
PreferencesUtils.setInt(sharedPreferences, context, R.string.prefs_last_version_key, PREFERENCES_VERSION);
switch (PREFERENCES_VERSION) {
case 1:
upgradeFrom0to1();
}
}
private void upgradeFrom0to1() {
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout(context));
}
}
@@ -16,9 +16,11 @@
package de.dennisguse.opentracks.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.TypedArray;
import android.net.Uri;
import android.util.Log;
@@ -26,8 +28,16 @@ import androidx.annotation.VisibleForTesting;
import androidx.documentfile.provider.DocumentFile;
import androidx.preference.PreferenceManager;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Distance;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
/**
@@ -44,7 +54,9 @@ public class PreferencesUtils {
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
public static SharedPreferences getSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
PreferencesOpenHelper.newInstance(context, sharedPreferences).checkForUpgrade();
return sharedPreferences;
}
public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
@@ -80,7 +92,7 @@ public class PreferencesUtils {
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
}
private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
try {
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
} catch (ClassCastException e) {
@@ -123,6 +135,12 @@ public class PreferencesUtils {
editor.apply();
}
static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
Editor editor = sharedPreferences.edit();
editor.putInt(getKey(context, keyId), value);
editor.apply();
}
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
final String STATS_UNIT = context.getString(R.string.stats_units_default);
return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
@@ -196,16 +214,6 @@ public class PreferencesUtils {
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static boolean isShowStatsAltitude(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_ALTITUDE = context.getResources().getBoolean(R.bool.stats_show_altitude_default);
return getBoolean(sharedPreferences, context, R.string.stats_show_grade_altitude_key, STATS_SHOW_ALTITUDE);
}
public static boolean isStatsShowCoordinate(SharedPreferences sharedPreferences, Context context) {
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default);
return getBoolean(sharedPreferences, context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
}
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
@@ -318,4 +326,78 @@ public class PreferencesUtils {
public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
}
public static int getLayoutColumns(SharedPreferences sharedPreferences, Context context) {
return getInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, context.getResources().getInteger(R.integer.stats_custom_layout_columns_default));
}
public static void setLayoutColumns(SharedPreferences sharedPreferences, Context context, int columns) {
setInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, columns);
}
private static List<TypedArray> getMultiTypedArray(Context context, String key) {
List<TypedArray> typedArrays = new ArrayList<>();
try {
Class<R.array> resource = R.array.class;
Field field;
int i = 0;
do {
field = resource.getField(key + context.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
typedArrays.add(context.getResources().obtainTypedArray(field.getInt(null)));
i++;
} while (field != null);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, e.getMessage());
}
return typedArrays;
}
@SuppressLint("ResourceType")
static String buildDefaultLayout(Context context) {
List<TypedArray> fieldsArrays = getMultiTypedArray(context, "stats_custom_layout_fields_default_value");
return context.getString(R.string.default_activity_default) + CsvConstants.LINE_SEPARATOR
+ fieldsArrays.stream().map(i -> i.getString(0) + CsvConstants.ITEM_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
+ CsvConstants.LINE_SEPARATOR;
}
public static Layout getCustomLayout(SharedPreferences sharedPreferences, Context context) {
String csvCustomLayout = getString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, buildDefaultLayout(context));
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
Layout layout = new Layout(csvParts.get(0));
for (int i = 1; i < csvParts.size(); i++) {
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
layout.addField(fieldParts[0], DataField.getTitleByKey(context, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(context.getString(R.string.stats_custom_layout_coordinates_key)));
}
return layout;
}
public static void setCustomLayout(SharedPreferences sharedPreferences, Context context, Layout layout) {
List<DataField> fields = layout.getFields();
if (fields.isEmpty()) {
return;
}
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR)) + CsvConstants.LINE_SEPARATOR;
setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, csv);
}
public static void resetCustomLayoutPreferences(Context context) {
SharedPreferences settings = getSharedPreferences(context);
if (settings.contains(context.getString(R.string.stats_custom_layout_fields_key))) {
SharedPreferences.Editor editor = settings.edit();
editor.remove(context.getString(R.string.stats_custom_layout_fields_key));
editor.commit();
}
if (settings.contains(context.getString(R.string.stats_custom_layout_columns_key))) {
SharedPreferences.Editor editor = settings.edit();
editor.remove(context.getString(R.string.stats_custom_layout_columns_key));
editor.commit();
}
}
}
@@ -0,0 +1,31 @@
package de.dennisguse.opentracks.util;
import android.content.Context;
import androidx.annotation.NonNull;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Layout;
public class StatisticsUtils {
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_fastest_pace_key, statTitle)) {
return context.getString(R.string.stats_empty_value_time);
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_distance_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_max_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_speed_key, statTitle)) {
return context.getString(R.string.stats_empty_value_float);
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_coordinates_key, statTitle)) {
return context.getString(R.string.stats_empty_value_coordinates);
} else {
return context.getString(R.string.stats_empty_value_integer);
}
}
public static Layout filterVisible(Layout layout, boolean visible) {
Layout result = new Layout(layout.getProfile());
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
return result;
}
}
@@ -124,10 +124,22 @@ public class StringUtils {
/**
* Formats a coordinate
*
* @param context the context's object.
* @param coordinate the coordinate
*/
public static String formatCoordinate(double coordinate) {
return Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE;
public static String formatCoordinate(Context context, double coordinate) {
return context.getString(R.string.location_coordinate, Location.convert(coordinate, Location.FORMAT_DEGREES));
}
/**
* Formats a complete coordinate (latitude, longitude)
*
* @param context the context's object.
* @param latitude the coordinate's latitude.
* @param longitude the coordinate's longitude.
*/
public static String formatCoordinate(Context context, double latitude, double longitude) {
return context.getString(R.string.location_latitude_longitude, Location.convert(latitude, Location.FORMAT_DEGREES), Location.convert(longitude, Location.FORMAT_DEGREES));
}
/**
@@ -191,6 +203,33 @@ public class StringUtils {
return new Pair<>(context.getString(R.string.time, minutes, seconds), unitString);
}
public static Pair<String, String> getHeartRateParts(Context context, Float heartrate_bpm) {
String value = context.getString(R.string.value_none);
if (heartrate_bpm != null) {
value = StringUtils.formatDecimal(heartrate_bpm, 0);
}
return new Pair<>(value, context.getString(R.string.sensor_unit_beats_per_minute));
}
public static Pair<String, String> getCadenceParts(Context context, Float cadence_rpm) {
String value = context.getString(R.string.value_none);
if (cadence_rpm != null) {
value = StringUtils.formatDecimal(cadence_rpm, 0);
}
return new Pair<>(value, context.getString(R.string.sensor_unit_rounds_per_minute));
}
public static Pair<String, String> getPowerParts(Context context, Float power_w) {
String value = context.getString(R.string.value_none);
if (power_w != null) {
value = StringUtils.formatDecimal(power_w, 0);
}
return new Pair<>(value, context.getString(R.string.sensor_unit_power));
}
/**
* Gets a string for category.
*
@@ -1,56 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.sensor.SensorData;
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
import de.dennisguse.opentracks.content.sensor.SensorDataCyclingPower;
import de.dennisguse.opentracks.content.sensor.SensorDataHeartRate;
import de.dennisguse.opentracks.util.StringUtils;
public class SensorDataModel {
private int labelId;
private final String sensorValue;
private String sensorName;
private int unitId;
public SensorDataModel(int labelId, int unitId, float sensorValue) {
this.labelId = labelId;
this.unitId = unitId;
this.sensorValue = StringUtils.formatDecimal(sensorValue, 0);
}
public SensorDataModel(SensorData<Float> sensorData) {
this.sensorName = sensorData.getSensorNameOrAddress();
this.sensorValue = sensorData.hasValue() && sensorData.isRecent() ? StringUtils.formatDecimal(sensorData.getValue(), 0) : null;
if (sensorData instanceof SensorDataHeartRate) {
this.labelId = R.string.sensor_state_heart_rate;
this.unitId = R.string.sensor_unit_beats_per_minute;
} else if (sensorData instanceof SensorDataCycling.Cadence) {
this.labelId = R.string.sensor_state_cadence;
this.unitId = R.string.sensor_unit_rounds_per_minute;
} else if (sensorData instanceof SensorDataCyclingPower) {
this.labelId = R.string.sensor_state_power;
this.unitId = R.string.sensor_unit_power;
}
}
public int getLabelId() {
return labelId;
}
public int getUnitId() {
return unitId;
}
public String getSensorName() {
return sensorName;
}
public String getSensorValue() {
return sensorValue;
}
public boolean hasValue() {
return sensorValue != null;
}
}
@@ -0,0 +1,45 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.content.data.DataField;
public class StatisticData {
private final DataField dataField;
private final String value;
private final String unit;
private final String description;
public StatisticData(@NonNull DataField dataField, @NonNull Pair<String, String> valueAndUnit, String description) {
this.dataField = dataField;
this.value = valueAndUnit.first;
this.unit = valueAndUnit.second;
this.description = description;
}
public DataField getField() {
return dataField;
}
public String getValue() {
return value;
}
public boolean hasValue() {
return value != null;
}
public String getUnit() {
return unit;
}
public String getDescription() {
return description;
}
public boolean hasDescription() {
return description != null;
}
}
@@ -0,0 +1,205 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import android.util.Pair;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.DataField;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.content.data.Speed;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.util.StringUtils;
public class StatisticDataBuilder {
public static List<StatisticData> fromRecordingData(@NonNull Context context, @NonNull TrackRecordingService.RecordingData recordingData, @NonNull Layout layout, boolean metricUnits) {
List<StatisticData> statisticDataList = layout.getFields().stream()
.filter(DataField::isVisible)
.map(field -> build(context, recordingData, field.getKey(), field.isPrimary(), metricUnits))
.filter(Objects::nonNull)
.collect(Collectors.toList());
statisticDataList.addAll(getSensorStatsDataIfNeeded(context, recordingData, statisticDataList, metricUnits));
return statisticDataList;
}
private static StatisticData build(@NonNull Context context, @NonNull TrackRecordingService.RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, boolean metricUnits) {
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
String title = null;
String description = null;
Pair<String, String> valueAndUnit = null;
boolean isWide = false;
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getTotalTime()), null);
title = context.getString(R.string.stats_total_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(recordingData.getTrackStatistics().getMovingTime()), null);
title = context.getString(R.string.stats_moving_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
valueAndUnit = StringUtils.getDistanceParts(context, recordingData.getTrackStatistics().getTotalDistance(), metricUnits);
title = context.getString(R.string.stats_distance);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
boolean reportSpeed = fieldKey.equals("speed");
title = fieldKey.equals("speed") ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null && sensorDataSet.getCyclingDistanceSpeed().hasValue() && sensorDataSet.getCyclingDistanceSpeed().isRecent()) {
valueAndUnit = StringUtils.getSpeedParts(context, sensorDataSet.getCyclingDistanceSpeed().getValue().getSpeed(), metricUnits, reportSpeed);
description = context.getString(R.string.description_speed_source_sensor, sensorDataSet.getCyclingDistanceSpeed().getSensorNameOrAddress());
} else {
valueAndUnit = StringUtils.getSpeedParts(context, speed, metricUnits, reportSpeed);
description = context.getString(R.string.description_speed_source_gps);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageMovingSpeed(), metricUnits, true);
title = context.getString(R.string.stats_average_moving_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageSpeed(), metricUnits, true);
title = context.getString(R.string.stats_average_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getMaxSpeed(), metricUnits, true);
title = context.getString(R.string.stats_max_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageMovingSpeed(), metricUnits, false);
title = context.getString(R.string.stats_average_moving_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getAverageSpeed(), metricUnits, false);
title = context.getString(R.string.stats_average_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
valueAndUnit = StringUtils.getSpeedParts(context, recordingData.getTrackStatistics().getMaxSpeed(), metricUnits, true);
title = context.getString(R.string.stats_fastest_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
title = context.getString(R.string.stats_altitude);
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, metricUnits);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeGain(), metricUnits);
title = context.getString(R.string.stats_gain);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, recordingData.getTrackStatistics().getTotalAltitudeLoss(), metricUnits);
title = context.getString(R.string.stats_loss);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
title = context.getString(R.string.stats_coordinates);
isWide = true;
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
valueAndUnit = new Pair<>(StringUtils.formatCoordinate(context, latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude()), null);
} else {
valueAndUnit = new Pair<>(context.getString(R.string.value_none), null);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
title = context.getString(R.string.stats_sensors_heart_rate);
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().getValue());
description = sensorDataSet.getHeartRate().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getHeartRateParts(context, null);
description = context.getString(R.string.value_none);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
title = context.getString(R.string.stats_sensors_cadence);
if (sensorDataSet != null && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
valueAndUnit = StringUtils.getCadenceParts(context, sensorDataSet.getCyclingCadence().getValue());
description = sensorDataSet.getCyclingCadence().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getCadenceParts(context, null);
description = context.getString(R.string.value_none);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
title = context.getString(R.string.stats_sensors_power);
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getPowerParts(context, null);
description = context.getString(R.string.value_none);
}
}
if (valueAndUnit == null) {
return null;
}
return new StatisticData(new DataField(fieldKey, title, true, isPrimary, isWide), valueAndUnit, description);
}
/**
* Builds a list of StatisticData with sensors connected but not in statisticDataList.
*/
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, TrackRecordingService.RecordingData recordingData, List<StatisticData> statisticDataList, boolean metricUnits) {
List<StatisticData> sensorDataList = new ArrayList<>();
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
if (sensorDataSet == null) {
return sensorDataList;
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null && sensorDataSet.getHeartRate().hasValue() && sensorDataSet.getHeartRate().isRecent()) {
sensorDataList.add(build(context, recordingData, "heart_rate", true, metricUnits));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCyclingCadence() != null && sensorDataSet.getCyclingCadence().hasValue() && sensorDataSet.getCyclingCadence().isRecent()) {
sensorDataList.add(build(context, recordingData, "cadence", true, metricUnits));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue() && sensorDataSet.getCyclingPower().isRecent()) {
sensorDataList.add(build(context, recordingData, "power", true, metricUnits));
}
return sensorDataList;
}
public static List<StatisticData> fromSensorStatistics(@NonNull Context context, @NonNull SensorStatistics sensorStatistics) {
List<StatisticData> sensorDataList = new ArrayList<>();
if (sensorStatistics.hasHeartRate()) {
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_max), true, true, false),
StringUtils.getHeartRateParts(context, sensorStatistics.getMaxHeartRate()),
null
)
);
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_average_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_avg), true, true, false),
StringUtils.getHeartRateParts(context, sensorStatistics.getAvgHeartRate()),
null
)
);
}
if (sensorStatistics.hasCadence()) {
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.sensor_state_cadence_max), true, true, false),
StringUtils.getCadenceParts(context, sensorStatistics.getMaxCadence()),
null
)
);
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_average_cadence_key), context.getString(R.string.sensor_state_cadence_avg), true, true, false),
StringUtils.getCadenceParts(context, sensorStatistics.getAvgCadence()),
null
)
);
}
if (sensorStatistics.hasPower()) {
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.sensor_state_power_avg), true, true, false),
StringUtils.getPowerParts(context, sensorStatistics.getAvgPower()),
null
)
);
}
return sensorDataList;
}
}
@@ -0,0 +1,35 @@
package de.dennisguse.opentracks.viewmodels;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import de.dennisguse.opentracks.content.data.Layout;
import de.dennisguse.opentracks.services.TrackRecordingService;
public class StatisticsDataModel extends AndroidViewModel {
private MutableLiveData<List<StatisticData>> statsData;
public StatisticsDataModel(@NonNull Application application) {
super(application);
}
public MutableLiveData<List<StatisticData>> getStatsData() {
if (statsData == null) {
statsData = new MutableLiveData<>();
}
return statsData;
}
public void update(TrackRecordingService.RecordingData recordingData, Layout layout, boolean metricUnit) {
new Thread(() -> {
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, metricUnit);
statsData.postValue(statisticDataList);
}).start();
}
}
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11,18c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2 0.9,-2 2,-2 2,0.9 2,2zM9,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM9,4c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM15,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM15,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM15,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="horizontal">
<TextView
style="@style/TextMediumPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/settings_recording_customize_layout_select_columns" />
<Spinner
android:id="@+id/spinner_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_visible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/recycler_view_not_visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_not_visible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/recycler_view_visible"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
@@ -42,7 +42,7 @@
<TextView
android:id="@+id/aggregated_stats_type_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
android:layout_marginStart="16dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
@@ -53,7 +53,7 @@
<TextView
android:id="@+id/aggregated_stats_num_tracks"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
android:layout_marginStart="4dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
@@ -63,12 +63,9 @@
tools:text="(1)" />
<!-- Distance -->
<!-- Time -->
<TextView
android:id="@+id/aggregated_stats_distance"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@id/aggregated_stats_distance_unit"
app:layout_constraintHorizontal_chainStyle="packed"
@@ -86,9 +83,10 @@
app:layout_constraintStart_toEndOf="@+id/aggregated_stats_distance"
tools:text="km" />
<!-- Time -->
<TextView
android:id="@+id/aggregated_stats_time"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="@id/guideline3"
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/StatsCardView">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/stats_iv_drag_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_drag_indicator_24"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/stats_layout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<include
android:id="@+id/stats_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
layout="@layout/stats_item"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toEndOf="@id/stats_iv_drag_indicator"
app:layout_constraintEnd_toStartOf="@id/stats_icon_show_status"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/stats_icon_show_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:src="@drawable/ic_baseline_visibility_24"
android:layout_marginEnd="8dp"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/stats_layout"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
+1 -3
View File
@@ -7,9 +7,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<include layout="@layout/toolbar" />
<androidx.core.widget.NestedScrollView
android:layout_weight="1"
+12 -12
View File
@@ -88,7 +88,7 @@
<!-- Distance -->
<TextView
android:id="@+id/stats_distance_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
android:text="@string/stats_distance"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -97,7 +97,7 @@
<TextView
android:id="@+id/stats_distance_value"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -116,7 +116,7 @@
<!-- Moving time -->
<TextView
android:id="@+id/stats_moving_time_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -125,7 +125,7 @@
<TextView
android:id="@+id/stats_moving_time_value"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -170,7 +170,7 @@
<!-- Speed/Pace max -->
<TextView
android:id="@+id/stats_max_speed_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintEnd_toStartOf="@id/guideline2"
@@ -179,7 +179,7 @@
<TextView
android:id="@+id/stats_max_speed_value"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -198,7 +198,7 @@
<!-- Avg. moving speed/pace -->
<TextView
android:id="@+id/stats_moving_speed_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintEnd_toStartOf="@id/guideline3"
@@ -207,7 +207,7 @@
<TextView
android:id="@+id/stats_moving_speed_value"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -277,7 +277,7 @@
<TextView
android:id="@+id/stats_altitude_gain_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
android:text="@string/stats_gain"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -286,7 +286,7 @@
<TextView
android:id="@+id/stats_altitude_gain_value"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
@@ -304,7 +304,7 @@
<TextView
android:id="@+id/stats_altitude_loss_label"
style="@style/StatsLargeLabel"
style="@style/LargeLabel"
android:text="@string/stats_loss"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/guideline2"
@@ -313,7 +313,7 @@
<TextView
android:id="@+id/stats_altitude_loss_value"
style="@style/StatsLargeValue"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
+12 -452
View File
@@ -1,457 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/StatsScrollView">
style="@style/StatsScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/stats_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="match_parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
<!-- Guidelines -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_begin="8dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_end="8dp" />
<!-- Distance -->
<TextView
android:id="@+id/stats_distance_label"
style="@style/StatsLargeLabel"
android:text="@string/stats_distance"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/stats_distance_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_distance_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_distance_label"
tools:text="100" />
<TextView
android:id="@+id/stats_distance_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_distance_value"
tools:text="km" />
<!-- Activity Type -->
<TextView
android:id="@+id/stats_activity_type_label"
style="@style/StatsLargeLabel"
android:text="@string/track_edit_activity_type_hint"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/stats_activity_type_icon"
style="@style/StatsLargeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_distance_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toTopOf="@id/stats_distance_value"
app:tint="?android:textColorPrimary" />
<!-- Total time -->
<TextView
android:id="@+id/stats_total_time_label"
style="@style/StatsLargeLabel"
android:text="@string/stats_total_time"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_distance_value" />
<TextView
android:id="@+id/stats_total_time_value"
style="@style/StatsLargeValue"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_total_time_label"
tools:text="00:00:00" />
<!-- Moving time -->
<TextView
android:id="@+id/stats_moving_time_label"
style="@style/StatsLargeLabel"
android:text="@string/stats_moving_time"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@id/stats_total_time_label"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toTopOf="@id/stats_total_time_label"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/stats_moving_time_value"
style="@style/StatsLargeValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_moving_time_label"
tools:text="00:00:00" />
<!-- Barrier -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/stats_time_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="stats_total_time_value,stats_moving_time_value" />
<!-- Horizontal line -->
<View
android:id="@+id/stats_speed_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_time_barrier" />
<!-- Speed/pace -->
<TextView
android:id="@+id/stats_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
tools:text="Speed" />
<TextView
android:id="@+id/stats_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_speed_value"
tools:text="km/h" />
<!-- Average speed/pace -->
<TextView
android:id="@+id/stats_average_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_speed_horizontal_line"
tools:text="Avg. Speed" />
<TextView
android:id="@+id/stats_average_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_average_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_average_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_average_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_average_speed_value"
tools:text="km/h" />
<!-- Speed max -->
<TextView
android:id="@+id/stats_max_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
tools:text="Max. Speed" />
<TextView
android:id="@+id/stats_max_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_max_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_max_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_max_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_max_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_max_speed_value"
tools:text="km/h" />
<!-- Moving speed max -->
<TextView
android:id="@+id/stats_moving_speed_label"
style="@style/StatsSmallLabel"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_average_speed_value"
tools:text="Max. Moving Speed" />
<TextView
android:id="@+id/stats_moving_speed_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_moving_speed_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_moving_speed_label"
tools:text="30.00" />
<TextView
android:id="@+id/stats_moving_speed_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_moving_speed_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_moving_speed_value"
tools:text="km/h" />
<!-- Barrier -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/stats_speed_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="stats_max_speed_value,stats_moving_speed_value" />
<!-- Horizontal line -->
<View
android:id="@+id/stats_altitude_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_speed_barrier" />
<!-- altitude -->
<androidx.constraintlayout.widget.Group
android:id="@+id/stats_altitude_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="stats_altitude_horizontal_line,stats_altitude_current_label,stats_altitude_current_value,stats_altitude_current_unit,stats_altitude_gain_label,stats_altitude_gain_value,stats_altitude_gain_unit,stats_altitude_loss_label,stats_altitude_loss_value,stats_altitude_loss_unit,stats_altitude_current_label_egm" />
<!-- altitude -->
<TextView
android:id="@+id/stats_altitude_current_label"
style="@style/StatsSmallLabel"
android:text="@string/stats_altitude"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_horizontal_line" />
<TextView
android:id="@+id/stats_altitude_current_label_egm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@string/egm2008"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_current_value" />
<TextView
android:id="@+id/stats_altitude_current_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_altitude_current_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_current_label"
tools:text="100" />
<TextView
android:id="@+id/stats_altitude_current_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_current_value"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/stats_altitude_current_value"
tools:text="m" />
<!-- Altitude gain -->
<TextView
android:id="@+id/stats_altitude_gain_label"
style="@style/StatsSmallLabel"
android:text="@string/stats_gain"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_horizontal_line" />
<TextView
android:id="@+id/stats_altitude_gain_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_altitude_gain_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_gain_label"
tools:text="100" />
<TextView
android:id="@+id/stats_altitude_gain_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_gain_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_altitude_gain_value"
tools:text="m" />
<!-- Altitude loss -->
<TextView
android:id="@+id/stats_altitude_loss_label"
style="@style/StatsSmallLabel"
android:text="@string/stats_loss"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_gain_value" />
<TextView
android:id="@+id/stats_altitude_loss_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_altitude_loss_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_loss_label"
tools:text="100" />
<TextView
android:id="@+id/stats_altitude_loss_unit"
style="@style/StatsUnit"
app:layout_constraintBottom_toBottomOf="@id/stats_altitude_loss_value"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/stats_altitude_loss_value"
tools:text="m" />
<!-- Coordinate group -->
<androidx.constraintlayout.widget.Group
android:id="@+id/stats_coordinate_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="stats_latitude_label,stats_latitude_value,stats_longitude_label,stats_longitude_value,stats_coordinate_horizontal_line" />
<!-- Barrier -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/stats_altitude_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="stats_altitude_current_value,stats_altitude_gain_value,stats_altitude_loss_value" />
<!-- Horizontal line -->
<View
android:id="@+id/stats_coordinate_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_altitude_barrier" />
<!-- Latitude -->
<TextView
android:id="@+id/stats_latitude_label"
style="@style/StatsSmallLabel"
android:text="@string/stats_latitude"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
<TextView
android:id="@+id/stats_latitude_value"
style="@style/StatsSmallValue"
app:layout_constraintEnd_toStartOf="@id/guideline2"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_latitude_label"
tools:text="0.834937848" />
<!-- Longitude -->
<TextView
android:id="@+id/stats_longitude_label"
style="@style/StatsSmallLabel"
android:text="@string/stats_longitude"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_coordinate_horizontal_line" />
<TextView
android:id="@+id/stats_longitude_value"
style="@style/StatsSmallValue"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline2"
app:layout_constraintTop_toBottomOf="@id/stats_longitude_label"
tools:text="0.834937848" />
<!-- Barrier -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/stats_location_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="stats_latitude_value,stats_longitude_value" />
<!-- Horizontal line -->
<View
android:id="@+id/stats_sensor_horizontal_line"
style="@style/StatsHorizontalLine"
android:layout_marginBottom="8dp"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_location_barrier" />
<!-- Sensors data -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/stats_sensors_recycler_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/guideline3"
app:layout_constraintStart_toEndOf="@id/guideline"
app:layout_constraintTop_toBottomOf="@id/stats_sensor_horizontal_line" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/stats_value"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/stats_unit"
tools:text="90" />
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit"
tools:text="@string/sensor_unit_beats_per_minute"
app:layout_constraintStart_toEndOf="@id/stats_value"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/stats_value" />
<TextView
android:id="@+id/stats_description_main"
style="@style/StatsPrimaryLabel"
tools:text="@string/sensor_state_heart_rate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_value" />
<TextView
android:id="@+id/stats_description_secondary"
style="@style/StatsTinyValue"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
tools:visibility="visible"
tools:text="90" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,42 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
android:paddingBottom="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/stats_sensor_label"
style="@style/StatsLargeLabel"
android:id="@+id/stats_description_main"
style="@style/LargeLabel"
tools:text="@string/sensor_state_heart_rate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/stats_sensor_sensor_value"
style="@style/StatsTinyValue"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_sensor_label"
tools:text="90" />
<TextView
android:id="@+id/stats_sensor_value"
style="@style/StatsLargeValue"
android:id="@+id/stats_value"
style="@style/StatsPrimaryValue"
android:value="@string/value_unknown"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/stats_sensor_unit"
app:layout_constraintTop_toBottomOf="@id/stats_sensor_sensor_value"
app:layout_constraintEnd_toStartOf="@id/stats_unit"
tools:text="90" />
<TextView
android:id="@+id/stats_sensor_unit"
android:id="@+id/stats_unit"
style="@style/StatsUnit"
tools:text="@string/sensor_unit_beats_per_minute"
app:layout_constraintStart_toEndOf="@id/stats_sensor_value"
app:layout_constraintStart_toEndOf="@id/stats_value"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/stats_sensor_value" />
app:layout_constraintBottom_toBottomOf="@id/stats_value" />
<TextView
android:id="@+id/stats_description_secondary"
style="@style/StatsTinyValue"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_value"
tools:visibility="visible"
tools:text="90" />
</androidx.constraintlayout.widget.ConstraintLayout>
-1
View File
@@ -217,7 +217,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">البلوتوث</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">خط العرض/خط الطول</string>
<string name="settings_stats_rate_title">المعدل المفضل</string>
<string name="settings_stats_units_imperial">وحدات القياس البريطانية (ميل، قدم)</string>
<string name="settings_stats_units_metric">وحدات القياس المترية (كم، م)</string>
-1
View File
@@ -176,7 +176,6 @@
<string name="settings_reset_confirm_title">Reset now?</string>
<string name="settings_reset_done">All settings have been reverted to default values</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_rate_title">Preferred rate</string>
<string name="settings_stats_units_imperial">Imperial (miles, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
+2 -3
View File
@@ -178,7 +178,6 @@
<string name="settings_reset_confirm_title">¿Restablecer ahora?</string>
<string name="settings_reset_done">Se restablecieron los valores predeterminados de todas las opciones de configuración</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitud/longitud</string>
<string name="settings_stats_rate_title">Tasa preferida</string>
<string name="settings_stats_units_imperial">Imperiales (millas, pies)</string>
<string name="settings_stats_units_metric">Métricas (km, m)</string>
@@ -399,8 +398,8 @@
<string name="help_feature_content">* crear pistas de GPS
\n* utilizar un sensor de frecuencia cardíaca (solo Bluetooth LE)
\n* anuncios de voz (por tiempo o distancia)
\n* crear marcadores (incluidas imágenes)
\n* funciona completamente sin conexión
\n* crear marcadores (incluidas imágenes)
\n* funciona completamente sin conexión
\n* mostrar pistas en un mapa (ver más abajo)</string>
<string name="help_track_map_title">¿Cómo mostrar un camino en un mapa\?</string>
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\?</string>
+1 -1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Ширина и дължина</string>
<string name="settings_stats_rate_title">Предпочитана мярка</string>
<string name="settings_stats_units_imperial">Имперски (мил, фт)</string>
<string name="settings_stats_units_metric">Метрични (км, м)</string>
@@ -506,4 +505,5 @@ limitations under the License.
<string name="instant_export_enabled_summary">Експортиране на запис в хранилището след приключване на записа</string>
<string name="settings_sensor_cycling_cadence">Колоездене: каданс</string>
<string name="settings_sensor_cycling_distance_speed">Колоездене: дължина и скорост</string>
<string name="settings_sensor_running_speed_and_cadence">Бягане: разстояние, скорост и каданс</string>
</resources>
-1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitud/Longitud</string>
<string name="settings_stats_rate_title">Mesura preferida</string>
<string name="settings_stats_units_imperial">Sistema anglosaxó (milles, peus)</string>
<string name="settings_stats_units_metric">Sistema mètric (km, m)</string>
+2 -2
View File
@@ -51,7 +51,7 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
\n b) zkuste zlepšit příjem GPS (např. vyjít ven),
\n c) zkontrolujte, zda používáte aplikaci <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest </a> a
\n d) deaktivujte úsporu energie.
\n
\n
\n Pokud nic nepomůže, nahlašte jako chybu. </string>
<string name="help_support_title">Jak mohu podpořit OpenTracks\?</string>
<string name="help_support_content"> Můžete:
@@ -280,7 +280,6 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="settings_sensor_connecting">Připojeno k senzoru %1$s</string>
<string name="settings_sensor_disconnected">Odpojeno od %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Zeměpisná šířka/délka</string>
<string name="settings_stats_rate_title">Preferovaná rychlost</string>
<string name="settings_stats_units_imperial">Imperiální jednotky (míle, stopy)</string>
<string name="settings_stats_units_metric">Metrické jednotky (km, m)</string>
@@ -521,4 +520,5 @@ Pokud zařízení GPS hlásí nepřesná data (např. Polohu, rychlost, nadmořs
<string name="track_delete_not_recording">Není možno vymazat aktuálně zaznamenávanou trasu.</string>
<string name="settings_sensor_cycling_distance_speed">Jízda na kole: vzdálenost a rychlost</string>
<string name="settings_sensor_cycling_cadence">Cyklistika: kadence</string>
<string name="settings_sensor_running_speed_and_cadence">Běh: vzdálenost, rychlost a kadence</string>
</resources>
+1 -1
View File
@@ -282,7 +282,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
<string name="settings_sensor_connecting">Tilsluttet %1$s</string>
<string name="settings_sensor_disconnected">Frakoblet %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Breddegrad/længdegrad</string>
<string name="settings_stats_rate_title">Foretrukkenfartenhed</string>
<string name="settings_stats_units_imperial">Britisk (mil, ft)</string>
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
@@ -513,4 +512,5 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
<string name="track_delete_not_recording">Dette spor kan ikke slettes, da det er optaget i øjeblikket.</string>
<string name="settings_sensor_cycling_distance_speed">Cykling: afstand og hastighed</string>
<string name="settings_sensor_cycling_cadence">Cykling: kadance</string>
<string name="settings_sensor_running_speed_and_cadence">Løb: distance, hastighed og kadence</string>
</resources>
-1
View File
@@ -284,7 +284,6 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
<string name="settings_sensor_connecting">Verbunden mit %1$s</string>
<string name="settings_sensor_disconnected">Verbindung getrennt von %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Breiten-/Längengrad</string>
<string name="settings_stats_rate_title">Bevorzugtes Maß</string>
<string name="settings_stats_units_imperial">Englisch (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
-1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Πλάτος/Μήκος</string>
<string name="settings_stats_rate_title">Προτιμώμενος ρυθμός</string>
<string name="settings_stats_units_imperial">Αγγλοσαξονικό (μίλια, πόδια)</string>
<string name="settings_stats_units_metric">Μετρικό (χλμ, μέτρα)</string>
+3 -1
View File
@@ -279,7 +279,6 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
<string name="settings_sensor_connecting">Conectado a %1$s</string>
<string name="settings_sensor_disconnected">Desconectado de %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitud/Longitud</string>
<string name="settings_stats_rate_title">Ritmo preferido</string>
<string name="settings_stats_units_imperial">Imperial (millas, pies)</string>
<string name="settings_stats_units_metric">Métrica (km, m)</string>
@@ -510,4 +509,7 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="menu_voice_rate">Velocidad de voz</string>
<string name="track_delete_not_recording">Esta pista no se puede borrar, ya que está grabada.</string>
<string name="settings_sensor_running_speed_and_cadence">Correr: distancia, velocidad y cadencia</string>
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distancia y velocidad</string>
<string name="settings_sensor_cycling_cadence">Ciclismo: cadencia</string>
</resources>
+1 -1
View File
@@ -279,7 +279,6 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
<string name="settings_sensor_connecting">Ühendatud anduriga %1$s</string>
<string name="settings_sensor_disconnected">Lahti ühendatud %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Laiuskraad/pikkuskraad</string>
<string name="settings_stats_rate_title">Eelistatud määr</string>
<string name="settings_stats_units_imperial">Inglise mõõtühikud (miili, jalga)</string>
<string name="settings_stats_units_metric">Meetermõõdustik (km, m)</string>
@@ -512,4 +511,5 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
<string name="track_delete_not_recording">Seda pala ei saa kustutada, kuna see on praegu salvestatud.</string>
<string name="settings_sensor_cycling_cadence">Jalgrattasport: cadence</string>
<string name="settings_sensor_cycling_distance_speed">Jalgrattasport: kaugus ja kiirus</string>
<string name="settings_sensor_running_speed_and_cadence">Running: kaugus, kiirus ja kadents</string>
</resources>
+1 -1
View File
@@ -280,7 +280,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
<string name="settings_sensor_connecting">Honekin konektatuta: %1$s</string>
<string name="settings_sensor_disconnected">Deskonektatuta %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitudea/Longitudea</string>
<string name="settings_stats_rate_title">Erritmo hobetsia</string>
<string name="settings_stats_units_imperial">Inperiala (mi, ft)</string>
<string name="settings_stats_units_metric">Metrikoa (km, m)</string>
@@ -513,4 +512,5 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
<string name="track_delete_not_recording">Pista hau ezin da ezabatu da gaur egun grabatzen.</string>
<string name="settings_sensor_cycling_cadence">Txirrindularitza: kadentzia</string>
<string name="settings_sensor_cycling_distance_speed">Txirrindularitza: distantzia eta abiadura</string>
<string name="settings_sensor_running_speed_and_cadence">Korrika: distantzia, abiadura, eta kadentzia</string>
</resources>
-1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">بلوتوث</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">عرض جغرافیایی/ طول جغرافیایی</string>
<string name="settings_stats_rate_title">اولویت سنجه سرعت</string>
<string name="settings_stats_units_imperial">انگلیسی (مایل، فوت)</string>
<string name="settings_stats_units_metric">متریک (کیلومتر، متر)</string>
+2 -2
View File
@@ -30,7 +30,7 @@ limitations under the License.
* luo merkkejä (mukaan lukien kuvia)\n
* toimii täysin ilman verkkoyhteyttä\n
* näytä jäljet kartalla (ks. alla)</string>
<string name="help_track_map_title">Miten radan voi näyttää kartalla\?</string>
<string name="help_track_map_title">Miten näyttää rata kartalla\?</string>
<string name="help_track_map_content"> Käytä pientä karttakuvaketta.
\n a) (live-päivityksiä) Asenna <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Se tarjoaa online-tai offline-karttoja kautta <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n OSMDashboard on erillinen sovellus, joka voi lukea väliaikaisesti reittien dataa näyttääkseen ne.
@@ -284,7 +284,6 @@ limitations under the License.
<string name="settings_sensor_connecting">Yhdistetty: %1$s</string>
<string name="settings_sensor_disconnected">Yhteys katkaistu: %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Leveys-/pituusaste</string>
<string name="settings_stats_rate_title">Haluttu nopeus</string>
<string name="settings_stats_units_imperial">Brittiläinen järj.
(maili, jalka)</string>
@@ -515,4 +514,5 @@ limitations under the License.
<string name="track_delete_not_recording">Tätä raitaa ei voi poistaa, koska se on parhaillaan tallennettuna.</string>
<string name="settings_sensor_cycling_cadence">Pyöräily: tahtiaika</string>
<string name="settings_sensor_cycling_distance_speed">Pyöräily: etäisyys ja nopeus</string>
<string name="settings_sensor_running_speed_and_cadence">Juoksu: etäisyys, nopeus ja askeltiheys</string>
</resources>
-1
View File
@@ -176,7 +176,6 @@
<string name="settings_reset_confirm_title">Réinitialiser maintenant?</string>
<string name="settings_reset_done">Les valeurs par défaut de tous les paramètres ont bien été rétablies</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitude/longitude</string>
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
-1
View File
@@ -176,7 +176,6 @@
<string name="settings_reset_confirm_title">Réinitialiser maintenant ?</string>
<string name="settings_reset_done">Les valeurs par défaut de tous les paramètres ont bien été rétablies.</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
+3 -3
View File
@@ -279,7 +279,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="settings_sensor_connecting">Connecté à %1$s</string>
<string name="settings_sensor_disconnected">Déconnecté de %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_rate_title">Mode de calcul souhaité</string>
<string name="settings_stats_units_imperial">Unités impériales (mi, pi)</string>
<string name="settings_stats_units_metric">Unités métriques (km, m)</string>
@@ -498,8 +497,8 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="import_progress_summary_exists_msg">Il existe déjà</string>
<string name="import_progress_summary_errors_msg">Erreur</string>
<plurals name="generic_completed_with_errors">
<item quantity="one" tools:ignore="ImpliedQuantity">Terminé avec une erreur</item>
<item quantity="other">Terminé avec des erreurs %1$d</item>
<item quantity="one">Terminé avec %1$d erreur</item>
<item quantity="other">Terminé avec des %1$d erreurs</item>
</plurals>
<string name="settings_default_trackfileformat">Format de fichier d\'exportation / partage</string>
<string name="description_altitude_loss">Dénivelé négatif : %1$d m (%2$d ft)</string>
@@ -512,4 +511,5 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
<string name="track_delete_not_recording">Cette piste ne peut pas être supprimée car elle est en cours d\'enregistrement.</string>
<string name="settings_sensor_cycling_distance_speed">Cyclisme : distance et vitesse</string>
<string name="settings_sensor_cycling_cadence">Vélo : cadence</string>
<string name="settings_sensor_running_speed_and_cadence">Course à pied : distance, vitesse et cadence</string>
</resources>
+1 -1
View File
@@ -294,7 +294,6 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="settings_sensor_disconnected">Desconectado de %1$s</string>
<string name="settings_sensor_wheel_circumference">Circunferencia da roda (mm)</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitude/lonxitude</string>
<string name="settings_stats_rate_title">Indicador preferido</string>
<string name="settings_stats_units_imperial">Imperiais (millas, pés)</string>
<string name="settings_stats_units_metric">Métricas (km, m)</string>
@@ -518,4 +517,5 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
<string name="track_delete_not_recording">Esta pista non se pode eliminar porque está a funcionar a gravación.</string>
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distancia e velocidade</string>
<string name="settings_sensor_cycling_cadence">Ciclismo: cadencia</string>
<string name="settings_sensor_running_speed_and_cadence">Execución: distancia, velocidade, e cadencia</string>
</resources>
-1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">अक्षांश/देशांतर</string>
<string name="settings_stats_rate_title">पसंदीदा दर</string>
<string name="settings_stats_units_imperial">इंपीरियल (मील, फुट)</string>
<string name="settings_stats_units_metric">मेट्रिक (किमी, मी)</string>
-1
View File
@@ -214,7 +214,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Geografska širina/duljina</string>
<string name="settings_stats_rate_title">Preferirana stopa</string>
<string name="settings_stats_units_imperial">Imperijalne (milje, stope)</string>
<string name="settings_stats_units_metric">Metričke (km, m)</string>
+1 -1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Szélesség/hosszúság</string>
<string name="settings_stats_rate_title">Mérték választása</string>
<string name="settings_stats_units_imperial">Angolszász (mérföld, láb)</string>
<string name="settings_stats_units_metric">Metrikus (km, m)</string>
@@ -506,4 +505,5 @@ limitations under the License.
<string name="track_delete_not_recording">Ez a sáv nem törölhető, mivel jelenleg is rögzítve van.</string>
<string name="settings_sensor_cycling_cadence">Kerékpározás: lépésszám</string>
<string name="settings_sensor_cycling_distance_speed">Kerékpározás: táv és sebesség</string>
<string name="settings_sensor_running_speed_and_cadence">Futás: távolság, sebesség és ritmus</string>
</resources>
-1
View File
@@ -175,7 +175,6 @@
<string name="settings_reset_confirm_title">Setel ulang sekarang?</string>
<string name="settings_reset_done">Semua setelan telah dikembalikan ke nilai default</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Lintang/Bujur</string>
<string name="settings_stats_rate_title">Laju yang dipilih</string>
<string name="settings_stats_units_imperial">Imperial (mil, kaki)</string>
<string name="settings_stats_units_metric">Metrik (km, m)</string>
+1 -1
View File
@@ -255,7 +255,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
<string name="settings_stats_rate_title">Velocità preferita</string>
<string name="settings_stats_units_imperial">Imperiale (mi, ft)</string>
<string name="settings_stats_units_metric">Metrico (km, m)</string>
@@ -518,4 +517,5 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
<string name="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente registrata.</string>
<string name="settings_sensor_cycling_cadence">Ciclismo: cadenza</string>
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distanza e velocità</string>
<string name="settings_sensor_running_speed_and_cadence">Come mostrare una traccia su una mappa</string>
</resources>
-1
View File
@@ -178,7 +178,6 @@
<string name="settings_reset_confirm_title">לאפס כעת?</string>
<string name="settings_reset_done">כל ההגדרות הוחזרו לערכי ברירת המחדל</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">קו רוחב/קו אורך</string>
<string name="settings_stats_rate_title">קצב מועדף</string>
<string name="settings_stats_units_imperial">אימפריאלי (מייל, רגל)</string>
<string name="settings_stats_units_metric">מטרי (ק\"מ, מ\')</string>
-1
View File
@@ -212,7 +212,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">緯度/経度</string>
<string name="settings_stats_rate_title">計測方法</string>
<string name="settings_stats_units_imperial">ヤード・ポンド法(マイル、フィート)</string>
<string name="settings_stats_units_metric">メートル法(km、m)</string>
-1
View File
@@ -212,7 +212,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">블루투스</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">위도/경도</string>
<string name="settings_stats_rate_title">선호하는 속도</string>
<string name="settings_stats_units_imperial">야드파운드법(마일, 피트)</string>
<string name="settings_stats_units_metric">미터법(km,m)</string>
+24 -2
View File
@@ -214,7 +214,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Platuma ir ilguma</string>
<string name="settings_stats_rate_title">Pageidaujamas greitis</string>
<string name="settings_stats_units_imperial">Imperinės (myl., pėdos)</string>
<string name="settings_stats_units_metric">Metrinės (km, m)</string>
@@ -396,7 +395,13 @@ limitations under the License.
\n * sukurti žymeklius (su paveikslėliais)
\n * veikia visiškai neprisijungus
\n * rodyti takelius žemėlapyje (žiūrėti žemiau)</string>
<string name="help_gps_troubleshooting_content"></string>
<string name="help_gps_troubleshooting_content"> Ką galite padaryti:
\n Patikrinkite, ar įjungtas GPS,
\n b) pagerinti GPS priėmimą (pvz., išeiti į lauką),
\n c) patikrinkite naudodami <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> programą ir
\n d) išjungti energijos taupymą.
\n
\n Jei viskas nepadeda, praneškite apie klaidą. </string>
<string name="export_all_do_it_for_all">darykite tai dėl visų konfliktų</string>
<string name="sensor_state_power_avg">Vidutinė galia</string>
<string name="track_distance_notification">Atstumas: %1$s</string>
@@ -458,4 +463,21 @@ limitations under the License.
<string name="generic_show_errors">Rodyti klaidas</string>
<string name="gps_fixed_and_ready">GPS fiksuotas ir paruoštas</string>
<string name="help_feature_title">OpenTracks turi šias funkcijas:</string>
<string name="import_unable_to_import_file">Nepavyksta importuoti failo: %1$s</string>
<string name="settings_sensor_running_speed_and_cadence">Bėgimas: atstumas, greitis ir dažnis</string>
<string name="settings_night_mode_title">UI tema</string>
<string name="import_file_imported">Importuotas failas %1$s</string>
<string name="import_parser_error">Parserio klaida: %1$s</string>
<string name="help_track_map_content"> Naudokite mažą žemėlapio piktogramą.
\n a) (tiesioginiai atnaujinimai) Įdiekite <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Joje pateikiami internetiniai arba neprisijungusieji žemėlapiai per <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n Iš tikrųjų OSMDashboard yra atskira programa, kuri gali tik pasiekti (laikinai) kelio (-ų), kuris (-ie) bus rodomas (-i), duomenis.
\n b) Per įprastą žemėlapių programą (pvz., <a href="http://osmand.net/">OsmAnd</a> ir <a href="https://maps.me/">MAPS.ME</a>). Šios programos turi palaikyti KMZ failo formatą (tik vietovių ir laiko žymas). Tiesą sakant, programa turi palaikyti <i>AKCIJŲ_VADOVĄ</i> su MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_support_content"> Galite:
\n a) skleisti žinią apie \"OpenTracks\",
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">pasiūlyti funkcijas</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">pranešti apie klaidas</a>,
\n d) padėti kurti programinę įrangą,
\n e) pirkti <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">\"Google Play\" parduotuvėje</a>,
\n f) aukoti ir
\n g) darykite viską, kas, jūsų manymu, padeda \"OpenTracks\". </string>
</resources>
+133 -7
View File
@@ -186,7 +186,7 @@ limitations under the License.
<string name="sensor_state_heart_rate_value">%1$d sit./min</string>
<string name="sensor_state_power">Jauda</string>
<string name="sensor_state_power_value">%1$d W</string>
<string name="settings_not_while_recording">Nav pieejams maršruta ierakstīšanas laikā.</string>
<string name="settings_not_while_recording">Nav pieejams maršruta ierakstīšanas laikā</string>
<string name="settings_show">Rādīt</string>
<!-- Settings Advanced -->
<string name="settings_advanced">Papildu iestatījumi</string>
@@ -196,7 +196,7 @@ limitations under the License.
<string name="settings_recording">Notiek ierakstīšana</string>
<string name="settings_recording_default_activity_title">Noklusējuma aktivitātes veids</string>
<string name="settings_recording_location_frequency_summary">%1$s starp ierakstītajām atrašanās vietām</string>
<string name="settings_recording_max_recording_distance_summary">Ja attālums starp divām atrašanās vietām ir lielāks par %1$s, tiek izveidots jauns atzars.</string>
<string name="settings_recording_max_recording_distance_summary">Ja attālums starp divām atrašanās vietām ir lielāks par %1$s, tiek izveidots jauns atzars</string>
<string name="settings_recording_max_recording_distance_title">Maksimālais attālums</string>
<string name="settings_recording_min_recording_distance_title">Attāluma intervāla ierakstīšana</string>
<string name="settings_recording_min_recording_interval_title">Laika intervāla ierakstīšana</string>
@@ -214,14 +214,14 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Platums/garums</string>
<string name="settings_stats_rate_title">Vēlamais rādītājs</string>
<string name="settings_stats_units_imperial">Standarta (jūdzes, pēdas)</string>
<string name="settings_stats_units_metric">Metriskās (km, m)</string>
<string name="settings_stats_units_title">Vēlamās mērvienības</string>
<!-- Share Track -->
<string name="share_track_share_file_body">Manuprāt, jūs varētu interesēt šis maršruts.\n\nLai to aplūkotu, izmantojiet pakalpojumu Google Earth vai citu
kartēšanas programmatūru.\n\n%1$s</string>
<string name="share_track_share_file_body">Manuprāt, jūs varētu interesēt šis maršruts.
\n
\n%1$s</string>
<string name="share_track_subject">Vēlos ar jums koplietot maršrutu</string>
<!-- Stats -->
<string name="stats_average_moving_pace">Vidējais pārvietošanās temps</string>
@@ -262,7 +262,7 @@ limitations under the License.
<string name="unit_kilometer_per_hour">km/h</string>
<string name="unit_meter">m</string>
<string name="unit_mile">jūdzes</string>
<string name="unit_mile_per_hour">jūdzes/stundā</string>
<string name="unit_mile_per_hour">jūdzes</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/jūdzē</string>
<!-- Value -->
@@ -280,7 +280,7 @@ limitations under the License.
<string name="value_integer_mile">%1$d jūdze(-es)</string>
<string name="value_integer_mile_hour">%1$d jūdze(-es)/h</string>
<string name="value_integer_minute">%1$d min</string>
<string name="value_integer_second">%1$d sek.</string>
<string name="value_integer_second">%1$d sek</string>
<string name="value_none">Nav</string>
<string name="value_off">Izslēgts</string>
<string name="value_smallest_recommended">Mazākais (ieteicams)</string>
@@ -367,4 +367,130 @@ limitations under the License.
<string name="waypoint_type_waterfall">ūdenskritums</string>
<string name="waypoint_type_water_fountain">ūdens strūklaka</string>
<string name="waypoint_type_waypoint">atrašanās vietas punkts</string>
<string name="export_progress_summary_new_msg">Eksportēti jauni celiņi</string>
<string name="export_all_do_it_for_all">darīt to visos konfliktos</string>
<string name="import_unable_to_import_file">Nevar importēt failu: %1$s</string>
<string name="menu_voice_rate">Balsošanas ātrums</string>
<string name="track_delete_not_recording">Šo ierakstu nevar dzēst, jo tas pašlaik ir ierakstīts.</string>
<string name="about_url">Projekta tīmekļa vietne: <a href="%1$s">%1$s</a></string>
<string name="about_version_code">Versijas kods: %1$d</string>
<string name="about_preference_title">Par OpenTracks</string>
<string name="export_error_post_workout">Pēc treniņa eksportēšana neizdevās, lūdzu, pārbaudiet eksporta direktoriju.</string>
<string name="settings_sensor_running_speed_and_cadence">Skriešana: distance, ātrums un kadence</string>
<string name="help_support_title">Kā atbalstīt OpenTracks\?</string>
<string name="open_track_as_KML">Atvērts kā KML</string>
<string name="import_parser_error">Kļūda parserī: %1$s</string>
<string name="export_progress_summary_skip_msg">Izlaistie celiņi</string>
<string name="help_storage_pictures_title">Kā OpenTracks saglabā attēlus, kas uzņemti kā marķieri\?</string>
<string name="generic_click_twice_cancel">Vēlreiz noklikšķiniet atpakaļ, lai atceltu</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="settings_sensor_cycling_distance_speed">Riteņbraukšana: distance un ātrums</string>
<string name="track_detail_intervals_tab">Intervāli</string>
<string name="track_distance_notification">Atstatums: %1$s</string>
<string name="about_description"><i>OpenTracks</i> ir sporta izsekošanas programma, kas pilnībā ievēro jūsu konfidencialitāti.</string>
<string name="stats_loss">Zaudējums</string>
<string name="about_history">OpenTracks ir atvasinājums no <a href="https://code.google.com/archive/p/mytracks/"><i>Google MyTracks</i></a> (pārtraukta 2016. gadā).</string>
<string name="settings_prevent_reimport_tracks_title">Dziesmu atkārtotas importēšanas novēršana</string>
<string name="about_third_party_data">Trešo pušu dati</string>
<string name="settings_sensor_wheel_circumference">Riteņa apkārtmērs (mm)</string>
<string name="help_feature_title">OpenTracks ir šādas iespējas:</string>
<string name="settings_sensor_cycling_cadence">Riteņbraukšana: cadence</string>
<string name="help_feature_content">* izveidot GPS trases
\n * izmantot sirdsdarbības sensoru (tikai Bluetooth LE)
\n * balss paziņojumi (pēc laika vai attāluma)
\n * izveidot marķierus (tostarp attēlus)
\n * darbojas pilnībā bezsaistē
\n * rādīt trases kartē (skatiet tālāk)</string>
<string name="sensor_state_heart_rate_max">Maksimālais pulss</string>
<string name="help_storage_pictures_content">Tie tiek glabāti lietotnes iekšējā atmiņā (t. i., pieejami tikai OpenTracks ietvaros).</string>
<string name="sensor_state_cadence_max">Maks. kadence</string>
<string name="help_gps_troubleshooting_title">OpenTracks nereģistrē vietas\?</string>
<string name="menu_resume_track">Atkārtot dziesmu</string>
<string name="help_track_export_content"> Trases var eksportēt no iestatījumiem kā KMZ, KML vai GPX. KMZ/KML visi dati; GPX tikai vietas. Trases, kas kopīgotas, izmantojot <i>kopīgošanas ikonu</i>, tiek kopīgotas kā KMZ. </string>
<string name="import_progress_summary_errors_msg">Kļūdas</string>
<string name="help_gps_troubleshooting_content"> Ko jūs varat darīt:
\n a) pārbaudīt, vai ir ieslēgts GPS,
\n b) uzlabot GPS uztveršanu (piemēram, doties ārā),
\n c) pārbaudīt, izmantojot lietotni <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, un
\n d) atslēgt enerģijas taupīšanu.
\n
\n Ja viss nepalīdz, lūdzu, ziņojiet par kļūdu. </string>
<string name="import_progress_summary_ok_msg">Importēti faili</string>
<string name="export_track_errors">Neeksportētie celiņi</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">Rādīt kļūmes</string>
<string name="generic_completed">Pabeigti</string>
<string name="gps_disabled_msg">GPS izslēgts</string>
<string name="import_no_kml_file_found">KMZ nav atrasts dok.kml fails</string>
<string name="settings_default_trackfileformat">Eksporta/ koplietošanas faila formāts</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Rādīt statistiku bloķēšanas ekrānā</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Ierakstīšanas laikā ir pilnekrāna režīmā.</string>
<string name="settings_error_initial_values_restored">Atvainojiet, bet ir notikusi kļūda. Visi iestatījumi ir atjaunoti līdz noklusējuma vērtībām.</string>
<string name="settings_default_export_uri_title">Dziesmu eksporta direktorija</string>
<string name="instant_export_enabled_title">Tūlītējs eksports pēc treniņa</string>
<string name="voice_pace_per_kilometer">%1$s par kilometru</string>
<string name="interval_list_empty_message">Pagaidām nav intervālu</string>
<string name="settings_night_mode_option_night">Nakts</string>
<string name="import_thread_interrupted">Vītne pārtraukts</string>
<string name="track_speed_notification">Ātrums: %1$s</string>
<string name="permission_gps_failed">Programmai OpenTracks ir nepieciešama atļauja izmantot GPS.</string>
<string name="help_recording_content">OpenTracks izmanto ierīces GPS sensoru, lai nepārtraukti reģistrētu atrašanās vietu. Ja GPS ierīce ziņo neprecīzus datus (piemēram, atrašanās vietas, ātruma, augstuma), OpenTracks nevar neko darīt.</string>
<string name="sensor_could_not_scan">Nebija iespējams skenēt Bluetooth ierīces (kļūda %1$i).</string>
<string name="export_without_photos">bez fotogrāfijām</string>
<string name="import_prevent_reimport">Jūs esat iestatījis novērst atkārtotas importēšanas dziesmu</string>
<string name="hold_to_pause">Turiet, lai pauzē</string>
<string name="gps_fixed_and_ready">GPS ir fiksēts un gatavs</string>
<string name="settings_night_mode_title">UI tēma</string>
<string name="stats_split_interval">Sadalītais intervāls</string>
<string name="hold_to_stop">Turēt, lai apturētu</string>
<string name="export_track_already_exists_msg">Dziesma %1$s jau eksistē galamērķa direktorijā.</string>
<string name="marker_add_photo_canceled">Atcelts, foto zīme nav pievienota</string>
<string name="help_track_export_title">Kā es varu eksportēt/ kopīgot ierakstītos ierakstus\?</string>
<string name="voice_pace_lap">Klēpes laiks %1$s</string>
<string name="instant_export_enabled_summary">Dziesmas eksportēšana uz krātuvi pēc ierakstīšanas pabeigšanas</string>
<string name="activity_type_escooter">eScooter</string>
<string name="track_delete_progress">%1$d/%2$d</string>
<string name="sensor_state_power_avg">Vidējā jauda</string>
<string name="voice_template">%1$s in %2$s pie %3$s</string>
<string name="about_libraries">Bibliotēkas</string>
<string name="description_default_speed_or_pace">Pēc aktivitātes veida</string>
<string name="import_progress_summary_exists_msg">Jau eksistē</string>
<string name="delete_service">Dzēšanas pakalpojuma</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Ierakstīšanas laikā rādiet statistiku, neatbloķējot ierīci.</string>
<string name="import_file_imported">Failu %1$s importēšana</string>
<string name="settings_sensor_connecting">Saistīts ar %1$s</string>
<string name="track_recording_notification_accuracy">Atrašanās vietas precizitāte: %1$s</string>
<string name="share_image_subject">Vēlos dalīties ar jums kādā attēlā</string>
<string name="import_error_list_dialog_title">Faili nav importēti</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Pilna ekrāna</string>
<string name="generic_skip">Izlaist .</string>
<string name="open_track_as_trackfileformat">Atvērts kā %1$s</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Ekrāna ieslēgšana</string>
<string name="share_image_body">Domāju, ka jūs varētu ieinteresēt šis attēls.</string>
<string name="export_progress_summary_overwrite_msg">Pārrakstītās dziesmas</string>
<string name="help_data_google_play_services_title">Vai OpenTracks ir nepieciešams/izmantots Google Play pakalpojums\?</string>
<string name="menu_insert_gallery_img">Galerijas attēlu ievietošana</string>
<string name="help_data_storage_content"> Sensoru dati tiek saglabāti katrai reģistrētajai vietai. Runājot par sirdsdarbības sensoru, tas nozīmē, ka tikai <i>viens</i> sirdsdarbības frekvences mērījums tiek saglabāts katrā atrašanās vietā. Ja stāvat nekustīgi vai atrodaties iekštelpās, sensora dati netiks ierakstīti (bet tie tiks parādīti). </string>
<string name="export_progress_summary_errors_msg">Kļūdas</string>
<string name="settings_night_mode_option_day">Diena</string>
<string name="open_track_as_GPX">Atvērts kā GPX</string>
<string name="description_altitude_loss">Augstuma zudums: %1$d m (%2$d ft)</string>
<string name="sensor_state_cadence_avg">Vidējā kadence</string>
<string name="settings_night_mode_option_system">Sistēmas</string>
<string name="help_track_map_title">Kā attēlot trasi kartē\?</string>
<string name="voice_speed_lap">Klēpes ātrums %1$s</string>
<string name="generic_overwrite">Pārraksti</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Ierakstīšanas laikā ekrāns ir ieslēgts.</string>
<string name="import_unsupported_format">Neatbalstīts faila formāts</string>
<string name="track_pace_notification">Temps: %1$s</string>
<string name="about_version_name">Versijas vārds: %1$s</string>
<string name="help_data_storage_title">Kā OpenTracks glabā sensoru datus\?</string>
<string name="help_recording_title">Kā OpenTracks ieraksta datus\?</string>
<string name="voice_pace_per_mile">%1$s par jūdzi</string>
<string name="export_with_photos">ar fotogrāfijām</string>
<string name="sensor_state_heart_rate_avg">Vidējais sirdsdarbība</string>
<string name="generic_open_track">Atvērtā trase</string>
<string name="aggregated_stats_empty_message">Ierakstiet savu pirmo dziesmu, lai redzētu apkopoto statistiku</string>
<string name="settings_sensor_disconnected">Atslēgts no %1$s</string>
<string name="bluetooth_disabled">Lūdzu, iespējojiet Bluetooth.</string>
</resources>
-1
View File
@@ -177,7 +177,6 @@
<string name="settings_reset_confirm_title">Resetați acum?</string>
<string name="settings_reset_done">Toate setările au revenit la valorile prestabilite</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitudine/longitudine</string>
<string name="settings_stats_rate_title">Rata de preferat</string>
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
<string name="settings_stats_units_metric">Metric (km, m)</string>
-1
View File
@@ -212,7 +212,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitud/Longitud</string>
<string name="settings_stats_rate_title">Kadar pilihan</string>
<string name="settings_stats_units_imperial">Imperial (bt, kaki)</string>
<string name="settings_stats_units_metric">Metrik (km, m)</string>
+2 -2
View File
@@ -183,7 +183,7 @@ limitations under the License.
<string name="sensor_state_cadence_value">%1$d o/min</string>
<string name="sensor_state_heart_rate">Puls</string>
<string name="sensor_state_heart_rate_value">%1$d slag/min</string>
<string name="sensor_state_power">Strøm</string>
<string name="sensor_state_power">Effekt</string>
<string name="sensor_state_power_value">%1$d W</string>
<string name="settings_not_while_recording">Ikke tilgjengelig mens du tar opp et spor</string>
<string name="settings_show">Vis</string>
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Bredde- og lengdegrad</string>
<string name="settings_stats_rate_title">Foretrukket måling</string>
<string name="settings_stats_units_imperial">Britisk (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
@@ -506,4 +505,5 @@ limitations under the License.
<string name="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string>
<string name="settings_sensor_cycling_cadence">Sykling: kadens</string>
<string name="settings_sensor_cycling_distance_speed">Sykling: distanse og hastighet</string>
<string name="settings_sensor_running_speed_and_cadence">Løping: distanse, hastighet og kadens</string>
</resources>
+1 -1
View File
@@ -279,7 +279,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="settings_sensor_connecting">Verbonden met %1$s</string>
<string name="settings_sensor_disconnected">Ontkoppeld van %1$s</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Breedte-/lengtegraad</string>
<string name="settings_stats_rate_title">Voorkeursverhouding</string>
<string name="settings_stats_units_imperial">Engels (mi, ft)</string>
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
@@ -512,4 +511,5 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
<string name="track_delete_not_recording">Dit fragment kan niet worden gewist omdat het momenteel is opgenomen.</string>
<string name="settings_sensor_cycling_cadence">Fietsen: cadans</string>
<string name="settings_sensor_cycling_distance_speed">Fietsen: afstand en snelheid</string>
<string name="settings_sensor_running_speed_and_cadence">Lopen: afstand, snelheid en cadans</string>
</resources>
+1 -1
View File
@@ -242,7 +242,6 @@ limitations under the License.
<string name="settings_sensor_disconnected">Rozłaczony z %1$s</string>
<string name="settings_sensor_wheel_circumference">Obwód koła (mm)</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Długości i szerokości geograficznej</string>
<string name="settings_stats_rate_title">Wybór tempo/prędkość</string>
<string name="settings_stats_units_imperial">Imperialne (mile, stopy)</string>
<string name="settings_stats_units_metric">Metryczne (km, m)</string>
@@ -528,4 +527,5 @@ limitations under the License.
<string name="track_delete_not_recording">Tej ścieżki nie można usunąć, ponieważ jest ona aktualnie nagrywana.</string>
<string name="settings_sensor_cycling_distance_speed">Kolarstwo: dystans i prędkość</string>
<string name="settings_sensor_cycling_cadence">Jazda na rowerze: kadencja</string>
<string name="settings_sensor_running_speed_and_cadence">Bieganie: dystans, prędkość i kadencja</string>
</resources>
+1 -1
View File
@@ -176,7 +176,6 @@
<string name="settings_reset_confirm_title">Redefinir agora?</string>
<string name="settings_reset_done">Todas as configurações foram revertidas para os valores padrão</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitude/longitude</string>
<string name="settings_stats_rate_title">Taxa preferencial</string>
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
<string name="settings_stats_units_metric">Métrica (km, m)</string>
@@ -457,4 +456,5 @@
</plurals>
<string name="settings_sensor_cycling_cadence">Ciclismo: cadência</string>
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distância e velocidade</string>
<string name="settings_sensor_running_speed_and_cadence">Corrida: distância, velocidade e cadência</string>
</resources>
-1
View File
@@ -176,7 +176,6 @@
<string name="settings_reset_confirm_title">Repor agora?</string>
<string name="settings_reset_done">Todas as definições foram revertidas para os valores predefinidos</string>
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_stats_coordinate">Latitude/Longitude</string>
<string name="settings_stats_rate_title">Avaliação preferida</string>
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
<string name="settings_stats_units_metric">Métrico (km, m)</string>
-1
View File
@@ -213,7 +213,6 @@ limitations under the License.
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate">Latitude/longitude</string>
<string name="settings_stats_rate_title">Taxa preferencial</string>
<string name="settings_stats_units_imperial">Imperial (milhas, pés)</string>
<string name="settings_stats_units_metric">Métrica (km, m)</string>

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