Compare commits

..

88 Commits

Author SHA1 Message Date
Dennis Guse 0605dbb0ab Release: v4.3.1 2023-04-07 18:11:52 +02:00
Dennis Guse bf03de6be7 Bugfix: GPS disabled warning was showing (repeatedly but incorrectly).
Fixes #1429.
2023-04-07 11:13:57 +02:00
Dennis Guse d65ce361f7 Upgrade gradlew to 8.0.2. 2023-04-06 07:37:43 +02:00
Dennis Guse 3f600f6be7 Voice announcements: added setting to skip announcements on the device's internal speaker.
Fixes #1392.
2023-04-05 18:26:29 +02:00
dependabot[bot] 9aab3c216c Bump com.android.tools:desugar_jdk_libs from 2.0.2 to 2.0.3
Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-03 19:25:16 +02:00
Dennis Guse 7935185812 UI: split stats_item between generic and sensor (some bugfixes).
Part of #24.
2023-03-29 21:50:42 +02:00
Dennis Guse 4c1b1be96c UI: split stats_item between generic and sensor.
Part of #24.
2023-03-28 21:08:46 +02:00
Dennis Guse 5762830655 Merge pull request #1461 from OpenTracksApp/refactorSensor
Refactor: unify the internal sensor handling.
2023-03-28 21:08:08 +02:00
Dennis Guse 6d61206782 Renamed GPSHandler to GPSManager. 2023-03-28 20:58:23 +02:00
Dennis Guse b9dedef292 Refactor: introduced SensorManager. 2023-03-28 20:58:23 +02:00
Dennis Guse f3caecd049 Refactor: rename BluetoothConnectionManager. 2023-03-27 22:59:31 +02:00
Dennis Guse e16780277f Refactor: introduced SensorConnector interface. 2023-03-27 22:59:31 +02:00
Dennis Guse 13484685a3 Merge remote-tracking branch 'weblate/main' 2023-03-25 09:06:27 +01:00
plum7x 7b745d9837 Update strings.xml 2023-03-17 07:41:32 +01:00
plum7x 52742064f6 Updated traditional Chinese translation (#1498) 2023-03-14 20:50:51 +01:00
josé m 2edf5582aa Translated using Weblate (Galician)
Currently translated at 100.0% (505 of 505 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/gl/
2023-03-11 18:37:57 +01:00
dependabot[bot] 808eb92bf2 Bump org.mockito:mockito-android from 4.11.0 to 5.2.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 4.11.0 to 5.2.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.11.0...v5.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-10 06:13:23 +01:00
Dennis Guse c0b84c3d72 Release: v4.3.0 2023-03-09 05:20:22 +01:00
Miguel Almeida e275d3e5c0 Enable backups if client side encryption is enabled 2023-03-08 23:12:26 +01:00
Dennis Guse b4ebad2c3c Merge remote-tracking branch 'weblate/main' 2023-03-07 21:25:56 +01:00
dependabot[bot] 1ab76e1317 Bump com.android.tools.build:gradle from 7.4.1 to 7.4.2
Bumps com.android.tools.build:gradle from 7.4.1 to 7.4.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-28 06:04:48 -05:00
nobkd 3d1104dd0b remove typo 2023-02-17 10:44:41 -05:00
dependabot[bot] 2df45d31de Bump androidx.appcompat:appcompat from 1.6.0 to 1.6.1
Bumps androidx.appcompat:appcompat from 1.6.0 to 1.6.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-12 14:06:47 +01:00
Allan Nordhøy 5436981e0d Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.4% (492 of 505 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2023-02-10 07:38:30 +01:00
dependabot[bot] fd2b1bec7d Bump com.android.tools:desugar_jdk_libs from 2.0.1 to 2.0.2
Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 12:18:53 +01:00
elastic rock bb1eaa5b56 Android system per-app language preferences 2023-02-05 00:58:21 +01:00
Peter Storch dae8eaa97a Release: v4.2.2 2023-02-04 21:53:35 +01:00
Peter Storch 565b1ce94d Correct link to F-Droid build monitor 2023-02-04 21:53:24 +01:00
dependabot[bot] 543d5e18bf Bump com.android.tools:desugar_jdk_libs from 2.0.0 to 2.0.1
Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-03 05:05:29 +01:00
dependabot[bot] 01368df6b1 Bump com.android.tools.build:gradle from 7.4.0 to 7.4.1
Bumps com.android.tools.build:gradle from 7.4.0 to 7.4.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>
2023-02-02 07:03:02 +01:00
Dennis Guse c666d55211 Bug: crash due to color not found.
Introduced in 674d231b7a
2023-01-30 23:11:21 +01:00
Dennis Guse 8cc4fd1d4f Bugfix: not all activityTypes were shown in dropdownlist. 2023-01-29 16:50:04 +01:00
Dennis Guse c241a35861 Refactor: introduced ActivityType. 2023-01-29 16:50:04 +01:00
Dennis Guse 674d231b7a Bug: textcolor for heartrate zone 1 was ignoring theme.
Fixes #1455.
2023-01-27 02:00:38 +01:00
Dennis Guse bebee59eb2 Bug: Track icon wrongly shown in dialog.
Fixes #1453.
2023-01-25 16:37:35 +01:00
dependabot[bot] 24f843b0db Bump material from 1.7.0 to 1.8.0
Bumps [material](https://github.com/material-components/material-components-android) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.7.0...1.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-25 07:04:21 +01:00
Dennis Guse f0259d13dd Release: v4.2.1 2023-01-21 19:03:07 +01:00
Dennis Guse 9dff965a52 Cycling speed sensor: wheel diameter can now be 100mm until 4000mm
Fixes #1447.
2023-01-17 22:47:59 +01:00
Mario Ruiz b4e6d10fbe Translated using Weblate (Spanish)
Currently translated at 100.0% (503 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/es/
2023-01-16 12:22:26 +01:00
Dennis Guse 1a9d88277f Fix pt-PT translations. 2023-01-13 08:08:49 +01:00
Dennis Guse 4ea956ca0e Merge branch 'translations' 2023-01-13 08:02:27 +01:00
dependabot[bot] 8060c2e1e3 Bump desugar_jdk_libs from 1.2.2 to 2.0.0
Bumps [desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.2.2 to 2.0.0.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

---
updated-dependencies:
- dependency-name: com.android.tools:desugar_jdk_libs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 08:01:58 +01:00
dependabot[bot] 8c96b65e59 Bump gradle from 7.3.1 to 7.4.0
Bumps gradle from 7.3.1 to 7.4.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 07:47:56 +01:00
Dennis Guse a407e94d3c Upgrade to appcompat:1.6.0 2023-01-11 22:58:23 +01:00
ssantos 310c790324 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (503 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/pt_PT/
2023-01-11 13:50:39 +01:00
Dennis Guse 12b3d31f5c Bugfix: BluetoothLE requires to trigger reconnect on either disconnect or never connected.
Initial connection timeout seems to be 30s.
Fixes #1442.
2023-01-05 20:52:15 +01:00
dependabot[bot] d43dca61c1 Bump runner from 1.5.1 to 1.5.2
Bumps runner from 1.5.1 to 1.5.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-04 07:28:24 +01:00
dependabot[bot] 20c14a03b2 Bump junit from 1.1.4 to 1.1.5
Bumps junit from 1.1.4 to 1.1.5.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-04 07:27:28 +01:00
dependabot[bot] a6601f4eea Bump espresso-core from 3.5.0 to 3.5.1
Bumps espresso-core from 3.5.0 to 3.5.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-04 07:27:04 +01:00
Dennis Guse 220c7b1443 Release: v4.2.0 2023-01-03 23:10:05 +01:00
Dennis Guse 33bb63976d Merge branch 'translations' 2023-01-03 23:04:44 +01:00
Dennis Guse 8db8f68d69 Fix pt translations. 2023-01-03 23:04:37 +01:00
Dennis Guse 99b417217e Bugfix: StatisticsRecordingFragment should only clear viewHolder when the views get destroyed.
Introduced in d70e7b8788
2023-01-02 23:37:38 +01:00
Dennis Guse ac80386b8a Refactor tests: use mocks for BluetoothRemoteSensorManager. 2022-12-30 16:22:46 +01:00
Dennis Guse b96441a964 Refactor: BluetoothUtils return HeartRate. 2022-12-30 16:11:22 +01:00
dependabot[bot] 207f9563df Bump mockito-android from 4.10.0 to 4.11.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.10.0 to 4.11.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.10.0...v4.11.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>
2022-12-29 12:55:30 +01:00
ssantos 7b84bf448b Translated using Weblate (Portuguese)
Currently translated at 100.0% (503 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/pt/
2022-12-29 11:49:03 +01:00
Dennis Guse 41a62fd412 Merge remote-tracking branch 'weblate2/main' 2022-12-27 22:26:30 +01:00
Dan 1087dbf387 Translated using Weblate (Ukrainian)
Currently translated at 88.8% (447 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/uk/
2022-12-18 00:55:30 +01:00
dependabot[bot] e16d1d53c6 Bump mockito-android from 4.9.0 to 4.10.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.9.0 to 4.10.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.9.0...v4.10.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>
2022-12-15 07:31:29 +01:00
Dennis Guse 00c9ba6595 Merge remote-tracking branch 'weblate2/main' 2022-12-08 20:11:00 +01:00
Dennis Guse 7c1995c67f Bugfix: NPE; introduced in b53ef455a6
Fixes #1431.
2022-12-08 20:07:26 +01:00
ovl-1 d2e0ae8b11 Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.8% (492 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2022-11-28 16:47:53 +01:00
Allan Nordhøy f0aa49537d Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.8% (492 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2022-11-26 06:46:57 +01:00
Dennis Guse 14ace294f3 Make Public API summary translatable.
Fixes #1430.
2022-11-25 21:17:18 +01:00
Dennis Guse b53ef455a6 Merge pull request #1427 from OpenTracksApp/heartrate#1118
Heartrate zones
2022-11-25 19:03:56 +01:00
Dennis Guse 3dc6c6607d Removed obsolete test.
Introduced in d70e7b8788
2022-11-25 07:50:25 +01:00
Dennis Guse dd91e05fb6 Bugfix: sensor name/address must be visible.
Introduced in d70e7b8788
2022-11-25 07:42:18 +01:00
Dennis Guse 19fe5275b9 Heartrate zones: adding preference for max heartrate.
Fixes #1118.
2022-11-25 07:41:04 +01:00
Dennis Guse 1aeb9ca332 Heartrate zones: adding preference for max heartrate.
Part of #1118.
2022-11-25 07:05:55 +01:00
Allan Nordhøy 520c240a19 Translated using Weblate (Norwegian Bokmål)
Currently translated at 97.6% (491 of 503 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/nb_NO/
2022-11-25 00:13:22 +01:00
Dennis Guse 48aaca13d0 Migrated to hosted.weblate.org
Reason: weblate.bubu1.eu shutdown end of 2022.
Alternative: translate.codeberg.org
Selection method: flipped a coin

Fixes #1396.
2022-11-24 20:57:47 +01:00
Hosted Weblate 88a31f4f39 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/
2022-11-24 20:49:19 +01:00
Dennis Guse 5ce9e6606b Merge pull request #1386 from OpenTracksApp/refactor#1118
Simplify RecordingLayout so we can create custom UIs #1118
2022-11-22 07:12:12 +01:00
Dennis Guse 4ea70b15e7 Renamed Layout to RecordingLayout.
Part of #1118.
2022-11-22 07:06:56 +01:00
Dennis Guse d3cf2957cc StatisticsCustomLayoutEditAdapter: use StatisticsViewHolder (with demo data).
Part of #1118.
2022-11-22 07:06:47 +01:00
Dennis Guse dbac595754 StatisticsCustomLayoutEditAdapter: use viewBinding.
Part of #1118.
2022-11-22 07:06:47 +01:00
Dennis Guse d70e7b8788 StatisticsRecordingFragment: render Views manually (instead of RecyclerView).
Part of #1118.
2022-11-22 07:06:45 +01:00
dependabot[bot] f6835852b6 Bump mockito-android from 4.8.1 to 4.9.0
Bumps [mockito-android](https://github.com/mockito/mockito) from 4.8.1 to 4.9.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v4.8.1...v4.9.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>
2022-11-15 06:37:13 +01:00
Dennis Guse d5864726f6 StatisticsData: do not expose DataField.
Part of #1118.
2022-11-10 21:45:28 +01:00
Dennis Guse 3cc3271726 Cleanup: move Layout/DataField persistence into one class.
Part of #1118.
2022-11-10 21:45:28 +01:00
Dennis Guse 8aef9c8d28 Refactor StatisticsAdapter to use ViewBindings.
Part of #1118.
2022-11-10 21:45:28 +01:00
dependabot[bot] bcca00d110 Bump core from 1.4.0 to 1.5.0
Bumps core from 1.4.0 to 1.5.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-10 07:41:28 +01:00
dependabot[bot] aa929161b4 Bump orchestrator from 1.4.1 to 1.4.2
Bumps orchestrator from 1.4.1 to 1.4.2.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-10 07:19:57 +01:00
dependabot[bot] 70cb9cbf51 Bump junit from 1.1.3 to 1.1.4
Bumps junit from 1.1.3 to 1.1.4.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-10 07:19:49 +01:00
dependabot[bot] fef3b97661 Bump runner from 1.4.0 to 1.5.1
Bumps runner from 1.4.0 to 1.5.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-09 22:39:56 +01:00
dependabot[bot] 945f366195 Bump espresso-core from 3.4.0 to 3.5.0
Bumps espresso-core from 3.4.0 to 3.5.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-09 22:36:09 +01:00
dependabot[bot] 8c0a851d87 Bump rules from 1.4.0 to 1.5.0
Bumps rules from 1.4.0 to 1.5.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-09 22:35:54 +01:00
108 changed files with 2949 additions and 2053 deletions
+3 -3
View File
@@ -40,8 +40,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</tr>
</table>
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/horizontal-auto.svg" alt="Translation status" />
<a href="https://hosted.weblate.org/engage/opentracks/">
<img src="https://hosted.weblate.org/widgets/opentracks/-/horizontal-auto.svg" alt="Translation status" />
</a>
## Screenshots
@@ -150,7 +150,7 @@ The developer documentation is in [README_API.md](README_API.md).
## Project history
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
_My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
+5 -3
View File
@@ -17,10 +17,10 @@ Application id: de.dennisguse.opentracks
It if it properly tagged F-Droid takes care of building the app and publishing.
Usually, it takes two days before the update is published (build, signed, and published).
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
The recent build activity can be found [here](https://monitor.f-droid.org/builds/build)
## Google Play Store
Application id: de.dennisguse.opentracks.playstore
Application id: `de.dennisguse.opentracks.playstore`
Requirement: `gem install fastlane`
@@ -35,4 +35,6 @@ Supported languages: https://support.google.com/googleplay/android-developer/tab
## Translations (localization)
Translations of the OpenTracks are handled in Weblate: [https://weblate.bubu1.eu/projects/OpenTracks/](https://weblate.bubu1.eu/projects/OpenTracks/).
Translations of the OpenTracks are handled in
Weblate: [https://hosted.weblate.org/projects/opentracks/](https://hosted.weblate.org/projects/opentracks)
.
+17 -13
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.android.tools.build:gradle:7.4.2'
}
}
@@ -59,8 +59,8 @@ android {
defaultConfig {
applicationId "de.dennisguse.opentracks"
versionCode 5080
versionName "v4.1.2"
versionCode 5168
versionName "v4.3.1"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -69,6 +69,8 @@ android {
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
resConfigs "ar", "bg", "ca", "cs", "da", "de", "el", "en", "es", "et", "eu", "fa", "fi", "fr", "ga", "gl", "hi", "hr", "hu", "in", "it", "iw", "ja", "ko", "lt", "lv", "mo", "ms", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "sv", "th", "tl", "tr", "uk", "vi", "zh"
}
signingConfigs {
nightly {
@@ -120,21 +122,23 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-splashscreen:1.0.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1'
androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
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:4.8.1'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'org.mockito:mockito-android:5.2.0'
androidTestUtil 'androidx.test:orchestrator:1.4.1'
androidTestUtil 'androidx.test:orchestrator:1.4.2'
}
@@ -7,5 +7,5 @@ Changes:
Bugfix:
- Speech announcements use MAY_DUCK (again)
- ChartView shows markers (again)
- Fixed landscape layout of TrackListActivity
- Fixed landscape recordingLayout of TrackListActivity
- Request legacy file API behavior (necessary for Android 10)
@@ -0,0 +1,8 @@
v4.2.0: OpenTracks
Changes:
- Show heartrate zone information
Developer:
- Migrated to hosted.weblate.org
- Recording UI uses Views directly
@@ -0,0 +1,8 @@
v4.2.1: OpenTracks
Changes:
- Speed sensor: acceptable wheel diameter is 100mm-4000mm
Bugfix:
- Reconnect to a sensor if connection is lost
@@ -0,0 +1,9 @@
v4.2.2: OpenTracks
Bugfix:
- Track icon wrongly shown in dialog
- Textcolor for heartrate zone 1 was ignoring theme
- Not all activityTypes were shown in dropdownlist
- Crash due to color not found
@@ -0,0 +1,5 @@
v4.3.0: OpenTracks
Changes:
- Allow backups (incl. encrypted to the Google)
- Android 13: allow app-specific language settings
@@ -0,0 +1,8 @@
v4.3.1: OpenTracks
Changes:
- Setting to skip voice announcements on device speaker
Bugfix:
- GPS disabled warning was falsely shown
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+6
View File
@@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+8 -6
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
@@ -12,7 +12,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -166,7 +165,8 @@ public class ExportImportTest {
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
trackPointCreator.setClock("2020-02-02T02:02:18Z");
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
service.endCurrentTrack();
trackPointCreator.setClock("2020-02-02T02:03:20Z");
@@ -179,7 +179,7 @@ public class ExportImportTest {
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
trackPointCreator.setClock("2020-02-02T02:03:24Z");
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
service.endCurrentTrack();
Track track = contentProviderUtils.getTrack(trackId);
@@ -495,37 +495,38 @@ public class ExportImportTest {
}
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator) {
@Override
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
SensorDataSet sensorDataSet = new SensorDataSet();
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
Mockito.when(remoteSensorManager.fill(Mockito.any())).thenAnswer(invocation -> {
TrackPoint trackPoint = invocation.getArgument(0);
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
sensorDataSet.set(cyclingCadence);
SensorDataSet sensorDataSet = new SensorDataSet();
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
if (distance != null && speed != null) {
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
sensorDataSet.set(distanceSpeed);
}
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
sensorDataSet.set(cyclingCadence);
sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet;
if (distance != null && speed != null) {
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
sensorDataSet.set(distanceSpeed);
}
sensorDataSet.fillTrackPoint(trackPoint);
return sensorDataSet;
});
trackPointCreator.getSensorManager().setBluetoothSensorManager(remoteSensorManager);
trackPointCreator.onChange(new SensorDataSet());
}
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
AltitudeSumManager altitudeSumManager = trackPointCreator.getSensorManager().getAltitudeSumManager();
altitudeSumManager.setAltitudeGain_m(altitudeGain);
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
}
@@ -9,6 +9,7 @@ import org.junit.Test;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
@@ -23,10 +24,10 @@ public class BluetoothUtilsTest {
characteristic.setValue(new byte[]{0x02, 0x3C});
// when
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
// then
assertEquals(60, heartRate);
assertEquals(HeartRate.of(60), heartRate);
}
@Test
@@ -36,10 +37,10 @@ public class BluetoothUtilsTest {
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
// when
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
// then
assertEquals(257, heartRate);
assertEquals(HeartRate.of(257), heartRate);
}
@Test
@@ -45,6 +45,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
/**
* Tests insert location.
* Note: on API23, the TrackRecordingService may not be stopped properly before the next test.
* So, if something fails, subsequent tests may be affected.
*/
@RunWith(AndroidJUnit4.class)
public class TrackRecordingServiceTestRecording {
@@ -112,7 +114,7 @@ public class TrackRecordingServiceTestRecording {
String startTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// then
assertEquals(new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null)
@@ -149,7 +151,7 @@ public class TrackRecordingServiceTestRecording {
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String pauseTime = "2020-02-02T02:02:03Z";
@@ -195,7 +197,7 @@ public class TrackRecordingServiceTestRecording {
String starTime = "2020-02-02T02:02:02Z";
trackPointCreator.setClock(starTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
String pauseTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(pauseTime);
@@ -227,7 +229,7 @@ public class TrackRecordingServiceTestRecording {
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
String stopTime = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(stopTime);
@@ -237,7 +239,7 @@ public class TrackRecordingServiceTestRecording {
String resumeTime = "2020-02-02T02:02:04Z";
trackPointCreator.setClock(resumeTime);
service.resumeTrack(trackId);
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// then
new TrackPointAssert().assertEquals(List.of(
@@ -258,12 +260,13 @@ public class TrackRecordingServiceTestRecording {
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
// when
String sensor1 = "2020-02-02T02:02:03Z";
trackPointCreator.setClock(sensor1);
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
String sensor3 = "2020-02-02T02:02:13Z";
@@ -297,7 +300,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
@@ -371,7 +374,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -436,7 +439,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -500,7 +503,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -564,7 +567,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -652,7 +655,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -702,7 +705,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -748,7 +751,7 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
// when
String gps1 = "2020-02-02T02:02:03Z";
@@ -812,8 +815,8 @@ public class TrackRecordingServiceTestRecording {
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
trackPointCreator.setClock(startTime);
Track.Id trackId = service.startNewTrack();
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
// when
String sensor1 = "2020-02-02T02:02:03Z";
@@ -1,15 +1,12 @@
package de.dennisguse.opentracks.services;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import androidx.test.rule.ServiceTestRule;
import java.time.Clock;
import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -22,14 +19,6 @@ public class TrackRecordingServiceTestUtils {
public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException {
// Let's use default values.
PreferencesUtils.clear();
// Reset service (if some previous test failed)
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
.getService();
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, service.getTrackPointCreator()));
service.getTrackPointCreator().setClock(Clock.systemUTC());
service.endCurrentTrack();
}
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(MockitoJUnitRunner.class)
public class GPSHandlerTest {
public class GPSManagerTest {
private final Context context = ApplicationProvider.getApplicationContext();
@@ -34,7 +34,7 @@ public class GPSHandlerTest {
private TrackPointCreator trackPointCreator;
@InjectMocks
private GPSHandler locationHandler;
private GPSManager locationHandler;
@BeforeClass
public static void preSetUp() {
@@ -50,7 +50,7 @@ public class GPSHandlerTest {
Mockito.when(trackPointCreator.createNow())
.thenReturn(Instant.now());
locationHandler.onStart(context, new Handler());
locationHandler.start(context, new Handler());
}
/**
@@ -25,7 +25,7 @@ public class TrackPointCreatorTest {
private TrackPointCreator.Callback server;
@Mock
private GPSHandler locationHandler;
private GPSManager locationHandler;
private TrackPointCreator subject;
@@ -17,7 +17,8 @@ import org.junit.runner.RunWith;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
@RunWith(AndroidJUnit4.class)
public class PreferencesOpenHelperTest {
@@ -38,10 +39,10 @@ public class PreferencesOpenHelperTest {
PreferencesOpenHelper.newInstance(1).check();
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(recordingLayouts);
assertEquals(recordingLayouts.size(), 1);
assertEquals(recordingLayouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
}
@Test
@@ -63,10 +64,10 @@ public class PreferencesOpenHelperTest {
+ PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;";
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(recordingLayouts);
assertEquals(recordingLayouts.size(), 1);
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
}
@Test
@@ -87,10 +88,10 @@ public class PreferencesOpenHelperTest {
String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
// then there should be one layout with old custom layout that has the new CSV value.
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(layouts);
assertEquals(layouts.size(), 1);
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
assertNotNull(recordingLayouts);
assertEquals(recordingLayouts.size(), 1);
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
}
@Test
@@ -109,10 +110,10 @@ public class PreferencesOpenHelperTest {
PreferencesOpenHelper.newInstance(1).check();
// then custom layout should be equals to default layout.
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
RecordingLayout defaultRecordingLayout = RecordingLayoutIO.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
List<RecordingLayout> customRecordingLayout = PreferencesUtils.getAllCustomLayouts();
assertEquals(customLayout.size(), 1);
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
assertEquals(customRecordingLayout.size(), 1);
assertEquals(defaultRecordingLayout.toCsv(), customRecordingLayout.get(0).toCsv());
}
}
@@ -22,7 +22,8 @@ import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
@RunWith(AndroidJUnit4.class)
public class PreferencesUtilsTest {
@@ -85,13 +86,13 @@ public class PreferencesUtilsTest {
editor.commit();
// when
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
// then
assertEquals(layouts.size(), 1);
assertTrue(layouts.get(0).getFields().size() > 0);
assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
assertEquals(recordingLayouts.size(), 1);
assertTrue(recordingLayouts.get(0).getFields().size() > 0);
assertEquals(recordingLayouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(recordingLayouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
}
@Test
@@ -103,12 +104,12 @@ public class PreferencesUtilsTest {
editor.commit();
// when
Layout layout = PreferencesUtils.getCustomLayout();
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
// then
assertTrue(layout.getFields().size() > 0);
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
assertTrue(recordingLayout.getFields().size() > 0);
assertEquals(recordingLayout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
assertTrue(recordingLayout.getFields().stream().anyMatch(DataField::isVisible));
}
@Test
@@ -126,28 +127,28 @@ public class PreferencesUtilsTest {
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout();
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getName(), "run");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(recordingLayout.getFields().size(), 4);
assertEquals(recordingLayout.getName(), "run");
assertEquals(recordingLayout.getColumnsPerRow(), 2);
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(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(recordingLayout.getFields().get(0).isVisible());
assertTrue(recordingLayout.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(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(recordingLayout.getFields().get(1).isVisible());
assertFalse(recordingLayout.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(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
assertFalse(recordingLayout.getFields().get(2).isVisible());
assertTrue(recordingLayout.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());
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(recordingLayout.getFields().get(3).isVisible());
assertFalse(recordingLayout.getFields().get(3).isPrimary());
}
@Test
@@ -165,44 +166,44 @@ public class PreferencesUtilsTest {
editor.apply();
// when
Layout layout = PreferencesUtils.getCustomLayout();
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
// then
assertEquals(layout.getFields().size(), 4);
assertEquals(layout.getName(), "walking");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(recordingLayout.getFields().size(), 4);
assertEquals(recordingLayout.getName(), "walking");
assertEquals(recordingLayout.getColumnsPerRow(), 2);
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(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
assertTrue(recordingLayout.getFields().get(0).isVisible());
assertTrue(recordingLayout.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(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertTrue(recordingLayout.getFields().get(1).isVisible());
assertFalse(recordingLayout.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(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
assertFalse(recordingLayout.getFields().get(2).isVisible());
assertTrue(recordingLayout.getFields().get(2).isPrimary());
assertTrue(recordingLayout.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());
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertFalse(recordingLayout.getFields().get(3).isVisible());
assertFalse(recordingLayout.getFields().get(3).isPrimary());
}
@Test
public void testSetCustomLayout() {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(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));
RecordingLayout recordingLayoutSrc = new RecordingLayout("road cycling");
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), true, true, false));
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), true, false, false));
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), false, true, false));
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), false, false, false));
// when
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
editor.putString(context.getString(R.string.stats_custom_layouts_key), recordingLayoutSrc.toCsv());
editor.commit();
// then
@@ -215,13 +216,13 @@ public class PreferencesUtilsTest {
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
Layout layoutDst = PreferencesUtils.getCustomLayout();
assertEquals(layoutSrc.getName(), layoutDst.getName());
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());
RecordingLayout recordingLayoutDst = PreferencesUtils.getCustomLayout();
assertEquals(recordingLayoutSrc.getName(), recordingLayoutDst.getName());
assertEquals(recordingLayoutSrc.getFields().size(), recordingLayoutDst.getFields().size());
for (int i = 0; i < recordingLayoutSrc.getFields().size(); i++) {
assertEquals(recordingLayoutSrc.getFields().get(i).getKey(), recordingLayoutDst.getFields().get(i).getKey());
assertEquals(recordingLayoutSrc.getFields().get(i).isVisible(), recordingLayoutDst.getFields().get(i).isVisible());
assertEquals(recordingLayoutSrc.getFields().get(i).isPrimary(), recordingLayoutDst.getFields().get(i).isPrimary());
}
}
@@ -247,7 +248,7 @@ public class PreferencesUtilsTest {
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.apply();
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
// when cyling profile is updated
String cyclingProfileUpdated = "cycling;2;"
@@ -256,14 +257,14 @@ public class PreferencesUtilsTest {
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
List<Layout> layoutsToBeUpdated = new ArrayList<>();
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
List<RecordingLayout> layoutsToBeUpdated = new ArrayList<>();
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources));
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(runningProfile, resources));
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
// then only updated profile is modified in the custom layouts
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
List<RecordingLayout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
assertEquals(layoutsBefore.size(), 2);
assertEquals(layoutsAfter.size(), 2);
@@ -294,7 +295,7 @@ public class PreferencesUtilsTest {
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
editor.apply();
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
// when cyling profile is updated
String cyclingProfileUpdated = "cycling;2;"
@@ -302,11 +303,11 @@ public class PreferencesUtilsTest {
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources);
PreferencesUtils.updateCustomLayout(layoutToBeUpdated);
RecordingLayout recordingLayoutToBeUpdated = RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources);
PreferencesUtils.updateCustomLayout(recordingLayoutToBeUpdated);
// then only updated profile is modified in the custom layouts
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
List<RecordingLayout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
assertEquals(layoutsBefore.size(), 2);
assertEquals(layoutsAfter.size(), 2);
@@ -337,9 +338,9 @@ public class PreferencesUtilsTest {
editor.apply();
// when it gets the custom layout
Layout layout = PreferencesUtils.getCustomLayout();
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
// then the first one was returned
assertEquals(layout.getName(), "cycling");
assertEquals(recordingLayout.getName(), "cycling");
}
}
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class CsvLayoutUtilsTest extends TestCase {
public class CsvRecordingLayoutUtilsTest extends TestCase {
@Test
public void testGetCsvLineParts() {
@@ -1,76 +0,0 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.is;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.GrantPermissionRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class EspressoCustomLayoutTest {
@Rule
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
@Rule
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@Test
public void customLayoutTest() {
// TrackListActivity: start recording
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.track_list_fab_action));
trackControllerRecordButton.perform(click());
// Get custom layout preferences and check all data fields are showed.
Layout layout = PreferencesUtils.getCustomLayout();
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.track_recording_fab_action));
trackControllerStopButton.perform(longClick());
}
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));
}
}
}
@@ -17,7 +17,7 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@RunWith(AndroidJUnit4.class)
public class LayoutTest extends TestCase {
public class RecordingLayoutTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
@@ -28,21 +28,17 @@ public class LayoutTest extends TestCase {
String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv, resources);
List<DataField> dataFieldList = layout.getFields();
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv, resources);
List<DataField> dataFieldList = recordingLayout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "running");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(recordingLayout.getName(), "running");
assertEquals(recordingLayout.getColumnsPerRow(), 2);
assertEquals(dataFieldList.size(), 4);
assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
assertEquals(dataFieldList.get(0).getTitle(), context.getString(R.string.stats_speed));
assertEquals(dataFieldList.get(1).getTitle(), context.getString(R.string.stats_distance));
assertEquals(dataFieldList.get(2).getTitle(), context.getString(R.string.stats_altitude));
assertEquals(dataFieldList.get(3).getTitle(), context.getString(R.string.stats_gain));
assertTrue(dataFieldList.get(0).isVisible());
assertTrue(dataFieldList.get(0).isPrimary());
assertFalse(dataFieldList.get(0).isWide());
@@ -64,13 +60,13 @@ public class LayoutTest extends TestCase {
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
List<DataField> dataFieldList = recordingLayout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(layout.getFields().size(), 0);
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(recordingLayout.getFields().size(), 0);
}
@Test
@@ -79,13 +75,13 @@ public class LayoutTest extends TestCase {
String csv1 = "Layout Name;2;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
List<DataField> dataFieldList = recordingLayout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(layout.getFields().size(), 0);
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
assertEquals(recordingLayout.getFields().size(), 0);
}
@Test
@@ -94,13 +90,13 @@ public class LayoutTest extends TestCase {
String csv1 = "Layout Name;2;speed,distance,total time;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
List<DataField> dataFieldList = recordingLayout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "Layout Name");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().size(), 0);
assertEquals(recordingLayout.getName(), "Layout Name");
assertEquals(recordingLayout.getColumnsPerRow(), 2);
assertEquals(recordingLayout.getFields().size(), 0);
}
@Test
@@ -109,27 +105,27 @@ public class LayoutTest extends TestCase {
String csv1 = "Layout Name;2;speed,1,0;distance;";
// when create a layout from CSV line
Layout layout = Layout.fromCsv(csv1, resources);
List<DataField> dataFieldList = layout.getFields();
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
List<DataField> dataFieldList = recordingLayout.getFields();
// then layout and data fields are built correctly
assertEquals(layout.getName(), "Layout Name");
assertEquals(layout.getColumnsPerRow(), 2);
assertEquals(layout.getFields().size(), 1);
assertEquals(recordingLayout.getName(), "Layout Name");
assertEquals(recordingLayout.getColumnsPerRow(), 2);
assertEquals(recordingLayout.getFields().size(), 1);
}
@Test
public void testToCsv() {
// given a layout's object
Layout layout = new Layout("Test Layout", 2);
layout.addField("key1", "Title 1", false, false, false);
layout.addField("key2", "Title 2", false, true, false);
layout.addField("key3", "Title 3", true, false, false);
layout.addField("key4", "Title 4", true, true, false);
layout.addField("key5", "Title 5", true, true, true);
RecordingLayout recordingLayout = new RecordingLayout("Test Layout", 2);
recordingLayout.addField(new DataField("key1", false, false, false));
recordingLayout.addField(new DataField("key2", false, true, false));
recordingLayout.addField(new DataField("key3", true, false, false));
recordingLayout.addField(new DataField("key4", true, true, false));
recordingLayout.addField(new DataField("key5", true, true, true));
// when converts it to CSV
String csv = layout.toCsv();
String csv = recordingLayout.toCsv();
// then csv is well built
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
@@ -138,15 +134,15 @@ public class LayoutTest extends TestCase {
@Test
public void testToCsv_columnsByDefault() {
// given a layout's object
Layout layout = new Layout("Test Layout");
layout.addField("key1", "Title 1", false, false, false);
layout.addField("key2", "Title 2", false, true, false);
layout.addField("key3", "Title 3", true, false, false);
layout.addField("key4", "Title 4", true, true, false);
layout.addField("key5", "Title 5", true, true, true);
RecordingLayout recordingLayout = new RecordingLayout("Test Layout");
recordingLayout.addField(new DataField("key1", false, false, false));
recordingLayout.addField(new DataField("key2", false, true, false));
recordingLayout.addField(new DataField("key3", true, false, false));
recordingLayout.addField(new DataField("key4", true, true, false));
recordingLayout.addField(new DataField("key5", true, true, true));
// when converts it to CSV
String csv = layout.toCsv();
String csv = recordingLayout.toCsv();
// then csv is well built
assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
@@ -12,7 +12,7 @@ import org.junit.runner.RunWith;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
@RunWith(AndroidJUnit4.class)
public class StatisticsUtilsTest extends TestCase {
@@ -44,16 +44,16 @@ public class StatisticsUtilsTest extends TestCase {
@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));
RecordingLayout recordingLayout = new RecordingLayout("profile");
recordingLayout.addField(new DataField("key1", true, true, false));
recordingLayout.addField(new DataField("key2", false, true, false));
recordingLayout.addField(new DataField("key3", true, true, false));
recordingLayout.addField(new DataField("key4", false, true, false));
recordingLayout.addField(new DataField("key5", true, true, false));
// when
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true);
RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false);
// then
assertEquals(resultTrue.getFields().size(), 3);
@@ -1,199 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import static org.mockito.Mockito.when;
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.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(MockitoJUnitRunner.class)
public class StatisticDataBuilderTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private Track trackMock;
@Mock
private TrackStatistics trackStatisticsMock;
@Mock
private 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, UnitSystem.METRIC);
// 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, UnitSystem.METRIC);
// 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(HeartRate.of(200f));
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(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(Cadence.of(90f));
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(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(Power.of(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(HeartRate.of(200f));
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(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))));
}
}
+5 -2
View File
@@ -62,13 +62,16 @@ limitations under the License.
<application
android:name=".Startup"
android:allowBackup="false"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules_30_lower"
android:dataExtractionRules="@xml/backup_rules_31_higher"
android:hardwareAccelerated="true"
android:hasFragileUserData="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/ThemeCustom">
android:theme="@style/ThemeCustom"
android:localeConfig="@xml/locales_config">
<activity
android:name=".publicapi.StartRecording"
@@ -24,6 +24,7 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.TrackEditBinding;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
@@ -75,7 +76,7 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
viewBinding.fields.trackEditActivityType.setText(track.getCategory());
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
viewBinding.fields.trackEditActivityType.setAdapter(adapter);
viewBinding.fields.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.fields.trackEditActivityType.getAdapter().getItem(position))));
viewBinding.fields.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
@@ -381,13 +381,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
}
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
snackbar.dismiss();
boolean snackbarShowing = snackbar != null && snackbar.isShown();
if (gpsStatusValue.isGpsStarted()) {
if (snackbarShowing) {
snackbar.dismiss();
}
return;
}
if (gpsStatusValue != GpsStatusValue.GPS_DISABLED) {
if (snackbarShowing) {
return;
}
snackbar = Snackbar
.make(viewBinding.trackRecordingCoordinatorLayout,
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
@@ -8,6 +8,7 @@ import android.view.View;
import android.widget.ArrayAdapter;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
@@ -51,7 +52,7 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
viewBinding.trackEditActivityType.setText(track.getCategory());
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(this));
viewBinding.trackEditActivityType.setAdapter(adapter);
viewBinding.trackEditActivityType.setOnItemClickListener((parent, view, position, id) -> setActivityTypeIcon(TrackIconUtils.getIconValue(this, (String) viewBinding.trackEditActivityType.getAdapter().getItem(position))));
viewBinding.trackEditActivityType.setOnFocusChangeListener((v, hasFocus) -> {
@@ -1,176 +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.TextClock;
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.ui.customRecordingLayout.CustomLayoutFieldType;
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;
if (viewType == CustomLayoutFieldType.CLOCK.value()) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_clock_item, parent, false);
return new StatisticsAdapter.ViewClockHolder(view);
} else {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_generic_item, parent, false);
return new StatisticsAdapter.ViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
StatisticData statisticData = statisticDataList.get(position);
if (getItemViewType(position) == CustomLayoutFieldType.CLOCK.value()) {
StatisticsAdapter.ViewClockHolder viewHolder = (StatisticsAdapter.ViewClockHolder) holder;
viewHolder.setData(statisticData);
} else {
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
viewHolder.setData(statisticData);
}
}
@Override
public int getItemCount() {
if (statisticDataList == null) {
return 0;
} else {
return statisticDataList.size();
}
}
@Override
public int getItemViewType(int position) {
if (statisticDataList.get(position).getField().getType(context) == CustomLayoutFieldType.CLOCK) {
return CustomLayoutFieldType.CLOCK.value();
} else {
return CustomLayoutFieldType.GENERIC.value();
}
}
public boolean isItemWide(int position) {
return statisticDataList.get(position).getField().isWide();
}
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.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
this.unit.setText(statisticData.getUnit());
this.descMain.setText(statisticData.getField().getTitle());
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
if (statisticData.hasDescription()) {
this.descSecondary.setVisibility(View.VISIBLE);
this.descSecondary.setText(statisticData.getDescription());
} else {
this.descSecondary.setVisibility(View.GONE);
}
}
}
private class ViewClockHolder extends RecyclerView.ViewHolder {
final TextClock value;
final TextView descMain;
public ViewClockHolder(@NonNull View itemView) {
super(itemView);
value = itemView.findViewById(R.id.stats_clock);
descMain = itemView.findViewById(R.id.stats_description_main);
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
}
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,79 @@
package de.dennisguse.opentracks.data.models;
import android.content.Context;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
public enum ActivityType {
AIRPLANE("AIRPLANE", R.drawable.ic_activity_flight_24dp, true, R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane),
BIKE("BIKE", R.drawable.ic_activity_bike_24dp, true, R.string.activity_type_biking, R.string.activity_type_cycling, R.string.activity_type_dirt_bike, R.string.activity_type_road_biking, R.string.activity_type_track_cycling),
MOUNTAIN_BIKE("MOUNTAIN_BIKE", R.drawable.ic_activity_mtb_24dp, true, R.string.activity_type_mountain_biking),
MOTOR_BIKE("MOTOR_BIKE", R.drawable.ic_activity_motorbike_24dp, true, R.string.activity_type_motor_bike),
KAYAK("KAYAK", R.drawable.ic_activity_kayaking_24dp, true, R.string.activity_type_kayaking),
BOAT("BOAT", R.drawable.ic_activity_boat_24dp, true, R.string.activity_type_boat, R.string.activity_type_ferry, R.string.activity_type_motor_boating, R.string.activity_type_rc_boat),
SAILING("SAILING", R.drawable.ic_activity_sailing_24dp, true, R.string.activity_type_sailing),
DRIVE("DRIVE", R.drawable.ic_activity_drive_24dp, true, R.string.activity_type_atv, R.string.activity_type_driving, R.string.activity_type_driving_bus, R.string.activity_type_driving_car),
RUN("RUN", R.drawable.ic_activity_run_24dp, false, R.string.activity_type_running, R.string.activity_type_street_running, R.string.activity_type_track_running, R.string.activity_type_trail_running),
SKI("SKI", R.drawable.ic_activity_skiing_24dp, true, R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing),
SNOW_BOARDING("SNOW_BOARDING", R.drawable.ic_activity_snowboarding_24dp, true, R.string.activity_type_snow_boarding),
UNKNOWN("UNKNOWN", R.drawable.ic_logo_24dp, true, R.string.activity_type_unknown),
WALK("WALK", R.drawable.ic_activity_walk_24dp, false, R.string.activity_type_hiking, R.string.activity_type_off_trail_hiking, R.string.activity_type_speed_walking, R.string.activity_type_trail_hiking, R.string.activity_type_walking),
ESCOOTER("ESCOOTER", R.drawable.ic_activity_escooter_24dp, true, R.string.activity_type_escooter),
INLINE_SKATING("INLINES_SKATING", R.drawable.ic_activity_inline_skating_24dp, true, R.string.activity_type_inline_skating),
SKATE_BOARDING("SKATE_BOARDING", R.drawable.ic_activity_skateboarding_24dp, true, R.string.activity_type_skate_boarding),
CLIMBING("CLIMBING", R.drawable.ic_activity_climbing_24dp, false, R.string.activity_type_climbing),
SWIMMING("SWIMMING", R.drawable.ic_activity_swimming_24dp, false, R.string.activity_type_swimming),
SWIMMING_OPEN("SWIMMING_OPEN", R.drawable.ic_activity_swimming_open_24dp, false, R.string.activity_type_swimming_open),
WORKOUT("WORKOUT", R.drawable.ic_activity_workout_24dp, false, R.string.activity_type_workout);
final String id;
final int iconId;
final boolean showSpeedPreferred;
final int[] localizedStringIds;
//isSpeed?
ActivityType(String id, int iconId, boolean showSpeedPreferred, int... localizedStringIds) {
this.id = id;
this.iconId = iconId;
this.showSpeedPreferred = showSpeedPreferred;
this.localizedStringIds = localizedStringIds;
}
public String getId() {
return id;
}
public int getIconId() {
return iconId;
}
public boolean isShowSpeedPreferred() {
return showSpeedPreferred;
}
public int[] getLocalizedStringIds() {
return localizedStringIds;
}
public int getFirstLocalizedStringId() {
return localizedStringIds[0];
}
public static List<String> getLocalizedStrings(Context context) {
List<String> result = new ArrayList<>();
for (ActivityType activityType : values()) {
List<String> strings = Arrays.stream(activityType.localizedStringIds)
.mapToObj(context::getString)
.collect(Collectors.toList());
result.addAll(strings);
}
return result;
}
}
@@ -0,0 +1,38 @@
package de.dennisguse.opentracks.data.models;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.ui.util.ThemeUtils;
public class HeartRateZones {
private final HeartRate max;
public HeartRateZones(@NonNull HeartRate max) {
this.max = max;
}
public int getTextColorForZone(Context context, HeartRate current) {
if (current != null) {
if (current.getBPM() >= max.getBPM() * 0.9) {
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_5);
}
if (current.getBPM() >= max.getBPM() * 0.8) {
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_4);
}
if (current.getBPM() >= max.getBPM() * 0.7) {
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_3);
}
if (current.getBPM() >= max.getBPM() * 0.6) {
return ContextCompat.getColor(context, R.color.heart_rate_zone_textcolor_2);
}
}
// Zone 1
return ThemeUtils.getTextColorPrimary(context);
}
}
@@ -385,10 +385,6 @@ public class TrackPoint {
return this;
}
public boolean hasSensorData() {
return hasHeartRate() || hasCadence() || hasPower();
}
public boolean hasHeartRate() {
return heartRate != null;
}
@@ -31,11 +31,8 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
@@ -47,8 +44,6 @@ import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
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}.
@@ -72,7 +67,6 @@ public class StatisticsRecordedFragment extends Fragment {
}
private SensorStatistics sensorStatistics;
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
private Track.Id trackId;
@Nullable // Lazily loaded.
@@ -113,8 +107,6 @@ public class StatisticsRecordedFragment extends Fragment {
trackId = getArguments().getParcelable(TRACK_ID_KEY);
contentProviderUtils = new ContentProviderUtils(getContext());
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
}
@Override
@@ -123,7 +115,7 @@ public class StatisticsRecordedFragment extends Fragment {
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
sensorsRecyclerView.setAdapter(sensorsAdapter);
// sensorsRecyclerView.setAdapter(sensorsAdapter);
return viewBinding.getRoot();
}
@@ -261,10 +253,6 @@ public class StatisticsRecordedFragment extends Fragment {
if (sensorStatistics == null) {
return;
}
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
if (sensorDataList.size() > 0) {
sensorsAdapter.swapData(sensorDataList);
}
//TODO
}
}
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -9,16 +10,14 @@ 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 androidx.gridlayout.widget.GridLayout;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.RecordingData;
@@ -26,9 +25,10 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.viewmodels.Mapping;
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
/**
* A fragment to display track statistics to the user for a currently recording {@link Track}.
@@ -48,32 +48,22 @@ public class StatisticsRecordingFragment extends Fragment {
private RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
private Layout layout;
private final List<StatisticViewHolder<?>> viewHolders = new LinkedList<>();
private RecordingLayout recordingLayout;
private StatisticsRecordingBinding viewBinding;
private StatisticsAdapter statisticsAdapter;
private GridLayoutManager gridLayoutManager;
private StatisticsDataModel viewModel;
private LiveData<List<StatisticData>> statisticsLiveData;
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
updateUInecessary = true;
unitSystem = PreferencesUtils.getUnitSystem();
updateDataOnUI();
}
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
updateUInecessary = true;
layout = PreferencesUtils.getCustomLayout();
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
}
if (key != null && updateUInecessary && isResumed()) {
getActivity().runOnUiThread(this::updateUI);
onLayoutChanged(PreferencesUtils.getCustomLayout());
}
};
@@ -84,30 +74,11 @@ public class StatisticsRecordingFragment extends Fragment {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
statisticsAdapter = new StatisticsAdapter(getContext());
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
RecyclerView recyclerView = viewBinding.statsRecyclerView;
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
layout = PreferencesUtils.getCustomLayout();
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (statisticsAdapter.isItemWide(position)) {
return layout.getColumnsPerRow();
}
return 1;
}
});
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setAdapter(statisticsAdapter);
return viewBinding.getRoot();
}
@@ -117,10 +88,6 @@ public class StatisticsRecordingFragment extends Fragment {
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
statisticsLiveData = viewModel.getStatsData();
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
trackRecordingServiceConnection.startConnection(getContext());
}
@@ -139,6 +106,7 @@ public class StatisticsRecordingFragment extends Fragment {
@Override
public void onDestroyView() {
super.onDestroyView();
viewHolders.clear();
viewBinding = null;
}
@@ -146,17 +114,56 @@ public class StatisticsRecordingFragment extends Fragment {
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection = null;
viewModel = null;
if (statisticsLiveData != null) {
statisticsLiveData.removeObservers(getActivity());
}
statisticsLiveData = null;
}
private void updateUI() {
if (isResumed()) {
viewModel.update(recordingData, layout, unitSystem);
private void onLayoutChanged(@NonNull RecordingLayout newRecordingLayout) {
if (newRecordingLayout.equals(recordingLayout)) {
return;
}
recordingLayout = newRecordingLayout;
viewBinding.statsLayout.setColumnCount(recordingLayout.getColumnsPerRow());
viewBinding.statsLayout.removeAllViews(); //Let's start from scratch
viewHolders.clear();
Map<String, Callable<StatisticViewHolder<?>>> m = Mapping.create(getContext());
int rowIndex = 0;
int columnIndex = 0;
for (int i = 0; i < recordingLayout.getFields().size(); i++) {
DataField dataField = recordingLayout.getFields().get(i);
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
param.setGravity(Gravity.FILL_HORIZONTAL);
param.width = 0;
if (dataField.isWide()) {
rowIndex++;
param.columnSpec = GridLayout.spec(0, recordingLayout.getColumnsPerRow(), 1);
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
columnIndex = 0;
rowIndex++;
} else {
if (columnIndex >= recordingLayout.getColumnsPerRow()) {
columnIndex = 0;
rowIndex++;
}
param.columnSpec = GridLayout.spec(columnIndex, 1, 1);
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
columnIndex++;
}
try {
StatisticViewHolder<?> viewHolder = m.get(dataField.getKey()).call();
viewHolder.initialize(getContext(), getLayoutInflater());
viewHolder.configureUI(dataField);
viewHolders.add(viewHolder);
viewBinding.statsLayout.addView(viewHolder.getView(), param);
} catch (Exception e) {
throw new RuntimeException("Could not add " + dataField.getKey(), e);
}
}
}
@@ -169,6 +176,12 @@ public class StatisticsRecordingFragment extends Fragment {
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
}
updateUI();
updateDataOnUI();
}
private void updateDataOnUI() {
if (isResumed()) {
viewHolders.forEach(i -> i.onChanged(unitSystem, recordingData));
}
}
}
@@ -40,9 +40,9 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorData;
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
* Also parses the transferred data into {@link SensorDataObserver}.
*/
public abstract class BluetoothConnectionManager<DataType> {
public abstract class AbstractBluetoothConnectionManager<DataType> {
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
private static final String TAG = AbstractBluetoothConnectionManager.class.getSimpleName();
private final SensorDataObserver observer;
@@ -54,20 +54,23 @@ public abstract class BluetoothConnectionManager<DataType> {
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTING:
Log.d(TAG, "Connecting to sensor: " + gatt.getDevice());
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
break;
case BluetoothProfile.STATE_CONNECTED:
Log.d(TAG, "Connected to sensor: " + gatt.getDevice());
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTING:
Log.d(TAG, "Disconnecting from sensor: " + gatt.getDevice());
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
break;
case BluetoothProfile.STATE_DISCONNECTED:
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
//This is also triggered, if no connection was established (ca. 30s)
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
if (gatt.connect()) {
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
}
clearData();
break;
}
@@ -133,12 +136,12 @@ public abstract class BluetoothConnectionManager<DataType> {
}
};
BluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
AbstractBluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
this.observer = observer;
}
BluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
AbstractBluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
this.serviceMeasurementUUIDs = serviceUUUID;
this.observer = observer;
}
@@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
/**
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
*/
public class AltitudeSumManager implements SensorEventListener {
public class AltitudeSumManager implements SensorConnector, SensorEventListener {
private static final String TAG = AltitudeSumManager.class.getSimpleName();
@@ -8,11 +8,11 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
public class BluetoothRemoteSensorManagerCyclingCadence extends BluetoothConnectionManager<Cadence> {
public class BluetoothConnectionManagerCyclingCadence extends AbstractBluetoothConnectionManager<Cadence> {
private static final String TAG = BluetoothRemoteSensorManagerCyclingCadence.class.getSimpleName();
private static final String TAG = BluetoothConnectionManagerCyclingCadence.class.getSimpleName();
BluetoothRemoteSensorManagerCyclingCadence(SensorDataObserver observer) {
BluetoothConnectionManagerCyclingCadence(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_CADENCE, observer);
}
@@ -5,9 +5,9 @@ import android.bluetooth.BluetoothGattCharacteristic;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
public class BluetoothRemoteSensorManagerCyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
public class BluetoothConnectionManagerCyclingDistanceSpeed extends AbstractBluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
BluetoothRemoteSensorManagerCyclingDistanceSpeed(SensorDataObserver observer) {
BluetoothConnectionManagerCyclingDistanceSpeed(SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
}
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
public class BluetoothRemoteSensorManagerCyclingPower extends BluetoothConnectionManager<Power> {
public class BluetoothConnectionManagerCyclingPower extends AbstractBluetoothConnectionManager<Power> {
BluetoothRemoteSensorManagerCyclingPower(@NonNull SensorDataObserver observer) {
BluetoothConnectionManagerCyclingPower(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.CYCLING_POWER, observer);
}
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionManager<HeartRate> {
public class BluetoothConnectionManagerHeartRate extends AbstractBluetoothConnectionManager<HeartRate> {
BluetoothRemoteSensorManagerHeartRate(@NonNull SensorDataObserver observer) {
BluetoothConnectionManagerHeartRate(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.HEARTRATE, observer);
}
@@ -20,8 +20,8 @@ public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionMa
@Override
protected SensorDataHeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
return heartRate != null ? new SensorDataHeartRate(address, sensorName, HeartRate.of(heartRate)) : null;
return heartRate != null ? new SensorDataHeartRate(address, sensorName, heartRate) : null;
}
}
@@ -6,9 +6,9 @@ import androidx.annotation.NonNull;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
public class BluetoothRemoteSensorManagerRunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
public class BluetoothConnectionRunningSpeedAndCadence extends AbstractBluetoothConnectionManager<SensorDataRunning.Data> {
BluetoothRemoteSensorManagerRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
BluetoothConnectionRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
super(BluetoothUtils.RUNNING_SPEED_CADENCE, observer);
}
@@ -52,7 +52,7 @@ import de.dennisguse.opentracks.util.PermissionRequester;
*
* @author Sandor Dornbush
*/
public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.SensorDataObserver {
public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBluetoothConnectionManager.SensorDataObserver {
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
@@ -65,15 +65,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
private Distance preferenceWheelCircumference;
private final BluetoothRemoteSensorManagerHeartRate heartRate = new BluetoothRemoteSensorManagerHeartRate(this);
private final BluetoothRemoteSensorManagerCyclingCadence cyclingCadence = new BluetoothRemoteSensorManagerCyclingCadence(this);
private final BluetoothRemoteSensorManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothRemoteSensorManagerCyclingDistanceSpeed(this);
private final BluetoothRemoteSensorManagerCyclingPower cyclingPower = new BluetoothRemoteSensorManagerCyclingPower(this);
private final BluetoothRemoteSensorManagerRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothRemoteSensorManagerRunningSpeedAndCadence(this);
private final BluetoothConnectionManagerHeartRate heartRate = new BluetoothConnectionManagerHeartRate(this);
private final BluetoothConnectionManagerCyclingCadence cyclingCadence = new BluetoothConnectionManagerCyclingCadence(this);
private final BluetoothConnectionManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManagerCyclingDistanceSpeed(this);
private final BluetoothConnectionManagerCyclingPower cyclingPower = new BluetoothConnectionManagerCyclingPower(this);
private final BluetoothConnectionRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionRunningSpeedAndCadence(this);
private final SensorDataSet sensorDataSet = new SensorDataSet();
private final SensorDataSetChangeObserver observer;
private final SensorManager.SensorDataSetChangeObserver observer;
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
@@ -114,21 +114,23 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
}
};
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorDataSetChangeObserver observer) {
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorManager.SensorDataSetChangeObserver observer) {
this.context = context;
this.handler = handler;
this.observer = observer;
bluetoothAdapter = BluetoothUtils.getAdapter(context);
}
public void start() {
@Override
public void start(Context context, Handler handler) {
started = true;
//Registering triggers connection startup
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
}
public synchronized void stop() {
@Override
public synchronized void stop(Context context) {
heartRate.disconnect();
cyclingCadence.disconnect();
cyclingSpeed.disconnect();
@@ -145,7 +147,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
private synchronized void connect(BluetoothConnectionManager<?> connectionManager, String address) {
private synchronized void connect(AbstractBluetoothConnectionManager<?> connectionManager, String address) {
if (!isEnabled()) {
Log.w(TAG, "Bluetooth not enabled.");
return;
@@ -230,8 +232,4 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
public Handler getHandler() {
return handler;
}
public interface SensorDataSetChangeObserver {
void onChange(SensorDataSet sensorDataSet);
}
}
@@ -30,6 +30,7 @@ import java.util.UUID;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
@@ -101,7 +102,7 @@ public class BluetoothUtils {
return BluetoothUtils.getAdapter(context) != null;
}
public static Integer parseHeartRate(BluetoothGattCharacteristic characteristic) {
public static HeartRate parseHeartRate(BluetoothGattCharacteristic characteristic) {
//DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
byte[] raw = characteristic.getValue();
if (raw.length == 0) {
@@ -110,10 +111,10 @@ public class BluetoothUtils {
boolean formatUINT16 = ((raw[0] & 0x1) == 1);
if (formatUINT16 && raw.length >= 3) {
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1));
}
if (!formatUINT16 && raw.length >= 2) {
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1));
}
return null;
@@ -0,0 +1,11 @@
package de.dennisguse.opentracks.sensors;
import android.content.Context;
import android.os.Handler;
public interface SensorConnector {
void start(Context context, Handler handler);
void stop(Context context);
}
@@ -0,0 +1,85 @@
package de.dennisguse.opentracks.sensors;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
public class SensorManager {
private static final String TAG = SensorManager.class.getSimpleName();
private BluetoothRemoteSensorManager bluetoothSensorManager;
private AltitudeSumManager altitudeSumManager;
public SensorManager(Context context, Handler handler, SensorDataSetChangeObserver observer) {
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, observer);
altitudeSumManager = new AltitudeSumManager();
}
public void start(Context context, Handler handler) {
bluetoothSensorManager.start(context, handler);
altitudeSumManager.start(context, handler);
}
public void stop(Context context) {
if (bluetoothSensorManager != null) {
bluetoothSensorManager.stop(context);
bluetoothSensorManager = null;
}
if (altitudeSumManager != null) {
altitudeSumManager.stop(context);
altitudeSumManager = null;
}
}
public SensorDataSet fill(TrackPoint trackPoint) {
altitudeSumManager.fill(trackPoint);
return bluetoothSensorManager.fill(trackPoint);
}
public void reset() {
if (bluetoothSensorManager == null || altitudeSumManager == null) {
Log.d(TAG, "No recording running and no reset necessary.");
return;
}
bluetoothSensorManager.reset();
altitudeSumManager.reset();
}
@Deprecated
@VisibleForTesting
public BluetoothRemoteSensorManager getBluetoothSensorManager() {
return bluetoothSensorManager;
}
@Deprecated
@VisibleForTesting
public void setBluetoothSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
this.bluetoothSensorManager = remoteSensorManager;
}
@Deprecated
@VisibleForTesting
public AltitudeSumManager getAltitudeSumManager() {
return altitudeSumManager;
}
@Deprecated
@VisibleForTesting
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
this.altitudeSumManager = altitudeSumManager;
}
@Deprecated
public interface SensorDataSetChangeObserver {
void onChange(SensorDataSet sensorDataSet);
}
}
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks.sensors.sensorData;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.time.Instant;
@@ -32,16 +31,6 @@ public abstract class SensorData<T> {
this.time = time;
}
@NonNull
public String getSensorAddress() {
return sensorAddress;
}
@Nullable
public String getSensorName() {
return sensorName;
}
public String getSensorNameOrAddress() {
return sensorName != null ? sensorName : sensorAddress;
}
@@ -111,7 +111,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
egm2008CorrectionManager = new EGM2008CorrectionManager();
trackRecordingManager = new TrackRecordingManager(this);
trackRecordingManager.start();
trackPointCreator = new TrackPointCreator(this);
trackPointCreator = new TrackPointCreator(this, this, handler);
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
notificationManager = new TrackRecordingServiceNotificationManager(this);
@@ -177,7 +177,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
}
public void resumeTrack(Track.Id trackId) {
trackPointCreator.reset();
if (!trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator)) {
Log.w(TAG, "Cannot resume a non-existing track.");
return;
@@ -328,7 +327,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
TrackPoint trackPoint = data.second.first;
egm2008CorrectionManager.correctAltitude(this, trackPoint);
localVoiceAnnouncementManager.update(data.first);
localVoiceAnnouncementManager.update(this, data.first);
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
}
@@ -15,12 +15,14 @@
*/
package de.dennisguse.opentracks.services.announcement;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.mediarouter.media.MediaRouter;
import java.time.Duration;
@@ -73,12 +75,20 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
updateNextTaskDistance();
}
public void update(@NonNull Track track) {
public void update(@NonNull Context context, @NonNull Track track) {
if (voiceAnnouncement == null) {
Log.e(TAG, "Cannot update when in status shutdown.");
return;
}
if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker()
&& MediaRouter.getInstance(context)
.getSelectedRoute()
.isDeviceSpeaker()) {
Log.i(TAG, "No voice announcement on device speaker.");
return;
}
boolean announce = false;
this.trackStatistics = track.getTrackStatistics();
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
@@ -20,14 +20,15 @@ import java.time.Instant;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.SensorConnector;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.LocationUtils;
import de.dennisguse.opentracks.util.PermissionRequester;
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
public class GPSManager implements SensorConnector, LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
private final String TAG = GPSHandler.class.getSimpleName();
private final String TAG = GPSManager.class.getSimpleName();
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
@@ -40,11 +41,11 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
private Duration gpsInterval;
private Distance thresholdHorizontalAccuracy;
public GPSHandler(TrackPointCreator trackPointCreator) {
public GPSManager(TrackPointCreator trackPointCreator) {
this.trackPointCreator = trackPointCreator;
}
public void onStart(@NonNull Context context, @NonNull Handler handler) {
public void start(@NonNull Context context, @NonNull Handler handler) {
this.context = context;
this.handler = handler;
@@ -61,7 +62,7 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
@SuppressWarnings({"MissingPermission"})
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
public void onStop() {
public void stop(Context context) {
if (locationManager != null && context != null) {
if (PermissionRequester.GPS.hasPermission(context)) {
LocationManagerCompat.removeUpdates(locationManager, this);
@@ -16,14 +16,13 @@ import java.time.ZoneId;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.sensors.SensorManager;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
/**
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
*/
public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDataSetChangeObserver {
public class TrackPointCreator implements SensorManager.SensorDataSetChangeObserver {
private static final String TAG = TrackPointCreator.class.getSimpleName();
@@ -34,45 +33,34 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
@NonNull
private Clock clock = new MonotonicClock();
private final GPSHandler gpsHandler;
private BluetoothRemoteSensorManager remoteSensorManager;
private AltitudeSumManager altitudeSumManager;
private final GPSManager gpsManager;
private SensorManager sensorManager;
public TrackPointCreator(Callback service) {
public TrackPointCreator(Callback service, Context context, Handler handler) {
this.service = service;
this.gpsHandler = new GPSHandler(this);
this.gpsManager = new GPSManager(this);
this.sensorManager = new SensorManager(context, handler, this);
}
@VisibleForTesting
TrackPointCreator(GPSHandler gpsHandler, Callback service) {
TrackPointCreator(GPSManager gpsManager, Callback service) {
this.service = service;
this.gpsHandler = gpsHandler;
this.gpsManager = gpsManager;
}
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
this.context = context;
gpsHandler.onStart(context, handler);
remoteSensorManager = new BluetoothRemoteSensorManager(context, handler, this);
altitudeSumManager = new AltitudeSumManager();
remoteSensorManager.start();
altitudeSumManager.start(context, handler);
gpsManager.start(context, handler);
sensorManager.start(context, handler);
}
private boolean isStarted() {
return context != null;
}
public synchronized void reset() {
if (remoteSensorManager == null || altitudeSumManager == null) {
Log.d(TAG, "No recording running and no reset necessary.");
return;
}
remoteSensorManager.reset();
altitudeSumManager.reset();
private synchronized void reset() {
sensorManager.reset();
}
private SensorDataSet addSensorData(TrackPoint trackPoint) {
@@ -80,31 +68,12 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
Log.w(TAG, "Not started, should not be called.");
return null;
}
SensorDataSet sensorDataSet = null;
BluetoothRemoteSensorManager localRemoteSensorManager = remoteSensorManager;
if (localRemoteSensorManager != null) {
sensorDataSet = localRemoteSensorManager.fill(trackPoint);
}
AltitudeSumManager localAltitudeSumManager = altitudeSumManager;
if (localAltitudeSumManager != null) {
localAltitudeSumManager.fill(trackPoint);
}
return sensorDataSet;
return sensorManager.fill(trackPoint);
}
public synchronized void stop() {
gpsHandler.onStop();
if (remoteSensorManager != null) {
remoteSensorManager.stop();
remoteSensorManager = null;
}
if (altitudeSumManager != null) {
altitudeSumManager.stop(context);
altitudeSumManager = null;
}
gpsManager.stop(context);
this.context = null;
}
@@ -125,7 +94,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
addSensorData(trackPoint);
boolean stored = service.newTrackPoint(trackPoint, gpsHandler.getThresholdHorizontalAccuracy());
boolean stored = service.newTrackPoint(trackPoint, gpsManager.getThresholdHorizontalAccuracy());
if (stored) {
reset();
}
@@ -169,26 +138,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
}
@VisibleForTesting
public AltitudeSumManager getAltitudeSumManager() {
return altitudeSumManager;
}
@Deprecated
@VisibleForTesting
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
this.altitudeSumManager = altitudeSumManager;
}
@Deprecated
@VisibleForTesting
public BluetoothRemoteSensorManager getRemoteSensorManager() {
return remoteSensorManager;
}
@Deprecated
@VisibleForTesting
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
this.remoteSensorManager = remoteSensorManager;
public SensorManager getSensorManager() {
return sensorManager;
}
@VisibleForTesting
@@ -202,8 +153,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
}
@VisibleForTesting
public GPSHandler getGpsHandler() {
return gpsHandler;
public GPSManager getGpsHandler() {
return gpsManager;
}
void sendGpsStatus(GpsStatusValue gpsStatusValue) {
@@ -28,6 +28,7 @@ import androidx.preference.DialogPreference;
import androidx.preference.PreferenceDialogFragmentCompat;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -79,7 +80,7 @@ public class ActivityTypePreference extends DialogPreference {
textView = view.findViewById(R.id.activity_type_preference_text_view);
String category = PreferencesUtils.getDefaultActivity();
textView.setText(category);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, ActivityType.getLocalizedStrings(context));
textView.setAdapter(adapter);
textView.setOnItemClickListener((parent, v, position, id) -> {
String iconValue = TrackIconUtils.getIconValue(context, (String) textView.getAdapter().getItem(position));
@@ -42,11 +42,14 @@ import java.util.stream.Stream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.HeartRateZones;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
@@ -278,6 +281,12 @@ public class PreferencesUtils {
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
}
public static HeartRateZones getHeartRateZones() {
final int DEFAULT = Integer.parseInt(resources.getString(R.string.settings_sensor_heart_rate_max_default));
int value = getInt(R.string.settings_sensor_heart_rate_max_key, DEFAULT);
return new HeartRateZones(HeartRate.of(value));
}
public static boolean shouldShowStatsOnLockscreen() {
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = resources.getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
return getBoolean(R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
@@ -293,6 +302,11 @@ public class PreferencesUtils {
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
}
public static boolean shouldVoiceAnnouncementOnDeviceSpeaker() {
final boolean DEFAULT = resources.getBoolean(R.bool.voice_on_device_speaker_default);
return getBoolean(R.string.voice_on_device_speaker_key, DEFAULT);
}
public static Duration getVoiceAnnouncementFrequency() {
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
@@ -753,35 +767,35 @@ public class PreferencesUtils {
/**
* @return custom layout selected or the first one if any has been selected or the one selected is not exists anymore.
*/
public static Layout getCustomLayout() {
public static RecordingLayout getCustomLayout() {
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
if (layoutSelected == null) {
return Layout.fromCsv(csvLines[0], resources);
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
}
for (String line : csvLines) {
Layout layout = Layout.fromCsv(line, resources);
if (layout.sameName(layoutSelected)) {
return layout;
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(line, resources);
if (recordingLayout.sameName(layoutSelected)) {
return recordingLayout;
}
}
return Layout.fromCsv(csvLines[0], resources);
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
}
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
public static void updateCustomLayouts(@NonNull List<RecordingLayout> recordingLayouts) {
setString(R.string.stats_custom_layouts_key, RecordingLayoutIO.toCSV(recordingLayouts));
}
public static void updateCustomLayout(@NonNull Layout layout) {
List<Layout> preferenceLayouts = PreferencesUtils.getAllCustomLayouts();
Optional<Layout> layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst();
public static void updateCustomLayout(@NonNull RecordingLayout recordingLayout) {
List<RecordingLayout> preferenceRecordingLayouts = PreferencesUtils.getAllCustomLayouts();
Optional<RecordingLayout> layoutToBeUpdated = preferenceRecordingLayouts.stream().filter(l -> l.sameName(recordingLayout)).findFirst();
if (layoutToBeUpdated.isPresent()) {
layoutToBeUpdated.get().replaceAllFields(layout.getFields());
layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow());
PreferencesUtils.updateCustomLayouts(preferenceLayouts);
layoutToBeUpdated.get().replaceAllFields(recordingLayout.getFields());
layoutToBeUpdated.get().setColumnsPerRow(recordingLayout.getColumnsPerRow());
PreferencesUtils.updateCustomLayouts(preferenceRecordingLayouts);
}
}
@@ -795,19 +809,19 @@ public class PreferencesUtils {
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
}
public static List<Layout> getAllCustomLayouts() {
List<Layout> layouts = new ArrayList<>();
public static List<RecordingLayout> getAllCustomLayouts() {
List<RecordingLayout> recordingLayouts = new ArrayList<>();
String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR);
for (String line : csvLines) {
layouts.add(Layout.fromCsv(line, resources));
recordingLayouts.add(RecordingLayoutIO.fromCsv(line, resources));
}
return layouts;
return recordingLayouts;
}
public static List<String> getAllCustomLayoutNames() {
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
return getAllCustomLayouts().stream().map(RecordingLayout::getName).collect(Collectors.toList());
}
public static void resetCustomLayoutPreferences() {
@@ -45,7 +45,7 @@ public class SensorsSettingsFragment extends PreferenceFragmentCompat {
if (newValue instanceof String) {
try {
int newValueInt = Integer.parseInt((String) newValue);
return newValueInt > 500 && newValueInt < 4000;
return newValueInt >= 100 && newValueInt < 4000;
} catch (NumberFormatException e) {
Log.w(TAG, "Entered string is no number.");
}
@@ -17,7 +17,7 @@ import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter;
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
import de.dennisguse.opentracks.util.StatisticsUtils;
@@ -30,8 +30,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
private String profile;
private Layout layoutFieldsVisible;
private Layout layoutFieldsHidden;
private RecordingLayout recordingLayoutFieldsVisible;
private RecordingLayout recordingLayoutFieldsHidden;
private int numColumns;
@Override
@@ -39,12 +39,12 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
super.onCreate(savedInstanceState);
// Recycler view with visible stats.
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
profile = layout.getName();
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
profile = recordingLayout.getName();
recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true);
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible);
numColumns = layout.getColumnsPerRow();
numColumns = recordingLayout.getColumnsPerRow();
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
gridLayoutManager = new GridLayoutManager(this, numColumns);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@@ -65,7 +65,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
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);
recordingLayoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
return true;
}
@@ -90,8 +90,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
// Recycler view with not visible stats.
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false);
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden);
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
@@ -103,18 +103,18 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
@Override
protected void onPause() {
super.onPause();
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
Layout layout = new Layout(profile, numColumns);
layout.addFields(layoutFieldsVisible.getFields());
layout.addFields(layoutFieldsHidden.getFields());
PreferencesUtils.updateCustomLayout(layout);
if (!recordingLayoutFieldsVisible.getFields().isEmpty() || !recordingLayoutFieldsHidden.getFields().isEmpty()) {
RecordingLayout recordingLayout = new RecordingLayout(profile, numColumns);
recordingLayout.addFields(recordingLayoutFieldsVisible.getFields());
recordingLayout.addFields(recordingLayoutFieldsHidden.getFields());
PreferencesUtils.updateCustomLayout(recordingLayout);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
layoutFieldsVisible = null;
recordingLayoutFieldsVisible = null;
}
@Override
@@ -132,24 +132,24 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
if (which == 0) {
field.togglePrimary();
} else {
layoutFieldsVisible.removeField(field);
recordingLayoutFieldsVisible.removeField(field);
field.toggleVisibility();
layoutFieldsHidden.addField(field);
recordingLayoutFieldsHidden.addField(field);
}
adapterFieldsVisible.swapValues(layoutFieldsVisible);
adapterFieldsHidden.swapValues(layoutFieldsHidden);
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
})
.create()
.show();
} else {
layoutFieldsHidden.removeField(field);
recordingLayoutFieldsHidden.removeField(field);
field.toggleVisibility();
layoutFieldsVisible.addField(field);
recordingLayoutFieldsVisible.addField(field);
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
adapterFieldsVisible.swapValues(layoutFieldsVisible);
adapterFieldsHidden.swapValues(layoutFieldsHidden);
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
}
}
}
@@ -23,7 +23,7 @@ import com.google.android.material.textfield.TextInputLayout;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutListAdapter;
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
@@ -96,7 +96,7 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
final int position = viewHolder.getAdapterPosition();
final Layout item = adapter.getLayouts().get(position);
final RecordingLayout item = adapter.getLayouts().get(position);
adapter.removeLayout(position);
@@ -150,9 +150,9 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
}
@Override
public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) {
public void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout) {
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout);
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, recordingLayout);
startActivity(intent);
}
@@ -1,25 +1,19 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import de.dennisguse.opentracks.R;
import java.util.Objects;
public class DataField implements Parcelable {
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) {
public DataField(String key, boolean isVisible, boolean isPrimary, boolean isWide) {
this.key = key;
this.title = title;
this.isVisible = isVisible;
this.isPrimary = isPrimary;
this.isWide = isWide;
@@ -27,7 +21,6 @@ public class DataField implements Parcelable {
protected DataField(Parcel in) {
key = in.readString();
title = in.readString();
isVisible = in.readByte() != 0;
isPrimary = in.readByte() != 0;
isWide = in.readByte() != 0;
@@ -49,10 +42,6 @@ public class DataField implements Parcelable {
return key;
}
public String getTitle() {
return title;
}
public boolean isVisible() {
return isVisible;
}
@@ -73,65 +62,6 @@ public class DataField implements Parcelable {
return isWide;
}
public CustomLayoutFieldType getType(Context context) {
if (key.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
return CustomLayoutFieldType.CLOCK;
} else {
return CustomLayoutFieldType.GENERIC;
}
}
public String toCsv() {
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
String wide = this.isWide ? YES_VALUE : NOT_VALUE;
return key + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
}
public static String getTitleByKey(Resources resources, String key) {
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
return resources.getString(R.string.stats_total_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
return resources.getString(R.string.stats_moving_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
return resources.getString(R.string.stats_distance);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
return resources.getString(R.string.stats_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
return resources.getString(R.string.stats_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return resources.getString(R.string.stats_average_moving_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
return resources.getString(R.string.stats_average_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
return resources.getString(R.string.stats_max_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return resources.getString(R.string.stats_average_moving_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
return resources.getString(R.string.stats_average_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return resources.getString(R.string.stats_fastest_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
return resources.getString(R.string.stats_altitude);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
return resources.getString(R.string.stats_gain);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
return resources.getString(R.string.stats_loss);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
return resources.getString(R.string.stats_coordinates);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
return resources.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
return resources.getString(R.string.stats_sensors_cadence);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
return resources.getString(R.string.stats_sensors_power);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_clock_key))) {
return resources.getString(R.string.stats_clock);
} else {
throw new RuntimeException("It doesn't exists a field with key: " + key);
}
}
@Override
public int describeContents() {
return 0;
@@ -140,9 +70,31 @@ public class DataField implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(key);
parcel.writeString(title);
parcel.writeByte((byte) (isVisible ? 1 : 0));
parcel.writeByte((byte) (isPrimary ? 1 : 0));
parcel.writeByte((byte) (isWide ? 1 : 0));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DataField dataField = (DataField) o;
return isVisible == dataField.isVisible && isPrimary == dataField.isPrimary && isWide == dataField.isWide && Objects.equals(key, dataField.key);
}
@Override
public int hashCode() {
return Objects.hash(key, isVisible, isPrimary, isWide);
}
@Override
public String toString() {
return "DataField{" +
"key='" + key + '\'' +
", isVisible=" + isVisible +
", isPrimary=" + isPrimary +
", isWide=" + isWide +
'}';
}
}
@@ -1,78 +1,50 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
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.settings.PreferencesUtils;
public class Layout implements Parcelable {
private static final String TAG = Layout.class.getSimpleName();
public class RecordingLayout implements Parcelable {
// User-generated layout's name.
private final String name;
private int columnsPerRow;
private final List<DataField> dataFields = new ArrayList<>();
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
if (csvParts == null) {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return new Layout(PreferencesUtils.getDefaultLayoutName());
}
Layout layout = new Layout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
for (int i = 2; i < csvParts.size(); i++) {
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
if (fieldParts == null) {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return layout;
}
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
}
return layout;
}
public Layout(String name) {
public RecordingLayout(String name) {
this.name = name;
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
}
public Layout(String name, int columnsPerRow) {
public RecordingLayout(String name, int columnsPerRow) {
this.name = name;
this.columnsPerRow = columnsPerRow;
}
protected Layout(Parcel in) {
protected RecordingLayout(Parcel in) {
name = in.readString();
columnsPerRow = in.readInt();
in.readList(dataFields, DataField.class.getClassLoader());
}
public static final Creator<Layout> CREATOR = new Creator<>() {
public static final Creator<RecordingLayout> CREATOR = new Creator<>() {
@Override
public Layout createFromParcel(Parcel in) {
return new Layout(in);
public RecordingLayout createFromParcel(Parcel in) {
return new RecordingLayout(in);
}
@Override
public Layout[] newArray(int size) {
return new Layout[size];
public RecordingLayout[] newArray(int size) {
return new RecordingLayout[size];
}
};
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);
}
@@ -111,8 +83,8 @@ public class Layout implements Parcelable {
this.columnsPerRow = columnsPerRow;
}
public boolean sameName(Layout layout) {
return this.name.equalsIgnoreCase(layout.getName());
public boolean sameName(RecordingLayout recordingLayout) {
return this.name.equalsIgnoreCase(recordingLayout.getName());
}
public boolean sameName(String name) {
@@ -126,7 +98,7 @@ public class Layout implements Parcelable {
}
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
+ fields.stream().map(RecordingLayoutIO::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
+ CsvLayoutUtils.ITEM_SEPARATOR;
}
@@ -141,4 +113,17 @@ public class Layout implements Parcelable {
parcel.writeInt(columnsPerRow);
parcel.writeList(dataFields);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecordingLayout recordingLayout = (RecordingLayout) o;
return columnsPerRow == recordingLayout.columnsPerRow && Objects.equals(name, recordingLayout.name) && Objects.equals(dataFields, recordingLayout.dataFields);
}
@Override
public int hashCode() {
return Objects.hash(name, columnsPerRow, dataFields);
}
}
@@ -0,0 +1,58 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.res.Resources;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
public class RecordingLayoutIO {
private static final String TAG = RecordingLayout.class.getSimpleName();
private static final String YES_VALUE = "1";
private static final String NOT_VALUE = "0";
public static RecordingLayout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
if (csvParts == null) {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return new RecordingLayout(PreferencesUtils.getDefaultLayoutName());
}
RecordingLayout recordingLayout = new RecordingLayout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
for (int i = 2; i < csvParts.size(); i++) {
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
if (fieldParts == null) {
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
return recordingLayout;
}
recordingLayout.addField(fromCSV(fieldParts, resources));
}
return recordingLayout;
}
public static String toCSV(List<RecordingLayout> recordingLayouts) {
return recordingLayouts.stream().map(RecordingLayout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR));
}
private static DataField fromCSV(String[] fieldParts, @NonNull Resources resources) {
return new DataField(
fieldParts[0],
fieldParts[1].equals(YES_VALUE),
fieldParts[2].equals(YES_VALUE),
fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
}
static String toCsv(DataField datafield) {
String visible = datafield.isVisible() ? YES_VALUE : NOT_VALUE;
String primary = datafield.isPrimary() ? YES_VALUE : NOT_VALUE;
String wide = datafield.isWide() ? YES_VALUE : NOT_VALUE;
return datafield.getKey() + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
}
}
@@ -1,106 +1,151 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.Context;
import android.util.Log;
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.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.StatisticsUtils;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.CustomStatsItemBinding;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.viewmodels.Mapping;
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Layout layout;
private static final String TAG = SettingsCustomLayoutEditAdapter.class.getSimpleName();
private static final RecordingData demoData;
static {
TrackStatistics trackStatistics = new TrackStatistics();
trackStatistics.setStartTime(Instant.ofEpochMilli(0));
trackStatistics.setMovingTime(Duration.ofMinutes(0));
trackStatistics.setTotalTime(Duration.ofMinutes(0));
trackStatistics.setTotalDistance(Distance.of(0));
trackStatistics.setTotalAltitudeGain(0f);
trackStatistics.setTotalAltitudeLoss(0f);
Track track = new Track(ZoneOffset.UTC);
track.setTrackStatistics(trackStatistics);
TrackPoint lastTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
lastTrackPoint.setLatitude(0);
lastTrackPoint.setLongitude(0);
lastTrackPoint.setAltitude(Altitude.EGM2008.of(0));
lastTrackPoint.setSpeed(Speed.of(0));
demoData = new RecordingData(track, lastTrackPoint, null);
}
private RecordingLayout recordingLayout;
private final Context context;
private final SettingsCustomLayoutItemClickListener itemClickListener;
private final Map<String, Callable<StatisticViewHolder<?>>> mapping;
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, RecordingLayout recordingLayout) {
this.context = context;
this.itemClickListener = itemClickListener;
this.layout = layout;
this.recordingLayout = recordingLayout;
mapping = Mapping.create(context);
}
@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 SettingsCustomLayoutEditAdapter.ViewHolder(view);
CustomStatsItemBinding binding = CustomStatsItemBinding.inflate(LayoutInflater.from(context), parent, false);
return new SettingsCustomLayoutEditAdapter.ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
DataField field = layout.getFields().get(position);
DataField field = recordingLayout.getFields().get(position);
viewHolder.itemView.setTag(field.getKey());
viewHolder.title.setText(field.getTitle());
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
try {
StatisticViewHolder<?> m = mapping.get(field.getKey()).call();
m.initialize(context, LayoutInflater.from(context));
m.configureUI(field);
m.onChanged(UnitSystem.METRIC, demoData);
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader) : R.style.TextAppearance_OpenTracks_HiddenHeader);
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue) : R.style.TextAppearance_OpenTracks_HiddenValue);
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);
viewHolder.viewBinding.statsLayout.removeAllViews(); //TODO this is not really performant
viewHolder.viewBinding.statsLayout.addView(m.getView());
} catch (Exception e) {
Log.e(TAG, "Couldn't to instantiate UI for DataField with key " + field.getKey() + " " + e.getMessage());
throw new RuntimeException(e);
}
viewHolder.viewBinding.statsIconShowStatus.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
viewHolder.viewBinding.statsIconShowStatus.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
viewHolder.viewBinding.statsIvDragIndicator.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
}
@Override
public int getItemCount() {
if (layout == null) {
if (recordingLayout == null) {
return 0;
} else {
return layout.getFields().size();
return recordingLayout.getFields().size();
}
}
public boolean isItemWide(int position) {
return layout.getFields().get(position).isWide();
return recordingLayout.getFields().get(position).isWide();
}
public DataField getItem(int position) {
return layout.getFields().get(position);
return recordingLayout.getFields().get(position);
}
public void swapValues(Layout data) {
this.layout = data;
if (this.layout != null) {
public void swapValues(RecordingLayout data) {
this.recordingLayout = data;
if (this.recordingLayout != null) {
this.notifyDataSetChanged();
}
}
public Layout move(int fromPosition, int toPosition) {
layout.moveField(fromPosition, toPosition);
public RecordingLayout move(int fromPosition, int toPosition) {
recordingLayout.moveField(fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
return layout;
return recordingLayout;
}
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);
CustomStatsItemBinding viewBinding;
public ViewHolder(@NonNull CustomStatsItemBinding viewBinding) {
super(viewBinding.getRoot());
this.viewBinding = viewBinding;
viewBinding.getRoot().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();
Optional<DataField> optionalField = recordingLayout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
}
}
@@ -17,14 +17,14 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Layout> layoutList;
private List<RecordingLayout> recordingLayoutList;
private final Context context;
private final SettingsCustomLayoutProfileClickListener itemClickListener;
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
this.context = context;
this.itemClickListener = itemClickListener;
layoutList = PreferencesUtils.getAllCustomLayouts();
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
}
@NonNull
@@ -37,37 +37,37 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
Layout layout = layoutList.get(position);
viewHolder.itemView.setTag(layout.getName());
viewHolder.title.setText(layout.getName());
RecordingLayout recordingLayout = recordingLayoutList.get(position);
viewHolder.itemView.setTag(recordingLayout.getName());
viewHolder.title.setText(recordingLayout.getName());
}
@Override
public int getItemCount() {
if (layoutList == null) {
if (recordingLayoutList == null) {
return 0;
}
return layoutList.size();
return recordingLayoutList.size();
}
public List<Layout> getLayouts() {
return layoutList;
public List<RecordingLayout> getLayouts() {
return recordingLayoutList;
}
public void reloadLayouts() {
layoutList = PreferencesUtils.getAllCustomLayouts();
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
notifyDataSetChanged();
}
public void removeLayout(int position) {
layoutList.remove(position);
PreferencesUtils.updateCustomLayouts(layoutList);
recordingLayoutList.remove(position);
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
notifyDataSetChanged();
}
public void restoreItem(Layout layout, int position) {
layoutList.add(position, layout);
PreferencesUtils.updateCustomLayouts(layoutList);
public void restoreItem(RecordingLayout recordingLayout, int position) {
recordingLayoutList.add(position, recordingLayout);
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
notifyDataSetChanged();
}
@@ -83,12 +83,12 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
@Override
public void onClick(View view) {
String profile = (String) view.getTag();
Optional<Layout> optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst();
Optional<RecordingLayout> optionalLayout = recordingLayoutList.stream().filter(layout -> layout.sameName(new RecordingLayout(profile))).findFirst();
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
}
}
public interface SettingsCustomLayoutProfileClickListener {
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout);
}
}
@@ -8,10 +8,11 @@ import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
public class StatisticsUtils {
@Deprecated
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
if (PreferencesUtils.isKey(R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_fastest_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_clock_key, statTitle)) {
return context.getString(R.string.stats_empty_value_time);
@@ -24,9 +25,10 @@ public class StatisticsUtils {
}
}
public static Layout filterVisible(Layout layout, boolean visible) {
Layout result = new Layout(layout.getName());
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
@Deprecated //Add to Layout?
public static RecordingLayout filterVisible(RecordingLayout recordingLayout, boolean visible) {
RecordingLayout result = new RecordingLayout(recordingLayout.getName());
result.addFields(recordingLayout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
return result;
}
}
@@ -18,15 +18,15 @@ package de.dennisguse.opentracks.util;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.ActivityType;
/**
* Utilities for track icon.
@@ -35,218 +35,71 @@ import de.dennisguse.opentracks.R;
*/
public class TrackIconUtils {
private static final String AIRPLANE = "AIRPLANE";
private static final String BIKE = "BIKE";
private static final String MOUNTAIN_BIKE = "MOUNTAIN_BIKE";
private static final String MOTOR_BIKE = "MOTOR_BIKE";
private static final String KAYAK = "KAYAK";
private static final String BOAT = "BOAT";
private static final String SAILING = "SAILING";
private static final String DRIVE = "DRIVE";
private static final String RUN = "RUN";
private static final String SKI = "SKI";
private static final String SNOW_BOARDING = "SNOW_BOARDING";
private static final String UNKNOWN = "UNKNOWN";
private static final String WALK = "WALK";
private static final String ESCOOTER = "ESCOOTER";
private static final String INLINE_SKATING = "INLINES_SKATING";
private static final String SKATE_BOARDING = "SKATE_BOARDING";
private static final String CLIMBING = "CLIMBING";
private static final String SWIMMING = "SWIMMING";
private static final String SWIMMING_OPEN = "SWIMMING_OPEN";
private static final String WORKOUT = "WORKOUT";
public static int getIconDrawable(String activityTypeId) {
Optional<ActivityType> found = Arrays.stream(ActivityType.values()).filter(
it -> it.getId().equals(activityTypeId)
).findFirst();
private static final int ACTIVITY_UNKNOWN_LOGO = R.drawable.ic_logo_24dp;
private static final int[] AIRPLANE_LIST = new int[]{R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane};
private static final int[] BIKE_LIST = new int[]{R.string.activity_type_biking, R.string.activity_type_cycling, R.string.activity_type_dirt_bike, R.string.activity_type_road_biking, R.string.activity_type_track_cycling};
private static final int[] BOAT_LIST = new int[]{R.string.activity_type_boat, R.string.activity_type_ferry, R.string.activity_type_motor_boating, R.string.activity_type_rc_boat};
private static final int[] CLIMBING_LIST = new int[]{R.string.activity_type_climbing};
private static final int[] DRIVE_LIST = new int[]{R.string.activity_type_atv, R.string.activity_type_driving, R.string.activity_type_driving_bus, R.string.activity_type_driving_car};
private static final int[] MOTOR_BIKE_LIST = new int[]{R.string.activity_type_motor_bike};
private static final int[] MOUNTAIN_BIKE_LIST = new int[]{R.string.activity_type_mountain_biking};
private static final int[] ESCOOTER_LIST = new int[]{R.string.activity_type_escooter};
private static final int[] INLINE_SKATING_LIST = new int[]{R.string.activity_type_inline_skating};
private static final int[] RUN_LIST = new int[]{R.string.activity_type_running, R.string.activity_type_street_running, R.string.activity_type_track_running, R.string.activity_type_trail_running};
private static final int[] SAILING_LIST = new int[]{R.string.activity_type_sailing};
private static final int[] SKI_LIST = new int[]{R.string.activity_type_cross_country_skiing, R.string.activity_type_skiing};
private static final int[] SNOW_BOARDING_LIST = new int[]{R.string.activity_type_snow_boarding};
private static final int[] SKATE_BOARDING_LIST = new int[]{R.string.activity_type_skate_boarding};
private static final int[] KAYAKING_LIST = new int[]{R.string.activity_type_kayaking};
private static final int[] WALK_LIST = new int[]{R.string.activity_type_hiking, R.string.activity_type_off_trail_hiking, R.string.activity_type_speed_walking, R.string.activity_type_trail_hiking, R.string.activity_type_walking};
private static final int[] SWIM_LIST = new int[]{R.string.activity_type_swimming, R.string.activity_type_swimming_open};
private static final int[] WORKOUT_LIST = new int[]{R.string.activity_type_workout};
// List of icons whose sports associated use speed (in km/h or mi/h).
private static final int[] SPEED_ICON = {
// Unknown.
R.string.activity_type_unknown,
// All airplane categories.
R.string.activity_type_airplane, R.string.activity_type_commercial_airplane, R.string.activity_type_rc_airplane,
// All bike categories.
R.string.activity_type_biking, R.string.activity_type_cycling, R.string.activity_type_dirt_bike, R.string.activity_type_motor_bike, R.string.activity_type_mountain_biking, R.string.activity_type_road_biking, R.string.activity_type_track_cycling, R.string.activity_type_inline_skating,
// All boat categories.
R.string.activity_type_boat, R.string.activity_type_ferry, R.string.activity_type_motor_boating, R.string.activity_type_rc_boat, R.string.activity_type_sailing, R.string.activity_type_kayaking,
// All drive categories.
R.string.activity_type_atv, R.string.activity_type_driving, R.string.activity_type_driving_bus, R.string.activity_type_driving_car, R.string.activity_type_escooter, R.string.activity_type_skate_boarding,
// All wintersport categories
R.string.activity_type_skiing, R.string.activity_type_snow_boarding
};
private static final LinkedHashMap<String, Pair<Integer, Integer>> MAP = new LinkedHashMap<>();
static {
//Reflects order in ChooseActivityTypeDialogFragmentActivity
MAP.put(UNKNOWN, new Pair<>(R.string.activity_type_unknown, ACTIVITY_UNKNOWN_LOGO));
MAP.put(RUN, new Pair<>(R.string.activity_type_running, R.drawable.ic_activity_run_24dp));
MAP.put(WALK, new Pair<>(R.string.activity_type_walking, R.drawable.ic_activity_walk_24dp));
MAP.put(CLIMBING, new Pair<>(R.string.activity_type_climbing, R.drawable.ic_activity_climbing_24dp));
MAP.put(SKATE_BOARDING, new Pair<>(R.string.activity_type_skate_boarding, R.drawable.ic_activity_skateboarding_24dp));
MAP.put(INLINE_SKATING, new Pair<>(R.string.activity_type_inline_skating, R.drawable.ic_activity_inline_skating_24dp));
MAP.put(SNOW_BOARDING, new Pair<>(R.string.activity_type_snow_boarding, R.drawable.ic_activity_snowboarding_24dp));
MAP.put(SKI, new Pair<>(R.string.activity_type_skiing, R.drawable.ic_activity_skiing_24dp));
MAP.put(ESCOOTER, new Pair<>(R.string.activity_type_escooter, R.drawable.ic_activity_escooter_24dp));
MAP.put(BIKE, new Pair<>(R.string.activity_type_biking, R.drawable.ic_activity_bike_24dp));
MAP.put(MOUNTAIN_BIKE, new Pair<>(R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp));
MAP.put(MOTOR_BIKE, new Pair<>(R.string.activity_type_motor_bike, R.drawable.ic_activity_motorbike_24dp));
MAP.put(DRIVE, new Pair<>(R.string.activity_type_driving, R.drawable.ic_activity_drive_24dp));
MAP.put(AIRPLANE, new Pair<>(R.string.activity_type_airplane, R.drawable.ic_activity_flight_24dp));
MAP.put(KAYAK, new Pair<>(R.string.activity_type_kayaking, R.drawable.ic_activity_kayaking_24dp));
MAP.put(BOAT, new Pair<>(R.string.activity_type_boat, R.drawable.ic_activity_boat_24dp));
MAP.put(SAILING, new Pair<>(R.string.activity_type_sailing, R.drawable.ic_activity_sailing_24dp));
MAP.put(SWIMMING, new Pair<>(R.string.activity_type_swimming, R.drawable.ic_activity_swimming_24dp));
MAP.put(SWIMMING_OPEN, new Pair<>(R.string.activity_type_swimming_open, R.drawable.ic_activity_swimming_open_24dp));
MAP.put(WORKOUT, new Pair<>(R.string.activity_type_workout, R.drawable.ic_activity_workout_24dp));
}
private TrackIconUtils() {
}
/**
* Gets the icon drawable.
*
* @param iconValue the icon value
*/
public static int getIconDrawable(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return ACTIVITY_UNKNOWN_LOGO;
if (found.isEmpty()) {
return ActivityType.UNKNOWN.getIconId();
}
Pair<Integer, Integer> pair = MAP.get(iconValue);
return pair == null ? ACTIVITY_UNKNOWN_LOGO : pair.second;
return found.get().getIconId();
}
/**
* Gets the icon activity type.
*
* @param iconValue the icon value
*/
public static int getIconActivityType(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return R.string.activity_type_unknown;
public static int getIconActivityType(String activityTypeId) {
Optional<ActivityType> found = Arrays.stream(ActivityType.values()).filter(
it -> it.getId().equals(activityTypeId)
).findFirst();
if (found.isEmpty()) {
return ActivityType.UNKNOWN.getFirstLocalizedStringId();
}
Pair<Integer, Integer> pair = MAP.get(iconValue);
return pair == null ? R.string.activity_type_unknown : pair.first;
return found.get().getFirstLocalizedStringId();
}
/**
* Gets all icon values.
*/
public static List<String> getAllIconValues() {
return new ArrayList<>(MAP.keySet());
return Arrays.stream(ActivityType.values())
.map(ActivityType::getId)
.collect(Collectors.toList());
}
/**
* Gets the icon value.
*
* @param context the context
* @param activityType the activity type
* @param context the context
* @param activityTypeId the activity type
*/
@NonNull
public static String getIconValue(Context context, String activityType) {
Resources resources = context.getResources();
if (activityType == null || activityType.equals("")) {
return UNKNOWN;
public static String getIconValue(Context context, String activityTypeId) {
Optional<ActivityType> selected = Arrays.stream(ActivityType.values())
.filter(
it -> Arrays.stream(it.getLocalizedStringIds())
.anyMatch(id -> context.getString(id).equals(activityTypeId))
)
.findFirst();
if (selected.isEmpty()) {
return ActivityType.UNKNOWN.getId();
}
if (inList(resources, activityType, AIRPLANE_LIST)) {
return AIRPLANE;
}
if (inList(resources, activityType, BIKE_LIST)) {
return BIKE;
}
if (inList(resources, activityType, MOUNTAIN_BIKE_LIST)) {
return MOUNTAIN_BIKE;
}
if (inList(resources, activityType, CLIMBING_LIST)) {
return CLIMBING;
}
if (inList(resources, activityType, MOTOR_BIKE_LIST)) {
return MOTOR_BIKE;
}
if (inList(resources, activityType, KAYAKING_LIST)) {
return KAYAK;
}
if (inList(resources, activityType, BOAT_LIST)) {
return BOAT;
}
if (inList(resources, activityType, SAILING_LIST)) {
return SAILING;
}
if (inList(resources, activityType, DRIVE_LIST)) {
return DRIVE;
}
if (inList(resources, activityType, INLINE_SKATING_LIST)) {
return INLINE_SKATING;
}
if (inList(resources, activityType, ESCOOTER_LIST)) {
return ESCOOTER;
}
if (inList(resources, activityType, RUN_LIST)) {
return RUN;
}
if (inList(resources, activityType, SKI_LIST)) {
return SKI;
}
if (inList(resources, activityType, SNOW_BOARDING_LIST)) {
return SNOW_BOARDING;
}
if (inList(resources, activityType, SKATE_BOARDING_LIST)) {
return SKATE_BOARDING;
}
if (inList(resources, activityType, WALK_LIST)) {
return WALK;
}
if (inList(resources, activityType, SWIM_LIST)) {
return SWIMMING;
}
if (inList(resources, activityType, WORKOUT_LIST)) {
return WORKOUT;
}
return UNKNOWN;
return selected.get().getId();
}
/**
* Returns true if the activity type is in the list.
*
* @param context the context
* @param activityType the activity type
* @param list the list
*/
private static boolean inList(Resources context, String activityType, int[] list) {
for (int i : list) {
if (context.getString(i).equals(activityType)) {
return true;
}
}
return false;
}
public static boolean isSpeedIcon(Resources resources, String activityTypeId) {
Optional<ActivityType> selected = Arrays.stream(ActivityType.values())
.filter(
it -> Arrays.stream(it.getLocalizedStringIds())
.anyMatch(id -> resources.getString(id).equals(activityTypeId))
)
.findFirst();
/**
* Returns true if category is in the SPEED_ICON array. Otherwise returns false.
*
* @param category the name of the category, activity type.
*/
public static boolean isSpeedIcon(Resources resources, String category) {
return inList(resources, category, SPEED_ICON);
if (selected.isEmpty()) {
return false;
}
return selected.get().isShowSpeedPreferred();
}
}
@@ -0,0 +1,27 @@
package de.dennisguse.opentracks.viewmodels;
import android.view.LayoutInflater;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.StatsClockItemBinding;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
public class ClockViewHolder extends StatisticViewHolder<StatsClockItemBinding> {
@Override
protected StatsClockItemBinding createViewBinding(LayoutInflater inflater) {
return StatsClockItemBinding.inflate(inflater);
}
@Override
public void configureUI(DataField dataField) {
//TODO Unify with GenericStatisticsViewHolder?
getBinding().statsClock.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
}
}
@@ -0,0 +1,256 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import android.view.LayoutInflater;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.util.StringUtils;
public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<StatsGenericItemBinding> {
@Override
protected StatsGenericItemBinding createViewBinding(LayoutInflater inflater) {
return StatsGenericItemBinding.inflate(inflater);
}
@Override
public void configureUI(DataField dataField) {
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
public static class Distance extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = DistanceFormatter.Builder()
.setUnit(unitSystem)
.build(getContext()).getDistanceParts(data.getTrackStatistics().getTotalDistance());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_distance));
}
}
public static class TotalTime extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(data.getTrackStatistics().getTotalTime()), null);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_total_time));
}
}
public static class MovingTime extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
String value = StringUtils.formatElapsedTime(data.getTrackStatistics().getMovingTime());
getBinding().statsValue.setText(value);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_moving_time));
}
}
public static class SpeedOrPace extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
// TODO Pace wont work for now
boolean reportSpeed = true;
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(reportSpeed).build(getContext());
Pair<String, String> valueAndUnit;
SensorDataSet sensorDataSet = data.getSensorDataSet();
final TrackPoint latestTrackPoint = data.getLatestTrackPoint();
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
getBinding().statsDescriptionMain.setText(sensorDataSet.getSpeed().second);
} else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.description_speed_source_gps));
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
}
}
public static class AverageMovingSpeed extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(true)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_speed));
}
}
public static class AverageSpeed extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(true)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_speed));
}
}
public static class MaxSpeed extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(true)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_max_speed));
}
}
public static class AverageMovingPace extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(false)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_pace));
}
}
public static class AveragePace extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(false)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_pace));
}
}
public static class FastestPace extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(false)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_fastest_pace));
}
}
public static class Altitude extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
String altitudeReference = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? getContext().getString(latestTrackPoint.getAltitude().getLabelId()) : null;
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), altitude, unitSystem);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_altitude);
getBinding().statsDescriptionSecondary.setText(altitudeReference);
}
}
public static class Gain extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeGain(), unitSystem);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_gain);
}
}
public static class Loss extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeLoss(), unitSystem);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_loss);
}
}
public static class Coordinates extends GenericStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
String value;
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
value = StringUtils.formatCoordinate(getContext(), latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude());
} else {
value = getContext().getString(R.string.value_unknown);
}
getBinding().statsValue.setText(value);
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
}
}
}
@@ -0,0 +1,41 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R;
public class Mapping {
public static Map<String, Callable<StatisticViewHolder<?>>> create(Context context) {
HashMap<String, Callable<StatisticViewHolder<?>>> m = new HashMap<>();
m.put(context.getString(R.string.stats_custom_layout_total_time_key), GenericStatisticsViewHolder.TotalTime::new);
m.put(context.getString(R.string.stats_custom_layout_moving_time_key), GenericStatisticsViewHolder.MovingTime::new);
m.put(context.getString(R.string.stats_custom_layout_distance_key), GenericStatisticsViewHolder.Distance::new);
m.put(context.getString(R.string.stats_custom_layout_speed_key), GenericStatisticsViewHolder.SpeedOrPace::new);
m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.SpeedOrPace::new);
m.put(context.getString(R.string.stats_custom_layout_average_moving_speed_key), GenericStatisticsViewHolder.AverageMovingSpeed::new);
m.put(context.getString(R.string.stats_custom_layout_average_speed_key), GenericStatisticsViewHolder.AverageSpeed::new);
m.put(context.getString(R.string.stats_custom_layout_max_speed_key), GenericStatisticsViewHolder.MaxSpeed::new);
m.put(context.getString(R.string.stats_custom_layout_average_moving_pace_key), GenericStatisticsViewHolder.AverageMovingPace::new);
m.put(context.getString(R.string.stats_custom_layout_average_pace_key), GenericStatisticsViewHolder.AveragePace::new);
m.put(context.getString(R.string.stats_custom_layout_fastest_pace_key), GenericStatisticsViewHolder.FastestPace::new);
m.put(context.getString(R.string.stats_custom_layout_altitude_key), GenericStatisticsViewHolder.Altitude::new);
m.put(context.getString(R.string.stats_custom_layout_gain_key), GenericStatisticsViewHolder.Gain::new);
m.put(context.getString(R.string.stats_custom_layout_loss_key), GenericStatisticsViewHolder.Loss::new);
m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new);
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), SensorStatisticsViewHolder.SensorHeartRate::new);
m.put(context.getString(R.string.stats_custom_layout_cadence_key), SensorStatisticsViewHolder.SensorCadence::new);
m.put(context.getString(R.string.stats_custom_layout_power_key), SensorStatisticsViewHolder.SensorPower::new);
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
return m;
}
}
@@ -0,0 +1,108 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import android.view.LayoutInflater;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.HeartRateZones;
import de.dennisguse.opentracks.databinding.StatsSensorItemBinding;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.util.StringUtils;
public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<StatsSensorItemBinding> {
@Override
protected StatsSensorItemBinding createViewBinding(LayoutInflater inflater) {
return StatsSensorItemBinding.inflate(inflater);
}
@Override
public void configureUI(DataField dataField) {
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
public static class SensorHeartRate extends SensorStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
sensorName = sensorDataSet.getHeartRate().second;
} else {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
}
//TODO Loads preference every time
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
int textColor;
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
textColor = zones.getTextColorForZone(getContext(), sensorDataSet.getHeartRate().first);
} else {
textColor = zones.getTextColorForZone(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
getBinding().statsDescriptionSecondary.setText(sensorName);
getBinding().statsValue.setTextColor(textColor);
}
}
public static class SensorCadence extends SensorStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
sensorName = sensorDataSet.getCadence().second;
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
public static class SensorPower extends SensorStatisticsViewHolder {
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
}
@@ -1,45 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.ui.customRecordingLayout.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;
}
}
@@ -1,232 +0,0 @@
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.data.models.Cadence;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.util.StringUtils;
public class StatisticDataBuilder {
public static List<StatisticData> fromRecordingData(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull Layout layout, UnitSystem unitSystem) {
List<StatisticData> statisticDataList = layout.getFields().stream()
.filter(DataField::isVisible)
.map(field -> build(context, recordingData, field.getKey(), field.isPrimary(), unitSystem))
.filter(Objects::nonNull)
.collect(Collectors.toList());
statisticDataList.addAll(getSensorStatsDataIfNeeded(context, recordingData, statisticDataList, unitSystem));
return statisticDataList;
}
private static StatisticData build(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, UnitSystem unitSystem) {
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
final TrackStatistics trackStatistics = recordingData.getTrackStatistics();
String title = null;
String description = null;
Pair<String, String> valueAndUnit = null;
boolean isWide = false;
final String sensorUnknown = context.getString(R.string.value_unknown);
SpeedFormatter speedFormatterPace = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(false).build(context);
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context);
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.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(trackStatistics.getMovingTime()), null);
title = context.getString(R.string.stats_moving_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
valueAndUnit = DistanceFormatter.Builder()
.setUnit(unitSystem)
.build(context).getDistanceParts(trackStatistics.getTotalDistance());
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 = reportSpeed ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
description = sensorDataSet.getSpeed().second;
} else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
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 = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageMovingSpeed());
title = context.getString(R.string.stats_average_moving_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageSpeed());
title = context.getString(R.string.stats_average_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getMaxSpeed());
title = context.getString(R.string.stats_max_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageMovingSpeed());
title = context.getString(R.string.stats_average_moving_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageSpeed());
title = context.getString(R.string.stats_average_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getMaxSpeed());
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, unitSystem);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeGain(), unitSystem);
title = context.getString(R.string.stats_gain);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeLoss(), unitSystem);
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_unknown), 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) {
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().first);
description = sensorDataSet.getHeartRate().second;
} else {
valueAndUnit = StringUtils.getHeartRateParts(context, null);
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
title = context.getString(R.string.stats_sensors_cadence);
Cadence cadence = null;
if (sensorDataSet != null) {
Pair<Cadence, String> cadenceData = sensorDataSet.getCadence();
if (cadenceData != null) {
cadence = cadenceData.first;
description = cadenceData.second;
}
}
valueAndUnit = StringUtils.getCadenceParts(context, cadence);
if (description == null) {
description = sensorUnknown;
}
} 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()) {
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getPowerParts(context, null);
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
title = context.getString(R.string.stats_clock);
valueAndUnit = new Pair<>(null, null);
}
if (valueAndUnit == null) {
return null;
}
return new StatisticData(new DataField(fieldKey, title, true, isPrimary, isWide), valueAndUnit, description);
}
/**
* Builds a list of StatisticData with sensors configured but not in statisticDataList.
*/
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, RecordingData recordingData, List<StatisticData> statisticDataList, UnitSystem unitSystem) {
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) {
sensorDataList.add(build(context, recordingData, "heart_rate", true, unitSystem));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCadence() != null) {
sensorDataList.add(build(context, recordingData, "cadence", true, unitSystem));
}
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
sensorDataList.add(build(context, recordingData, "power", true, unitSystem));
}
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,41 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import androidx.viewbinding.ViewBinding;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
public abstract class StatisticViewHolder<T extends ViewBinding> {
private Context context;
private T binding;
public void initialize(Context context, LayoutInflater inflater) {
this.context = context;
this.binding = createViewBinding(inflater);
}
protected abstract T createViewBinding(LayoutInflater inflater);
public abstract void configureUI(DataField dataField);
public abstract void onChanged(UnitSystem unitSystem, RecordingData data);
public View getView() {
return binding.getRoot();
}
T getBinding() {
return binding;
}
Context getContext() {
return context;
}
}
@@ -1,36 +0,0 @@
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.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
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(RecordingData recordingData, Layout layout, UnitSystem unitSystem) {
new Thread(() -> {
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, unitSystem);
statsData.postValue(statisticDataList);
}).start();
}
}
+3 -1
View File
@@ -120,6 +120,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -56,6 +56,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -75,6 +75,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -21,6 +21,8 @@
</ScrollView>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -26,6 +26,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+1 -2
View File
@@ -22,11 +22,10 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<include
<FrameLayout
android:id="@+id/stats_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
layout="@layout/stats_generic_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"
+3 -1
View File
@@ -307,6 +307,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -143,6 +143,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -251,6 +251,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -28,6 +28,8 @@ limitations under the License.
android:layout_height="match_parent" />
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -138,6 +138,8 @@ limitations under the License.
</LinearLayout>
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -38,6 +38,8 @@ limitations under the License.
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -17,6 +17,8 @@
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+6 -11
View File
@@ -1,16 +1,11 @@
<?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"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/stats_recycler_view"
<androidx.gridlayout.widget.GridLayout
android:id="@+id/stats_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</ScrollView>
+52
View File
@@ -0,0 +1,52 @@
<?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:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/stats_value"
style="@style/TextAppearance.OpenTracks.PrimaryValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:value="@string/value_unknown"
app:layout_constraintEnd_toStartOf="@id/stats_unit"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="90" />
<TextView
android:id="@+id/stats_unit"
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/stats_value"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/stats_value"
tools:text="@string/sensor_unit_beats_per_minute" />
<TextView
android:id="@+id/stats_description_main"
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_value"
tools:text="@string/sensor_state_heart_rate" />
<TextView
android:id="@+id/stats_description_secondary"
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
tools:text="90" />
</androidx.constraintlayout.widget.ConstraintLayout>
+3 -1
View File
@@ -46,6 +46,8 @@ limitations under the License.
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+3 -1
View File
@@ -36,6 +36,8 @@ limitations under the License.
</LinearLayout>
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
<include
android:id="@+id/bottom_app_bar_layout"
layout="@layout/bottomappbar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+49 -3
View File
@@ -23,7 +23,7 @@ limitations under the License.
<string name="about_libraries">Bibliotecas</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks tiene las siguientes características:</string>
<string name="help_feature_content">* crear pistas de GPS
<string name="help_feature_content">* crear recorridos 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)
@@ -220,7 +220,7 @@ limitations under the License.
<!-- OpenTracks -->
<string name="recording_service">Servicio de grabación</string>
<!-- Permission -->
<string name="permission_gps_failed">OpenTracks necesita permiso para usar el GPS.</string>
<string name="permission_gps_failed">OpenTracks requiere permiso para usar el GPS.</string>
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_heart_rate">Frecuencia cardíaca</string>
@@ -325,22 +325,27 @@ limitations under the License.
<!-- Voice -->
<plurals name="voiceHours">
<item quantity="one">1 hora</item>
<item quantity="many">%1$d horas</item>
<item quantity="other">%1$d horas</item>
</plurals>
<plurals name="voiceMinutes">
<item quantity="one">1 minuto</item>
<item quantity="many">%1$d minutos</item>
<item quantity="other">%1$d minutos</item>
</plurals>
<plurals name="voiceSeconds">
<item quantity="one">1 segundo</item>
<item quantity="many">%1$d segundos</item>
<item quantity="other">%1$d segundos</item>
</plurals>
<plurals name="voiceSpeedKilometersPerHour">
<item quantity="one">1 kilómetro por hora</item>
<item quantity="many">%1$.1f kilómetros por hora</item>
<item quantity="other">%1$.1f kilómetros por hora</item>
</plurals>
<plurals name="voiceSpeedMilesPerHour">
<item quantity="one">1 milla por hora</item>
<item quantity="many">%1$.1f millas por hora</item>
<item quantity="other">%1$.1f millas por hora</item>
</plurals>
<!-- Waypoint Type -->
@@ -409,6 +414,7 @@ limitations under the License.
<string name="generic_show_errors">Mostrar errores</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Completado con un error</item>
<item quantity="many">Completado con %1$d errores</item>
<item quantity="other">Completado con %1$d errores</item>
</plurals>
<string name="generic_completed">Completado</string>
@@ -476,7 +482,7 @@ limitations under the License.
<string name="stats_sensors_power">Poder</string>
<string name="field_set_primary">Primario</string>
<string name="description_speed_source_gps">GPS</string>
<string name="settings_recording_customize_layout_title">Personalice su pantalla de grabación</string>
<string name="settings_recording_customize_layout_title">Personalizar pantalla de grabación</string>
<string name="settings_recording_customize_layout_select_columns">Datos por columna</string>
<string name="settings_layout_reset_confirm_message">Todos los ajustes de diseño se revertirán a los valores predeterminados. Esto no eliminará ningún recorrido en el dispositivo.</string>
<string name="settings_layout_reset_confirm_title">¿Restablecer todos los diseños\?</string>
@@ -562,13 +568,53 @@ limitations under the License.
<string name="total_distance">Distancia total</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">1 kilómetro</item>
<item quantity="many">%1$.1f kilómetros</item>
<item quantity="other">%1$.1f kilómetros</item>
</plurals>
<plurals name="voiceDistanceMiles">
<item quantity="one">1 milla</item>
<item quantity="many">%1$.1f millas</item>
<item quantity="other">%1$.1f millas</item>
</plurals>
<string name="average_heart_rate">Ritmo cardíaco promedio</string>
<string name="current_heart_rate">Ritmo cardíaco actual</string>
<string name="settings_export_filename_title">Formato del nombre del archivo</string>
<string name="help_data_google_play_services_content">No</string>
<string name="description_pace_nautical">Ritmo (min/NM)</string>
<string name="description_speed_nautical">Velocidad (Nudos)</string>
<string name="image_discard">Descartar</string>
<string name="settings_announcements_average_heart_rate">Frecuencia cardíaca media</string>
<string name="settings_announcements_average_speed_pace">Velocidad/Ritmo medio</string>
<string name="settings_announcements_lap_speed_pace">Velocidad/Ritmo por vuelta</string>
<string name="settings_stats_units_nautical">Náutico (NM, pie)</string>
<string name="track_delete_number_of_tracks">%1$d recorridos</string>
<string name="track_discarding">Descartando recorrido</string>
<string name="unit_nautical_mile">NM</string>
<string name="unit_minute_per_nautical_mile">min/NM</string>
<string name="value_integer_knots">%1$d nudos</string>
<string name="value_float_knots">%1$.1f nudos</string>
<string name="value_float_knots_recommended">%1$.1f nudos (recomendado)</string>
<string name="voice_per_nautical_mile">por milla náutica</string>
<string name="error_crash_title">%1$s falló</string>
<string name="report_bug">Reportar error</string>
<string name="show_on_map_format_ask">Preguntar</string>
<string name="show_on_dashboard">Panel de control del API de OpenTracks</string>
<string name="show_on_dashboard_not_installed">No se ha instalado ninguna aplicación compatible con el panel de control del API de OpenTracks.</string>
<string name="always">Siempre</string>
<string name="settings_announcements_lap_heart_rate">Frecuencia cardiaca por vuelta</string>
<string name="settings_public_api_package_title">Nombre del paquete para el API público</string>
<string name="unit_knots">Nudo</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">%1$.1f nudo</item>
<item quantity="many">%1$.1f nudos</item>
<item quantity="other">%1$.1f nudos</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">%1$.1f milla náutica</item>
<item quantity="many">%1$.1f millas náuticas</item>
<item quantity="other">%1$.1f millas náuticas</item>
</plurals>
<string name="select_show_on_map_behavior">Seleccionar el comportamiento de \"mostrar en el mapa\"</string>
<string name="introduction_osm_dashboard">OpenTracks en sí no proporciona un mapa. Por favor, instale OSMDashboard para ver sus grabaciones en un mapa.</string>
<string name="share_crash_report">Compartir informe de fallo</string>
</resources>
+32 -31
View File
@@ -14,40 +14,38 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--><resources>
<string name="about_preference_title">Acerca de OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> é unha aplicación para rexistrar as túas actividades deportivas que respecta a túa privacidade.</string>
<string name="about_preference_title">Sobre OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> é unha aplicación para rexistrar actividades deportivas que respecta a túa privacidade.</string>
<string name="about_url">Web do proxecto: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Nome da versión: %1$s</string>
<string name="about_version_code">Código da versión: %1$d</string>
<string name="about_history">OpenTracks é un fork de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a>(abandonado en 2016).</string>
<string name="about_history">OpenTracks é unha bifurcación de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a> (abandonado en 2016).</string>
<string name="about_libraries">Bibliotecas</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks ten as seguintes funcións:</string>
<string name="help_feature_content">
* crea rutas GPS\n
* usa monitor do pulso cardíaco (só Bluetooth LE)\n
* anuncios por voz (de tempo ou distancia)\n
* crea marcadores (incl. fotos)\n
* funciona sen precisar conexión a rede\n
* mostra as rutas nun mapa (ver abaixo)
</string>
<string name="help_track_map_title">¿Como mostrar a ruta nun mapa?</string>
<string name="help_feature_title">Características de OpenTracks:</string>
<string name="help_feature_content">* crear rutas GPS
\n * usar monitor do pulso cardíaco (só Bluetooth LE)
\n * anuncios por voz (de tempo ou distancia)
\n * crear marcadores (incl. fotos)
\n * funciona sen precisar conexión á rede
\n * mostra as rutas nun mapa (ver abaixo)</string>
<string name="help_track_map_title">Como mostrar a ruta no mapa\?</string>
<string name="help_track_map_content"> Usa a icona do mapa pequeno.
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard"> OSMDashboard </a>. Ofrece mapas en liña ou fóra de liña a través de <a href="https://www.openstreetmap.org"> OpenStreetMap </a>.
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Proporciona mapas en liña ou sen conexión grazas a <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n En realidade, OSMDashboard é unha aplicación independente que só pode acceder (temporalmente) aos datos das pistas que se amosarán.
\n b) A través dunha aplicación de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicacións precisan admitir o formato de ficheiro KMZ (só localizacións e marcas de tempo). De feito, unha aplicación ten que admitir <i> ACTION_VIEW </i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">¿De que xeito garda OpenTracks as fotos tomadas como marcadores?</string>
<string name="help_storage_pictures_content">Gárdanse no almacenaxe interno da app (só accesible desde OpenTracks).</string>
<string name="help_track_export_title">¿De que xeito exporto/importo as rutas gravadas?</string>
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As ruts compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
<string name="help_data_google_play_services_title">¿OpenTracks precisa/utiliza Google Play Service?</string>
<string name="help_recording_title">¿Como grava OpenTracks os datos?</string>
\n b) A través dunha aplicación de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicacións precisan admitir o formato de ficheiro KMZ (só localizacións e marcas de tempo). De feito, unha aplicación ten que admitir <i>ACTION_VIEW</i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_storage_pictures_title">De que xeito garda OpenTracks as fotos tomadas como marcadores\?</string>
<string name="help_storage_pictures_content">Gárdanse na almacenaxe interna da app (só accesible desde OpenTracks).</string>
<string name="help_track_export_title">De que xeito exporto/importo as rutas gravadas\?</string>
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
<string name="help_data_google_play_services_title">OpenTracks precisa/utiliza Google Play Service\?</string>
<string name="help_recording_title">Como grava OpenTracks os datos\?</string>
<string name="help_recording_content">
OpenTracks usa o sensor GPS do dispositivo para gravar a localización de xeito contínuo.
Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, elevación), OpenTracks non pode facer nada ao respecto.</string>
<string name="help_data_storage_title">De que xeito garda OpenTracks os datos dos sensores\?</string>
<string name="help_data_storage_content"> Os datos dos sensores gárdanse asociados á localización. Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). </string>
<string name="help_gps_troubleshooting_title">¿OpenTracks non grava as localizacións?</string>
<string name="help_gps_troubleshooting_title">OpenTracks non grava as localizacións\?</string>
<string name="help_gps_troubleshooting_content"> Podes intentar:
\n a) mira que o GPS esté activado,
\n b) mellora a recepción GPS (ex. sae ao exterior),
@@ -55,7 +53,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
\n d) desactiva o aforro de enerxía.
\n
\n Se isto non axuda, infórmanos do problema. </string>
<string name="help_support_title">¿Como podo axudar a OpenTracks?</string>
<string name="help_support_title">Como podo axudar a OpenTracks\?</string>
<string name="help_support_content"> Podes:
\n a) falar de OpenTracks coas túas amizades,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">propoñer novas funcións</a>,
@@ -154,7 +152,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="description_recorded_time">Hora da gravación: %1$s</string>
<string name="description_speed_ms">Velocidade (m/s)</string>
<string name="description_sensor_cadence">Cadencia (rpm)</string>
<string name="description_sensor_heart_rate">Frecuencia cardíaca (pulsacións por minuto)</string>
<string name="description_sensor_heart_rate">Pulso cardíaco (latexos por minuto)</string>
<string name="description_sensor_power">Potencia (W)</string>
<string name="description_default_speed_or_pace">Por tipo de actividade</string>
<string name="description_speed_imperial">Velocidade (mi/h)</string>
@@ -233,8 +231,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="permission_gps_failed">OpenTracks precisa permiso para usar o GPS.</string>
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_heart_rate">Frecuencia cardíaca</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_heart_rate">Pulso cardíaco</string>
<string name="sensor_state_heart_rate_value">%1$d lpm</string>
<string name="sensor_state_power">Potencia</string>
<string name="sensor_could_not_scan">Non se escanearon dispositivos Bluetooth (fallo %1$i).</string>
<string name="bluetooth_disabled">Activa o Bluetooth.</string>
@@ -508,10 +506,10 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recomendado)</string>
<string name="stats_clock">Reloxo</string>
<string name="settings_open_tracks_title">Open Tracks</string>
<string name="settings_defaults_summary">O Preferido Unidades, Por Defecto Actividade, Controlar O Nome Por Defecto</string>
<string name="settings_defaults_summary">Unidades preferidas, Actividade por defecto, Nome da ruta</string>
<string name="settings_announcements_summary">A Distancia, Tempo, Voz Velocidade</string>
<string name="settings_open_tracks_summary">Todo Sobre OpenTracks Proxecto</string>
<string name="settings_defaults_title">Por defecto Unidades e Actividade</string>
<string name="settings_defaults_title">Unidades e Actividade por defecto</string>
<string name="settings_ui_title">Interface da app</string>
<string name="settings_ui_summary">Aparencia, Gravación, Deseño, Tema</string>
<string name="settings_gps_title">GPS</string>
@@ -523,7 +521,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="settings_import_export_summary">Importación, Exportación, Auto Exportación</string>
<string name="settings_units_category">Unidades</string>
<string name="settings_activity_defaults_category">Valores por defecto</string>
<string name="settings_reset_summary">Axustar a Configuración de Valores por Defecto</string>
<string name="settings_reset_summary">Restablecer Axustes dos Valores</string>
<string name="menu_select_layout">Elixe disposición</string>
<string name="custom_layout_list_title">Disposicións</string>
<string name="custom_layout_list_edit_name">Escribe o nome da nova disposición</string>
@@ -576,8 +574,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">OpenTracks está gravando sen datos GPS.</string>
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
<string name="settings_announcements_lap_heart_rate">Ritmo cardíaco na volta</string>
<string name="settings_announcements_average_heart_rate">Ritmo cardíaco medio</string>
<string name="settings_announcements_lap_heart_rate">Pulso durante a volta</string>
<string name="settings_announcements_average_heart_rate">Pulso cardíaco medio</string>
<string name="image_discard">Descartar</string>
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo medio</string>
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo da volta</string>
@@ -611,4 +609,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="settings_public_api_package_title">Nome do paquete para Public API</string>
<string name="track_delete_number_of_tracks">%1$d rutas</string>
<string name="introduction_osm_dashboard">OpenTracks non proporciona un mapa. Instala OSMDashboard para ver as gravacións nun mapa.</string>
<string name="help_data_google_play_services_content">Non</string>
<string name="settings_sensor_heart_rate_max">Zonas: Pulso máximo (lpm)</string>
<string name="settings_api_summary">Control remoto e API de estatísticas para outras apps</string>
</resources>
+118 -116
View File
@@ -47,25 +47,25 @@ limitations under the License.
<string name="activity_type_motor_boating">motorbåtkjøring</string>
<string name="activity_type_mountain_biking">terrengsykling</string>
<string name="activity_type_off_trail_hiking">fotturer utenfor sti</string>
<string name="activity_type_other">andre</string>
<string name="activity_type_other">annet</string>
<string name="activity_type_paddling">padling</string>
<string name="activity_type_para_gliding">paragliding</string>
<string name="activity_type_rc_airplane">Radiostyrt fly</string>
<string name="activity_type_rc_boat">Radiostyrt båt</string>
<string name="activity_type_rc_helicopter">Radiostyrt helikopter</string>
<string name="activity_type_riding">ridning</string>
<string name="activity_type_road_biking">sykling (vei)</string>
<string name="activity_type_road_biking">landeveissykling</string>
<string name="activity_type_roller_skiing">rulleski</string>
<string name="activity_type_rowing">roing</string>
<string name="activity_type_running">jogging</string>
<string name="activity_type_sailing">seiling</string>
<string name="activity_type_seaplane">sjøfly</string>
<string name="activity_type_skate_boarding">skateboarding</string>
<string name="activity_type_skate_boarding">rullebrett</string>
<string name="activity_type_skating">skøyter</string>
<string name="activity_type_skiing">skisport</string>
<string name="activity_type_sky_jumping">fallskjermhopping</string>
<string name="activity_type_sled">sledekjøring</string>
<string name="activity_type_snow_boarding">snowboardkjøring</string>
<string name="activity_type_snow_boarding">snøbrett</string>
<string name="activity_type_snow_shoeing">truger</string>
<string name="activity_type_speed_walking">hurtig gange</string>
<string name="activity_type_street_running">gateløp</string>
@@ -84,9 +84,9 @@ limitations under the License.
<!-- Description -->
<string name="description_activity_type">Aktivitetstype: %1$s</string>
<string name="description_average_moving_pace_in_minute">Gjennomsnittlig forflytningstempo: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_moving_speed">Gjennomsnittlig forflytningshastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_average_pace_in_minute">Gjennomsnittlig tempo: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_speed">Gjennomsnittlig hastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_average_moving_speed">Gjen. forflytningshastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_average_pace_in_minute">Gjen. tempo: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_speed">Gjen. hastighet: %1$.2f km/t (%2$.1f mi/h)</string>
<string name="description_altitude_gain">Total stigning: %1$d m (%2$d ft)</string>
<string name="description_altitude_imperial">Høyde (ft)</string>
<string name="description_altitude_metric">Høyde (m)</string>
@@ -108,11 +108,11 @@ limitations under the License.
<string name="description_total_distance">Total distanse: %1$.2f km (%2$.1f mi)</string>
<string name="description_total_time">Total tid: %1$s</string>
<!-- Export -->
<string name="export_progress_message">Eksporterer til %1$s…</string>
<string name="export_progress_message">Eksporterer til %1$s …</string>
<!-- External Storage -->
<!-- Generic -->
<string name="generic_add">Legg til</string>
<string name="generic_delete_selected_confirm_title">Vil du slette de valgte elementene?</string>
<string name="generic_delete_selected_confirm_title">Slett valgte elementer\?</string>
<string name="generic_description">Beskrivelse</string>
<string name="generic_description_line">Beskrivelse: %1$s</string>
<string name="generic_name">Navn</string>
@@ -133,14 +133,14 @@ limitations under the License.
<string name="image_stop">Stopp</string>
<string name="image_track">Spor</string>
<!-- Import -->
<string name="import_progress_message">Importerer fra %1$s…</string>
<string name="import_progress_message">Importerer fra %1$s …</string>
<!-- Marker -->
<string name="marker_add_error">Kan ikke sette inn markør. Har ikke GPS-signal. Prøv på nytt.</string>
<string name="marker_add_success">En markør ble satt inn</string>
<string name="marker_add_canceled">Avbrutt – ingen markør lagt til</string>
<string name="marker_delete_one_confirm_message">Markøren blir slettet permanent fra enheten</string>
<string name="marker_delete_one_confirm_title">Vil du slette markøren?</string>
<string name="marker_delete_multiple_confirm_message">De valgte markørene blir slettet permanent fra enheten</string>
<string name="marker_add_canceled">Avbrutt. Ingen markør lagt til.</string>
<string name="marker_delete_one_confirm_message">Markøren blir slettet fra enheten for godt</string>
<string name="marker_delete_one_confirm_title">Slett markøren\?</string>
<string name="marker_delete_multiple_confirm_message">De valgte markørene blir slettet fra enheten for godt</string>
<string name="marker_name_format">Markør %1$d</string>
<string name="marker_title">%1$d av %2$d</string>
<!-- Marker Detail -->
@@ -175,12 +175,12 @@ limitations under the License.
<!-- Settings Advanced -->
<string name="settings_advanced">Avansert</string>
<!-- Settings Chart -->
<string name="settings_chart_by_distance">Etter distanse</string>
<string name="settings_chart_by_distance">Etter avstand</string>
<string name="settings_chart_by_time">Etter tid</string>
<string name="settings_recording_default_activity_title">Standard aktivitetstype</string>
<string name="settings_recording_default_activity_title">Forvalgt aktivitetstype</string>
<string name="settings_recording_location_frequency_summary">%1$s mellom registrerte posisjoner</string>
<string name="settings_recording_max_recording_distance_summary">Det blir opprettet et nytt segment hvis avstanden mellom to posisjoner er større enn %1$s</string>
<string name="settings_recording_max_recording_distance_title">Maks avstand</string>
<string name="settings_recording_max_recording_distance_title">Maks. avstand</string>
<string name="settings_recording_min_recording_distance_title">Avstandsintervall for opptak</string>
<string name="settings_recording_min_recording_interval_title">Tidsintervall for opptak</string>
<string name="settings_recording_min_required_accuracy_summary">Posisjonen ignoreres hvis nøyaktigheten er under %1$s</string>
@@ -188,17 +188,17 @@ limitations under the License.
<string name="settings_recording_track_name_date_iso_8601_option">Dato (ISO 8601)</string>
<string name="settings_recording_track_name_date_local_option">Dato (lokal)</string>
<string name="settings_recording_track_name_number_option">Nummer</string>
<string name="settings_recording_track_name_title">Standard-spornavn</string>
<string name="settings_recording_track_name_title">Forvalgt spornavn</string>
<!-- Settings Reset -->
<string name="settings_reset_confirm_message">Alle innstillinger blir tilbakestilt til standardverdiene. Dette vil ikke slette noen spor på enheten.</string>
<string name="settings_reset_confirm_title">Vil du tilbakestille nå?</string>
<string name="settings_reset_done">Alle innstillinger er tilbakestilt til standardverdiene</string>
<string name="settings_reset_confirm_message">Alle innstillinger blir tilbakestilt til forvalg. Dette sletter ikke spor på enheten.</string>
<string name="settings_reset_confirm_title">Tilbakestill nå\?</string>
<string name="settings_reset_done">Alle innstillinger tilbakestilt til forvalg</string>
<!-- Settings Sensor -->
<string name="settings_sensor_bluetooth">Bluetooth</string>
<string name="settings_sensor_bluetooth">Blåtann</string>
<!-- Settings Stats -->
<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>
<string name="settings_stats_units_imperial">Britiske (mi, ft)</string>
<string name="settings_stats_units_metric">Metriske (km, m)</string>
<string name="settings_stats_units_title">Foretrukne enheter</string>
<!-- Share Track -->
<string name="share_track_share_file_body">Jeg tror du kan være interessert i dette sporet.
@@ -206,10 +206,10 @@ limitations under the License.
\n%1$s</string>
<string name="share_track_subject">Jeg vil dele et spor med deg</string>
<!-- Stats -->
<string name="stats_average_moving_pace">Gj. forflytningstempo</string>
<string name="stats_average_moving_speed">Gj. forflytningshastighet</string>
<string name="stats_average_pace">Gj. tempo</string>
<string name="stats_average_speed">Gj. hastighet</string>
<string name="stats_average_moving_pace">Gjen. forflytningstempo</string>
<string name="stats_average_moving_speed">Gjen. forflytningshastighet</string>
<string name="stats_average_pace">Gjen. tempo</string>
<string name="stats_average_speed">Gjen. hastighet</string>
<string name="stats_altitude">Høyde</string>
<string name="stats_fastest_pace">Raskeste tempo</string>
<string name="stats_gain">Total stigning</string>
@@ -225,9 +225,9 @@ limitations under the License.
<string name="track_name_format">Spor %1$d</string>
<string name="track_markers">Markører for %1$s</string>
<!-- Track Delete -->
<string name="track_delete_one_confirm_message">Sporet og markørene slettes permanent fra enheten.</string>
<string name="track_delete_one_confirm_title">Vil du slette sporet?</string>
<string name="track_delete_multiple_confirm_message">De valgte sporene og markørene slettes permanent fra enheten.</string>
<string name="track_delete_one_confirm_message">Sporet og markørene slettes fra enheten for godt.</string>
<string name="track_delete_one_confirm_title">Slett sporet\?</string>
<string name="track_delete_multiple_confirm_message">Valgte spor og markører slettes fra enheten for godt.</string>
<string name="track_delete_progress_message">Sletter …</string>
<!-- Track Detail -->
<string name="track_detail_stats_tab">Statistikk</string>
@@ -349,25 +349,25 @@ limitations under the License.
<string name="gps_disabled_msg">GPS slått av</string>
<string name="import_unable_to_import_file">Kan ikke importere filen: %1$s</string>
<string name="import_error_list_dialog_title">Filer ikke importert</string>
<string name="import_file_imported">Fil %1$s importert</string>
<string name="marker_add_photo_canceled">Avbrutt, ingen fotomarkør lagt til</string>
<string name="import_file_imported">«%1$s»-fil importert</string>
<string name="marker_add_photo_canceled">Avbrutt. Ingen fotomarkør lagt til.</string>
<string name="menu_resume_track">Gjenoppta spor</string>
<string name="settings_default_trackfileformat">Filformat for eksport/deling</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Vis statistikk på låseskjermen</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Hold skjermen på</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Hold skjermen på mens du tar opp.</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Behold skjermen påslått</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Behold skjermen påslått under opptak</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Fullskjerm</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Mens du tar opp, vis i fullskjerm.</string>
<string name="bluetooth_disabled">Slå på Bluetooth.</string>
<string name="settings_night_mode_title">Brukerflatetema</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Alltid vis opptak i fullskjerm.</string>
<string name="bluetooth_disabled">Slå på Blåtann</string>
<string name="settings_night_mode_title">Brukerflatedrakt</string>
<string name="settings_night_mode_option_system">System</string>
<string name="settings_night_mode_option_day">Dag</string>
<string name="settings_night_mode_option_night">Natt</string>
<string name="track_recording_notification_accuracy">Posisjonsnøyaktighet: %1$s</string>
<string name="track_detail_intervals_tab">Intervaller</string>
<string name="aggregated_stats_empty_message">Ta opp ditt første spor for å se samlet statistikk</string>
<string name="stats_split_interval">Intervall</string>
<string name="interval_list_empty_message">Det er ingen intervaller ennå</string>
<string name="stats_split_interval">Intervallinndeling</string>
<string name="interval_list_empty_message">Ingen intervaller enda</string>
<string name="hold_to_stop">Hold nede for å stoppe</string>
<string name="instant_export_enabled_summary">Eksporter spor etter at opptak er ferdig</string>
<string name="instant_export_enabled_title">Auto-eksport etter opptak</string>
@@ -377,69 +377,69 @@ limitations under the License.
<string name="open_track_as_GPX">Åpne som GPX</string>
<string name="open_track_as_trackfileformat">Åpne som %1$s</string>
<string name="export_with_photos">med bilder</string>
<string name="export_track_already_exists_msg">Sporet %1$s finnes allerede i målmappen.</string>
<string name="export_track_already_exists_msg">«%1$s»-sporet finnes allerede i målmappen.</string>
<string name="import_unsupported_format">Filformatet støttes ikke</string>
<string name="menu_insert_gallery_img">Sett inn galleribilde</string>
<string name="permission_gps_failed">OpenTracks krever tillatelse til å bruke GPS.</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Vis statistikk under opptak uten å låse opp enheten.</string>
<string name="share_image_subject">Jeg vil gjerne dele et bilde med deg</string>
<string name="import_thread_interrupted">Tråden avbrutt</string>
<string name="sensor_could_not_scan">Kunne ikke skanne etter Bluetooth-enheter (feil %1$i).</string>
<string name="help_data_google_play_services_title">Krever/bruker OpenTracks Google Play Service\?</string>
<string name="import_thread_interrupted">Tråd avbrutt</string>
<string name="sensor_could_not_scan">Kunne ikke se etter Blåtannsenheter (feil %1$i).</string>
<string name="help_data_google_play_services_title">Krever/bruker OpenTracks Google Play-tjenesten\?</string>
<string name="help_feature_title">OpenTracks har følgende funksjoner:</string>
<string name="help_feature_content">* opprette GPS-spor
\n * bruke en pulssensor (kun Bluetooth LE)
\n * talemeldinger (etter tid eller distanse)
\n * opprette markører (inkl. bilder)
\n * fungerer helt uten internettforbindelse
\n * viser spor på et kart (se nedenfor)</string>
<string name="help_feature_content">* Opprett GPS-spor
\n * Bruk av pulssensor (kun Blåtann-LE)
\n * Talemeldinger (etter tid eller distanse)
\n * Opprett markører (inkl. bilder)
\n * Fungerer uten Internett-forbindelse
\n * Vis spor på et kart (se nedenfor)</string>
<string name="help_track_map_title">Hvordan vise et spor på et kart\?</string>
<string name="about_description"><i>OpenTracks</i> er en sportssporingsapp som fullt ut respekterer ditt personvern.</string>
<string name="about_description"><i>OpenTracks</i> er et sportssporingsprogram som fullt ut respekterer ditt personvern.</string>
<string name="about_url">Prosjektnettside: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Versjon: %1$s</string>
<string name="about_version_code">Versjonskode: %1$d</string>
<string name="about_history">OpenTracks er en forgrening av <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (avviklet i 2016).</string>
<string name="about_libraries">Biblioteker</string>
<string name="about_libraries">Bibliotek</string>
<string name="help_support_title">Hvordan kan jeg støtte OpenTracks\?</string>
<string name="activity_type_escooter">elsparkesykkel</string>
<string name="generic_separator_done_total">/</string>
<string name="gps_fixed_and_ready">GPS bestemt og klar</string>
<string name="import_parser_error">Analysefeil: %1$s</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="help_storage_pictures_title">Hvordan lagrer OpenTracks bilder som er tatt som markører\?</string>
<string name="help_storage_pictures_content">Disse lagres i appens interne lagring (dvs. bare tilgjengelig i OpenTracks).</string>
<string name="help_storage_pictures_title">Hvordan lagrer OpenTracks bilder tatt som markører\?</string>
<string name="help_storage_pictures_content">Disse lagres i programmets internlager (dvs. kun tilgjengelig i OpenTracks).</string>
<string name="generic_click_twice_cancel">Trykk tilbake-knappen igjen for å avbryte</string>
<string name="help_track_map_content"> Bruk det lille kartikonet.
\n a) (live-oppdateringer) Installer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. OSMDashboard sørger for nettbaserte eller nettfrakoblede kart via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n Egentlig er OSMDashboard en egen app som bare har tilgang (midlertidig) på dataene til spor som skal vises.
\n b) Via et vanlig kartprogram (f.eks. <a href="http://osmand.net/">OsmAnd</a> og <a href="https://maps.me/">MAPS.ME</a>). Disse appene må støtte KMZ-filformatet (bare posisjoner og tidsstempler). Faktisk trenger en app å støtte <i>ACTION_VIEW</i> med MIME <i>application/vnd.google-earth.kmz</i>. </string>
\n a) (sanntidsoppdateringer) Installer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Dette gir nettbaserte eller nettfrakoblede kart via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n Egentlig er OSMDashboard et egen program som bare har tilgang (midlertidig) på dataene til spor som skal vises.
\n b) Via et vanlig kartprogram (f.eks. <a href="http://osmand.net/">OsmAnd</a> og <a href="https://maps.me/">MAPS.ME</a>). Disse programmene må støtte KMZ-filformatet (bare posisjoner og tidsstempler). Faktisk trenger et program å støtte <i>ACTION_VIEW</i> med MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_track_export_title">Hvordan eksportere/dele spor som er tatt opp\?</string>
<string name="help_track_export_content"> Spor kan eksporteres fra Innstillinger som KMZ, KML eller GPX. KMZ/KML alle data; GPX bare posisjoner. Spor som deles via <i>deleikonet</i> blir delt som KMZ. </string>
<string name="help_recording_title">Hvordan OpenTracks tar opp data\?</string>
<string name="help_recording_content">OpenTracks bruker enhetens GPS-sensor til å ta opp posisjonen kontinuerlig. Hvis GPS-en rapporterer unøyaktige data (f.eks. posisjon, hastighet, høyde), kan ikke OpenTracks gjøre noe med det.</string>
<string name="help_data_storage_title">Hvordan lagrer OpenTracks sensordata\?</string>
<string name="help_data_storage_content"> Sensordata lagres per posisjon som er tatt opp. Når det gjelder en pulssensor betyr dette at bare <i>en</i> pulsmåling lagres per posisjon. Hvis du står stille eller er innendørs, blir ingen sensordata tatt opp (men de vil bli vist). </string>
<string name="help_data_storage_content"> Sensordata lagres per posisjon som er tatt opp. Når det gjelder pulssensorer betyr dette at kun <i>én</i> pulsmåling lagres per posisjon. Hvis du står stille eller er innendørs, blir ingen sensordata tatt opp (men de vil bli vist). </string>
<string name="help_gps_troubleshooting_title">OpenTracks tar ikke opp posisjoner\?</string>
<string name="help_gps_troubleshooting_content"> Hva du kan gjøre:
\n a) sjekke om GPS er slått på,
\n a) sjekk om GPS er slått på,
\n b) forbedre GPS-mottak (f.eks. gå utendørs),
\n c) sjekke med appen <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, og
\n c) sjekk med programmet <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, og
\n d) slå av strømsparing.
\n
\n Hvis alt dette ikke hjelper, kan du rapportere en feil. </string>
<string name="help_support_content"> Du kan:
\n a) spre informasjon om OpenTracks,
\n a) spre info om OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">foreslå funksjoner</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">rapportere feil</a>,
\n d) hjelpe med programvareutvikling,
\n e) kjøpe i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\n d) hjelpe til med programvareutvikling,
\n e) kjøpe programmet i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\n f) donere, og
\n g) gjøre hva du tror hjelper OpenTracks. </string>
\n g) gjøre noe du tror hjelper OpenTracks. </string>
<string name="description_altitude_loss">Total nedstigning: %1$d m (%2$d ft)</string>
<string name="stats_loss">Total nedstigning</string>
<string name="import_prevent_reimport">Du har stilt inn å forhindre at spor importeres om igjen</string>
<string name="import_prevent_reimport">Du har stilt inn forhindring av at spor importeres om igjen</string>
<string name="settings_prevent_reimport_tracks_title">Forhindre reimport av spor</string>
<string name="export_error_post_workout">Eksport etter opptak feilet, sjekk eksportmappen.</string>
<string name="export_error_post_workout">Eksport etter opptak feilet. Sjekk eksportmappen.</string>
<string name="export_progress_summary_new_msg">Nye spor eksportert</string>
<string name="import_progress_summary_ok_msg">Filer importert</string>
<string name="import_no_kml_file_found">Ingen doc.kml-fil funnet i KMZ</string>
@@ -449,11 +449,11 @@ limitations under the License.
<string name="generic_open_track">Åpne spor</string>
<string name="import_progress_summary_exists_msg">Finnes allerede</string>
<string name="import_progress_summary_errors_msg">Feil</string>
<string name="sensor_state_cadence_avg">Gj. kadens</string>
<string name="sensor_state_cadence_max">Maks kadens</string>
<string name="sensor_state_heart_rate_avg">Gj. puls</string>
<string name="sensor_state_heart_rate_max">Maks puls</string>
<string name="sensor_state_power_avg">Gj. kraft</string>
<string name="sensor_state_cadence_avg">Gjen. kadens</string>
<string name="sensor_state_cadence_max">Maks. kadens</string>
<string name="sensor_state_heart_rate_avg">Gjen. puls</string>
<string name="sensor_state_heart_rate_max">Maks. puls</string>
<string name="sensor_state_power_avg">Gjen. kraft</string>
<string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
<string name="track_distance_notification">Distanse: %1$s</string>
<string name="track_speed_notification">Hastighet: %1$s</string>
@@ -461,20 +461,20 @@ limitations under the License.
<string name="about_third_party_data">Tredjepartsdata</string>
<string name="delete_service">Slettingstjeneste</string>
<string name="menu_voice_rate">Talehastighet</string>
<string name="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string>
<string name="track_delete_not_recording">Dette sporet kan ikke slettes siden det tas opp nå.</string>
<string name="settings_sensor_cycling_cadence">Kadens</string>
<string name="settings_sensor_cycling_distance_speed">Distanse og hastighet</string>
<string name="settings_sensor_running_speed_and_cadence">Distanse, hastighet og kadens</string>
<string name="settings_recording_customize_layout_title">Tilpass opptakslayouten</string>
<string name="settings_layout_reset_confirm_message">Alle layoutinnstillinger blir tilbakestilt til standardverdiene. Dette vil ikke slette spor på enheten.</string>
<string name="stats_coordinates">Breddegrad/Lengdegrad</string>
<string name="settings_recording_customize_layout_title">Endre opptakstilpasningen</string>
<string name="settings_layout_reset_confirm_message">Alle tilpasningsinnstillinger blir tilbakestilt til forvalgsverdier. Dette sletter ikke spor på enheten.</string>
<string name="stats_coordinates">Breddegrad/lengdegrad</string>
<string name="stats_sensors_heart_rate">Puls</string>
<string name="description_speed_source_gps">GPS</string>
<string name="generic_choose_an_option">Velg et alternativ</string>
<string name="settings_recording_customize_layout_select_columns">Datafelter per rekke</string>
<string name="settings_layout_reset">Tilbakestill tilpasset layout</string>
<string name="settings_layout_reset_confirm_title">Tilbakestille layoutene dine\?</string>
<string name="settings_layout_reset_done">Alle layoutinnstillinger er tilbakestilt til standardverdiene</string>
<string name="settings_layout_reset">Tilbakestill egendefinert tilpasning</string>
<string name="settings_layout_reset_confirm_title">Tilbakestille tilpasningene dine\?</string>
<string name="settings_layout_reset_done">Alle tilpasninger er tilbakestilt til forvalg</string>
<string name="settings_all_reset">Tilbakestill</string>
<string name="stats_sensors_cadence">Kadens</string>
<string name="stats_sensors_power">Kraft</string>
@@ -485,9 +485,9 @@ limitations under the License.
<string name="field_remove_from_layout">Fjern</string>
<string name="settings_announcements_totaltime_frequency">Tidsintervall</string>
<string name="settings_announcements_distance_frequency">Distanseintervall</string>
<string name="permission_bluetooth_failed">OpenTracks krever tillatelse til å bruke Bluetooth.</string>
<string name="permission_bluetooth_failed_rejected">Du må manuelt gi OpenTracks tilgang til enheter i nærheten i systeminnstillinger.</string>
<string name="settings_generic_sensor">Generell</string>
<string name="permission_bluetooth_failed">OpenTracks krever tillatelse til å bruke Blåtann.</string>
<string name="permission_bluetooth_failed_rejected">Du må manuelt gi OpenTracks tilgang til «enheter i nærheten» i systeminnstillingene.</string>
<string name="settings_generic_sensor">Generisk</string>
<string name="settings_cycling_sensor">Sykling</string>
<string name="settings_running_sensor">Løping</string>
<string name="settings_import_section">Import</string>
@@ -499,46 +499,46 @@ limitations under the License.
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (anbefales)</string>
<string name="stats_clock">Klokke</string>
<string name="settings_open_tracks_title">OpenTracks</string>
<string name="settings_defaults_summary">Foretrukne enheter, Standardaktivitet, Standard-spornavn</string>
<string name="settings_sensors_summary">Sykkelsensorer, Hjulomkrets, Løpesensorer</string>
<string name="settings_open_tracks_summary">Alt om prosjektet OpenTracks</string>
<string name="settings_defaults_title">Standardenheter og aktivitet</string>
<string name="settings_defaults_summary">Foretrukne enheter, forvalgsaktivitet, forvalgt spornavn</string>
<string name="settings_sensors_summary">Sykkelsensorer, hjulomkrets, løpesensorer</string>
<string name="settings_open_tracks_summary">Alt om OpenTracks-prosjektet</string>
<string name="settings_defaults_title">Forvalgsenheter og aktivitet</string>
<string name="settings_ui_title">Brukerflate</string>
<string name="settings_ui_summary">Utseende, Opptakslayout, Tema</string>
<string name="settings_ui_summary">Utseende, opptakstilpasning, drakt</string>
<string name="settings_gps_title">GPS</string>
<string name="settings_gps_summary">Tidsintervall, Distanseintervall, Maks distanse, Nøyaktighet</string>
<string name="settings_sensors_title">Bluetooth-sensorer</string>
<string name="settings_gps_summary">Tidsintervall, distanseintervall, maks, distanse, nøyaktighet</string>
<string name="settings_sensors_title">Blåtannssensorer</string>
<string name="settings_announcements_title">Talemeldinger</string>
<string name="settings_announcements_summary">Tid, Distanse, Talehastighet</string>
<string name="settings_import_export_title">Import/Eksport</string>
<string name="settings_import_export_summary">Import, Eksport, Auto-eksport</string>
<string name="settings_announcements_summary">Tid, distanse, talehastighet</string>
<string name="settings_import_export_title">Import/eksport</string>
<string name="settings_import_export_summary">Import, eksport, auto-eksport</string>
<string name="settings_units_category">Enheter</string>
<string name="settings_activity_defaults_category">Aktiviteters standardvalg</string>
<string name="settings_reset_summary">Tilbakestill innstillinger til standardverdier</string>
<string name="settings_activity_defaults_category">Aktivitetens forvalg</string>
<string name="settings_reset_summary">Tilbakestill innstillinger til forvalg</string>
<string name="generic_undo">Angre</string>
<string name="menu_select_layout">Velg layout</string>
<string name="menu_add_custom_layout">Ny layout</string>
<string name="custom_layout_list_title">Layouter</string>
<string name="custom_layout_list_edit_name">Skriv inn navn på layout for å legge til</string>
<string name="custom_layout_list_edit_already_exists">Layoutens navn finnes allerede</string>
<string name="custom_layout_list_layout_removed">Layout ble fjernet</string>
<string name="custom_layout_select_layout">Velg layout</string>
<string name="stats_custom_layout_default_layout">Standard layout</string>
<string name="menu_select_layout">Velg tilpasning</string>
<string name="menu_add_custom_layout">Ny tilpasning</string>
<string name="custom_layout_list_title">Tilpasninger</string>
<string name="custom_layout_list_edit_name">Skriv inn navn på tilpasning å legge til</string>
<string name="custom_layout_list_edit_already_exists">Tilpasningens navn finnes allerede</string>
<string name="custom_layout_list_layout_removed">Tilpasning fjernet</string>
<string name="custom_layout_select_layout">Velg tilpasning</string>
<string name="stats_custom_layout_default_layout">Forvalgt tilpasning</string>
<string name="generic_filter">Filter</string>
<string name="menu_filter">Filter</string>
<string name="generic_from">Fra</string>
<string name="generic_to">Til</string>
<string name="aggregated_stats_filter_no_results">Det er ikke aktiviteter for det angitte filteret</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">Bare vis Bluetooth-enheter som melder om nødvendige tjenester</string>
<string name="aggregated_stats_filter_no_results">Ingen aktiviteter for det angitte filteret</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">Kun vis Blåtannsenheter som melder om nødvendige tjenester</string>
<string name="generic_today">I dag</string>
<string name="generic_yesterday">I går</string>
<string name="export_dir_not_writable">" (ikke skrivbar!)"</string>
<string name="export_cannot_write_to_dir">Kan ikke skrive til eksportmappe.</string>
<string name="no_compatible_file_manager_installed">Ingen kompatibel filbehandler installert</string>
<string name="settings_public_api_enabled_title">Offentlig API</string>
<string name="settings_public_api_enabled_summary_on">Andre installerte apper kan starte eller stoppe opptak.</string>
<string name="settings_public_api_enabled_summary_off">Bare OpenTracks kan starte og stoppe opptak.</string>
<string name="settings_public_api_disabled_toast">OpenTracks offentlige API er slått av: det kan slås på i innstillingene.</string>
<string name="settings_public_api_enabled_summary_on">Andre installerte programmer kan starte eller stoppe opptak.</string>
<string name="settings_public_api_enabled_summary_off">Kun OpenTracks kan starte og stoppe opptak.</string>
<string name="settings_public_api_disabled_toast">OpenTracks offentlige API er slått av: Det kan slås på i innstillingene.</string>
<string name="activity_type_swimming">svømming</string>
<string name="activity_type_swimming_open">svømming i åpent vann</string>
<string name="activity_type_workout">styrketrening</string>
@@ -554,13 +554,13 @@ limitations under the License.
<string name="lap_time">Rundetid</string>
<string name="pace">Tempo</string>
<string name="lap_speed">Rundehastighet</string>
<string name="speed">Gjennomsnittlig forflytningshastighet</string>
<string name="speed">Gjen. forflytningshastighet</string>
<string name="total_distance">Total distanse</string>
<plurals name="voiceDistanceMiles">
<item quantity="one">1 engelsk mil</item>
<item quantity="other">%1$.1f engelske mil</item>
</plurals>
<string name="average_heart_rate">Gjennomsnittlig puls</string>
<string name="average_heart_rate">Gjen. puls</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">1 kilometer</item>
<item quantity="other">%1$.1f kilometer</item>
@@ -570,18 +570,18 @@ limitations under the License.
<string name="error_crash_title">%1$s krasjet</string>
<string name="share_crash_report">Del krasjrapport</string>
<string name="report_bug">Rapporter feil</string>
<string name="select_show_on_map_behavior">Velg virkemåte for \"Vis på kart\"</string>
<string name="select_show_on_map_behavior">Velg virkemåte for «Vis på kart»</string>
<string name="show_on_map_format_ask">Spør</string>
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
<string name="show_on_dashboard">OpenTracks-API for oversikten</string>
<string name="always">Alltid</string>
<string name="settings_announcements_average_heart_rate">Gjennomsnittlig puls</string>
<string name="settings_announcements_average_heart_rate">Gjen. puls</string>
<string name="settings_announcements_lap_heart_rate">Runde-puls</string>
<string name="settings_announcements_average_speed_pace">Gjennomsnittlig hastighet/tempo</string>
<string name="settings_announcements_average_speed_pace">Gjen. hastighet/tempo</string>
<string name="image_discard">Forkast</string>
<string name="settings_announcements_lap_speed_pace">Runde-hastighet/tempo</string>
<string name="description_pace_nautical">Tempo (min/NM)</string>
<string name="description_speed_nautical">Hastighet (knop)</string>
<string name="settings_stats_units_nautical">Nautisk (NM, ft)</string>
<string name="settings_stats_units_nautical">Nautiske (NM, ft)</string>
<string name="track_discarding">Forkaster spor</string>
<string name="unit_nautical_mile">NM</string>
<string name="unit_knots">kn</string>
@@ -598,9 +598,11 @@ limitations under the License.
<item quantity="one">1 nautisk mil</item>
<item quantity="other">%1$.1f nautiske mil</item>
</plurals>
<string name="show_on_dashboard_not_installed">Ingen app som støtter OpenTracks Dashboard API er installert.</string>
<string name="show_on_dashboard_not_installed">Ingen app som støtter OpenTracks-API for oversikten er installert.</string>
<string name="track_delete_number_of_tracks">%1$d spor</string>
<string name="settings_public_api_package_title">Pakkenavn for Offentlig API</string>
<string name="introduction_osm_dashboard">OpenTracks sørger ikke selv for et kart. Installer OSMDashboard for å vise opptakene dine på et kart.</string>
<string name="settings_public_api_package_title">Pakkenavn for offentlig API</string>
<string name="introduction_osm_dashboard">OpenTracks sørger ikke selv for kart. Installer OSMDashboard for å vise opptakene dine på et kart.</string>
<string name="help_data_google_play_services_content">Nei</string>
<string name="settings_sensor_heart_rate_max">Soner: Makspuls (bpm)</string>
<string name="settings_api_summary">Fjernkontroll- og statistikk-API for andre programmer</string>
</resources>
+413 -140
View File
@@ -1,173 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_type_airplane">avião</string>
<string name="activity_type_atv">Todo-o-Terreno</string>
<string name="activity_type_atv">quadriciclos</string>
<string name="activity_type_biking">ciclismo</string>
<string name="activity_type_blimp">dirigível</string>
<string name="activity_type_boat">barco</string>
<string name="activity_type_climbing">escalada</string>
<string name="activity_type_commercial_airplane">avião comercial</string>
<string name="activity_type_cross_country_skiing">esqui cross-country</string>
<string name="activity_type_cross_country_skiing">Esqui cross-country</string>
<string name="activity_type_cycling">ciclismo</string>
<string name="activity_type_dirt_bike">motocross</string>
<string name="activity_type_donkey_back_riding">passeio de burro</string>
<string name="activity_type_driving">condução</string>
<string name="activity_type_driving_bus">condução de autocarro</string>
<string name="activity_type_driving_car">condução de automóvel</string>
<string name="activity_type_ferry">ferry</string>
<string name="activity_type_frisbee">disco</string>
<string name="activity_type_gliding">voo livre</string>
<string name="activity_type_hang_gliding">asa delta</string>
<string name="activity_type_donkey_back_riding">passeio a burro</string>
<string name="activity_type_driving">dirigindo</string>
<string name="activity_type_driving_bus">ônibus</string>
<string name="activity_type_driving_car">carro</string>
<string name="activity_type_ferry">balsa</string>
<string name="activity_type_frisbee">frízebee</string>
<string name="activity_type_gliding">asa-delta</string>
<string name="activity_type_hang_gliding">voo livre</string>
<string name="activity_type_helicopter">helicóptero</string>
<string name="activity_type_hiking">caminhada</string>
<string name="activity_type_horse_back_riding">equitação</string>
<string name="activity_type_horse_back_riding">passeio a cavalo</string>
<string name="activity_type_hot_air_balloon">balão de ar quente</string>
<string name="activity_type_ice_sailing">vela sobre gelo</string>
<string name="activity_type_inline_skating">patins em linha</string>
<string name="activity_type_ice_sailing">iatismo no gelo</string>
<string name="activity_type_inline_skating">patinação em linha</string>
<string name="activity_type_kayaking">caiaque</string>
<string name="activity_type_kite_surfing">kite surfing</string>
<string name="activity_type_land_sailing">vela em terra</string>
<string name="activity_type_kite_surfing">kitesurfe</string>
<string name="activity_type_land_sailing">iatismo em terra</string>
<string name="activity_type_mixed_type">tipo misto</string>
<string name="activity_type_motor_bike">motos</string>
<string name="activity_type_motor_boating">barcos a motor</string>
<string name="activity_type_mountain_biking">BTT</string>
<string name="activity_type_off_trail_hiking">caminhada fora do trilho</string>
<string name="activity_type_other">outro</string>
<string name="activity_type_paddling">remar</string>
<string name="activity_type_motor_bike">motociclismo</string>
<string name="activity_type_motor_boating">barco a motor</string>
<string name="activity_type_mountain_biking">mountain bike</string>
<string name="activity_type_off_trail_hiking">caminhada fora da trilha</string>
<string name="activity_type_other">outra</string>
<string name="activity_type_paddling">paddle</string>
<string name="activity_type_para_gliding">parapente</string>
<string name="activity_type_rc_airplane">avião de controlo remoto</string>
<string name="activity_type_rc_boat">Barco de controlo remoto</string>
<string name="activity_type_rc_helicopter">Helicóptero de controlo remoto</string>
<string name="activity_type_riding">ciclismo</string>
<string name="activity_type_road_biking">ciclismo de estrada</string>
<string name="activity_type_roller_skiing">rollerski</string>
<string name="activity_type_rc_airplane">avião de controle remoto</string>
<string name="activity_type_rc_boat">barco de controle remoto</string>
<string name="activity_type_rc_helicopter">helicóptero de controle remoto</string>
<string name="activity_type_riding">equitação</string>
<string name="activity_type_road_biking">ciclismo em estrada</string>
<string name="activity_type_roller_skiing">esqui roller</string>
<string name="activity_type_rowing">remo</string>
<string name="activity_type_running">corrida</string>
<string name="activity_type_sailing">vela</string>
<string name="activity_type_sailing">iatismo</string>
<string name="activity_type_seaplane">hidroavião</string>
<string name="activity_type_skate_boarding">skate boarding</string>
<string name="activity_type_skating">patinagem</string>
<string name="activity_type_skate_boarding">skate</string>
<string name="activity_type_skating">patinação</string>
<string name="activity_type_skiing">esqui</string>
<string name="activity_type_sky_jumping">queda livre</string>
<string name="activity_type_sky_jumping">paraquedismo</string>
<string name="activity_type_sled">trenó</string>
<string name="activity_type_snow_boarding">snow boarding</string>
<string name="activity_type_snow_shoeing">Caminhada em raquetes de neve</string>
<string name="activity_type_speed_walking">caminhada veloz</string>
<string name="activity_type_snow_boarding">snowboard</string>
<string name="activity_type_snow_shoeing">raquete de neve</string>
<string name="activity_type_speed_walking">caminhada rápida</string>
<string name="activity_type_street_running">corrida na rua</string>
<string name="activity_type_surfing">surf</string>
<string name="activity_type_surfing">surfe</string>
<string name="activity_type_track_cycling">ciclismo de pista</string>
<string name="activity_type_track_running">atletismo de pista</string>
<string name="activity_type_trail_hiking">caminhada</string>
<string name="activity_type_trail_running">corta-mato</string>
<string name="activity_type_train">comboio</string>
<string name="activity_type_ultimate_frisbee">jogo do disco</string>
<string name="activity_type_unknown">desconhecido</string>
<string name="activity_type_wakeboarding">wakeboarding</string>
<string name="activity_type_walking">passeio</string>
<string name="activity_type_track_running">pista de atletismo</string>
<string name="activity_type_trail_hiking">caminhada em trilha</string>
<string name="activity_type_trail_running">corrida em trilha</string>
<string name="activity_type_train">treino</string>
<string name="activity_type_ultimate_frisbee">frízebee final</string>
<string name="activity_type_unknown">desconhecida</string>
<string name="activity_type_wakeboarding">esqui aquático</string>
<string name="activity_type_walking">caminhada</string>
<string name="activity_type_water_skiing">esqui aquático</string>
<string name="activity_type_wind_surfing">windsurf</string>
<string name="activity_type_wind_surfing">windsurfe</string>
<string name="description_activity_type">Tipo de atividade: %1$s</string>
<string name="description_average_moving_pace_in_minute">Ritmo médio de deslocação: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_moving_speed">Velocidade média de deslocação: %1$.2f km/h ( %2$.1f mi / h)</string>
<string name="description_average_pace_in_minute">Ritmo médio: %1$s min/km ( %2$s min/mi)</string>
<string name="description_average_speed">Velocidade média: %1$.2f km/h ( %2$.1f mi/h)</string>
<string name="description_altitude_gain">Diferença de altitude: %1$d m ( %2$d ft)</string>
<string name="description_average_moving_pace_in_minute">Ritmo médio de deslocamento: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_moving_speed">Velocidade média de deslocamento: %1$.2f km/h (%2$.1f milhas/h)</string>
<string name="description_average_pace_in_minute">Ritmo médio: %1$s min/km (%2$s min/mi)</string>
<string name="description_average_speed">Velocidade média: %1$.2f km/h (%2$.1f milhas/h)</string>
<string name="description_altitude_gain">Ganho de elevação: %1$d m (%2$d pés)</string>
<string name="description_altitude_imperial">Elevação (pés)</string>
<string name="description_altitude_metric">Elevação (m)</string>
<string name="description_fastest_pace_in_minute">Ritmo mais rápido: %1$s min/km (%2$s min/mi)</string>
<string name="description_max_altitude">Altitude máxima: %1$d m ( %2$d ft)</string>
<string name="description_max_speed">Velocidade máxima: %1$.2f km/h ( %2$.1f mi/h)</string>
<string name="description_min_altitude">Altitude mínima: %1$d m ( %2$d ft)</string>
<string name="description_moving_time">Tempo de deslocação: %1$s</string>
<string name="description_max_altitude">Elevação máx.: %1$d m (%2$d pés)</string>
<string name="description_max_speed">Velocidade máx.: %1$.2f km/h (%2$.1f milhas/h)</string>
<string name="description_min_altitude">Elevação mín.: %1$d m (%2$d pés)</string>
<string name="description_moving_time">Tempo de deslocamento: %1$s</string>
<string name="description_pace_imperial">Ritmo (min/mi)</string>
<string name="description_pace_metric">Ritmo (min/km)</string>
<string name="description_recorded_time">Gravado: %1$s</string>
<string name="description_recorded_time">Registro: %1$s</string>
<string name="description_sensor_cadence">Cadência (rpm)</string>
<string name="description_sensor_heart_rate">Ritmo cardíaco (bpm)</string>
<string name="description_sensor_heart_rate">Frequência cardíaca (bpm)</string>
<string name="description_sensor_power">Potência (W)</string>
<string name="description_speed_imperial">Velocidade (mi/h)</string>
<string name="description_speed_imperial">Velocidade (milhas/h)</string>
<string name="description_speed_metric">Velocidade (km/h)</string>
<string name="description_time">Tempo</string>
<string name="description_total_distance">Distância total: %1$.2f km ( %2$.1f mi)</string>
<string name="description_total_distance">Distância total: %1$.2f km (%2$.1f milhas)</string>
<string name="description_total_time">Tempo total: %1$s</string>
<string name="export_progress_message">A exportar para %1$s …</string>
<string name="export_progress_message">Exportando para %1$s…</string>
<string name="generic_add">Adicionar</string>
<string name="generic_delete_selected_confirm_title">Eliminar seleção?</string>
<string name="generic_delete_selected_confirm_title">Excluir selecionados\?</string>
<string name="generic_description">Descrição</string>
<string name="generic_description_line">Descrição: %1$s</string>
<string name="generic_name">Nome</string>
<string name="generic_name_line">Nome: %1$s</string>
<string name="generic_recording">A gravar…</string>
<string name="generic_save">Guardar</string>
<string name="generic_tracks">Percursos</string>
<string name="gps_starting">A iniciar o GPS</string>
<string name="gps_wait_for_better_signal">A aguardar melhor sinal de GPS</string>
<string name="gps_wait_for_signal">A aguardar por sinal de GPS</string>
<string name="generic_recording">Gravando…</string>
<string name="generic_save">Salvar</string>
<string name="generic_tracks">Rotas</string>
<string name="gps_starting">Iniciando GPS</string>
<string name="gps_wait_for_better_signal">Aguardando sinal de GPS melhor</string>
<string name="gps_wait_for_signal">Aguardando sinal de GPS</string>
<string name="image_arrow">Seta</string>
<string name="image_marker">Marcador</string>
<string name="image_marker_count">Contagem do marcador</string>
<string name="image_photo_marker">Marcador de fotografias</string>
<string name="image_photo_marker">Marcador de foto</string>
<string name="image_record">Gravar</string>
<string name="image_stop">Parar</string>
<string name="image_track">Localizar</string>
<string name="import_progress_message">A importar de %1$s…</string>
<string name="marker_add_error">Não é possível inserir um marcador. Sem sinal de GPS. Tente novamente.</string>
<string name="marker_add_success">Foi inserido um marcador</string>
<string name="marker_add_canceled">Cancelado, nenhum marcador adicionado</string>
<string name="marker_delete_one_confirm_message">O marcador será eliminado permanentemente do dispositivo</string>
<string name="marker_delete_one_confirm_title">Eliminar marcador?</string>
<string name="marker_delete_multiple_confirm_message">Os marcadores selecionados serão eliminados permanentemente do dispositivo</string>
<string name="image_track">Rota</string>
<string name="import_progress_message">Importando de %1$s…</string>
<string name="marker_add_error">Não foi possível inserir um marcador. Não há sinal de GPS. Tente novamente.</string>
<string name="marker_add_success">Um marcador foi inserido</string>
<string name="marker_add_canceled">Cancelado. Nenhum marcador foi adicionado</string>
<string name="marker_delete_one_confirm_message">O marcador será excluído permanentemente do dispositivo</string>
<string name="marker_delete_one_confirm_title">Excluir marcador\?</string>
<string name="marker_delete_multiple_confirm_message">Os marcadores selecionados serão permanentemente excluídos do dispositivo</string>
<string name="marker_name_format">Marcador %1$d</string>
<string name="marker_title">%1$d de %2$d</string>
<string name="marker_edit_marker_type">Tipo de marcador (por exemplo, cascata)</string>
<string name="marker_list_empty_message">Não tem marcadores</string>
<string name="menu_aggregated_statistics">Estatísticas agregadas</string>
<string name="menu_delete">Eliminar</string>
<string name="marker_list_empty_message">Você não tem marcadores</string>
<string name="menu_aggregated_statistics">Estatísticas reunidas</string>
<string name="menu_delete">Excluir</string>
<string name="menu_edit">Editar</string>
<string name="menu_export_all">Exportar tudo</string>
<string name="menu_export_all">Exportar todas</string>
<string name="menu_help">Ajuda</string>
<string name="menu_import_all">Importar tudo</string>
<string name="menu_import_all">Importar todas</string>
<string name="menu_insert_marker">Inserir marcador</string>
<string name="menu_insert_photo">Inserir fotografia</string>
<string name="menu_insert_photo">Inserir foto</string>
<string name="menu_markers">Marcadores</string>
<string name="menu_search">Pesquisar</string>
<string name="menu_select_all">Selecionar tudo</string>
<string name="menu_settings">Definições</string>
<string name="menu_share">Partilhar</string>
<string name="menu_settings">Configurações</string>
<string name="menu_share">Compartilhar</string>
<string name="menu_show_on_map">Mostrar no mapa</string>
<string name="menu_start_gps">Ligar GPS</string>
<string name="menu_stop_gps">Desligar GPS</string>
<string name="menu_start_gps">Iniciar GPS</string>
<string name="menu_stop_gps">Parar GPS</string>
<string name="recording_service">Serviço de gravação</string>
<string name="sensor_state_heart_rate">Frequência cardíaca</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_power">Potência</string>
<string name="sensor_state_power">Alimentação</string>
<string name="settings_advanced">Avançado</string>
<string name="settings_chart_by_distance">Por distância</string>
<string name="settings_chart_by_time">Por tempo</string>
<string name="settings_recording_default_activity_title">Tipo de atividade predefinido</string>
<string name="settings_recording_location_frequency_summary">%1$s entre locais gravados</string>
<string name="settings_recording_max_recording_distance_summary">É criado um novo segmento quando a distância entre os dois locais é superior a %1$s</string>
<string name="settings_recording_default_activity_title">Tipo de atividade padrão</string>
<string name="settings_recording_location_frequency_summary">%1$s entre os locais gravados</string>
<string name="settings_recording_max_recording_distance_summary">Um novo segmento será criado se a distância entre os dois locais for maior que %1$s</string>
<string name="settings_recording_max_recording_distance_title">Distância máxima</string>
<string name="settings_recording_min_recording_distance_title">Distância entre gravações</string>
<string name="settings_recording_min_recording_interval_title">Tempo entre gravações</string>
<string name="settings_recording_min_required_accuracy_summary">A localização é ignorada se a precisão for inferior a %1$s</string>
<string name="settings_recording_min_recording_distance_title">Interv. distância grav</string>
<string name="settings_recording_min_recording_interval_title">Interv. tempo de grav</string>
<string name="settings_recording_min_required_accuracy_summary">O local será ignorado se a precisão for menor que %1$s</string>
<string name="settings_recording_min_required_accuracy_title">Precisão do GPS</string>
<string name="settings_recording_track_name_date_iso_8601_option">Data (ISO 8601)</string>
<string name="settings_recording_track_name_date_local_option">Data (local)</string>
<string name="settings_recording_track_name_number_option">Número</string>
<string name="settings_recording_track_name_title">Nome de percurso predefinido</string>
<string name="settings_reset_confirm_message">Todas as definições são revertidas para os valores predefinidos.</string>
<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_recording_track_name_title">Nome padrão para trilhas</string>
<string name="settings_reset_confirm_message">Todas as configurações serão revertidas para os valores padrão.</string>
<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_rate_title">Avaliação preferida</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étrico (km, m)</string>
<string name="settings_stats_units_title">Unidades preferidas</string>
<string name="share_track_share_file_body">Acho que pode estar interessado neste percurso.\n\nPara ver este percurso, utilize o Google Earth ou outro software de mapeamento.\n\n%1$s</string>
<string name="share_track_subject">Gostaria de partilhar um percurso consigo</string>
<string name="stats_average_moving_pace">Espaço médio de deslocação</string>
<string name="stats_average_moving_speed">Velocidade média de deslocação</string>
<string name="settings_stats_units_metric">Métrica (km, m)</string>
<string name="settings_stats_units_title">Unidades preferenciais</string>
<string name="share_track_share_file_body">Achei que você pudesse se interessar por essa trilha.
\n
\n%1$s</string>
<string name="share_track_subject">Eu gostaria de compartilhar uma trilha com você</string>
<string name="stats_average_moving_pace">Ritmo médio de movimentação</string>
<string name="stats_average_moving_speed">Velocidade média de movimentação</string>
<string name="stats_average_pace">Ritmo médio</string>
<string name="stats_average_speed">Velocidade média</string>
<string name="stats_altitude">Elevação</string>
@@ -175,39 +177,39 @@
<string name="stats_gain">Ganho</string>
<string name="stats_latitude">Latitude</string>
<string name="stats_longitude">Longitude</string>
<string name="stats_max_speed">Velocidade máx.</string>
<string name="stats_moving_time">Tempo em movimento</string>
<string name="stats_max_speed">Velocidade máx</string>
<string name="stats_moving_time">Tempo decorrido</string>
<string name="stats_pace">Ritmo</string>
<string name="stats_speed">Velocidade</string>
<string name="stats_distance">Distância</string>
<string name="stats_total_time">Tempo total</string>
<string name="track_name_format">Trajeto %1$d</string>
<string name="track_markers">Marcadores %1$s</string>
<string name="track_delete_one_confirm_message">O percurso e respetivos marcadores são eliminados permanentemente do dispositivo.</string>
<string name="track_delete_one_confirm_title">Eliminar percurso?</string>
<string name="track_delete_multiple_confirm_message">Os percursos selecionados e respetivos marcadores serão eliminados permanentemente do dispositivo.</string>
<string name="track_delete_progress_message">A eliminar…</string>
<string name="track_name_format">Trilha %1$d</string>
<string name="track_markers">Marcadores de %1$s</string>
<string name="track_delete_one_confirm_message">A trilha e seus marcadores serão excluídos permanentemente do dispositivo.</string>
<string name="track_delete_one_confirm_title">Excluir rota\?</string>
<string name="track_delete_multiple_confirm_message">As trilhas selecionadas e seus respectivos marcadores serão excluídos permanentemente do dispositivo.</string>
<string name="track_delete_progress_message">Excluindo…</string>
<string name="track_detail_stats_tab">Estatísticas</string>
<string name="track_edit_activity_type">Tipo de atividade (por exemplo, caminhada)</string>
<string name="track_edit_activity_type_hint">Tipo de atividade</string>
<string name="track_list_empty_message">Comece a gravar a sua próxima aventura aqui</string>
<string name="track_list_empty_message">Comece a gravar sua próxima aventura aqui</string>
<string name="unit_feet">pés</string>
<string name="unit_kilometer">km</string>
<string name="unit_kilometer_per_hour">km/h</string>
<string name="unit_meter">m</string>
<string name="unit_mile">mi</string>
<string name="unit_mile_per_hour">mi/h</string>
<string name="unit_mile">milhas</string>
<string name="unit_mile_per_hour">milhas/h</string>
<string name="unit_minute_per_kilometer">min/km</string>
<string name="unit_minute_per_mile">min/milha</string>
<string name="unit_minute_per_mile">min/milhas</string>
<string name="value_integer_feet_excellent_gps">%1$d pés (GPS excelente)</string>
<string name="value_integer_feet_recommended">%1$d pés (recomendado)</string>
<string name="value_integer_kilometer_hour">%1$d km/h</string>
<string name="value_integer_meter_excellent_gps">%1$d m (GPS excelente)</string>
<string name="value_integer_meter_poor_gps">%1$d m (GPS fraco)</string>
<string name="value_integer_meter_poor_gps">%1$d m (GPS com sinal baixo)</string>
<string name="value_integer_meter_recommended">%1$d m (recomendado)</string>
<string name="value_integer_mile_hour">%1$d mi/h</string>
<string name="value_integer_mile_hour">%1$d milhas/h</string>
<string name="value_integer_minute">%1$d min</string>
<string name="value_integer_second">%1$d seg</string>
<string name="value_integer_second">%1$d s</string>
<string name="value_none">Nenhuma</string>
<string name="value_off">Desativar</string>
<string name="value_smallest_recommended">Menor (recomendado)</string>
@@ -224,62 +226,333 @@
<item quantity="other">%1$d segundos</item>
</plurals>
<plurals name="voiceSpeedKilometersPerHour">
<item quantity="one">%1$.1f quilômetro por hora</item>
<item quantity="one">%1$.1f quilómetro por hora</item>
<item quantity="other">%1$.1f quilómetros por hora</item>
</plurals>
<plurals name="voiceSpeedMilesPerHour">
<item quantity="one">%1$.1f milha por hora</item>
<item quantity="other">%1$.1f milhas por hora</item>
</plurals>
<string name="waypoint_type_atm">Multibanco</string>
<string name="waypoint_type_atm">caixa eletrônico</string>
<string name="waypoint_type_bank">banco</string>
<string name="waypoint_type_bar">bar</string>
<string name="waypoint_type_barbecue">churrasco</string>
<string name="waypoint_type_building">edifício</string>
<string name="waypoint_type_bus_stop">paragem de autocarro</string>
<string name="waypoint_type_cafe">café</string>
<string name="waypoint_type_building">construção</string>
<string name="waypoint_type_bus_stop">ponto de ônibus</string>
<string name="waypoint_type_cafe">cafeteria</string>
<string name="waypoint_type_campfire">fogueira</string>
<string name="waypoint_type_campground">parque de campismo</string>
<string name="waypoint_type_campground">acampamento</string>
<string name="waypoint_type_church">igreja</string>
<string name="waypoint_type_coffee_house">café</string>
<string name="waypoint_type_coffee_house">cafeteria</string>
<string name="waypoint_type_convenience_store">loja de conveniência</string>
<string name="waypoint_type_drinking_water">água potável</string>
<string name="waypoint_type_falling_rocks">queda de pedras</string>
<string name="waypoint_type_ferry">ferry</string>
<string name="waypoint_type_falling_rocks">área com desmoronamento</string>
<string name="waypoint_type_ferry">balsa</string>
<string name="waypoint_type_fire_place">lareira</string>
<string name="waypoint_type_fire_station">quartel de bombeiros</string>
<string name="waypoint_type_fire_station">bombeiros</string>
<string name="waypoint_type_fishing">pesca</string>
<string name="waypoint_type_gas_station">estação de serviço</string>
<string name="waypoint_type_gas_station">posto de gasolina</string>
<string name="waypoint_type_golf_course">campo de golfe</string>
<string name="waypoint_type_grocery_store">mercearia</string>
<string name="waypoint_type_grocery_store">supermercado</string>
<string name="waypoint_type_hair_dresser">cabeleireiro</string>
<string name="waypoint_type_harbor">porto</string>
<string name="waypoint_type_hiking_path">trilho de caminhada</string>
<string name="waypoint_type_hiking_path">trilha para caminhada</string>
<string name="waypoint_type_hotel">hotel</string>
<string name="waypoint_type_house">casa</string>
<string name="waypoint_type_information">informação</string>
<string name="waypoint_type_intersection">intersecção</string>
<string name="waypoint_type_information">informações</string>
<string name="waypoint_type_intersection">cruzamento</string>
<string name="waypoint_type_mail_box">caixa de correio</string>
<string name="waypoint_type_market">mercado</string>
<string name="waypoint_type_meeting_point">ponto de encontro</string>
<string name="waypoint_type_opera">ópera</string>
<string name="waypoint_type_parking">estacionamento</string>
<string name="waypoint_type_police_station">esquadra da polícia</string>
<string name="waypoint_type_post_office">correios</string>
<string name="waypoint_type_police_station">delegacia</string>
<string name="waypoint_type_post_office">correio</string>
<string name="waypoint_type_pothole">buraco</string>
<string name="waypoint_type_railway_crossing">travessia ferroviária</string>
<string name="waypoint_type_railway_crossing">travessia de via férrea</string>
<string name="waypoint_type_restaurant">restaurante</string>
<string name="waypoint_type_shopping">compras</string>
<string name="waypoint_type_sight">paisagem</string>
<string name="waypoint_type_sight">vista</string>
<string name="waypoint_type_store">loja</string>
<string name="waypoint_type_swimming_pool">piscina</string>
<string name="waypoint_type_telephone">telefone</string>
<string name="waypoint_type_theater">cinema</string>
<string name="waypoint_type_toilet">WC</string>
<string name="waypoint_type_theater">teatro</string>
<string name="waypoint_type_toilet">banheiro</string>
<string name="waypoint_type_tourist_information">informações turísticas</string>
<string name="waypoint_type_tree">árvore</string>
<string name="waypoint_type_tunnel">túnel</string>
<string name="waypoint_type_waterfall">queda de água</string>
<string name="waypoint_type_waterfall">Cascata</string>
<string name="waypoint_type_water_fountain">fonte de água</string>
<string name="waypoint_type_waypoint">ponto de referência</string>
</resources>
<string name="about_preference_title">Sobre o OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> é uma aplicação de rastreamento de desportes que respeita totalmente a sua privacidade.</string>
<string name="about_url">Página do projeto: <a href="%1$s">%1$s</a></string>
<string name="about_third_party_data">Dados de terceiros</string>
<string name="help_track_map_title">Como mostrar uma pista num mapa\?</string>
<string name="help_storage_pictures_title">Como o OpenTracks armazena fotos tiradas como marcadores\?</string>
<string name="help_storage_pictures_content">Eles são armazenados no armazenamento interno da app (ou seja, acessíveis apenas no OpenTracks).</string>
<string name="help_data_google_play_services_title">O OpenTracks requer/usa o Google Play Services\?</string>
<string name="help_data_google_play_services_content">Não</string>
<string name="help_recording_title">Como o OpenTracks regista dados\?</string>
<string name="help_support_title">Como posso oferecer suporte a OpenTracks\?</string>
<string name="open_track_as_KML">Abrir como KML</string>
<string name="open_track_as_GPX">Abrir como GPX</string>
<string name="open_track_as_trackfileformat">Abrir como %1$s</string>
<string name="activity_type_escooter">escolta</string>
<string name="activity_type_swimming_open">nadando em águas abertas</string>
<string name="export_progress_summary_skip_msg">Trilhas puladas</string>
<string name="export_progress_summary_errors_msg">Erros</string>
<string name="generic_open_track">Trilho aberto</string>
<string name="sensor_state_cadence_max">Ritmo cardíaco médio</string>
<string name="sensor_state_heart_rate_avg">Ritmo cardíaco Avg</string>
<string name="sensor_state_heart_rate_max">Freqüência cardíaca máxima</string>
<string name="settings_default_trackfileformat">Exportar/compartilhar formato de ficheiro</string>
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
<string name="settings_recording_customize_layout_title">Personalize o seu layout de gravação</string>
<string name="settings_recording_customize_layout_select_columns">Campos de dados por linha</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Mostrar estatísticas no ecrã de bloqueio</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Durante a gravação, mostre estatísticas sem desbloquear o aparelho.</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Ecrã inteiro</string>
<string name="settings_night_mode_option_day">Dia</string>
<string name="settings_announcements_totaltime_frequency">Intervalo de tempo</string>
<string name="settings_announcements_distance_frequency">Intervalo de distância</string>
<string name="settings_announcements_average_heart_rate">Frequência cardíaca média</string>
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo de volta</string>
<string name="custom_layout_list_title">Estrutura</string>
<string name="custom_layout_list_edit_name">Digite o nome do layout a ser adicionado</string>
<string name="custom_layout_list_layout_removed">Layout foi removido</string>
<string name="settings_layout_reset_confirm_message">Todas as configurações de layout serão revertidas para os valores padrão. Isso não excluirá nenhuma trilha do aparelho.</string>
<string name="settings_layout_reset_confirm_title">Redefinir os seus layouts\?</string>
<string name="settings_all_reset">Redefinir</string>
<string name="settings_stats_units_nautical">Milha náutica (NM, ft)</string>
<string name="settings_api_title">API pública</string>
<string name="settings_public_api_package_title">Nome do pacote para API pública</string>
<string name="settings_public_api_enabled_summary_on">Outras aplicações instaladas podem iniciar ou interromper gravações.</string>
<string name="track_recording_notification_accuracy">Precisão da localização: %1$s</string>
<string name="unit_nautical_mile">NM</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="value_integer_knots">%1$d nós</string>
<string name="average_heart_rate">Frequência cardíaca média</string>
<string name="current_heart_rate">Frequência cardíaca atual</string>
<string name="share_image_subject">Gostaria de compartilhar uma imagem consigo</string>
<string name="field_remove_from_layout">Remover</string>
<string name="about_libraries">Bibliotecas</string>
<string name="help_feature_title">OpenTracks tem os seguintes recursos:</string>
<string name="help_feature_content">* criar pistas GPS
\n * utilizar um sensor do ritmo cardíaco (apenas Bluetooth LE)
\n * anúncios de voz (por tempo ou distância)
\n * criar marcadores (incl. imagens)
\n * funciona completamente offline
\n * mostrar pistas num mapa (ver abaixo)</string>
<string name="about_version_name">Nome da versão: %1$s</string>
<string name="about_version_code">Código da versão: %1$d</string>
<string name="about_history">OpenTracks é um fork do <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (descontinuado em 2016).</string>
<string name="help_track_export_title">Como posso exportar/compartilhar trilhas gravadas\?</string>
<string name="help_track_export_content"> As trilhas podem ser exportadas das configurações como KMZ, KML ou GPX. KMZ/KML todos os dados; Localizações apenas GPX. As trilhas compartilhadas por meio do <i>ícone de compartilhamento</i> são compartilhadas como KMZ. </string>
<string name="help_track_map_content"> Utilize o pequeno ícone do mapa.
\n a) (actualizações ao vivo) Instalar <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Fornece mapas online ou offline através de <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\n Na verdade, o OSMDashboard é uma aplicação separada que só pode aceder (temporariamente) aos dados da(s) faixa(s) que vão ser mostrados.
\n b) Através de uma aplicação de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicações precisam de suportar o formato de ficheiro KMZ (apenas locais e timestamps). De facto, uma aplicação precisa de suportar <i>ACTION_VIEW</i> com MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="help_recording_content">OpenTracks usa o sensor GPS do aparelho para registar a localização continuamente. Se o aparelho GPS relatar dados imprecisos (por exemplo, localização, velocidade, elevação), o OpenTracks não pode fazer nada a respeito.</string>
<string name="help_data_storage_title">Como o OpenTracks armazena dados do sensor\?</string>
<string name="help_data_storage_content"> Os dados do sensor são armazenados por local registado. Em termos de sensor de frequência cardíaca, isso significa que apenas <i>uma</i> medição de frequência cardíaca é armazenada por local. Se estiver parado ou dentro de casa, nenhum dado do sensor será registrdo (mas será mostrado). </string>
<string name="help_gps_troubleshooting_title">OpenTracks não está gravando locais\?</string>
<string name="help_gps_troubleshooting_content"> O que se pode fazer:
\n a) verificar se o GPS está activado,
\n b) melhorar a recepção GPS (por exemplo, ir para o exterior),
\n c) verificar com <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app, e
\n d) desactivar a poupança de energia.
\n
\n Se tudo isto não ajudar, por favor, informe um erro. </string>
<string name="help_support_content"> Pode:
\n a) espalhar a palavra sobre OpenTracks,
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">propose features</a>,
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">reportar bugs</a>,
\n d) ajuda no desenvolvimento de software,
\n e) comprar em <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\n f) doar, e
\n g) fazer o que achar que ajuda a OpenTracks. </string>
<string name="activity_type_swimming">natação</string>
<string name="activity_type_workout">treino</string>
<string name="description_altitude_loss">Perda de elevação: %1$d m (%2$d ft)</string>
<string name="description_speed_ms">Velocidade (m/s)</string>
<string name="description_pace_nautical">Ritmo (min/NM)</string>
<string name="description_default_speed_or_pace">Por tipo de atividade</string>
<string name="description_speed_nautical">Velocidade (nós)</string>
<string name="description_speed_source_gps">GPS</string>
<string name="export_with_photos">com fotos</string>
<string name="export_without_photos">sem fotos</string>
<string name="export_error_post_workout">A exportação pós-treino falhou, verifique o diretório de exportação.</string>
<string name="export_cannot_write_to_dir">Não é possível escrever ao diretório de exportação.</string>
<string name="export_dir_not_writable">" (não se escreve!)"</string>
<string name="export_track_already_exists_msg">A trilha %1$s já existe no diretório de destino.</string>
<string name="export_all_do_it_for_all">faça isso para todos os conflitos</string>
<string name="export_track_errors">Trilhas não exportadas</string>
<string name="export_progress_summary_new_msg">Novas trilhas exportadas</string>
<string name="export_progress_summary_overwrite_msg">Trilhas sobrescritas</string>
<string name="generic_overwrite">Sobrescrever</string>
<string name="generic_skip">Pular</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">Mostrar erros</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Concluído com %1$d erro</item>
<item quantity="other">Concluído com %1$d erros</item>
</plurals>
<string name="generic_completed">Concluído</string>
<string name="generic_click_twice_cancel">Clique novamente para cancelar</string>
<string name="generic_undo">Desfazer</string>
<string name="generic_choose_an_option">Escolha uma opção</string>
<string name="generic_filter">Filtro</string>
<string name="generic_from">De</string>
<string name="generic_to">Para</string>
<string name="generic_today">Hoje</string>
<string name="generic_yesterday">Ontem</string>
<string name="generic_dismiss">Dispensar</string>
<string name="gps_disabled_msg">GPS desativado</string>
<string name="gps_fixed_and_ready">GPS corrigido e pronto</string>
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">O OpenTracks está gravando sem dados de GPS.</string>
<string name="image_discard">Descartar</string>
<string name="import_thread_interrupted">Tópico interrompido</string>
<string name="import_unsupported_format">Formato de ficheiro não suportado</string>
<string name="import_parser_error">Erro do analisador: %1$s</string>
<string name="import_unable_to_import_file">Não foi possível importar o ficheiro: %1$s</string>
<string name="import_no_kml_file_found">Nenhum ficheiro doc.kml encontrado no KMZ</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="import_error_list_dialog_title">Ficheiros não importados</string>
<string name="import_file_imported">Ficheiro %1$s importado</string>
<string name="import_prevent_reimport">Definiu evitar reimportar a trilha</string>
<string name="import_progress_summary_ok_msg">Ficheiros importados</string>
<string name="import_progress_summary_exists_msg">Já existe</string>
<string name="import_progress_summary_errors_msg">Erros</string>
<string name="marker_add_photo_canceled">Cancelado, nenhum marcador de foto adicionado</string>
<string name="menu_insert_gallery_img">Inserir imagem da galeria</string>
<string name="menu_select_layout">Selecione o layout</string>
<string name="menu_voice_rate">Velocidade da voz</string>
<string name="menu_resume_track">Retomar trilha</string>
<string name="menu_add_custom_layout">Novo Layout</string>
<string name="menu_filter">Filtro</string>
<string name="delete_service">Serviço de exclusão</string>
<string name="permission_gps_failed">OpenTracks requer permissão para usar GPS.</string>
<string name="permission_bluetooth_failed">OpenTracks requer permissão para usar Bluetooth.</string>
<string name="sensor_state_cadence_avg">Avg Cadência</string>
<string name="permission_bluetooth_failed_rejected">Precisa conceder acesso ao OpenTracks manualmente para aparelhos próximos nas configurações do sistema.</string>
<string name="sensor_state_power_avg">Energia Avg</string>
<string name="sensor_could_not_scan">Não foi possível procurar aparelhos Bluetooth (erro %1$i).</string>
<string name="bluetooth_disabled">Ative o Bluetooth.</string>
<string name="settings_open_tracks_title">Trilhas abertas</string>
<string name="settings_open_tracks_summary">Tudo sobre o Projeto OpenTracks</string>
<string name="settings_defaults_title">Unidades e Atividade Default</string>
<string name="settings_defaults_summary">Unidades Preferidas, Atividade Padrão, Nome Padrão da Pista</string>
<string name="settings_ui_title">Interface do utilizador</string>
<string name="settings_ui_summary">Aparência, Layout de gravação, Tema</string>
<string name="settings_gps_title">GPS</string>
<string name="settings_gps_summary">Intervalo de tempo, Intervalo de distância, Distância máxima, Precisão</string>
<string name="settings_sensors_title">Sensores Bluetooth</string>
<string name="settings_sensors_summary">Voice announcements</string>
<string name="settings_announcements_title">Anúncios de voz</string>
<string name="settings_announcements_summary">Tempo, distância, velocidade da voz</string>
<string name="settings_import_export_title">Importar/Exportar</string>
<string name="settings_import_export_summary">Importar, Exportar, Auto Export</string>
<string name="settings_units_category">Unidades</string>
<string name="settings_activity_defaults_category">Defaults da atividade</string>
<string name="settings_reset_summary">Redefinir configurações para valores padrão</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Mantenha o ecrã ligada</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Durante a gravação, apresente em ecrã inteiro.</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Durante a gravação, mantenha o ecrã ligado.</string>
<string name="settings_recording_idle_speed_title">Limite de velocidade de marcha lenta</string>
<string name="settings_night_mode_title">Tema UI</string>
<string name="settings_night_mode_option_system">Sistema</string>
<string name="settings_night_mode_option_night">Noite</string>
<string name="settings_announcements_lap_heart_rate">Frequência cardíaca da volta</string>
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo médio</string>
<string name="custom_layout_list_edit_already_exists">O nome do layout já existe</string>
<string name="custom_layout_select_layout">Selecionar layout</string>
<string name="settings_layout_reset_done">Todas as configurações de layout foram revertidas para os valores padrão</string>
<string name="settings_generic_sensor">Genérico</string>
<string name="stats_custom_layout_default_layout">Layout padrão</string>
<string name="settings_layout_reset">Redefinir layout personalizado</string>
<string name="settings_cycling_sensor">Ciclismo</string>
<string name="settings_running_sensor">Correndo</string>
<string name="settings_sensor_cycling_distance_speed">Distância e velocidade</string>
<string name="settings_sensor_cycling_cadence">Cadência</string>
<string name="settings_sensor_running_speed_and_cadence">Distância, velocidade e cadência</string>
<string name="settings_sensor_wheel_circumference">Circunferência da Roda (mm)</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">Mostrar apenas aparelhos Bluetooth que anunciam os serviços necessários</string>
<string name="settings_public_api_enabled_summary_off">Apenas o OpenTracks pode iniciar e parar gravações.</string>
<string name="settings_public_api_enabled_title">API pública</string>
<string name="settings_public_api_disabled_toast">A API pública do OpenTracks está desativada: pode ser ativada nas configurações.</string>
<string name="settings_public_api_dashboard_enabled_title">Transferência automática de dados</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Uma aplicação que iniciou uma gravação também pode acessar os dados gravados.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Os dados gravados não serão compartilhados automaticamente com outras aplicações.</string>
<string name="settings_import_section">Importar</string>
<string name="settings_export_section">Exportar</string>
<string name="settings_prevent_reimport_tracks_title">Impedir a reimportação de trilhas</string>
<string name="stats_loss">Perda</string>
<string name="stats_coordinates">Latitude/Longitude</string>
<string name="stats_split_interval">Intervalo de divisão</string>
<string name="stats_sensors_power">Poder</string>
<string name="stats_clock">Relógio</string>
<string name="stats_sensors_heart_rate">Frequência cardíaca</string>
<string name="stats_sensors_cadence">Cadência</string>
<string name="track_distance_notification">Distância: %1$s</string>
<string name="track_speed_notification">Velocidade: %1$s</string>
<string name="track_pace_notification">Parcela: %1$s</string>
<string name="track_delete_number_of_tracks">%1$d trilhas</string>
<string name="track_detail_intervals_tab">intervalos</string>
<string name="aggregated_stats_empty_message">Registe a sua primeira trilha para ver as estatísticas agregadas</string>
<string name="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string>
<string name="track_discarding">Descartando trilha</string>
<string name="aggregated_stats_filter_no_results">Não há atividades para o filtro especificado</string>
<string name="interval_list_empty_message">Não há nenhum intervalo ainda</string>
<string name="unit_knots">kn</string>
<string name="location_coordinate">%1$s°</string>
<string name="unit_minute_per_nautical_mile">min/NM</string>
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (Recomendado)</string>
<string name="value_float_mile_hour">%1$.1f mi/h</string>
<string name="value_float_knots">%1$.1f nós</string>
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (Recomendado)</string>
<string name="value_float_knots_recommended">%1$.1f nós (recomendado)</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">%1$.1f nó</item>
<item quantity="other">%1$.1f nós</item>
</plurals>
<string name="voice_per_kilometer">por quilômetro</string>
<string name="voice_per_mile">por milha</string>
<string name="voice_per_nautical_mile">por milha náutica</string>
<string name="lap_time">Tempo da volta</string>
<string name="pace">Ritmo</string>
<string name="lap_speed">Velocidade da volta</string>
<string name="speed">Velocidade</string>
<string name="total_distance">Distância total</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">%1$.1f quilómetro</item>
<item quantity="other">%1$.1f quilómetros</item>
</plurals>
<plurals name="voiceDistanceMiles">
<item quantity="one">%1$.1f milha</item>
<item quantity="other">%1$.1f milhas</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">%1$.1f milha náutica</item>
<item quantity="other">%1$.1f milhas náuticas</item>
</plurals>
<string name="share_image_body">Acho que pode estar interessado nesta imagem.</string>
<string name="hold_to_stop">Segure para parar</string>
<string name="instant_export_enabled_summary">Exportar trilha para armazenamento depois que a gravação for concluída</string>
<string name="instant_export_enabled_title">Exportação instantânea pós-treino</string>
<string name="field_set_primary">Primário</string>
<string name="settings_default_export_uri_title">Diretório de exportação de trilhas</string>
<string name="field_set_secondary">Secundário</string>
<string name="no_compatible_file_manager_installed">Nenhum gestor de ficheiros compatível instalado</string>
<string name="error_crash_title">%1$s falharam</string>
<string name="share_crash_report">Compartilhar relatório de falha</string>
<string name="report_bug">Relatório de problema</string>
<string name="select_show_on_map_behavior">Selecionar comportamento de \"mostrar no mapa\"</string>
<string name="show_on_map_format_ask">Perguntas</string>
<string name="show_on_dashboard">Painel API do OpenTracks</string>
<string name="always">Sempre</string>
<string name="show_on_dashboard_not_installed">Nenhuma aplicação compatível com o painel API do OpenTracks instalado.</string>
<string name="introduction_osm_dashboard">O próprio OpenTracks não fornece um mapa. Instale o OSMDashboard para visualizar as suas gravações num mapa.</string>
</resources>
+254 -4
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
--><resources>
<!-- Activity Type -->
<string name="activity_type_airplane">avião</string>
<string name="activity_type_atv">quadriciclos</string>
@@ -263,22 +263,27 @@ limitations under the License.
<!-- Voice -->
<plurals name="voiceHours">
<item quantity="one">%1$d hora</item>
<item quantity="many">%1$d horas</item>
<item quantity="other">%1$d horas</item>
</plurals>
<plurals name="voiceMinutes">
<item quantity="one">%1$d minuto</item>
<item quantity="many">%1$d minutos</item>
<item quantity="other">%1$d minutos</item>
</plurals>
<plurals name="voiceSeconds">
<item quantity="one">%1$d segundo</item>
<item quantity="many">%1$d segundos</item>
<item quantity="other">%1$d segundos</item>
</plurals>
<plurals name="voiceSpeedKilometersPerHour">
<item quantity="one">%1$.1f quilômetro por hora</item>
<item quantity="one">%1$.1f quilómetro por hora</item>
<item quantity="many">%1$.1f quilómetros por hora</item>
<item quantity="other">%1$.1f quilómetros por hora</item>
</plurals>
<plurals name="voiceSpeedMilesPerHour">
<item quantity="one">%1$.1f milha por hora</item>
<item quantity="many">%1$.1f milhas por hora</item>
<item quantity="other">%1$.1f milhas por hora</item>
</plurals>
<!-- Waypoint Type -->
@@ -359,4 +364,249 @@ limitations under the License.
\n e) comprar em <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\n f) doar, e
\n g) fazer o que achar que ajuda a OpenTracks. </string>
<string name="about_preference_title">Sobre o OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> é uma aplicação de rastreamento de desportes que respeita totalmente a sua privacidade.</string>
<string name="about_url">Página do projeto: <a href="%1$s">%1$s</a></string>
<string name="about_version_name">Nome da versão: %1$s</string>
<string name="about_version_code">Código da versão: %1$d</string>
<string name="about_third_party_data">Dados de terceiros</string>
<string name="about_libraries">Bibliotecas</string>
<string name="help_storage_pictures_title">Como o OpenTracks armazena fotos tiradas como marcadores\?</string>
<string name="help_storage_pictures_content">Eles são armazenados no armazenamento interno da app (ou seja, acessíveis apenas no OpenTracks).</string>
<string name="help_data_google_play_services_title">O OpenTracks requer/usa o Google Play Services\?</string>
<string name="help_recording_title">Como o OpenTracks regista dados\?</string>
<string name="help_data_storage_title">Como o OpenTracks armazena dados do sensor\?</string>
<string name="help_data_google_play_services_content">Não</string>
<string name="help_gps_troubleshooting_title">OpenTracks não está gravando locais\?</string>
<string name="help_support_title">Como posso oferecer suporte a OpenTracks\?</string>
<string name="open_track_as_KML">Abrir como KML</string>
<string name="open_track_as_GPX">Abrir como GPX</string>
<string name="open_track_as_trackfileformat">Abrir como %1$s</string>
<string name="activity_type_escooter">escolta</string>
<string name="activity_type_swimming">natação</string>
<string name="activity_type_swimming_open">nadando em águas abertas</string>
<string name="activity_type_workout">treino</string>
<string name="description_altitude_loss">Perda de elevação: %1$d m (%2$d ft)</string>
<string name="description_pace_nautical">Ritmo (min/NM)</string>
<string name="description_default_speed_or_pace">Por tipo de atividade</string>
<string name="description_speed_nautical">Velocidade (nós)</string>
<string name="description_speed_source_gps">GPS</string>
<string name="export_with_photos">com fotos</string>
<string name="export_without_photos">sem fotos</string>
<string name="export_error_post_workout">A exportação pós-treino falhou, verifique o diretório de exportação.</string>
<string name="export_cannot_write_to_dir">Não é possível escrever ao diretório de exportação.</string>
<string name="export_dir_not_writable">" (não se escreve!)"</string>
<string name="export_track_already_exists_msg">A trilha %1$s já existe no diretório de destino.</string>
<string name="export_progress_summary_errors_msg">Erros</string>
<string name="generic_open_track">Trilho aberto</string>
<string name="generic_overwrite">Sobrescrever</string>
<string name="generic_skip">Pular</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">Mostrar erros</string>
<string name="generic_completed">Concluído</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Concluído com %1$d erro</item>
<item quantity="other">Concluído com %1$d erros</item>
</plurals>
<string name="generic_undo">Desfazer</string>
<string name="generic_filter">Filtro</string>
<string name="generic_from">De</string>
<string name="generic_to">Para</string>
<string name="generic_today">Hoje</string>
<string name="generic_dismiss">Dispensar</string>
<string name="gps_disabled_msg">GPS desativado</string>
<string name="gps_fixed_and_ready">GPS corrigido e pronto</string>
<string name="gps_recording_status">%1$s: %2$s</string>
<string name="gps_recording_without_signal">O OpenTracks está gravando sem dados de GPS.</string>
<string name="image_discard">Descartar</string>
<string name="import_thread_interrupted">Tópico interrompido</string>
<string name="import_unsupported_format">Formato de ficheiro não suportado</string>
<string name="import_parser_error">Erro do analisador: %1$s</string>
<string name="import_unable_to_import_file">Não foi possível importar o ficheiro: %1$s</string>
<string name="import_no_kml_file_found">Nenhum ficheiro doc.kml encontrado no KMZ</string>
<string name="import_file_imported">Ficheiro %1$s importado</string>
<string name="import_prevent_reimport">Definiu evitar reimportar a trilha</string>
<string name="import_progress_summary_ok_msg">Ficheiros importados</string>
<string name="import_progress_summary_exists_msg">Já existe</string>
<string name="import_progress_summary_errors_msg">Erros</string>
<string name="marker_add_photo_canceled">Cancelado, nenhum marcador de foto adicionado</string>
<string name="menu_insert_gallery_img">Inserir imagem da galeria</string>
<string name="menu_voice_rate">Velocidade da voz</string>
<string name="menu_resume_track">Retomar trilha</string>
<string name="menu_add_custom_layout">Novo Layout</string>
<string name="menu_filter">Filtro</string>
<string name="delete_service">Serviço de exclusão</string>
<string name="permission_gps_failed">OpenTracks requer permissão para usar GPS.</string>
<string name="sensor_state_cadence_avg">Avg Cadência</string>
<string name="sensor_state_cadence_max">Ritmo cardíaco médio</string>
<string name="sensor_state_heart_rate_avg">Ritmo cardíaco Avg</string>
<string name="sensor_state_heart_rate_max">Freqüência cardíaca máxima</string>
<string name="sensor_state_power_avg">Energia Avg</string>
<string name="sensor_could_not_scan">Não foi possível procurar aparelhos Bluetooth (erro %1$i).</string>
<string name="bluetooth_disabled">Ative o Bluetooth.</string>
<string name="settings_open_tracks_title">Trilhas abertas</string>
<string name="settings_open_tracks_summary">Tudo sobre o Projeto OpenTracks</string>
<string name="settings_defaults_title">Unidades e Atividade Default</string>
<string name="settings_gps_summary">Intervalo de tempo, Intervalo de distância, Distância máxima, Precisão</string>
<string name="settings_sensors_title">Sensores Bluetooth</string>
<string name="settings_sensors_summary">Voice announcements</string>
<string name="settings_ui_summary">Aparência, Layout de gravação, Tema</string>
<string name="settings_gps_title">GPS</string>
<string name="settings_announcements_title">Anúncios de voz</string>
<string name="settings_announcements_summary">Tempo, distância, velocidade da voz</string>
<string name="settings_import_export_title">Importar/Exportar</string>
<string name="settings_import_export_summary">Importar, Exportar, Auto Export</string>
<string name="settings_units_category">Unidades</string>
<string name="settings_recording_idle_speed_title">Limite de velocidade de marcha lenta</string>
<string name="settings_night_mode_title">Tema UI</string>
<string name="settings_layout_reset_confirm_message">Todas as configurações de layout serão revertidas para os valores padrão. Isso não excluirá nenhuma trilha do aparelho.</string>
<string name="settings_all_reset">Redefinir</string>
<string name="settings_sensor_running_speed_and_cadence">Distância, velocidade e cadência</string>
<string name="settings_sensor_wheel_circumference">Circunferência da Roda (mm)</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">Mostrar apenas aparelhos Bluetooth que anunciam os serviços necessários</string>
<string name="settings_stats_units_nautical">Milha náutica (NM, ft)</string>
<string name="settings_api_title">API pública</string>
<string name="settings_public_api_package_title">Nome do pacote para API pública</string>
<string name="settings_public_api_enabled_summary_off">Apenas o OpenTracks pode iniciar e parar gravações.</string>
<string name="settings_public_api_dashboard_enabled_summary_off">Os dados gravados não serão compartilhados automaticamente com outras aplicações.</string>
<string name="settings_import_section">Importar</string>
<string name="settings_export_section">Exportar</string>
<string name="settings_prevent_reimport_tracks_title">Impedir a reimportação de trilhas</string>
<string name="stats_loss">Perda</string>
<string name="stats_coordinates">Latitude/Longitude</string>
<string name="stats_split_interval">Intervalo de divisão</string>
<string name="stats_sensors_heart_rate">Frequência cardíaca</string>
<string name="stats_sensors_cadence">Cadência</string>
<string name="stats_sensors_power">Poder</string>
<string name="stats_clock">Relógio</string>
<string name="track_recording_notification_accuracy">Precisão da localização: %1$s</string>
<string name="track_distance_notification">Distância: %1$s</string>
<string name="track_delete_number_of_tracks">%1$d trilhas</string>
<string name="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string>
<string name="track_discarding">Descartando trilha</string>
<string name="track_detail_intervals_tab">intervalos</string>
<string name="aggregated_stats_empty_message">Registe a sua primeira trilha para ver as estatísticas agregadas</string>
<string name="interval_list_empty_message">Não há nenhum intervalo ainda</string>
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (Recomendado)</string>
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
<string name="value_integer_knots">%1$d nós</string>
<string name="value_float_mile_hour">%1$.1f mi/h</string>
<string name="value_float_knots">%1$.1f nós</string>
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (Recomendado)</string>
<string name="voice_per_kilometer">por quilômetro</string>
<plurals name="voiceSpeedMKnots">
<item quantity="one">%1$.1f nó</item>
<item quantity="other">%1$.1f nós</item>
</plurals>
<string name="voice_per_mile">por milha</string>
<string name="voice_per_nautical_mile">por milha náutica</string>
<plurals name="voiceDistanceMiles">
<item quantity="one">%1$.1f milha</item>
<item quantity="other">%1$.1f milhas</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">%1$.1f milha náutica</item>
<item quantity="other">%1$.1f milhas náuticas</item>
</plurals>
<string name="total_distance">Distância total</string>
<string name="average_heart_rate">Frequência cardíaca média</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">%1$.1f quilómetro</item>
<item quantity="many">%1$.1f quilómetros</item>
<item quantity="other">%1$.1f quilómetros</item>
</plurals>
<string name="current_heart_rate">Frequência cardíaca atual</string>
<string name="share_image_subject">Gostaria de compartilhar uma imagem consigo</string>
<string name="share_image_body">Acho que pode estar interessado nesta imagem.</string>
<string name="hold_to_stop">Segure para parar</string>
<string name="report_bug">Relatório de problema</string>
<string name="select_show_on_map_behavior">Selecionar comportamento de \"mostrar no mapa\"</string>
<string name="show_on_map_format_ask">Perguntas</string>
<string name="show_on_dashboard">Painel API do OpenTracks</string>
<string name="settings_reset_summary">Redefinir configurações para valores padrão</string>
<string name="settings_default_trackfileformat">Exportar/compartilhar formato de ficheiro</string>
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
<string name="settings_recording_customize_layout_title">Personalize o seu layout de gravação</string>
<string name="about_history">OpenTracks é um fork do <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (descontinuado em 2016).</string>
<string name="help_feature_title">OpenTracks tem os seguintes recursos:</string>
<string name="help_track_export_title">Como posso exportar/compartilhar trilhas gravadas\?</string>
<string name="help_track_export_content"> As trilhas podem ser exportadas das configurações como KMZ, KML ou GPX. KMZ/KML todos os dados; Localizações apenas GPX. As trilhas compartilhadas por meio do <i>ícone de compartilhamento</i> são compartilhadas como KMZ. </string>
<string name="help_recording_content">OpenTracks usa o sensor GPS do aparelho para registar a localização continuamente. Se o aparelho GPS relatar dados imprecisos (por exemplo, localização, velocidade, elevação), o OpenTracks não pode fazer nada a respeito.</string>
<string name="help_data_storage_content"> Os dados do sensor são armazenados por local registado. Em termos de sensor de frequência cardíaca, isso significa que apenas <i>uma</i> medição de frequência cardíaca é armazenada por local. Se estiver parado ou dentro de casa, nenhum dado do sensor será registrdo (mas será mostrado). </string>
<string name="export_all_do_it_for_all">faça isso para todos os conflitos</string>
<string name="export_track_errors">Trilhas não exportadas</string>
<string name="export_progress_summary_new_msg">Novas trilhas exportadas</string>
<string name="export_progress_summary_overwrite_msg">Trilhas sobrescritas</string>
<string name="export_progress_summary_skip_msg">Trilhas puladas</string>
<string name="generic_click_twice_cancel">Clique novamente para cancelar</string>
<string name="generic_choose_an_option">Escolha uma opção</string>
<string name="generic_yesterday">Ontem</string>
<string name="import_error_info">%1$s: %2$s</string>
<string name="import_error_list_dialog_title">Ficheiros não importados</string>
<string name="menu_select_layout">Selecione o layout</string>
<string name="permission_bluetooth_failed">OpenTracks requer permissão para usar Bluetooth.</string>
<string name="permission_bluetooth_failed_rejected">Precisa conceder acesso ao OpenTracks manualmente para aparelhos próximos nas configurações do sistema.</string>
<string name="settings_recording_customize_layout_select_columns">Campos de dados por linha</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Mostrar estatísticas no ecrã de bloqueio</string>
<string name="settings_defaults_summary">Unidades Preferidas, Atividade Padrão, Nome Padrão da Pista</string>
<string name="settings_ui_title">Interface do utilizador</string>
<string name="settings_activity_defaults_category">Defaults da atividade</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Durante a gravação, mostre estatísticas sem desbloquear o aparelho.</string>
<string name="settings_public_api_dashboard_enabled_title">Transferência automática de dados</string>
<string name="settings_public_api_dashboard_enabled_summary_on">Uma aplicação que iniciou uma gravação também pode acessar os dados gravados.</string>
<string name="settings_recording_keep_screen_on_while_recording_title">Mantenha o ecrã ligada</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">Durante a gravação, mantenha o ecrã ligado.</string>
<string name="settings_recording_fullscreen_on_while_recording_title">Ecrã inteiro</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">Durante a gravação, apresente em ecrã inteiro.</string>
<string name="settings_night_mode_option_system">Sistema</string>
<string name="settings_night_mode_option_day">Dia</string>
<string name="settings_night_mode_option_night">Noite</string>
<string name="settings_announcements_totaltime_frequency">Intervalo de tempo</string>
<string name="track_speed_notification">Velocidade: %1$s</string>
<string name="settings_announcements_distance_frequency">Intervalo de distância</string>
<string name="settings_announcements_average_heart_rate">Frequência cardíaca média</string>
<string name="settings_announcements_lap_heart_rate">Frequência cardíaca da volta</string>
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo médio</string>
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo de volta</string>
<string name="custom_layout_list_title">Estrutura</string>
<string name="custom_layout_list_edit_name">Digite o nome do layout a ser adicionado</string>
<string name="custom_layout_list_edit_already_exists">O nome do layout já existe</string>
<string name="custom_layout_list_layout_removed">Layout foi removido</string>
<string name="custom_layout_select_layout">Selecionar layout</string>
<string name="stats_custom_layout_default_layout">Layout padrão</string>
<string name="settings_layout_reset">Redefinir layout personalizado</string>
<string name="settings_layout_reset_confirm_title">Redefinir os seus layouts\?</string>
<string name="settings_layout_reset_done">Todas as configurações de layout foram revertidas para os valores padrão</string>
<string name="settings_generic_sensor">Genérico</string>
<string name="settings_cycling_sensor">Ciclismo</string>
<string name="settings_running_sensor">Correndo</string>
<string name="settings_sensor_cycling_distance_speed">Distância e velocidade</string>
<string name="settings_sensor_cycling_cadence">Cadência</string>
<string name="settings_public_api_enabled_title">API pública</string>
<string name="settings_public_api_enabled_summary_on">Outras aplicações instaladas podem iniciar ou interromper gravações.</string>
<string name="settings_public_api_disabled_toast">A API pública do OpenTracks está desativada: pode ser ativada nas configurações.</string>
<string name="track_pace_notification">Parcela: %1$s</string>
<string name="aggregated_stats_filter_no_results">Não há atividades para o filtro especificado</string>
<string name="unit_nautical_mile">NM</string>
<string name="unit_knots">kn</string>
<string name="unit_minute_per_nautical_mile">min/NM</string>
<string name="location_coordinate">%1$s°</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<string name="value_float_knots_recommended">%1$.1f nós (recomendado)</string>
<string name="lap_time">Tempo da volta</string>
<string name="pace">Ritmo</string>
<string name="lap_speed">Velocidade da volta</string>
<string name="speed">Velocidade</string>
<string name="instant_export_enabled_summary">Exportar trilha para armazenamento depois que a gravação for concluída</string>
<string name="instant_export_enabled_title">Exportação instantânea pós-treino</string>
<string name="settings_default_export_uri_title">Diretório de exportação de trilhas</string>
<string name="field_set_primary">Primário</string>
<string name="field_set_secondary">Secundário</string>
<string name="field_remove_from_layout">Remover</string>
<string name="no_compatible_file_manager_installed">Nenhum gestor de ficheiros compatível instalado</string>
<string name="error_crash_title">%1$s falharam</string>
<string name="share_crash_report">Compartilhar relatório de falha</string>
<string name="show_on_dashboard_not_installed">Nenhuma aplicação compatível com o painel API do OpenTracks instalado.</string>
<string name="always">Sempre</string>
<string name="introduction_osm_dashboard">O próprio OpenTracks não fornece um mapa. Instale o OSMDashboard para visualizar as suas gravações num mapa.</string>
</resources>
-1
View File
@@ -518,7 +518,6 @@ limitations under the License.
<string name="settings_sensors_summary">Cyklistické snímače, obvod kolies, snímače behu</string>
<string name="settings_open_tracks_summary">Viac o projekte OpenTracks</string>
<string name="settings_gps_summary">Interval času, interval vzdialenosti, Max. vzdialenosť, Presnosť</string>
<string name="help_support_content"/>
<string name="share_image_body">Myslím, že by vás mohol zaujímať tento obrázok.</string>
<string name="export_progress_summary_skip_msg">Vynechané skladby</string>
<string name="gps_disabled_msg">GPS vypnuté</string>
-1
View File
@@ -442,7 +442,6 @@ limitations under the License.
<string name="settings_gps_summary">Časovni interval, Interval razdalje, Največja razdalja, Točnost</string>
<string name="settings_sensors_summary">Kolesarski senzorji, Obod kolesa, Tekaški senzorji</string>
<string name="settings_sensors_title">Bluetooth senzorji</string>
<string name="settings_open_tracks_title"></string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">Prikaži statistiko na zaklenjenem zaslonu</string>
<string name="about_preference_title">O OpenTracks</string>
<string name="export_without_photos">brez fotografij</string>
-1
View File
@@ -589,7 +589,6 @@ limitations under the License.
<item quantity="one">1 deniz mili</item>
<item quantity="other">%1$.1f deniz mili</item>
</plurals>
<string name="image_discard"/>
<string name="settings_public_api_package_title">Genel API için paket adı</string>
<string name="show_on_dashboard_not_installed">OpenTracks Gösterge Paneli API\'sini destekleyen hiçbir uygulama yüklü değil.</string>
</resources>
+11 -11
View File
@@ -356,9 +356,9 @@ limitations under the License.
<string name="instant_export_enabled_summary">Експорт треку в сховище після завершення запису</string>
<string name="instant_export_enabled_title">Миттєвий експорт після тренування</string>
<string name="help_gps_troubleshooting_content"> Що ви можете зробити:
\na) перевірте, чи ввімкнено GPS,
\na) перевірте, чи увімкнено GPS,
\nb) покращити прийом GPS (наприклад, вийти на вулицю),
\nc) перевірити за допомогою додатку <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> та
\nc) перевірити за допомогою застосунку <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> та
\nd) вимкнути енергозбереження.
\n
\nЯкщо це не допомогло - будь ласка, повідомте про помилку. </string>
@@ -369,13 +369,13 @@ limitations under the License.
<string name="about_libraries">Бібліотеки</string>
<string name="help_feature_title">OpenTracks має такі особливості:</string>
<string name="help_storage_pictures_title">Як OpenTracks зберігає зображення зроблені за допомогою маркувань\?</string>
<string name="help_storage_pictures_content">Вони зберігаються у внутрішній пам\'яті додатку (тобто, доступні тільки для OpenTracks).</string>
<string name="help_storage_pictures_content">Вони зберігаються у внутрішній пам\'яті застосунку (тобто, доступні тільки для OpenTracks).</string>
<string name="help_data_google_play_services_title">Чи потребує OpenTracks використання Google Play Service\?</string>
<string name="help_track_map_title">Як показати трек на мапі\?</string>
<string name="help_data_storage_title">Як OpenTracks зберігає дані датчиків\?</string>
<string name="help_recording_title">Як OpenTracks записує дані\?</string>
<string name="help_track_export_title">Як я можу експортувати або обмінятися записаним треком\?</string>
<string name="settings_night_mode_title">Тема додатка</string>
<string name="settings_night_mode_title">Тема UI</string>
<string name="help_gps_troubleshooting_title">OpenTracks не записує моє місцеположення\?</string>
<string name="help_support_title">Як я можу підтримати OpenTracks\?</string>
<string name="open_track_as_KML">Відкрити як KML</string>
@@ -412,13 +412,13 @@ limitations under the License.
<string name="import_no_kml_file_found">Файл doc.kml не знайдено в KMZ</string>
<string name="import_error_list_dialog_title">Файли не імпортовано</string>
<string name="import_error_info">%1$s:%2$s</string>
<string name="marker_add_photo_canceled">Скасовано, фото-макркування не додано</string>
<string name="marker_add_photo_canceled">Скасовано, фото-маркування не додано</string>
<string name="menu_insert_gallery_img">Додати зображення з галереї</string>
<string name="menu_resume_track">Відновити трек</string>
<string name="permission_gps_failed">OpenTracks вимагає дозволу на використання GPS.</string>
<string name="settings_night_mode_option_system">Системна</string>
<string name="about_description"><i>Open Tracks</i> це спортивний GPS трекер який поважає вашу приватність.</string>
<string name="about_history">Open Tracks це відгалуження додатку <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (закритого в 2016р).</string>
<string name="about_history">Open Tracks це відгалуження застосунку <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (закритого в 2016р).</string>
<string name="help_feature_content">* запис GPS-шляху
\n* використання датчика ритму серця (тільки Bluetooth LE)
\n* голосовий путівник (за часом або дистацією)
@@ -432,9 +432,9 @@ limitations under the License.
\nа) поширювати інформацію про OpenTracks,
\nb) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">пропонувати функції</a>,
\nc) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">повідомляти про помилки</a>,
\nd) допомогnb в розробці програмного забезпечення,
\nd) допомогти в розробці програмного забезпечення,
\ne) купувати в <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
\nf) зробити фінаносовий внесок, та
\nf) зробити фінансовий внесок, та
\ng) робити все, що, на вашу думку, допомагає OpenTracks. </string>
<string name="description_default_speed_or_pace">За активністю</string>
<string name="export_error_post_workout">Не вдалося виконати експорт після тренування, перевірте каталог експорту.</string>
@@ -451,9 +451,9 @@ limitations under the License.
<string name="share_image_subject">Я хотів би поділитися з вами зображенням</string>
<string name="settings_default_export_uri_title">Каталог експорту треків</string>
<string name="help_track_map_content"> Натисніть іконку мапи.
\na) (оновлення в реальному часі) Встановіть додаток <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Він дозволить подивитися мапу онлайн й оффлайн завдяки <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\nНасправді, OSMDashboard є окремим додатком який може отримати тимчасовий доступ до даних треку які будуть відобрежені на мапі.
\nb) Через цвичайний картографічний додаток (на приклад, <a href="http://osmand.net/">OsmAnd</a> та <a href="https://maps.me/">MAPS.ME</a>). Ці додатки повинні підтримувати формати файлів треку KMZ (тільки місцеположення та мітки часу). Фактично, додаток повинен підтримувати функцію <i>ACTION_VIEW</i> із MIME <i>application/vnd.google-earth.kmz</i>. </string>
\na) (оновлення в реальному часі) Встановіть застосунок <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Він дозволить подивитися карту онлайн й оффлайн завдяки <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
\nНасправді, OSMDashboard є окремим застосунком, який може отримати тимчасовий доступ до даних треку які будуть відображені на карті.
\nb) Через звичайний картографічний застосунок (на приклад, <a href="http://osmand.net/">OsmAnd</a> та <a href="https://maps.me/">MAPS.ME</a>). Ці застосунки повинні підтримувати формати файлів треку KMZ (тільки місцеположення та мітки часу). Фактично, застосунок повинен підтримувати функцію <i>ACTION_VIEW</i> із MIME <i>application/vnd.google-earth.kmz</i>. </string>
<string name="settings_prevent_reimport_tracks_title">Заборонити повторний імпорт треків</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">Завершено з %1$d однією помилкою</item>
+423 -25
View File
@@ -1,5 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="about_preference_title">關於 OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> 是一個完全尊重您的隱私的運動蹤跡應用程式。</string>
<string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string>
<string name="about_version_name">版本名稱:%1$s</string>
<string name="about_version_code">版本代號:%1$d</string>
<string name="about_history">OpenTracks 是一個 <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> 的分支 (於 2016 年停止維護)。</string>
<string name="about_third_party_data">第三方資料</string>
<string name="about_libraries">函式庫</string>
<!-- Help -->
<string name="help_feature_title">OpenTracks 擁有以下特點:</string>
<string name="help_feature_content">
* 建立 GPS 蹤跡\n
* 使用一個心率感測器 (僅限藍牙 LE)\n
* 語音通知 (依時間或距離)\n
* 建立標記 (包含圖片)\n
* 完全離線工作\n
* 在地圖上顯示蹤跡 (見下文)
</string>
<string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string>
<string name="help_track_map_content">
使用小的地圖圖示。\n
a)
(即時更新) 安裝 <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>.
透過 <a href="https://www.openstreetmap.org">OpenStreetMap</a> 提供線上或離線地圖。\n
實際上,OSMDashboard 是一個單獨的應用程式,只能 (暫時) 存取將要顯示的蹤跡資料。
\n
b)
透過一個 "普通的" 地圖應用程式 (例如,<a href="http://osmand.net/">OsmAnd</a> 與 <a href="https://maps.me/">MAPS.ME</a>)。
這些 APP 需要支援 KMZ 檔案格式 (只有位置與時間戳記)。
事實上,APP 需要支援 <i>ACTION_VIEW</i> 與 MIME <i>application/vnd.google-earth.kmz</i>。
</string>
<string name="help_storage_pictures_title">OpenTracks 如何存儲作為標記拍攝的照片?</string>
<string name="help_storage_pictures_content">這些儲存在應用程序內部儲存區中 (即,只能在 OpenTracks 中存取)。</string>
<string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
<string name="help_track_export_content">
可以從設定匯出蹤跡為 KMZ、KML 或 GPX。
KMZ/KML 全部資料;GPX 只有位置。
通過<i>分享圖示</i>以分享蹤跡為 KMZ。
</string>
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</string>
<string name="help_data_google_play_services_content">否</string>
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
<string name="help_recording_content">
OpenTracks 使用裝置的 GPS 感測器以連續紀錄位置。
如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。
</string>
<string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string>
<string name="help_data_storage_content">
感測器資料儲存在每個記錄的位置。
就心率感測器而言,這意味著只有<i>一個</i>心率測量值被依照位置儲存。
如果您是站著不動或在室內,沒有感測器資料會被記錄 (但它會被顯示)。
</string>
<string name="help_gps_troubleshooting_title">OpenTracks 沒有紀錄位置?</string>
<string name="help_gps_troubleshooting_content">
您能做什麼:\n
a) 檢查 GPS 是否啟用,\n
b) 改善 GPS 收訊 (例如,前往戶外),\n
c) 使用 <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app 檢查,以及\n
d) 禁用省電。\n
\n
如果一切都沒有幫助,請回報 bug。
</string>
<string name="help_support_title">我如何支持 OpenTracks?</string>
<string name="help_support_content">
您可以:\n
a) 推廣 OpenTracks,\n
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">提出建議</a>,\n
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">回報 bugs</a>,\n
d) 幫助軟體開發,\n
e) 在 <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play 商店</a> 購買它,\n
f) 贊助,以及\n
g) 做任何您認為對 OpenTracks 有幫助的事情。
</string>
<string name="open_track_as_KML">開啟為 KML</string>
<string name="open_track_as_GPX">開啟為 GPX</string>
<string name="open_track_as_trackfileformat">開啟為 %1$s</string>
<!-- Activity Type -->
<string name="activity_type_airplane">飛機</string>
<string name="activity_type_atv">越野車</string>
<string name="activity_type_biking">騎自行車</string>
@@ -14,6 +116,7 @@
<string name="activity_type_driving">正在開車</string>
<string name="activity_type_driving_bus">駕駛公車</string>
<string name="activity_type_driving_car">駕車</string>
<string name="activity_type_escooter">電動滑板車</string>
<string name="activity_type_ferry">渡輪碼頭</string>
<string name="activity_type_frisbee">飛盤</string>
<string name="activity_type_gliding">滑翔</string>
@@ -66,61 +169,131 @@
<string name="activity_type_walking">步行</string>
<string name="activity_type_water_skiing">滑水</string>
<string name="activity_type_wind_surfing">風帆</string>
<string name="activity_type_swimming">游泳</string>
<string name="activity_type_swimming_open">開放水域游泳</string>
<string name="activity_type_workout">訓練</string>
<!-- Description -->
<string name="description_activity_type">活動類型:%1$s</string>
<string name="description_average_moving_pace_in_minute">平均移動速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_moving_pace_in_minute">平均移動步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_moving_speed">平均移動速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_average_pace_in_minute">平均速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_pace_in_minute">平均步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_average_speed">平均速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_altitude_gain">爬升高度:%1$d 公尺 (%2$d 英尺)</string>
<string name="description_altitude_loss">下降高度:%1$d m (%2$d ft)</string>
<string name="description_altitude_imperial">海拔高度 (英尺)</string>
<string name="description_altitude_metric">海拔高度 (公尺)</string>
<string name="description_fastest_pace_in_minute">最快速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_fastest_pace_in_minute">最高步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
<string name="description_max_altitude">最高海拔:%1$d 公尺 (%2$d 英尺)</string>
<string name="description_max_speed">最快速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_max_speed">最高速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
<string name="description_min_altitude">最低海拔:%1$d 公尺 (%2$d 英尺)</string>
<string name="description_moving_time">移動時間:%1$s</string>
<string name="description_pace_imperial">速度 (分鐘/英里)</string>
<string name="description_pace_metric">速度 (分鐘/公里)</string>
<string name="description_pace_imperial">步速 (分鐘/英里)</string>
<string name="description_pace_nautical">步速 (分鐘/海里)</string>
<string name="description_pace_metric">步速 (分鐘/公里)</string>
<string name="description_recorded_time">記錄時間:%1$s</string>
<string name="description_sensor_cadence">轉速 (rpm)</string>
<string name="description_speed_ms">速度 (公尺/秒)</string>
<string name="description_sensor_cadence">踏頻 (rpm)</string>
<string name="description_sensor_heart_rate">心率 (bpm)</string>
<string name="description_sensor_power">功率 (W)</string>
<string name="description_default_speed_or_pace">依照活動類型</string>
<string name="description_speed_imperial">速度 (英里/小時)</string>
<string name="description_speed_nautical">速度 (節)</string>
<string name="description_speed_metric">速度 (公里/小時)</string>
<string name="description_time">時間</string>
<string name="description_total_distance">總距離:%1$.2f 公里 (%2$.1f 英里)</string>
<string name="description_total_time">總時間:%1$s</string>
<string name="description_speed_source_gps">GPS</string>
<!-- Export -->
<string name="export_with_photos">包含照片</string>
<string name="export_without_photos">不包含照片</string>
<string name="export_error_post_workout">運動後匯出失敗,請檢查匯出目錄。</string>
<string name="export_cannot_write_to_dir">無法寫入匯出目錄。</string>
<string name="export_dir_not_writable">" (不可寫入!)"</string>
<string name="export_progress_message">正在匯出至 %1$s…</string>
<string name="export_track_already_exists_msg">蹤跡 %1$s 已存在於目標目錄之中。</string>
<string name="export_all_do_it_for_all">對所有衝突這麼做</string>
<string name="export_track_errors">蹤跡未匯出</string>
<string name="export_progress_summary_new_msg">新蹤跡已匯出</string>
<string name="export_progress_summary_overwrite_msg">蹤跡覆蓋</string>
<string name="export_progress_summary_skip_msg">蹤跡已略過</string>
<string name="export_progress_summary_errors_msg">錯誤</string>
<!-- External Storage -->
<!-- Generic -->
<string name="generic_add">新增</string>
<string name="generic_delete_selected_confirm_title">確定要刪除所選項目?</string>
<string name="generic_description">內容描述</string>
<string name="generic_description_line">內容描述:%1$s</string>
<string name="generic_name">名稱</string>
<string name="generic_name_line">名稱:%1$s</string>
<string name="generic_open_track">開啟蹤跡</string>
<string name="generic_recording">正在記錄…</string>
<string name="generic_save">儲存</string>
<string name="generic_tracks">足跡</string>
<string name="generic_tracks">蹤跡</string>
<string name="generic_overwrite">覆蓋</string>
<string name="generic_skip">略過</string>
<string name="generic_separator_done_total">/</string>
<string name="generic_show_errors">顯示錯誤</string>
<plurals name="generic_completed_with_errors">
<item quantity="one">已完成,但有一個錯誤</item>
<item quantity="other">已完成,但有 %1$d 個錯誤</item>
</plurals>
<string name="generic_completed">完成</string>
<string name="generic_click_twice_cancel">再次點擊返回以取消</string>
<string name="generic_choose_an_option">選擇一個選項</string>
<string name="generic_undo">撤消</string>
<string name="generic_filter">篩選</string>
<string name="generic_from">從</string>
<string name="generic_to">至</string>
<string name="generic_today">今日</string>
<string name="generic_yesterday">昨日</string>
<string name="generic_dismiss">Dismiss</string>
<!-- Gps -->
<string name="gps_starting">正在啟用 GPS</string>
<string name="gps_wait_for_better_signal">正在等待更清晰的 GPS 訊號</string>
<string name="gps_wait_for_signal">正在等待 GPS 訊號</string>
<string name="gps_disabled_msg">GPS 禁用</string>
<string name="gps_fixed_and_ready">GPS 已修復並準備就緒</string>
<string name="gps_recording_status">%1$s:%2$s</string>
<string name="gps_recording_without_signal">OpenTracks 紀錄中,不包含 GPS 資料。</string>
<!-- Image -->
<string name="image_arrow">箭頭</string>
<string name="image_marker">標記</string>
<string name="image_marker_count">標記數</string>
<string name="image_photo_marker">相片標記</string>
<string name="image_record">記錄</string>
<string name="image_stop">停止</string>
<string name="image_track">足跡</string>
<string name="image_discard">Discard</string>
<string name="image_track">蹤跡</string>
<!-- Import -->
<string name="import_thread_interrupted">執行緒中斷</string>
<string name="import_unsupported_format">未支援的檔案格式</string>
<string name="import_parser_error">分析器錯誤:%1$s</string>
<string name="import_unable_to_import_file">無法匯入檔案:%1$s</string>
<string name="import_no_kml_file_found">在 KMZ 中沒有發現 doc.kml 檔案</string>
<string name="import_error_info">%1$s:%2$s</string>
<string name="import_error_list_dialog_title">檔案未匯入</string>
<string name="import_file_imported">檔案 %1$s 已匯入</string>
<string name="import_prevent_reimport">您已設定防止重新匯入蹤跡</string>
<string name="import_progress_message">正在從 %1$s 匯入檔案…</string>
<string name="import_progress_summary_ok_msg">檔案已匯入</string>
<string name="import_progress_summary_exists_msg">已經存在</string>
<string name="import_progress_summary_errors_msg">錯誤</string>
<!-- Marker -->
<string name="marker_add_error">無法插入標記,因為無法取得 GPS 訊號。請再試一次。</string>
<string name="marker_add_success">標記已插入</string>
<string name="marker_add_canceled">已取消,未新增任何標記</string>
<string name="marker_add_photo_canceled">已取消,未加入照片標記</string>
<string name="marker_delete_one_confirm_message">系統將永久刪除裝置上的這個標記</string>
<string name="marker_delete_one_confirm_title">確定要刪除標記?</string>
<string name="marker_delete_multiple_confirm_message">系統將永久刪除裝置上您所選取的這些標記</string>
<string name="marker_name_format">標記 %1$d</string>
<string name="marker_title">%1$d 個位置 (共 %2$d 個)</string>
<!-- Marker Detail -->
<!-- Marker Edit -->
<string name="marker_edit_marker_type">標記類型 (例如瀑布)</string>
<!-- Marker List -->
<string name="marker_list_empty_message">您沒有任何標記</string>
<!-- Menu -->
<string name="menu_aggregated_statistics">彙總的統計資料</string>
<string name="menu_delete">刪除</string>
<string name="menu_edit">編輯</string>
@@ -129,21 +302,83 @@
<string name="menu_import_all">全部匯入</string>
<string name="menu_insert_marker">插入標記</string>
<string name="menu_insert_photo">插入相片</string>
<string name="menu_insert_gallery_img">插入圖片庫圖片</string>
<string name="menu_markers">標記</string>
<string name="menu_search">搜尋</string>
<string name="menu_select_all">全選</string>
<string name="menu_settings">設定</string>
<string name="menu_share">分享</string>
<string name="menu_select_layout">選擇布局</string>
<string name="menu_show_on_map">在地圖上顯示</string>
<string name="menu_start_gps">啟動 GPS</string>
<string name="menu_stop_gps">關閉 GPS</string>
<string name="menu_voice_rate">語速</string>
<string name="menu_resume_track">還原蹤跡</string>
<string name="menu_add_custom_layout">新布局</string>
<string name="menu_filter">篩選</string>
<!-- OpenTracks -->
<string name="recording_service">記錄服務</string>
<string name="delete_service">刪除服務</string>
<!-- Permission -->
<string name="permission_gps_failed">OpenTracks 需要授權使用 GPS。</string>
<string name="permission_bluetooth_failed">OpenTracks 需要授權使用藍芽。</string>
<string name="permission_bluetooth_failed_rejected">您需要手動在系統設定中授權 OpenTracks 存取附近裝置。</string>
<!-- Search -->
<!-- Sensor State -->
<string name="sensor_state_cadence_avg">平均踏頻</string>
<string name="sensor_state_cadence_max">最大踏頻</string>
<string name="sensor_state_heart_rate">心率</string>
<string name="sensor_state_heart_rate_avg">平均心率</string>
<string name="sensor_state_heart_rate_max">最大心率</string>
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
<string name="sensor_state_power">電源</string>
<string name="sensor_state_power">功率</string>
<string name="sensor_state_power_avg">平均功率</string>
<string name="sensor_could_not_scan">無法掃描藍牙裝置 (錯誤 %1$i).</string>
<string name="bluetooth_disabled">請啟用藍芽。</string>
<string name="settings_open_tracks_title">開啟蹤跡</string>
<string name="settings_open_tracks_summary">所有關於 OpenTracks 專案</string>
<string name="settings_defaults_title">預設單位與活動</string>
<string name="settings_defaults_summary">首選單位、預設活動、蹤跡預設名稱</string>
<string name="settings_ui_title">使用者介面</string>
<string name="settings_ui_summary">外觀、紀錄布局、主題</string>
<string name="settings_gps_title">GPS</string>
<string name="settings_gps_summary">時間間隔、距離間隔、最大距離、精度</string>
<string name="settings_sensors_title">藍芽感測器</string>
<string name="settings_sensors_summary">自行車感測器、輪周、跑步感測器</string>
<string name="settings_announcements_title">語音通知</string>
<string name="settings_announcements_summary">時間、距離、語速</string>
<string name="settings_import_export_title">匯入/匯出</string>
<string name="settings_import_export_summary">匯入、匯出、自動匯出</string>
<string name="settings_units_category">單位</string>
<string name="settings_activity_defaults_category">活動的預設值</string>
<string name="settings_reset_summary">重置設定為預設值</string>
<string name="settings_layout_reset_key" translatable="false">設定布局重置</string>
<!-- Settings Advanced -->
<string name="settings_advanced">進階</string>
<string name="settings_default_trackfileformat">匯出/分享檔案格式</string>
<string name="settings_export_filename_title">檔名的格式</string>
<!-- Settings Chart -->
<string name="settings_chart_by_distance">依距離</string>
<string name="settings_chart_by_time">依時間</string>
<!-- Settings Recording -->
<string name="settings_recording_customize_layout_title">自訂您的紀錄布局</string>
<string name="settings_recording_customize_layout_select_columns">每列資料欄位</string>
<string name="settings_recording_show_on_lockscreen_while_recording_title">在鎖定畫面顯示統計資料</string>
<string name="settings_recording_show_on_lockscreen_while_recording_summary">當紀錄時,在不解鎖裝置的情況下顯示統計資料。</string>
<string name="settings_recording_keep_screen_on_while_recording_title">維持螢幕開啟</string>
<string name="settings_recording_keep_screen_on_while_recording_summary">記錄中,維持螢幕開啟。</string>
<string name="settings_recording_fullscreen_on_while_recording_title">全螢幕</string>
<string name="settings_recording_fullscreen_on_while_recording_summary">記錄中,在全螢幕中呈現。</string>
<string name="settings_recording_default_activity_title">預設活動類型</string>
<string name="settings_recording_location_frequency_summary">每 %1$s記錄一次位置資訊</string>
<string name="settings_recording_max_recording_distance_summary">系統會在兩個地點之間的距離大於 %1$s時建立新的區段</string>
@@ -155,77 +390,214 @@
<string name="settings_recording_track_name_date_iso_8601_option">日期 (ISO 8601)</string>
<string name="settings_recording_track_name_date_local_option">日期 (當地)</string>
<string name="settings_recording_track_name_number_option">數字</string>
<string name="settings_recording_track_name_title">預設足跡名稱</string>
<string name="settings_recording_track_name_title">預設蹤跡名稱</string>
<string name="settings_recording_idle_speed_title">怠速閾值</string>
<string name="settings_night_mode_title">界面主題</string>
<string name="settings_night_mode_option_system">系統</string>
<string name="settings_night_mode_option_day">日</string>
<string name="settings_night_mode_option_night">夜</string>
<string name="settings_announcements_totaltime_frequency">時間間隔</string>
<string name="settings_announcements_distance_frequency">距離間隔</string>
<string name="settings_announcements_average_heart_rate">平均心率</string>
<string name="settings_announcements_lap_heart_rate">單圈心率</string>
<string name="settings_announcements_average_speed_pace">平均速度/步速</string>
<string name="settings_announcements_lap_speed_pace">單圈速度/步速</string>
<!-- Custom Layout -->
<string name="custom_layout_list_title">布局</string>
<string name="custom_layout_list_edit_name">輸入要新增的佈局名稱</string>
<string name="custom_layout_list_edit_already_exists">布局的名稱已經存在</string>
<string name="custom_layout_list_layout_removed">佈局已移除</string>
<string name="custom_layout_select_layout">選擇布局</string>
<string name="stats_custom_layout_default_layout">預設布局</string>
<!-- Custom Layout Reset -->
<string name="settings_layout_reset">重置自訂布局</string>
<string name="settings_layout_reset_confirm_message">所有的布局設定將被還原為預設值。這不會刪除任何裝置上的蹤跡。</string>
<string name="settings_layout_reset_confirm_title">重置您的布局?</string>
<string name="settings_layout_reset_done">所有的布局設定已被還原為預設值</string>
<!-- Settings Reset -->
<string name="settings_all_reset">重置</string>
<string name="settings_reset_confirm_message">系統會將所有設定還原為預設值。</string>
<string name="settings_reset_confirm_title">確定要立即重設?</string>
<string name="settings_reset_done">所有設定已還原為預設值</string>
<!-- Settings Sensor -->
<string name="settings_generic_sensor">通用</string>
<string name="settings_cycling_sensor">騎自行車</string>
<string name="settings_running_sensor">跑步</string>
<string name="settings_sensor_bluetooth">藍牙</string>
<string name="settings_sensor_cycling_distance_speed">距離與速度</string>
<string name="settings_sensor_cycling_cadence">踏頻</string>
<string name="settings_sensor_running_speed_and_cadence">距離、速度與踏頻</string>
<string name="settings_sensor_wheel_circumference">輪周 (公釐)</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">僅顯示宣告所需服務的藍芽裝置</string>
<string name="settings_sensor_heart_rate_max">區間:最大心率 (bpm)</string>
<!-- Settings Stats -->
<string name="settings_stats_rate_title">偏好速率</string>
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
<string name="settings_stats_units_nautical">航海 (海里、英尺)</string>
<string name="settings_stats_units_title">慣用單位</string>
<string name="share_track_share_file_body">你應該會喜歡這個足跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看足跡。\n\n%1$s</string>
<string name="share_track_subject">我想跟你分享足跡資訊。</string>
<string name="stats_average_moving_pace">平均移動速度</string>
<!-- Settings public api -->
<string name="settings_api_title">公開 API</string>
<string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string>
<string name="settings_public_api_package_title">公開 API 的套件名稱</string>
<string name="settings_public_api_enabled_title">公開 API</string>
<string name="settings_public_api_enabled_summary_on">其他已安裝的應用程式可以開始或停止紀錄。</string>
<string name="settings_public_api_enabled_summary_off">只有 OpenTracks 可以開始或停止紀錄。</string>
<string name="settings_public_api_disabled_toast">OpenTracks 公開 AIP 被禁用:它可以在設定中被啟用。</string>
<string name="settings_public_api_dashboard_enabled_title">自動資料傳輸</string>
<string name="settings_public_api_dashboard_enabled_summary_on">開始紀錄的應用程式也可以存取紀錄的資料。</string>
<string name="settings_public_api_dashboard_enabled_summary_off">記錄的資料將不會自動與其他應用程式共用。</string>
<!-- Settings import/export -->
<string name="settings_import_section">匯入</string>
<string name="settings_export_section">匯出</string>
<string name="settings_prevent_reimport_tracks_title">防止重新匯入蹤跡</string>
<!-- Share Track -->
<string name="share_track_share_file_body">你應該會喜歡這個蹤跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看蹤跡。\n\n%1$s</string>
<string name="share_track_subject">我想跟你分享蹤跡資訊。</string>
<!-- Stats -->
<string name="stats_average_moving_pace">平均移動步速</string>
<string name="stats_average_moving_speed">平均移動速度</string>
<string name="stats_average_pace">平均速度</string>
<string name="stats_average_pace">平均步速</string>
<string name="stats_average_speed">平均速度</string>
<string name="stats_altitude">海拔高度</string>
<string name="stats_fastest_pace">最快速度</string>
<string name="stats_fastest_pace">最高步速</string>
<string name="stats_gain">增加</string>
<string name="stats_loss">減少</string>
<string name="stats_coordinates">緯度/經度</string>
<string name="stats_latitude">緯度</string>
<string name="stats_longitude">經度</string>
<string name="stats_max_speed">速限</string>
<string name="stats_max_speed">最高速度</string>
<string name="stats_moving_time">移動時間</string>
<string name="stats_pace">速度</string>
<string name="stats_pace">步速</string>
<string name="stats_speed">速度</string>
<string name="stats_distance">距離</string>
<string name="stats_total_time">總時間</string>
<string name="track_name_format">%1$d 號足跡</string>
<string name="stats_split_interval">分段間隔</string>
<string name="stats_sensors_heart_rate">心率</string>
<string name="stats_sensors_cadence">踏頻</string>
<string name="stats_sensors_power">功率</string>
<string name="stats_clock">時鐘</string>
<!-- Track -->
<string name="track_name_format">%1$d 號蹤跡</string>
<string name="track_recording_notification_accuracy">定位精度:%1$s</string>
<string name="track_distance_notification">距離:%1$s</string>
<string name="track_speed_notification">速度:%1$s</string>
<string name="track_pace_notification">步速:%1$s</string>
<string name="track_markers">「%1$s」標記</string>
<string name="track_delete_one_confirm_message">系統將永久刪除裝置上儲存的這個足跡與其中的標記。</string>
<string name="track_delete_one_confirm_title">確定要刪除足跡?</string>
<string name="track_delete_multiple_confirm_message">系統將永久刪除您在裝置上所選的足跡與其中的標記。</string>
<!-- Track Delete -->
<string name="track_delete_one_confirm_message">系統將永久刪除裝置上儲存的這個蹤跡與其中的標記。</string>
<string name="track_delete_one_confirm_title">確定要刪除蹤跡?</string>
<string name="track_delete_multiple_confirm_message">系統將永久刪除您在裝置上所選的蹤跡與其中的標記。</string>
<string name="track_delete_progress_message">刪除中…</string>
<string name="track_delete_number_of_tracks">%1$d 蹤跡</string>
<string name="track_delete_not_recording">此蹤跡無法被刪除,因為它目前正在錄製。</string>
<string name="track_discarding">廢棄蹤跡</string>
<!-- Track Detail -->
<string name="track_detail_stats_tab">統計資料</string>
<string name="track_detail_intervals_tab">間隔</string>
<!-- Track Edit -->
<string name="track_edit_activity_type">活動類型 (例如健行)</string>
<string name="track_edit_activity_type_hint">活動類型</string>
<!-- Track List -->
<string name="track_list_empty_message">按這裡開始記錄新的冒險</string>
<!-- Aggregated Statistics -->
<string name="aggregated_stats_empty_message">紀錄您的第一個蹤跡以檢視綜合統計資料</string>
<string name="aggregated_stats_filter_no_results">沒有指定篩選的活動</string>
<!-- Interval List -->
<string name="interval_list_empty_message">還有有任何間格</string>
<!-- Unit -->
<string name="unit_feet">英尺</string>
<string name="unit_kilometer">公里</string>
<string name="unit_kilometer_per_hour">公里/小時</string>
<string name="unit_meter">公尺</string>
<string name="unit_mile">英里</string>
<string name="unit_mile_per_hour">英里/小時</string>
<string name="unit_nautical_mile">海里</string>
<string name="unit_knots">節</string>
<string name="unit_minute_per_kilometer">分鐘/公里</string>
<string name="unit_minute_per_mile">分鐘/英里</string>
<string name="unit_minute_per_nautical_mile">分鐘/海里</string>
<!-- Location Coordinates -->
<string name="location_coordinate">%1$s°</string>
<string name="location_latitude_longitude">%1$s, %2$s</string>
<!-- Value -->
<string name="value_integer_feet_excellent_gps">%1$d 英尺 (GPS 訊號佳)</string>
<string name="value_integer_feet_recommended">%1$d 英尺 (建議值)</string>
<string name="value_integer_kilometer_hour">%1$d 公里/小時</string>
<string name="value_float_kilometer_hour">%1$.1f 公里/小時</string>
<string name="value_float_kilometer_hour_recommended">%1$.1f 公里/小時 (建議值)</string>
<string name="value_integer_meter_excellent_gps">%1$d 公尺 (GPS 訊號佳)</string>
<string name="value_integer_meter_poor_gps">%1$d 公尺 (GPS 訊號差)</string>
<string name="value_integer_meter_recommended">%1$d 公尺 (建議值)</string>
<string name="value_integer_mile_hour">%1$d 英里/小時</string>
<string name="value_integer_knots">%1$d 節</string>
<string name="value_float_mile_hour">%1$.1f 英里/小時</string>
<string name="value_float_knots">%1$.1f 節</string>
<string name="value_float_mile_hour_recommended">%1$.1f 英里/小時 (建議值)</string>
<string name="value_float_knots_recommended">%1$.1f 節 (建議值)</string>
<string name="value_integer_minute">%1$d 分鐘</string>
<string name="value_integer_second">%1$d 秒</string>
<string name="value_none">無</string>
<string name="value_off">關閉</string>
<string name="value_smallest_recommended">最小 (建議值)</string>
<!-- Voice -->
<plurals name="voiceHours">
<item quantity="one">1 小時</item>
<item quantity="other">%1$d 小時</item>
</plurals>
<plurals name="voiceMinutes">
<item quantity="one">1 分鐘</item>
<item quantity="other">%1$d 分鐘</item>
</plurals>
<plurals name="voiceSeconds">
<item quantity="one">1 秒</item>
<item quantity="other">%1$d 秒</item>
</plurals>
<plurals name="voiceSpeedKilometersPerHour">
<item quantity="other">每小時 %1$.1f 公里</item>
<item quantity="one">1 公里每小時</item>
<item quantity="other">%1$.1f 公里每小時</item>
</plurals>
<plurals name="voiceSpeedMilesPerHour">
<item quantity="other">每小時 %1$.1f 公里</item>
<item quantity="one">1 英里每小時</item>
<item quantity="other">%1$.1f 英里每小時</item>
</plurals>
<plurals name="voiceSpeedMKnots">
<item quantity="one">1 節</item>
<item quantity="other">%1$.1f 節</item>
</plurals>
<string name="voice_per_kilometer">每公里</string>
<string name="voice_per_mile">每英里</string>
<string name="voice_per_nautical_mile">每海里</string>
<string name="lap_time">單圈時間</string>
<string name="pace">步速</string>
<string name="lap_speed">單圈速度</string>
<string name="speed">平均移動速度</string>
<string name="total_distance">總距離</string>
<plurals name="voiceDistanceKilometers">
<item quantity="one">1 公里</item>
<item quantity="other">%1$.1f 公里</item>
</plurals>
<plurals name="voiceDistanceMiles">
<item quantity="one">1 英里</item>
<item quantity="other">%1$.1f 英里</item>
</plurals>
<plurals name="voiceDistanceNauticalMiles">
<item quantity="one">1 海里</item>
<item quantity="other">%1$.1f 海里</item>
</plurals>
<string name="average_heart_rate">平均心率</string>
<string name="current_heart_rate">目前心率</string>
<!-- Waypoint Type -->
<string name="waypoint_type_atm">提款機</string>
<string name="waypoint_type_bank">銀行</string>
<string name="waypoint_type_bar">酒吧</string>
@@ -277,4 +649,30 @@
<string name="waypoint_type_waterfall">瀑布</string>
<string name="waypoint_type_water_fountain">噴泉</string>
<string name="waypoint_type_waypoint">途經點</string>
<!-- Share image -->
<string name="share_image_subject">我想與你分享一張圖片</string>
<string name="share_image_body">我認為你可能會對這張圖片感興趣。</string>
<string name="hold_to_stop">按住以停止</string>
<string name="instant_export_enabled_summary">在紀錄完成後匯出蹤跡至儲存區</string>
<string name="instant_export_enabled_title">即時運動後匯出</string>
<string name="settings_default_export_uri_title">蹤跡匯出目錄</string>
<!-- Fields -->
<string name="field_set_primary">主要的</string>
<string name="field_set_secondary">次要的</string>
<string name="field_remove_from_layout">移除</string>
<string name="no_compatible_file_manager_installed">沒有安裝相容的檔案管理器</string>
<string name="error_crash_title">%1$s 已崩潰</string>
<string name="share_crash_report">分享</string>
<string name="report_bug">回報 bug</string>
<string name="select_show_on_map_behavior">選擇 \"在地圖上顯示\" 行為</string>
<string name="show_on_map_format_ask">詢問</string>
<string name="show_on_dashboard">OpenTracks 儀表板 API</string>
<string name="show_on_dashboard_not_installed">沒有安裝支援 OpenTracks 儀表板 API 的應用程式。</string>
<string name="always">總是</string>
<string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。</string>
</resources>
+4
View File
@@ -57,4 +57,8 @@ limitations under the License.
<color name="splashscreen_background">#272727</color>
<color name="heart_rate_zone_textcolor_5">#FF0000</color>
<color name="heart_rate_zone_textcolor_4">#FFBD04</color>
<color name="heart_rate_zone_textcolor_3">#6ACC2B</color>
<color name="heart_rate_zone_textcolor_2">#64D1FD</color>
</resources>

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