Compare commits

..

26 Commits

Author SHA1 Message Date
Dennis Guse 9225d90e45 Release: v4.3.3 2023-04-27 18:49:23 +02:00
Dennis Guse 32dc6fef82 Bugfix: CustomLayout - title for pace was speed/GPS.
Introduced in d70e7b8788
2023-04-26 07:19:11 +02:00
Dennis Guse 96fb65b6b0 Bugfix: hidden DataFields should be hidden.
Introduced in d70e7b8788
2023-04-26 07:19:11 +02:00
dependabot[bot] 936bbf9264 Bump org.mockito:mockito-android from 5.3.0 to 5.3.1
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.3.0...v5.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 07:01:14 +02:00
Dennis Guse 241a52ee7c Release: v4.3.2 2023-04-23 09:57:41 +02:00
Dennis Guse c66e285bf5 README: move human tech badge to codeberg. 2023-04-23 09:06:15 +02:00
Dennis Guse 8c1ddc077d Merge remote-tracking branch 'weblate/main' 2023-04-22 13:29:01 +02:00
dependabot[bot] 61c18af639 Bump com.android.tools.build:gradle from 8.1.0-alpha11 to 8.1.0-beta01
Bumps com.android.tools.build:gradle from 8.1.0-alpha11 to 8.1.0-beta01.

---
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-04-21 10:19:35 +02:00
dependabot[bot] b83df3ac4b Bump androidx.core:core-splashscreen from 1.0.0 to 1.0.1
Bumps androidx.core:core-splashscreen from 1.0.0 to 1.0.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-20 07:29:34 +02:00
plum7x 53f1c4b7f3 Translated using Weblate (Chinese (Traditional))
Currently translated at 2.9% (3 of 103 strings)

Translation: OpenTracks/App store metadata files
Translate-URL: https://hosted.weblate.org/projects/opentracks/app-store-metadata-files/zh_Hant/
2023-04-19 11:50:26 +02:00
plum7x 6290bdbab0 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (506 of 506 strings)

Translation: OpenTracks/opentracks.src-main-res-values-strings-xml--main
Translate-URL: https://hosted.weblate.org/projects/opentracks/opentracks-src-main-res-values-strings-xml-main/zh_Hant/
2023-04-19 11:50:26 +02:00
josé m bd0d85cd37 Translated using Weblate (Galician)
Currently translated at 100.0% (506 of 506 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-04-19 11:50:25 +02:00
Dennis Guse ba08417236 Cleanup: moved non-translatable string to correct file.
Part of #1527.
2023-04-18 12:55:21 +02:00
Dennis Guse 63281a3ca8 README: add dedicated link ot weblate. 2023-04-16 20:02:09 +02:00
Dennis Guse fae0cf8454 Merge pull request #1525 from OpenTracksApp/java17cleanup
Java 17 cleanup
2023-04-13 15:38:18 +02:00
Dennis Guse efddb31391 Cleanup: use Java17 switch statements.
AndroidStudio code cleanup.
2023-04-13 15:10:17 +02:00
Dennis Guse 3a8e547e10 Gradle: use configureEach instead of all. 2023-04-13 14:52:41 +02:00
Dennis Guse fe09d18197 Merge pull request #1523 from OpenTracksApp/bugKML#1522
Bug kml#1522
2023-04-13 14:34:48 +02:00
Dennis Guse 93bcf8d494 Bugfix: KML header malformed.
Fixes #1522.
Introduced in 82fe859c84
2023-04-12 23:09:04 +02:00
Dennis Guse 819f454bd8 Refactored TrackFileFormat interface. 2023-04-12 22:41:56 +02:00
Dennis Guse 7d58014b15 Cleanup/Java 17: use multi-line text blocks. 2023-04-12 22:39:30 +02:00
Dennis Guse 9dac95569c AGP: use Java 17. 2023-04-12 22:31:41 +02:00
Dennis Guse 96c98ad275 Merge pull request #1521 from OpenTracksApp/agp81
Upgrade to AGP 8.1.0-alpha
2023-04-12 19:37:15 +02:00
Dennis Guse 7dcc0ec8bc AGP: generate local_config.xml 2023-04-12 19:30:57 +02:00
Dennis Guse 09ecc03be5 AGP: update 8.0.0 (incl. Java 17). 2023-04-12 19:30:57 +02:00
dependabot[bot] d7a5ffb938 Bump org.mockito:mockito-android from 5.2.0 to 5.3.0
Bumps [org.mockito:mockito-android](https://github.com/mockito/mockito) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.2.0...v5.3.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>
2023-04-12 09:32:03 +02:00
63 changed files with 546 additions and 845 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: '11' java-version: '17'
- name: Assemble APK - name: Assemble APK
shell: bash shell: bash
+1 -1
View File
@@ -16,6 +16,6 @@ jobs:
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '11' java-version: '17'
- run: ./gradlew lint - run: ./gradlew lint
+3 -1
View File
@@ -2,7 +2,7 @@
_OpenTracks_ is a sport tracking application that completely respects your privacy. _OpenTracks_ is a sport tracking application that completely respects your privacy.
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech) [![Awesome Humane Tech](https://codeberg.org/teaserbot-labs/delightful-humane-design/raw/branch/main/humane-tech-badge.svg)](https://delightful.club/delightful-humane-design/#health)
<table> <table>
<tr> <tr>
@@ -40,6 +40,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</tr> </tr>
</table> </table>
Translations are hosted on <a href="https://hosted.weblate.org/engage/opentracks/">
hosted.weblate.org</a>.
<a href="https://hosted.weblate.org/engage/opentracks/"> <a href="https://hosted.weblate.org/engage/opentracks/">
<img src="https://hosted.weblate.org/widgets/opentracks/-/horizontal-auto.svg" alt="Translation status" /> <img src="https://hosted.weblate.org/widgets/opentracks/-/horizontal-auto.svg" alt="Translation status" />
</a> </a>
+14 -9
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.android.tools.build:gradle:8.1.0-beta01'
} }
} }
@@ -47,20 +47,25 @@ android {
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_17
} }
compileSdk 33 compileSdk 33
buildFeatures { buildFeatures {
viewBinding true viewBinding true
buildConfig true
}
androidResources {
generateLocaleConfig = true
} }
defaultConfig { defaultConfig {
applicationId "de.dennisguse.opentracks" applicationId "de.dennisguse.opentracks"
versionCode 5168 versionCode 5194
versionName "v4.3.1" versionName "v4.3.3"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -108,11 +113,11 @@ android {
} }
} }
applicationVariants.all { variant -> applicationVariants.configureEach { variant ->
variant.resValue "string", "applicationId", variant.applicationId variant.resValue "string", "applicationId", variant.applicationId
if (variant.buildType.name == 'nightly') { if (variant.buildType.name == 'nightly') {
variant.outputs.all { variant.outputs.configureEach {
setVersionCodeOverride(getVersionCode()) setVersionCodeOverride(getVersionCode())
setVersionNameOverride(getVersionName()) setVersionNameOverride(getVersionName())
outputFileName = "${applicationId}_${variant.versionCode}.apk" outputFileName = "${applicationId}_${variant.versionCode}.apk"
@@ -130,7 +135,7 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.8.0' implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-splashscreen:1.0.0' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.mediarouter:mediarouter:1.3.1' implementation 'androidx.mediarouter:mediarouter:1.3.1'
androidTestImplementation 'androidx.test:core:1.5.0' androidTestImplementation 'androidx.test:core:1.5.0'
@@ -138,7 +143,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'org.mockito:mockito-android:5.2.0' androidTestImplementation 'org.mockito:mockito-android:5.3.1'
androidTestUtil 'androidx.test:orchestrator:1.4.2' androidTestUtil 'androidx.test:orchestrator:1.4.2'
} }
@@ -0,0 +1,9 @@
v4.3.2: OpenTracks
Bugfix:
- KML header was misformed (#1522)
Developer:
- Use Java 17 (multi-line text, switch)
- API33: locale config is generated
@@ -0,0 +1,5 @@
v4.3.3: OpenTracks
Bugfixes:
- Hidden fields were shown (introduced in v4.2.0)
- Speed was shown instead of pace (introduced in v4.2.0)
@@ -0,0 +1,22 @@
一個尊重您的隱私的運動蹤跡紀錄器。
運動與戶外活動賦予幸福、技能與自信。
由保持追蹤您的訓練來重視您的健康。
紀錄您的跑步或健行,並且在您騎自行車時給您一台具有大螢幕的自行車電腦。
用照片標記您一路上有趣的地點。
保持紀錄非常詳細的統計資料以供分析。
僅分享資料給您想要分享的其他人。
* 語音播報。
* 支援藍芽 LE 感測器:心律、速度與距離 (騎自行車)、踏頻 (騎自行車)、功率計 (騎自行車)。
* 海拔上升與下降:通過氣壓感測器。
* EGM2008 海拔顯示 (平均海平面以上);匯出為 WGS84。
* 匯出蹤跡資料為 KMZ (包含照片)、KML、GPX。
* 沒有網路存取或額外的權限需求。
* 深色與淺色主題,取決於系統設定。
* 沒有廣告。
此為 自由軟體 / 免費軟體 / 開放原始碼
這意味您可以使用、學習、修改與分享此專案的原始碼。
授權條款:Apache 2.0
@@ -0,0 +1 @@
一個尊重您的隱私的運動蹤跡紀錄器。
@@ -0,0 +1 @@
OpenTracks
+1 -1
View File
@@ -1,2 +1,2 @@
android.useAndroidX=true android.useAndroidX=true
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m org.gradle.jvmargs=-Xmx1024m
@@ -27,21 +27,23 @@ public class KmlTrackExporterTest {
*/ */
@Test @Test
public void writeCloseSegment_only_write_sensordata_if_present() { public void writeCloseSegment_only_write_sensordata_if_present() {
String expected = "<when>1970-01-01T00:00:00Z</when>\n" + String expected = """
"<coord/>\n" + <when>1970-01-01T00:00:00Z</when>
"<when>1970-01-01T01:00:00+01:00</when>\n" + <coord/>
"<coord/>\n" + <when>1970-01-01T01:00:00+01:00</when>
"<ExtendedData>\n" + <coord/>
"<SchemaData schemaUrl=\"#schema\">\n" + <ExtendedData>
"</SchemaData>\n" + <SchemaData schemaUrl="#schema">
"</ExtendedData>\n" + </SchemaData>
"</Track>\n"; </ExtendedData>
</Track>
""";
// given // given
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)); TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context); KMLTrackExporter kmlTrackWriter = (KMLTrackExporter) TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, null);
kmlTrackWriter.prepare(outputStream); kmlTrackWriter.prepare(outputStream);
kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint); kmlTrackWriter.writeTrackPoint(ZoneOffset.UTC, trackPoint);
@@ -204,7 +204,7 @@ public class ExportImportTest {
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, contentProviderUtils);
// when // when
// 1. export // 1. export
@@ -287,7 +287,7 @@ public class ExportImportTest {
editor.commit(); editor.commit();
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.createTrackExporter(context, contentProviderUtils);
// when // when
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri)); trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
@@ -310,7 +310,7 @@ public class ExportImportTest {
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
// when // when
// 1. export // 1. export
@@ -428,7 +428,7 @@ public class ExportImportTest {
editor.commit(); editor.commit();
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
// when // when
// 1. export // 1. export
@@ -453,7 +453,7 @@ public class ExportImportTest {
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context, contentProviderUtils);
// when // when
// 1. export // 1. export
@@ -231,7 +231,7 @@ public class GPXTrackImporterTest {
importTrackId = importer.importFile(inputStream).get(0); importTrackId = importer.importFile(inputStream).get(0);
Track importedTrack = contentProviderUtils.getTrack(importTrackId); Track importedTrack = contentProviderUtils.getTrack(importTrackId);
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context); TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
trackExporter.writeTrack(importedTrack, outputStream); trackExporter.writeTrack(importedTrack, outputStream);
// then // then
@@ -34,23 +34,24 @@ public class EGM2008UtilsTest {
int expectedLength = 18671444; int expectedLength = 18671444;
int expectedHeaderLength = 404; int expectedHeaderLength = 404;
String expectedHeader = "P5\n" + String expectedHeader = """
"# Geoid file in PGM format for the GeographicLib::Geoid class\n" + P5
"# Description WGS84 EGM2008, 5-minute grid\n" + # Geoid file in PGM format for the GeographicLib::Geoid class
"# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008\n" + # Description WGS84 EGM2008, 5-minute grid
"# DateTime 2009-08-29 18:45:00\n" + # URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008
"# MaxBilinearError 0.478\n" + # DateTime 2009-08-29 18:45:00
"# RMSBilinearError 0.012\n" + # MaxBilinearError 0.478
"# MaxCubicError 0.294\n" + # RMSBilinearError 0.012
"# RMSCubicError 0.005\n" + # MaxCubicError 0.294
"# Offset -108\n" + # RMSCubicError 0.005
"# Scale 0.003\n" + # Offset -108
"# Origin 90N 0E\n" + # Scale 0.003
"# AREA_OR_POINT Point\n" + # Origin 90N 0E
"# Vertical_Datum WGS84\n" + # AREA_OR_POINT Point
"4320 2161\n" + # Vertical_Datum WGS84
"65535" + 4320 2161
"\n"; 65535
""";
// when // when
try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) { try (InputStream inputStream = context.getResources().openRawResource(EGM2008Utils.EGM2008_5_DATA)) {
@@ -52,8 +52,8 @@ public class StatisticsUtilsTest extends TestCase {
recordingLayout.addField(new DataField("key5", true, true, false)); recordingLayout.addField(new DataField("key5", true, true, false));
// when // when
RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true); RecordingLayout resultTrue = recordingLayout.toRecordingLayout(true);
RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false); RecordingLayout resultFalse = recordingLayout.toRecordingLayout(false);
// then // then
assertEquals(resultTrue.getFields().size(), 3); assertEquals(resultTrue.getFields().size(), 3);
+1 -2
View File
@@ -70,8 +70,7 @@ limitations under the License.
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/ThemeCustom" android:theme="@style/ThemeCustom">
android:localeConfig="@xml/locales_config">
<activity <activity
android:name=".publicapi.StartRecording" android:name=".publicapi.StartRecording"
@@ -311,9 +311,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
searchMenuItem = menu.findItem(R.id.track_list_search); searchMenuItem = menu.findItem(R.id.track_list_search);
SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem); SearchView searchView = ActivityUtils.configureSearchWidget(this, searchMenuItem);
searchView.findViewById(R.id.search_edit_frame).setPadding(0, 0, 48, 0); searchView.findViewById(androidx.appcompat.R.id.search_edit_frame).setPadding(0, 0, 48, 0);
SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(R.id.search_src_text); SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(androidx.appcompat.R.id.search_src_text);
searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white)); searchAutoComplete.setHintTextColor(ContextCompat.getColor(this, android.R.color.white));
searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white)); searchAutoComplete.setTextColor(ContextCompat.getColor(this, android.R.color.white));
@@ -274,18 +274,14 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
@NonNull @NonNull
@Override @Override
public Fragment createFragment(int position) { public Fragment createFragment(int position) {
switch (position) { return switch (position) {
case 0: case 0 -> StatisticsRecordedFragment.newInstance(trackId);
return StatisticsRecordedFragment.newInstance(trackId); case 1 -> IntervalsFragment.newInstance(trackId, true);
case 1: case 2 -> ChartFragment.newInstance(false);
return IntervalsFragment.newInstance(trackId, true); case 3 -> ChartFragment.newInstance(true);
case 2: default ->
return ChartFragment.newInstance(false);
case 3:
return ChartFragment.newInstance(true);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position); throw new RuntimeException("There isn't Fragment associated with the position: " + position);
} };
} }
@Override @Override
@@ -294,18 +290,14 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
} }
public CharSequence getPageTitle(int position) { public CharSequence getPageTitle(int position) {
switch (position) { return switch (position) {
case 0: case 0 -> getString(R.string.track_detail_stats_tab);
return getString(R.string.track_detail_stats_tab); case 1 -> getString(R.string.track_detail_intervals_tab);
case 1: case 2 -> getString(R.string.settings_chart_by_time);
return getString(R.string.track_detail_intervals_tab); case 3 -> getString(R.string.settings_chart_by_distance);
case 2: default ->
return getString(R.string.settings_chart_by_time);
case 3:
return getString(R.string.settings_chart_by_distance);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position); throw new RuntimeException("There isn't Fragment associated with the position: " + position);
} };
} }
} }
@@ -333,18 +333,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
@NonNull @NonNull
@Override @Override
public Fragment createFragment(int position) { public Fragment createFragment(int position) {
switch (position) { return switch (position) {
case 0: case 0 -> StatisticsRecordingFragment.newInstance();
return StatisticsRecordingFragment.newInstance(); case 1 -> IntervalsFragment.newInstance(trackId, false);
case 1: case 2 -> ChartFragment.newInstance(false);
return IntervalsFragment.newInstance(trackId, false); case 3 -> ChartFragment.newInstance(true);
case 2: default ->
return ChartFragment.newInstance(false);
case 3:
return ChartFragment.newInstance(true);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position); throw new RuntimeException("There isn't Fragment associated with the position: " + position);
} };
} }
@Override @Override
@@ -353,18 +349,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
public CharSequence getPageTitle(int position) { public CharSequence getPageTitle(int position) {
switch (position) { return switch (position) {
case 0: case 0 -> getString(R.string.track_detail_stats_tab);
return getString(R.string.track_detail_stats_tab); case 1 -> getString(R.string.track_detail_intervals_tab);
case 1: case 2 -> getString(R.string.settings_chart_by_time);
return getString(R.string.track_detail_intervals_tab); case 3 -> getString(R.string.settings_chart_by_distance);
case 2: default ->
return getString(R.string.settings_chart_by_time);
case 3:
return getString(R.string.settings_chart_by_distance);
default:
throw new RuntimeException("There isn't Fragment associated with the position: " + position); throw new RuntimeException("There isn't Fragment associated with the position: " + position);
} };
} }
} }
@@ -210,16 +210,11 @@ abstract class ChartValueSeries {
} }
int getTitleId(UnitSystem unitSystem) { int getTitleId(UnitSystem unitSystem) {
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> metricTitleId;
return metricTitleId; case IMPERIAL -> imperialTitleId;
case IMPERIAL: case NAUTICAL_IMPERIAL -> nauticalTitleId;
return imperialTitleId; };
case NAUTICAL_IMPERIAL:
return nauticalTitleId;
default:
throw new RuntimeException("Not implemented");
}
} }
Paint getTitlePaint() { Paint getTitlePaint() {
@@ -733,16 +733,11 @@ public class ChartView extends View {
private String getXAxisLabel() { private String getXAxisLabel() {
Context context = getContext(); Context context = getContext();
if (chartByDistance) { if (chartByDistance) {
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> context.getString(R.string.unit_kilometer);
return context.getString(R.string.unit_kilometer); case IMPERIAL -> context.getString(R.string.unit_mile);
case IMPERIAL: case NAUTICAL_IMPERIAL -> context.getString(R.string.unit_nautical_mile);
return context.getString(R.string.unit_mile); };
case NAUTICAL_IMPERIAL:
return context.getString(R.string.unit_nautical_mile);
default:
throw new RuntimeException("Not implemented");
}
} else { } else {
return context.getString(R.string.description_time); return context.getString(R.string.description_time);
} }
@@ -133,20 +133,12 @@ public class CustomContentProvider extends ContentProvider {
@Override @Override
public int delete(@NonNull Uri url, String where, String[] selectionArgs) { public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
String table; String table = switch (getUrlType(url)) {
switch (getUrlType(url)) { case TRACKPOINTS -> TrackPointsColumns.TABLE_NAME;
case TRACKPOINTS: case TRACKS -> TracksColumns.TABLE_NAME;
table = TrackPointsColumns.TABLE_NAME; case MARKERS -> MarkerColumns.TABLE_NAME;
break; default -> throw new IllegalArgumentException("Unknown URL " + url);
case TRACKS: };
table = TracksColumns.TABLE_NAME;
break;
case MARKERS:
table = MarkerColumns.TABLE_NAME;
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
Log.w(TAG, "Deleting from table " + table); Log.w(TAG, "Deleting from table " + table);
int totalChangesBefore = getTotalChanges(); int totalChangesBefore = getTotalChanges();
@@ -186,24 +178,15 @@ public class CustomContentProvider extends ContentProvider {
@Override @Override
public String getType(@NonNull Uri url) { public String getType(@NonNull Uri url) {
switch (getUrlType(url)) { return switch (getUrlType(url)) {
case TRACKPOINTS: case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
return TrackPointsColumns.CONTENT_TYPE; case TRACKPOINTS_BY_ID, TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
case TRACKPOINTS_BY_ID: case TRACKS -> TracksColumns.CONTENT_TYPE;
case TRACKPOINTS_BY_TRACKID: case TRACKS_BY_ID -> TracksColumns.CONTENT_ITEMTYPE;
return TrackPointsColumns.CONTENT_ITEMTYPE; case MARKERS -> MarkerColumns.CONTENT_TYPE;
case TRACKS: case MARKERS_BY_ID, MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
return TracksColumns.CONTENT_TYPE; default -> throw new IllegalArgumentException("Unknown URL " + url);
case TRACKS_BY_ID: };
return TracksColumns.CONTENT_ITEMTYPE;
case MARKERS:
return MarkerColumns.CONTENT_TYPE;
case MARKERS_BY_ID:
case MARKERS_BY_TRACKID:
return MarkerColumns.CONTENT_ITEMTYPE;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
} }
@Override @Override
@@ -251,47 +234,47 @@ public class CustomContentProvider extends ContentProvider {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
String sortOrder = null; String sortOrder = null;
switch (getUrlType(url)) { switch (getUrlType(url)) {
case TRACKPOINTS: case TRACKPOINTS -> {
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER; sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
break; }
case TRACKPOINTS_BY_ID: case TRACKPOINTS_BY_ID -> {
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url)); queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url));
break; }
case TRACKPOINTS_BY_TRACKID: case TRACKPOINTS_BY_TRACKID -> {
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME); queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")"); queryBuilder.appendWhere(TrackPointsColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
break; }
case TRACKS: case TRACKS -> {
if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) { if (projection != null && Arrays.asList(projection).contains(TracksColumns.MARKER_COUNT)) {
queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)"); queryBuilder.setTables(TracksColumns.TABLE_NAME + " LEFT OUTER JOIN (SELECT " + MarkerColumns.TRACKID + " AS markerTrackId, COUNT(*) AS " + TracksColumns.MARKER_COUNT + " FROM " + MarkerColumns.TABLE_NAME + " GROUP BY " + MarkerColumns.TRACKID + ") ON (" + TracksColumns.TABLE_NAME + "." + TracksColumns._ID + "= markerTrackId)");
} else { } else {
queryBuilder.setTables(TracksColumns.TABLE_NAME); queryBuilder.setTables(TracksColumns.TABLE_NAME);
} }
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER; sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
break; }
case TRACKS_BY_ID: case TRACKS_BY_ID -> {
queryBuilder.setTables(TracksColumns.TABLE_NAME); queryBuilder.setTables(TracksColumns.TABLE_NAME);
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")"); queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
break; }
case TRACKS_SENSOR_STATS: case TRACKS_SENSOR_STATS -> {
long trackId = ContentUris.parseId(url); long trackId = ContentUris.parseId(url);
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)}); return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
case MARKERS: }
case MARKERS -> {
queryBuilder.setTables(MarkerColumns.TABLE_NAME); queryBuilder.setTables(MarkerColumns.TABLE_NAME);
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER; sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
break; }
case MARKERS_BY_ID: case MARKERS_BY_ID -> {
queryBuilder.setTables(MarkerColumns.TABLE_NAME); queryBuilder.setTables(MarkerColumns.TABLE_NAME);
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url)); queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
break; }
case MARKERS_BY_TRACKID: case MARKERS_BY_TRACKID -> {
queryBuilder.setTables(MarkerColumns.TABLE_NAME); queryBuilder.setTables(MarkerColumns.TABLE_NAME);
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")"); queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
break; }
default: default -> throw new IllegalArgumentException("Unknown url " + url);
throw new IllegalArgumentException("Unknown url " + url);
} }
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), url); cursor.setNotificationUri(getContext().getContentResolver(), url);
@@ -304,41 +287,40 @@ public class CustomContentProvider extends ContentProvider {
String table; String table;
String whereClause; String whereClause;
switch (getUrlType(url)) { switch (getUrlType(url)) {
case TRACKPOINTS: case TRACKPOINTS -> {
table = TrackPointsColumns.TABLE_NAME; table = TrackPointsColumns.TABLE_NAME;
whereClause = where; whereClause = where;
break; }
case TRACKPOINTS_BY_ID: case TRACKPOINTS_BY_ID -> {
table = TrackPointsColumns.TABLE_NAME; table = TrackPointsColumns.TABLE_NAME;
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url); whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) { if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")"; whereClause += " AND (" + where + ")";
} }
break; }
case TRACKS: case TRACKS -> {
table = TracksColumns.TABLE_NAME; table = TracksColumns.TABLE_NAME;
whereClause = where; whereClause = where;
break; }
case TRACKS_BY_ID: case TRACKS_BY_ID -> {
table = TracksColumns.TABLE_NAME; table = TracksColumns.TABLE_NAME;
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url); whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) { if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")"; whereClause += " AND (" + where + ")";
} }
break; }
case MARKERS: case MARKERS -> {
table = MarkerColumns.TABLE_NAME; table = MarkerColumns.TABLE_NAME;
whereClause = where; whereClause = where;
break; }
case MARKERS_BY_ID: case MARKERS_BY_ID -> {
table = MarkerColumns.TABLE_NAME; table = MarkerColumns.TABLE_NAME;
whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url); whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url);
if (!TextUtils.isEmpty(where)) { if (!TextUtils.isEmpty(where)) {
whereClause += " AND (" + where + ")"; whereClause += " AND (" + where + ")";
} }
break; }
default: default -> throw new IllegalArgumentException("Unknown url " + url);
throw new IllegalArgumentException("Unknown url " + url);
} }
int count; int count;
try { try {
@@ -371,16 +353,12 @@ public class CustomContentProvider extends ContentProvider {
* @param contentValues the content values * @param contentValues the content values
*/ */
private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) { private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) {
switch (urlType) { return switch (urlType) {
case TRACKPOINTS: case TRACKPOINTS -> insertTrackPoint(url, contentValues);
return insertTrackPoint(url, contentValues); case TRACKS -> insertTrack(url, contentValues);
case TRACKS: case MARKERS -> insertMarker(url, contentValues);
return insertTrack(url, contentValues); default -> throw new IllegalArgumentException("Unknown url " + url);
case MARKERS: };
return insertMarker(url, contentValues);
default:
throw new IllegalArgumentException("Unknown url " + url);
}
} }
private Uri insertTrackPoint(Uri url, ContentValues values) { private Uri insertTrackPoint(Uri url, ContentValues values) {
@@ -61,42 +61,18 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) { for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion); Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
switch (toVersion) { switch (toVersion) {
case 24: case 24 -> upgradeFrom23to24(db);
upgradeFrom23to24(db); case 25 -> upgradeFrom24to25(db);
break; case 26 -> upgradeFrom25to26(db);
case 25: case 27 -> upgradeFrom26to27(db);
upgradeFrom24to25(db); case 28 -> upgradeFrom27to28(db);
break; case 29 -> upgradeFrom28to29(db);
case 26: case 30 -> upgradeFrom29to30(db);
upgradeFrom25to26(db); case 31 -> upgradeFrom30to31(db);
break; case 32 -> upgradeFrom31to32(db);
case 27: case 33 -> upgradeFrom32to33(db);
upgradeFrom26to27(db); case 34 -> upgradeFrom33to34(db);
break; default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
case 28:
upgradeFrom27to28(db);
break;
case 29:
upgradeFrom28to29(db);
break;
case 30:
upgradeFrom29to30(db);
break;
case 31:
upgradeFrom30to31(db);
break;
case 32:
upgradeFrom31to32(db);
break;
case 33:
upgradeFrom32to33(db);
break;
case 34:
upgradeFrom33to34(db);
break;
default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
} }
} }
} }
@@ -106,41 +82,18 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) { for (int toVersion = oldVersion - 1; toVersion >= newVersion; toVersion--) {
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion); Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
switch (toVersion) { switch (toVersion) {
case 23: case 23 -> downgradeFrom24to23(db);
downgradeFrom24to23(db); case 24 -> downgradeFrom25to24(db);
break; case 25 -> downgradeFrom26to25(db);
case 24: case 26 -> downgradeFrom27to26(db);
downgradeFrom25to24(db); case 27 -> downgradeFrom28to27(db);
break; case 28 -> downgradeFrom29to28(db);
case 25: case 29 -> downgradeFrom30to29(db);
downgradeFrom26to25(db); case 30 -> downgradeFrom31to30(db);
break; case 31 -> downgradeFrom32to31(db);
case 26: case 32 -> downgradeFrom33to32(db);
downgradeFrom27to26(db); case 33 -> downgradeFrom34to33(db);
break; default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
case 27:
downgradeFrom28to27(db);
break;
case 28:
downgradeFrom29to28(db);
break;
case 29:
downgradeFrom30to29(db);
break;
case 30:
downgradeFrom31to30(db);
break;
case 31:
downgradeFrom32to31(db);
break;
case 32:
downgradeFrom33to32(db);
break;
case 33:
downgradeFrom34to33(db);
break;
default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
} }
} }
} }
@@ -129,24 +129,18 @@ public class ShareContentProvider extends CustomContentProvider {
} }
private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) { private static TrackFileFormat getTrackFileFormat(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) { return switch (uriMatcher.match(uri)) {
case URI_GPX: case URI_GPX -> TrackFileFormat.GPX;
return TrackFileFormat.GPX; case URI_KML_WITH_TRACKDETAIL_SENSORDATA ->
TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
case URI_KML_WITH_TRACKDETAIL_SENSORDATA: case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA ->
return TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA; TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA: TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA; case URI_CSV -> TrackFileFormat.CSV;
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES: default ->
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
case URI_CSV:
return TrackFileFormat.CSV;
default:
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri); throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
} };
} }
@Nullable @Nullable
@@ -170,14 +164,14 @@ public class ShareContentProvider extends CustomContentProvider {
int i = 0; int i = 0;
for (String col : projection) { for (String col : projection) {
switch (col) { switch (col) {
case OpenableColumns.DISPLAY_NAME: case OpenableColumns.DISPLAY_NAME -> {
cols[i] = OpenableColumns.DISPLAY_NAME; cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = uri.getLastPathSegment(); values[i++] = uri.getLastPathSegment();
break; }
case OpenableColumns.SIZE: case OpenableColumns.SIZE -> {
cols[i] = OpenableColumns.SIZE; cols[i] = OpenableColumns.SIZE;
values[i++] = -1; //Report unknown size; if applications need to know, one need to generate the file here also (count bytes that are written to OutputStream. values[i++] = -1; //Report unknown size; if applications need to know, one need to generate the file here also (count bytes that are written to OutputStream.
break; }
} }
} }
@@ -214,7 +208,7 @@ public class ShareContentProvider extends CustomContentProvider {
} }
} }
final TrackExporter trackExporter = getTrackFileFormat(uri).createTrackExporter(getContext()); final TrackExporter trackExporter = getTrackFileFormat(uri).createTrackExporter(getContext(), new ContentProviderUtils(getContext()));
PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> { PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) { try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
@@ -58,16 +58,11 @@ public class Distance {
} }
public static Distance one(UnitSystem unitSystem) { public static Distance one(UnitSystem unitSystem) {
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> Distance.ofKilometer(1);
return Distance.ofKilometer(1); case IMPERIAL -> Distance.ofMile(1);
case IMPERIAL: case NAUTICAL_IMPERIAL -> Distance.ofNauticalMile(1);
return Distance.ofMile(1); };
case NAUTICAL_IMPERIAL:
return Distance.ofNauticalMile(1);
default:
throw new RuntimeException("Not implemented");
}
} }
private final double distance_m; private final double distance_m;
@@ -133,28 +128,18 @@ public class Distance {
} }
public double toKM_Miles(UnitSystem unitSystem) { public double toKM_Miles(UnitSystem unitSystem) {
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> toKM();
return toKM(); case IMPERIAL -> toMI();
case IMPERIAL: case NAUTICAL_IMPERIAL -> toNauticalMiles();
return toMI(); };
case NAUTICAL_IMPERIAL:
return toNauticalMiles();
default:
throw new RuntimeException("Not implemented");
}
} }
public double toM_FT(UnitSystem unitSystem) { public double toM_FT(UnitSystem unitSystem) {
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> toM();
return toM(); case NAUTICAL_IMPERIAL, IMPERIAL -> toFT();
case NAUTICAL_IMPERIAL: };
case IMPERIAL:
return toFT();
default:
throw new RuntimeException("Not implemented");
}
} }
@Override @Override
@@ -46,38 +46,36 @@ public class DistanceFormatter {
public Pair<String, String> getDistanceParts(Distance distance) { public Pair<String, String> getDistanceParts(Distance distance) {
if (distance.isInvalid()) { if (distance.isInvalid()) {
String valueUnknown = resources.getString(R.string.value_unknown); String valueUnknown = resources.getString(R.string.value_unknown);
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
return new Pair<>(valueUnknown, resources.getString(R.string.unit_meter)); case IMPERIAL, NAUTICAL_IMPERIAL ->
case IMPERIAL: new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
case NAUTICAL_IMPERIAL: };
return new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
default:
throw new RuntimeException("Not implemented");
}
} }
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC -> {
if (distance.greaterThan(Distance.ofKilometer(threshold))) { if (distance.greaterThan(Distance.ofKilometer(threshold))) {
return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer)); return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer));
} else { } else {
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter)); return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
} }
case IMPERIAL: }
case IMPERIAL -> {
if (distance.greaterThan(Distance.ofMile(threshold))) { if (distance.greaterThan(Distance.ofMile(threshold))) {
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile)); return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
} else { } else {
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet)); return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
} }
case NAUTICAL_IMPERIAL: }
case NAUTICAL_IMPERIAL -> {
if (distance.greaterThan(Distance.ofNauticalMile(threshold))) { if (distance.greaterThan(Distance.ofNauticalMile(threshold))) {
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile)); return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
} else { } else {
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet)); return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
} }
default: }
throw new RuntimeException("Not implemented"); default -> throw new RuntimeException("Not implemented");
} }
} }
@@ -115,16 +115,11 @@ public class Speed {
} }
public double to(UnitSystem unitSystem) { public double to(UnitSystem unitSystem) {
switch (unitSystem) { return switch (unitSystem) {
case METRIC: case METRIC -> toKMH();
return toKMH(); case IMPERIAL -> toMPH();
case IMPERIAL: case NAUTICAL_IMPERIAL -> toKnots();
return toMPH(); };
case NAUTICAL_IMPERIAL:
return toKnots();
default:
throw new RuntimeException("Not implemented");
}
} }
@Override @Override
@@ -39,20 +39,14 @@ public class SpeedFormatter {
* @return the formatted speed (or null) and it's unit as {@link Pair} * @return the formatted speed (or null) and it's unit as {@link Pair}
*/ */
public Pair<String, String> getSpeedParts(Speed speed) { public Pair<String, String> getSpeedParts(Speed speed) {
int unitId; int unitId = switch (unitSystem) {
switch (unitSystem) { case METRIC ->
case METRIC: reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
unitId = reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer; case IMPERIAL ->
break; reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
case IMPERIAL: case NAUTICAL_IMPERIAL ->
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile; reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
break; };
case NAUTICAL_IMPERIAL:
unitId = reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
break;
default:
throw new RuntimeException("Not implemented");
}
String unitString = resources.getString(unitId); String unitString = resources.getString(unitId);
@@ -131,8 +131,7 @@ public class StatisticsRecordingFragment extends Fragment {
int rowIndex = 0; int rowIndex = 0;
int columnIndex = 0; int columnIndex = 0;
for (int i = 0; i < recordingLayout.getFields().size(); i++) { for (DataField dataField : recordingLayout.toRecordingLayout(true).getFields()) {
DataField dataField = recordingLayout.getFields().get(i);
GridLayout.LayoutParams param = new GridLayout.LayoutParams(); GridLayout.LayoutParams param = new GridLayout.LayoutParams();
param.setGravity(Gravity.FILL_HORIZONTAL); param.setGravity(Gravity.FILL_HORIZONTAL);
param.width = 0; param.width = 0;
@@ -3,6 +3,8 @@ package de.dennisguse.opentracks.io.file;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import androidx.annotation.NonNull;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Locale; import java.util.Locale;
@@ -25,8 +27,8 @@ public enum TrackFileFormat {
KML_WITH_TRACKDETAIL_AND_SENSORDATA("KML_WITH_TRACKDETAIL_AND_SENSORDATA") { KML_WITH_TRACKDETAIL_AND_SENSORDATA("KML_WITH_TRACKDETAIL_AND_SENSORDATA") {
@Override @Override
public TrackExporter createTrackExporter(Context context) { public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
return new KMLTrackExporter(context, false); return new KMLTrackExporter(context, contentProviderUtils, false);
} }
@Override @Override
@@ -45,9 +47,9 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false; private static final boolean exportPhotos = false;
@Override @Override
public TrackExporter createTrackExporter(Context context) { public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
KMLTrackExporter exporter = new KMLTrackExporter(context, exportPhotos); KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
return new KmzTrackExporter(context, new ContentProviderUtils(context), exporter, exportPhotos); return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
} }
@Override @Override
@@ -70,9 +72,9 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = true; private static final boolean exportPhotos = true;
@Override @Override
public TrackExporter createTrackExporter(Context context) { public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
KMLTrackExporter exporter = new KMLTrackExporter(context, exportPhotos); KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
return new KmzTrackExporter(context, new ContentProviderUtils(context), exporter, exportPhotos); return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
} }
@Override @Override
@@ -93,8 +95,8 @@ public enum TrackFileFormat {
GPX("GPX") { GPX("GPX") {
@Override @Override
public TrackExporter createTrackExporter(Context context) { public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
return new GPXTrackExporter(new ContentProviderUtils(context), context.getString(R.string.app_name)); return new GPXTrackExporter(contentProviderUtils, context.getString(R.string.app_name));
} }
@Override @Override
@@ -109,8 +111,8 @@ public enum TrackFileFormat {
CSV("CSV") { CSV("CSV") {
@Override @Override
public TrackExporter createTrackExporter(Context context) { public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
return new CSVTrackExporter(new ContentProviderUtils(context)); return new CSVTrackExporter(contentProviderUtils);
} }
@Override @Override
@@ -161,7 +163,7 @@ public enum TrackFileFormat {
* *
* @param context the context * @param context the context
*/ */
public abstract TrackExporter createTrackExporter(Context context); public abstract TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils);
/** /**
* Returns the file extension for each format. * Returns the file extension for each format.
@@ -93,23 +93,9 @@ public class CSVTrackExporter implements TrackExporter {
@Override @Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) { public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
List<Column> columns = Collections.unmodifiableList(Arrays.asList( List<Column> columns = Collections.unmodifiableList(Arrays.asList(new Column("time", null), new Column("trackpoint_type", t -> quote(t.getType().name())), new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""), new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""), new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""), new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""), new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
new Column("time", null),
new Column("trackpoint_type", t -> quote(t.getType().name())),
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
new Column("longitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLongitude()) : ""),
new Column("altitude", t -> t.hasAltitude() ? COORDINATE_FORMAT.format(t.getAltitude().toM()) : ""),
new Column("accuracy_horizontal", t -> t.hasHorizontalAccuracy() ? DISTANCE_FORMAT.format(t.getHorizontalAccuracy().toM()) : ""),
new Column("accuracy_vertical", t -> t.hasVerticalAccuracy() ? DISTANCE_FORMAT.format(t.getVerticalAccuracy().toM()) : ""),
new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""), new Column("speed", t -> t.hasSpeed() ? SPEED_FORMAT.format(t.getSpeed().toKMH()) : ""), new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""), new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""), new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""), new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""), new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""), new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : "")));
new Column("altitude_gain", t -> t.hasAltitudeGain() ? DISTANCE_FORMAT.format(t.getAltitudeGain()) : ""),
new Column("altitude_loss", t -> t.hasAltitudeLoss() ? DISTANCE_FORMAT.format(t.getAltitudeLoss()) : ""),
new Column("sensor_distance", t -> t.hasSensorDistance() ? DISTANCE_FORMAT.format(t.getSensorDistance().toM()) : ""),
new Column("heartrate", t -> t.hasHeartRate() ? HEARTRATE_FORMAT.format(t.getHeartRate().getBPM()) : ""),
new Column("cadence", t -> t.hasCadence() ? CADENCE_FORMAT.format(t.getCadence().getRPM()) : ""),
new Column("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : "")
));
try { try {
prepare(outputStream); prepare(outputStream);
@@ -144,14 +130,9 @@ public class CSVTrackExporter implements TrackExporter {
TrackPoint trackPoint = trackPointIterator.next(); TrackPoint trackPoint = trackPointIterator.next();
switch (trackPoint.getType()) { switch (trackPoint.getType()) {
case SEGMENT_START_MANUAL: case SEGMENT_START_MANUAL, SEGMENT_END_MANUAL, SEGMENT_START_AUTOMATIC, SENSORPOINT, TRACKPOINT ->
case SEGMENT_END_MANUAL:
case SEGMENT_START_AUTOMATIC:
case SENSORPOINT:
case TRACKPOINT:
writeTrackPoint(columns, trackPoint); writeTrackPoint(columns, trackPoint);
break; default ->
default:
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
} }
} }
@@ -171,18 +152,14 @@ public class CSVTrackExporter implements TrackExporter {
public void writeHeader(List<Column> columns) { public void writeHeader(List<Column> columns) {
if (printWriter != null) { if (printWriter != null) {
String columnNames = columns.stream().map(c -> c.columnName) String columnNames = columns.stream().map(c -> c.columnName).reduce((s, s2) -> s + "," + s2).orElseThrow(() -> new RuntimeException("No columns defined"));
.reduce((s, s2) -> s + "," + s2)
.orElseThrow(() -> new RuntimeException("No columns defined"));
printWriter.println("#" + columnNames); printWriter.println("#" + columnNames);
} }
} }
public void writeTrackPoint(List<Column> columns, TrackPoint trackPoint) { public void writeTrackPoint(List<Column> columns, TrackPoint trackPoint) {
if (printWriter != null) { if (printWriter != null) {
String columnNames = columns.stream().map(c -> c.extractor.apply(trackPoint)) String columnNames = columns.stream().map(c -> c.extractor.apply(trackPoint)).reduce((s, s2) -> s + "," + s2).orElseThrow(() -> new RuntimeException("No columns defined"));
.reduce((s, s2) -> s + "," + s2)
.orElseThrow(() -> new RuntimeException("No columns defined"));
printWriter.println(columnNames); printWriter.println(columnNames);
} }
} }
@@ -30,14 +30,9 @@ public class ExportServiceResultReceiver extends ResultReceiver {
protected void onReceiveResult(int resultCode, Bundle resultData) { protected void onReceiveResult(int resultCode, Bundle resultData) {
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID); Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
switch (resultCode) { switch (resultCode) {
case RESULT_CODE_SUCCESS: case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(trackId);
receiver.onExportSuccess(trackId); case RESULT_CODE_ERROR -> receiver.onExportError(trackId);
break; default -> throw new RuntimeException("Unknown resultCode.");
case RESULT_CODE_ERROR:
receiver.onExportError(trackId);
break;
default:
throw new RuntimeException("Unknown resultCode.");
} }
} }
@@ -145,36 +145,31 @@ public class GPXTrackExporter implements TrackExporter {
} }
switch (trackPoint.getType()) { switch (trackPoint.getType()) {
case SEGMENT_START_MANUAL: case SEGMENT_START_MANUAL ->
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported."); Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
break; case SEGMENT_END_MANUAL -> {
case SEGMENT_END_MANUAL:
if (wroteSegment) writeCloseSegment(); if (wroteSegment) writeCloseSegment();
wroteSegment = false; wroteSegment = false;
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_END_MANUAL.name() + " is not supported."); Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_END_MANUAL.name() + " is not supported.");
break; }
case SEGMENT_START_AUTOMATIC: case SEGMENT_START_AUTOMATIC -> {
if (wroteSegment) writeCloseSegment(); if (wroteSegment) writeCloseSegment();
writeOpenSegment(); writeOpenSegment();
wroteSegment = true; wroteSegment = true;
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance)); trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
sensorPoints.clear(); sensorPoints.clear();
break; }
case SENSORPOINT: case SENSORPOINT -> sensorPoints.add(trackPoint);
sensorPoints.add(trackPoint); case TRACKPOINT -> {
break;
case TRACKPOINT:
if (!wroteSegment) { if (!wroteSegment) {
// Might happen for older data (pre v3.15.0) // Might happen for older data (pre v3.15.0)
writeOpenSegment(); writeOpenSegment();
wroteSegment = true; wroteSegment = true;
} }
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance)); trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
sensorPoints.clear(); sensorPoints.clear();
break; }
default: default ->
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
} }
} }
@@ -93,10 +93,10 @@ public class KMLTrackExporter implements TrackExporter {
private final List<Float> accuracyHorizontal = new ArrayList<>(); private final List<Float> accuracyHorizontal = new ArrayList<>();
private final List<Float> accuracyVertical = new ArrayList<>(); private final List<Float> accuracyVertical = new ArrayList<>();
public KMLTrackExporter(Context context, boolean exportPhotos) { public KMLTrackExporter(Context context, ContentProviderUtils contentProviderUtils, boolean exportPhotos) {
this.context = context; this.context = context;
this.exportPhotos = exportPhotos; this.exportPhotos = exportPhotos;
this.contentProviderUtils = new ContentProviderUtils(context); this.contentProviderUtils = contentProviderUtils;
} }
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) { public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
@@ -169,29 +169,27 @@ public class KMLTrackExporter implements TrackExporter {
} }
switch (trackPoint.getType()) { switch (trackPoint.getType()) {
case SEGMENT_START_MANUAL: case SEGMENT_START_MANUAL, SEGMENT_START_AUTOMATIC -> {
case SEGMENT_START_AUTOMATIC:
if (wroteSegment) writeCloseSegment(); if (wroteSegment) writeCloseSegment();
writeOpenSegment(); writeOpenSegment();
writeTrackPoint(track.getZoneOffset(), trackPoint); writeTrackPoint(track.getZoneOffset(), trackPoint);
wroteSegment = true; wroteSegment = true;
break; }
case SEGMENT_END_MANUAL: case SEGMENT_END_MANUAL -> {
if (!wroteSegment) writeOpenSegment(); if (!wroteSegment) writeOpenSegment();
writeTrackPoint(track.getZoneOffset(), trackPoint); writeTrackPoint(track.getZoneOffset(), trackPoint);
writeCloseSegment(); writeCloseSegment();
wroteSegment = false; wroteSegment = false;
break; }
case SENSORPOINT: case SENSORPOINT, TRACKPOINT -> {
case TRACKPOINT:
if (!wroteSegment) { if (!wroteSegment) {
// Might happen for older data (pre v3.15.0) // Might happen for older data (pre v3.15.0)
writeOpenSegment(); writeOpenSegment();
wroteSegment = true; wroteSegment = true;
} }
writeTrackPoint(track.getZoneOffset(), trackPoint); writeTrackPoint(track.getZoneOffset(), trackPoint);
break; }
default: default ->
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType()); throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
} }
} }
@@ -226,15 +224,19 @@ public class KMLTrackExporter implements TrackExporter {
private void writeHeader(Track[] tracks) { private void writeHeader(Track[] tracks) {
if (printWriter != null) { if (printWriter != null) {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); printWriter.println(
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.3\""); """
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\""); <?xml version="1.0" encoding="UTF-8"?>
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\">"); """);
//TODO ADD xsi:schemaLocation for atom printWriter.println(
printWriter.println("xsi:schemaLocation=" + """
"\"http://www.opengis.net/kml/2.3 http://schemas.opengis.net/kml/2.3/ogckml23.xsd" <kml xmlns="http://www.opengis.net/kml/2.3"
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">"); xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:opentracks="http://opentracksapp.com/xmlschemas/v1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/kml/2.3 http://schemas.opengis.net/kml/2.3/ogckml23.xsd
http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd">
"""); //TODO ADD xsi:schemaLocation for atom
printWriter.println("<Document>"); printWriter.println("<Document>");
printWriter.println("<open>1</open>"); printWriter.println("<open>1</open>");
printWriter.println("<visibility>1</visibility>"); printWriter.println("<visibility>1</visibility>");
@@ -156,103 +156,94 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
@Override @Override
public void endElement(String uri, String localName, String tag) { public void endElement(String uri, String localName, String tag) {
switch (tag) { switch (tag) {
case TAG_GPX: case TAG_GPX -> onFileEnd();
onFileEnd(); case TAG_MARKER -> onMarkerEnd();
break; case TAG_TRACK -> {
case TAG_MARKER:
onMarkerEnd();
break;
case TAG_TRACK:
trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset); trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
zoneOffset = null; zoneOffset = null;
break; }
case TAG_TRACK_SEGMENT: case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
onTrackSegmentEnd(); case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint());
break; case TAG_NAME -> {
case TAG_TRACK_POINT:
currentSegment.add(createTrackPoint());
break;
case TAG_NAME:
if (content != null) { if (content != null) {
name = content.trim(); name = content.trim();
} }
break; }
case TAG_DESCRIPTION: case TAG_DESCRIPTION -> {
if (content != null) { if (content != null) {
description = content.trim(); description = content.trim();
} }
break; }
case TAG_TYPE: case TAG_TYPE -> {
if (content != null) { if (content != null) {
category = content.trim(); category = content.trim();
} }
break; }
case TAG_TIME: case TAG_TIME -> {
if (content != null) { if (content != null) {
time = content.trim(); time = content.trim();
} }
break; }
case TAG_ALTITUDE: case TAG_ALTITUDE -> {
if (content != null) { if (content != null) {
altitude = content.trim(); altitude = content.trim();
} }
break; }
case TAG_COMMENT: case TAG_COMMENT -> {
if (content != null) { if (content != null) {
markerType = content.trim(); markerType = content.trim();
} }
break; }
case TAG_EXTENSION_SPEED: case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> {
case TAG_EXTENSION_SPEED_COMPAT:
if (content != null) { if (content != null) {
speed = content.trim(); speed = content.trim();
} }
break; }
case TAG_EXTENSION_HEARTRATE: case TAG_EXTENSION_HEARTRATE -> {
if (content != null) { if (content != null) {
heartrate = content.trim(); heartrate = content.trim();
} }
break; }
case TAG_EXTENSION_CADENCE: case TAG_EXTENSION_CADENCE -> {
if (content != null) { if (content != null) {
cadence = content.trim(); cadence = content.trim();
} }
break; }
case TAG_EXTENSION_POWER: case TAG_EXTENSION_POWER -> {
if (content != null) { if (content != null) {
power = content.trim(); power = content.trim();
} }
break; }
case TAG_ID: case TAG_ID -> {
if (content != null) { if (content != null) {
uuid = content.trim(); uuid = content.trim();
} }
break; }
case TAG_EXTENSION_GAIN: case TAG_EXTENSION_GAIN -> {
if (content != null) { if (content != null) {
gain = content.trim(); gain = content.trim();
} }
break; }
case TAG_EXTENSION_LOSS: case TAG_EXTENSION_LOSS -> {
if (content != null) { if (content != null) {
loss = content.trim(); loss = content.trim();
} }
break; }
case TAG_EXTENSION_DISTANCE: case TAG_EXTENSION_DISTANCE -> {
if (content != null) { if (content != null) {
sensorDistance = content.trim(); sensorDistance = content.trim();
} }
break; }
case TAG_EXTENSION_ACCURACY_HORIZONTAL: case TAG_EXTENSION_ACCURACY_HORIZONTAL -> {
if (content != null) { if (content != null) {
accuracyHorizontal = content.trim(); accuracyHorizontal = content.trim();
} }
break; }
case TAG_EXTENSION_ACCURACY_VERTICAL: case TAG_EXTENSION_ACCURACY_VERTICAL -> {
if (content != null) { if (content != null) {
accuracyVertical = content.trim(); accuracyVertical = content.trim();
} }
break; }
} }
content = ""; content = "";
@@ -79,18 +79,16 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE); String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
switch (resultCode) { switch (resultCode) {
case ImportServiceResultReceiver.RESULT_CODE_ERROR: case ImportServiceResultReceiver.RESULT_CODE_ERROR -> {
summary.errorCount++; summary.errorCount++;
summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message)); summary.fileErrors.add(getApplication().getString(R.string.import_error_info, fileName, message));
break; }
case ImportServiceResultReceiver.RESULT_CODE_IMPORTED: case ImportServiceResultReceiver.RESULT_CODE_IMPORTED -> {
summary.importedTrackIds.addAll(trackIds); summary.importedTrackIds.addAll(trackIds);
summary.successCount++; summary.successCount++;
break; }
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS: case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS -> summary.existsCount++;
summary.existsCount++; default ->
break;
default:
throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode); throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode);
} }
@@ -136,27 +136,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
@Override @Override
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException { public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
switch (tag) { switch (tag) {
case TAG_PLACEMARK: case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
case TAG_PHOTO_OVERLAY:
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, category, and description. // Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, category, and description.
onMarkerStart(); onMarkerStart();
break; case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> trackImporter.newTrack();
case TAG_MULTI_TRACK: case TAG_TRACK, TAG_KML22_TRACK -> {
case TAG_KML22_MULTI_TRACK:
trackImporter.newTrack();
break;
case TAG_TRACK:
case TAG_KML22_TRACK:
if (trackImporter == null) { if (trackImporter == null) {
throw new SAXException("Missing " + TAG_MULTI_TRACK); throw new SAXException("Missing " + TAG_MULTI_TRACK);
} }
onTrackSegmentStart(); onTrackSegmentStart();
break; }
case TAG_DATA_CATEGORY: case TAG_DATA_CATEGORY, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA ->
case TAG_SIMPLE_ARRAY_DATA:
case TAG_KML22_SIMPLE_ARRAY_DATA:
dataType = attributes.getValue(ATTRIBUTE_NAME); dataType = attributes.getValue(ATTRIBUTE_NAME);
break;
} }
} }
@@ -168,32 +159,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
@Override @Override
public void endElement(String uri, String localName, String tag) throws SAXException { public void endElement(String uri, String localName, String tag) throws SAXException {
switch (tag) { switch (tag) {
case TAG_KML: case TAG_KML -> onFileEnd();
onFileEnd(); case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
break;
case TAG_PLACEMARK:
case TAG_PHOTO_OVERLAY:
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track. // Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
onMarkerEnd(); onMarkerEnd();
break; case TAG_COORDINATES -> onMarkerLocationEnd();
case TAG_COORDINATES: case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> {
onMarkerLocationEnd();
break;
case TAG_MULTI_TRACK:
case TAG_KML22_MULTI_TRACK:
trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset); trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
zoneOffset = null; zoneOffset = null;
break; }
case TAG_TRACK: case TAG_TRACK, TAG_KML22_TRACK -> onTrackSegmentEnd();
case TAG_KML22_TRACK: case TAG_COORD, TAG_KML22_COORD -> onCoordEnded();
onTrackSegmentEnd(); case TAG_VALUE, TAG_KML22_VALUE -> {
break;
case TAG_COORD:
case TAG_KML22_COORD:
onCoordEnded();
break;
case TAG_VALUE:
case TAG_KML22_VALUE:
if (KMLTrackExporter.EXTENDED_DATA_TYPE_CATEGORY.equals(dataType)) { if (KMLTrackExporter.EXTENDED_DATA_TYPE_CATEGORY.equals(dataType)) {
if (content != null) { if (content != null) {
category = content.trim(); category = content.trim();
@@ -201,28 +178,28 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
} else { } else {
onExtendedDataValueEnd(); onExtendedDataValueEnd();
} }
break; }
case TAG_NAME: case TAG_NAME -> {
if (content != null) { if (content != null) {
name = content.trim(); name = content.trim();
} }
break; }
case TAG_UUID: case TAG_UUID -> {
if (content != null) { if (content != null) {
uuid = content.trim(); uuid = content.trim();
} }
break; }
case TAG_DESCRIPTION: case TAG_DESCRIPTION -> {
if (content != null) { if (content != null) {
description = content.trim(); description = content.trim();
} }
break; }
case TAG_ICON: case TAG_ICON -> {
if (content != null) { if (content != null) {
icon = content.trim(); icon = content.trim();
} }
break; }
case TAG_WHEN: case TAG_WHEN -> {
if (content != null) { if (content != null) {
try { try {
OffsetDateTime time = StringUtils.parseTime(content.trim()); OffsetDateTime time = StringUtils.parseTime(content.trim());
@@ -234,18 +211,17 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", content.trim())), e); throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", content.trim())), e);
} }
} }
}
break; case TAG_STYLE_URL -> {
case TAG_STYLE_URL:
if (content != null) { if (content != null) {
markerType = content.trim(); markerType = content.trim();
} }
break; }
case TAG_HREF: case TAG_HREF -> {
if (content != null) { if (content != null) {
photoUrl = content.trim(); photoUrl = content.trim();
} }
break; }
} }
// Reset element content // Reset element content
@@ -436,34 +412,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
} }
} }
switch (dataType) { switch (dataType) {
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED: case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED -> sensorSpeedList.add(value);
sensorSpeedList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE -> sensorDistanceList.add(value);
break; case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER -> sensorPowerList.add(value);
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE: case KMLTrackExporter.EXTENDED_DATA_TYPE_HEART_RATE -> sensorHeartRateList.add(value);
sensorDistanceList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE -> sensorCadenceList.add(value);
break; case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN -> altitudeGainList.add(value);
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER: case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS -> altitudeLossList.add(value);
sensorPowerList.add(value); case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL ->
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_HEART_RATE:
sensorHeartRateList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE:
sensorCadenceList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN:
altitudeGainList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS:
altitudeLossList.add(value);
break;
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL:
accuracyHorizontal.add(value); accuracyHorizontal.add(value);
break; case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL ->
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL:
accuracyVertical.add(value); accuracyVertical.add(value);
break; default ->
default:
Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported."); Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
} }
} }
@@ -53,26 +53,22 @@ public abstract class AbstractBluetoothConnectionManager<DataType> {
@Override @Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) { switch (newState) {
case BluetoothProfile.STATE_CONNECTING: case BluetoothProfile.STATE_CONNECTING ->
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice()); Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
break; case BluetoothProfile.STATE_CONNECTED -> {
case BluetoothProfile.STATE_CONNECTED:
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services."); Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
gatt.discoverServices(); gatt.discoverServices();
break; }
case BluetoothProfile.STATE_DISCONNECTING: case BluetoothProfile.STATE_DISCONNECTING ->
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice()); Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
break; case BluetoothProfile.STATE_DISCONNECTED -> {
case BluetoothProfile.STATE_DISCONNECTED:
//This is also triggered, if no connection was established (ca. 30s) //This is also triggered, if no connection was established (ca. 30s)
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect"); Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
if (gatt.connect()) { if (gatt.connect()) {
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice()); Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
} }
clearData(); clearData();
break; }
} }
} }
@@ -76,9 +76,8 @@ public class SensorDataCyclingCadence extends SensorData<Cadence> {
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (!(obj instanceof SensorDataCyclingCadence)) return false; if (!(obj instanceof SensorDataCyclingCadence comp)) return false;
SensorDataCyclingCadence comp = (SensorDataCyclingCadence) obj;
if (hasData() && comp.hasData() == hasData()) { if (hasData() && comp.hasData() == hasData()) {
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime(); return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
} else { } else {
@@ -93,9 +93,8 @@ public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCycling
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
if (!(obj instanceof SensorDataCyclingDistanceSpeed)) return false; if (!(obj instanceof SensorDataCyclingDistanceSpeed comp)) return false;
SensorDataCyclingDistanceSpeed comp = (SensorDataCyclingDistanceSpeed) obj;
if (!(hasData() && comp.hasData())) { if (!(hasData() && comp.hasData())) {
return false; return false;
} }
@@ -28,6 +28,7 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
@@ -67,17 +68,8 @@ public class VoiceAnnouncement {
public void onAudioFocusChange(int focusChange) { public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "Audio focus changed to " + focusChange); Log.d(TAG, "Audio focus changed to " + focusChange);
boolean stop = false; boolean stop = List.of(AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)
switch (focusChange) { .contains(focusChange);
case AudioManager.AUDIOFOCUS_GAIN:
stop = false;
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
stop = true;
break;
}
if (stop && tts != null && tts.isSpeaking()) { if (stop && tts != null && tts.isSpeaking()) {
tts.stop(); tts.stop();
@@ -43,29 +43,28 @@ class VoiceAnnouncementUtils {
String unitDistanceTTS; String unitDistanceTTS;
String unitSpeedTTS; String unitSpeedTTS;
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC -> {
perUnitStringId = R.string.voice_per_kilometer; perUnitStringId = R.string.voice_per_kilometer;
distanceId = R.plurals.voiceDistanceKilometers; distanceId = R.plurals.voiceDistanceKilometers;
speedId = R.plurals.voiceSpeedKilometersPerHour; speedId = R.plurals.voiceSpeedKilometersPerHour;
unitDistanceTTS = "kilometer"; unitDistanceTTS = "kilometer";
unitSpeedTTS = "kilometer per hour"; unitSpeedTTS = "kilometer per hour";
break; }
case IMPERIAL: case IMPERIAL -> {
perUnitStringId = R.string.voice_per_mile; perUnitStringId = R.string.voice_per_mile;
distanceId = R.plurals.voiceDistanceMiles; distanceId = R.plurals.voiceDistanceMiles;
speedId = R.plurals.voiceSpeedMilesPerHour; speedId = R.plurals.voiceSpeedMilesPerHour;
unitDistanceTTS = "mile"; unitDistanceTTS = "mile";
unitSpeedTTS = "mile per hour"; unitSpeedTTS = "mile per hour";
break; }
case NAUTICAL_IMPERIAL: case NAUTICAL_IMPERIAL -> {
perUnitStringId = R.string.voice_per_nautical_mile; perUnitStringId = R.string.voice_per_nautical_mile;
distanceId = R.plurals.voiceDistanceNauticalMiles; distanceId = R.plurals.voiceDistanceNauticalMiles;
speedId = R.plurals.voiceSpeedMKnots; speedId = R.plurals.voiceSpeedMKnots;
unitDistanceTTS = "nautical mile"; unitDistanceTTS = "nautical mile";
unitSpeedTTS = "knots"; unitSpeedTTS = "knots";
break; }
default: default -> throw new RuntimeException("Not implemented");
throw new RuntimeException("Not implemented");
} }
double distanceInUnit = totalDistance.toKM_Miles(unitSystem); double distanceInUnit = totalDistance.toKM_Miles(unitSystem);
@@ -68,18 +68,10 @@ public class DefaultsSettingsFragment extends PreferenceFragmentCompat implement
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key)); ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
int entriesId; int entriesId = switch (unitSystem) {
switch (unitSystem) { case METRIC -> R.array.stats_rate_metric_options;
case METRIC: case IMPERIAL, NAUTICAL_IMPERIAL -> R.array.stats_rate_imperial_options;
entriesId = R.array.stats_rate_metric_options; };
break;
case IMPERIAL:
case NAUTICAL_IMPERIAL:
entriesId = R.array.stats_rate_imperial_options;
break;
default:
throw new RuntimeException("Not implemented");
}
String[] entries = getResources().getStringArray(entriesId); String[] entries = getResources().getStringArray(entriesId);
statsRatePreferences.setEntries(entries); statsRatePreferences.setEntries(entries);
@@ -64,7 +64,7 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
DocumentFile directory = IntentUtils.toDocumentFile(getContext(), directoryUri); DocumentFile directory = IntentUtils.toDocumentFile(getContext(), directoryUri);
//Use same value for not set as Androidx ListPreference and EditTextPreference //Use same value for not set as Androidx ListPreference and EditTextPreference
if (directory == null) { if (directory == null) {
return getString(R.string.not_set); return getString(androidx.preference.R.string.not_set);
} }
return directoryUri + (directory.canWrite() ? "" : getString(R.string.export_dir_not_writable)); return directoryUri + (directory.canWrite() ? "" : getString(R.string.export_dir_not_writable));
@@ -32,14 +32,9 @@ class PreferencesOpenHelper {
PreferencesUtils.setInt(R.string.prefs_last_version_key, version); PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
for (int i = 1; i <= version; i++) { for (int i = 1; i <= version; i++) {
switch (i) { switch (i) {
case 1: case 1 -> upgradeFrom0to1();
upgradeFrom0to1(); case 2 -> upgradeFrom1to2();
break; default -> throw new RuntimeException("Not implemented: upgrade to " + version);
case 2:
upgradeFrom1to2();
break;
default:
throw new RuntimeException("Not implemented: upgrade to " + version);
} }
} }
} }
@@ -448,23 +448,21 @@ public class PreferencesUtils {
String displayValue = formatter.formatDistance(distance); String displayValue = formatter.formatDistance(distance);
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC -> {
if (value == recordingDistanceIntervalDefault) { if (value == recordingDistanceIntervalDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value); entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else { } else {
entries[i] = displayValue; entries[i] = displayValue;
} }
break; }
case IMPERIAL: case IMPERIAL, NAUTICAL_IMPERIAL -> {
case NAUTICAL_IMPERIAL:
if (value == recordingDistanceIntervalDefault) { if (value == recordingDistanceIntervalDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT()); entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
} else { } else {
entries[i] = displayValue; entries[i] = displayValue;
} }
break; }
default: default -> throw new RuntimeException("Not implemented");
throw new RuntimeException("Not implemented");
} }
} }
@@ -494,23 +492,21 @@ public class PreferencesUtils {
String displayValue = formatter.formatDistance(distance); String displayValue = formatter.formatDistance(distance);
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC -> {
if (value == maxRecordingDistanceDefault) { if (value == maxRecordingDistanceDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value); entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else { } else {
entries[i] = displayValue; entries[i] = displayValue;
} }
break; }
case IMPERIAL: case IMPERIAL, NAUTICAL_IMPERIAL -> {
case NAUTICAL_IMPERIAL:
if (value == maxRecordingDistanceDefault) { if (value == maxRecordingDistanceDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT()); entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
} else { } else {
entries[i] = displayValue; entries[i] = displayValue;
} }
break; }
default: default -> throw new RuntimeException("Not implemented");
throw new RuntimeException("Not implemented");
} }
} }
@@ -569,7 +565,7 @@ public class PreferencesUtils {
String displayValue = formatter.formatDistance(distance); String displayValue = formatter.formatDistance(distance);
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC -> {
if (value == recordingGPSAccuracyDefault) { if (value == recordingGPSAccuracyDefault) {
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value); entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
} else if (value == recordingGPSAccuracyExcellent) { } else if (value == recordingGPSAccuracyExcellent) {
@@ -579,9 +575,8 @@ public class PreferencesUtils {
} else { } else {
entries[i] = displayValue; entries[i] = displayValue;
} }
break; }
case IMPERIAL: case IMPERIAL, NAUTICAL_IMPERIAL -> {
case NAUTICAL_IMPERIAL:
if (value == recordingGPSAccuracyDefault) { if (value == recordingGPSAccuracyDefault) {
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT()); entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
} else if (value == recordingGPSAccuracyExcellent) { } else if (value == recordingGPSAccuracyExcellent) {
@@ -589,9 +584,8 @@ public class PreferencesUtils {
} else { } else {
entries[i] = displayValue; entries[i] = displayValue;
} }
break; }
default: default -> throw new RuntimeException("Not implemented");
throw new RuntimeException("Not implemented");
} }
} }
@@ -617,32 +611,30 @@ public class PreferencesUtils {
float value = Float.parseFloat(entryValues[i]); float value = Float.parseFloat(entryValues[i]);
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC -> {
if (value == idleSpeedDefault) { if (value == idleSpeedDefault) {
entries[i] = resources.getString(R.string.value_float_kilometer_hour_recommended, value); entries[i] = resources.getString(R.string.value_float_kilometer_hour_recommended, value);
} else { } else {
entries[i] = resources.getString(R.string.value_float_kilometer_hour, value); entries[i] = resources.getString(R.string.value_float_kilometer_hour, value);
} }
break; }
case IMPERIAL: case IMPERIAL -> {
double valueMPH = Speed.ofKMH(value).toMPH(); double valueMPH = Speed.ofKMH(value).toMPH();
if (value == idleSpeedDefault) { if (value == idleSpeedDefault) {
entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH); entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH);
} else { } else {
entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH); entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH);
} }
break; }
case NAUTICAL_IMPERIAL: case NAUTICAL_IMPERIAL -> {
double valueKnots = Speed.ofKMH(value).toKnots(); double valueKnots = Speed.ofKMH(value).toKnots();
if (value == idleSpeedDefault) { if (value == idleSpeedDefault) {
entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots); entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots);
} else { } else {
entries[i] = resources.getString(R.string.value_float_knots, valueKnots); entries[i] = resources.getString(R.string.value_float_knots, valueKnots);
} }
break; }
default: default -> throw new RuntimeException("Not implemented");
throw new RuntimeException("Not implemented");
} }
} }
@@ -20,7 +20,6 @@ import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter; import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter;
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled; import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
import de.dennisguse.opentracks.util.StatisticsUtils;
public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener { public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener {
@@ -41,7 +40,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
// Recycler view with visible stats. // Recycler view with visible stats.
RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT); RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
profile = recordingLayout.getName(); profile = recordingLayout.getName();
recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true); recordingLayoutFieldsVisible = recordingLayout.toRecordingLayout(true);
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible); adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible);
numColumns = recordingLayout.getColumnsPerRow(); numColumns = recordingLayout.getColumnsPerRow();
@@ -90,7 +89,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false); viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
// Recycler view with not visible stats. // Recycler view with not visible stats.
recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false); recordingLayoutFieldsHidden = recordingLayout.toRecordingLayout(false);
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden); adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden);
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible; RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this)); recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
@@ -96,8 +96,7 @@ public class BluetoothLeAdapter extends BaseAdapter {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (!(o instanceof Device)) return false; if (!(o instanceof Device device)) return false;
Device device = (Device) o;
return address.equals(device.address); return address.equals(device.address);
} }
@@ -66,6 +66,12 @@ public class RecordingLayout implements Parcelable {
return new ArrayList<>(dataFields); return new ArrayList<>(dataFields);
} }
public RecordingLayout toRecordingLayout(boolean visibility) {
RecordingLayout result = new RecordingLayout(this.getName());
result.addFields(dataFields.stream().filter(f -> f.isVisible() == visibility).collect(Collectors.toList()));
return result;
}
public void moveField(int from, int to) { public void moveField(int from, int to) {
DataField dataFieldToMove = dataFields.remove(from); DataField dataFieldToMove = dataFields.remove(from);
dataFields.add(to, dataFieldToMove); dataFields.add(to, dataFieldToMove);
@@ -31,6 +31,8 @@ import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader; import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import java.util.Objects;
import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.ContentProviderUtils;
@@ -309,11 +311,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
public void setSearch(String searchQuery) { public void setSearch(String searchQuery) {
this.searchQuery = searchQuery; this.searchQuery = searchQuery;
restart(); restart();
if (searchQuery != null) { viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.menu_markers)));
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(searchQuery);
} else {
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_markers));
}
} }
public void restart() { public void restart() {
@@ -7,8 +7,6 @@ import android.util.TypedValue;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.R;
public class ThemeUtils { public class ThemeUtils {
private ThemeUtils() { private ThemeUtils() {
@@ -42,14 +40,14 @@ public class ThemeUtils {
} }
public static int getFontSizeSmallInPx(Context context) { public static int getFontSizeSmallInPx(Context context) {
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextAppearance_MaterialComponents_Body2, new int[]{android.R.attr.textSize}); TypedArray typedArray = context.obtainStyledAttributes(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body2, new int[]{android.R.attr.textSize});
int fontSize = typedArray.getDimensionPixelSize(0, 12); int fontSize = typedArray.getDimensionPixelSize(0, 12);
typedArray.recycle(); typedArray.recycle();
return fontSize; return fontSize;
} }
public static int getFontSizeMediumInPx(Context context) { public static int getFontSizeMediumInPx(Context context) {
TypedArray typedArray = context.obtainStyledAttributes(R.style.TextAppearance_MaterialComponents_Body1, new int[]{android.R.attr.textSize}); TypedArray typedArray = context.obtainStyledAttributes(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1, new int[]{android.R.attr.textSize});
int fontSize = typedArray.getDimensionPixelSize(0, 15); int fontSize = typedArray.getDimensionPixelSize(0, 15);
typedArray.recycle(); typedArray.recycle();
return fontSize; return fontSize;
@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.ExportService; import de.dennisguse.opentracks.io.file.exporter.ExportService;
@@ -56,7 +57,7 @@ public class ExportUtils {
} }
public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) { public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) {
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context); TrackExporter trackExporter = trackFileFormat.createTrackExporter(context, new ContentProviderUtils(context));
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory); Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
if (exportDocumentFileUri == null) { if (exportDocumentFileUri == null) {
@@ -29,6 +29,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
@@ -162,34 +163,8 @@ public class FileUtils {
* @param character the character * @param character the character
*/ */
private static boolean isSpecialFat32(char character) { private static boolean isSpecialFat32(char character) {
switch (character) { return List.of('$', '%', '\'', '-', '_', '@', '~', '`', '!', '(', ')', '{', '}', '^', '#', '&', '+', ',', ';', '=', '[', ']', ' ')
case '$': .contains(character);
case '%':
case '\'':
case '-':
case '_':
case '@':
case '~':
case '`':
case '!':
case '(':
case ')':
case '{':
case '}':
case '^':
case '#':
case '&':
case '+':
case ',':
case ';':
case '=':
case '[':
case ']':
case ' ':
return true;
default:
return false;
}
} }
/** /**
@@ -4,11 +4,8 @@ import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
public class StatisticsUtils { public class StatisticsUtils {
@@ -24,11 +21,4 @@ public class StatisticsUtils {
return context.getString(R.string.stats_empty_value_integer); return context.getString(R.string.stats_empty_value_integer);
} }
} }
@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;
}
} }
@@ -40,11 +40,9 @@ public class TrackIconUtils {
it -> it.getId().equals(activityTypeId) it -> it.getId().equals(activityTypeId)
).findFirst(); ).findFirst();
if (found.isEmpty()) { return found.map(ActivityType::getIconId)
return ActivityType.UNKNOWN.getIconId(); .orElseGet(ActivityType.UNKNOWN::getIconId);
}
return found.get().getIconId();
} }
public static int getIconActivityType(String activityTypeId) { public static int getIconActivityType(String activityTypeId) {
@@ -52,11 +50,9 @@ public class TrackIconUtils {
it -> it.getId().equals(activityTypeId) it -> it.getId().equals(activityTypeId)
).findFirst(); ).findFirst();
if (found.isEmpty()) { return found.map(ActivityType::getFirstLocalizedStringId)
return ActivityType.UNKNOWN.getFirstLocalizedStringId(); .orElseGet(ActivityType.UNKNOWN::getFirstLocalizedStringId);
}
return found.get().getFirstLocalizedStringId();
} }
/** /**
@@ -96,10 +92,8 @@ public class TrackIconUtils {
) )
.findFirst(); .findFirst();
if (selected.isEmpty()) { return selected.map(ActivityType::isShowSpeedPreferred)
return false; .orElse(false);
}
return selected.get().isShowSpeedPreferred();
} }
} }
@@ -65,15 +65,20 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
} }
} }
public static class SpeedOrPace extends GenericStatisticsViewHolder { public abstract static class SpeedOrPace extends GenericStatisticsViewHolder {
private final boolean reportSpeed;
public SpeedOrPace(boolean reportSpeed) {
this.reportSpeed = reportSpeed;
}
@Override @Override
public void onChanged(UnitSystem unitSystem, RecordingData data) { public void onChanged(UnitSystem unitSystem, RecordingData data) {
// TODO Pace wont work for now
boolean reportSpeed = true;
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder() SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder()
.setUnit(unitSystem) .setUnit(unitSystem)
.setReportSpeedOrPace(reportSpeed).build(getContext()); .setReportSpeedOrPace(reportSpeed)
.build(getContext());
Pair<String, String> valueAndUnit; Pair<String, String> valueAndUnit;
@@ -85,7 +90,9 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
} else { } else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null; Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = localSpeedFormatter.getSpeedParts(speed); valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.description_speed_source_gps));
String title = reportSpeed ? getContext().getString(R.string.stats_speed) : getContext().getString(R.string.stats_pace);
getBinding().statsDescriptionMain.setText(title);
} }
getBinding().statsValue.setText(valueAndUnit.first); getBinding().statsValue.setText(valueAndUnit.first);
@@ -93,6 +100,20 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
} }
} }
public static class SpeedVH extends SpeedOrPace {
public SpeedVH() {
super(true);
}
}
public static class PaceVH extends SpeedOrPace {
public PaceVH() {
super(false);
}
}
public static class AverageMovingSpeed extends GenericStatisticsViewHolder { public static class AverageMovingSpeed extends GenericStatisticsViewHolder {
@Override @Override
@@ -17,8 +17,8 @@ public class Mapping {
m.put(context.getString(R.string.stats_custom_layout_distance_key), GenericStatisticsViewHolder.Distance::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_speed_key), GenericStatisticsViewHolder.SpeedVH::new);
m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.SpeedOrPace::new); m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.PaceVH::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_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_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_max_speed_key), GenericStatisticsViewHolder.MaxSpeed::new);
+1
View File
@@ -0,0 +1 @@
unqualifiedResLocale=en-US
+1
View File
@@ -612,4 +612,5 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
<string name="help_data_google_play_services_content">Non</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_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> <string name="settings_api_summary">Control remoto e API de estatísticas para outras apps</string>
<string name="voice_on_device_speaker_title">Usar o altofalante do dispositivo</string>
</resources> </resources>
+13 -50
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); 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. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> --><resources>
<resources>
<string name="about_preference_title">關於 OpenTracks</string> <string name="about_preference_title">關於 OpenTracks</string>
<string name="about_description"><i>OpenTracks</i> 是一個完全尊重您的隱私的運動蹤跡應用程式。</string> <string name="about_description"><i>OpenTracks</i> 是一個完全尊重您的隱私的運動蹤跡應用程式。</string>
<string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string> <string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string>
@@ -23,16 +23,13 @@ limitations under the License.
<string name="about_third_party_data">第三方資料</string> <string name="about_third_party_data">第三方資料</string>
<string name="about_libraries">函式庫</string> <string name="about_libraries">函式庫</string>
<!-- Help --> <!-- Help -->
<string name="help_feature_title">OpenTracks 擁有以下特點:</string> <string name="help_feature_title">OpenTracks 擁有以下功能:</string>
<string name="help_feature_content"> <string name="help_feature_content">* 建立 GPS 蹤跡
* 建立 GPS 蹤跡\n \n * 使用一個心率感測器 (僅限藍牙 LE)
* 使用一個心率感測器 (僅限藍牙 LE)\n \n * 語音播報 (依時間或距離)
* 語音通知 (依時間或距離)\n \n * 建立標記 (包含圖片)
* 建立標記 (包含圖片)\n \n * 完全離線工作
* 完全離線工作\n \n * 在地圖上顯示蹤跡 (見下文)</string>
* 在地圖上顯示蹤跡 (見下文)
</string>
<string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string> <string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string>
<string name="help_track_map_content"> <string name="help_track_map_content">
使用小的地圖圖示。\n 使用小的地圖圖示。\n
@@ -47,33 +44,27 @@ limitations under the License.
這些 APP 需要支援 KMZ 檔案格式 (只有位置與時間戳記)。 這些 APP 需要支援 KMZ 檔案格式 (只有位置與時間戳記)。
事實上,APP 需要支援 <i>ACTION_VIEW</i> 與 MIME <i>application/vnd.google-earth.kmz</i>。 事實上,APP 需要支援 <i>ACTION_VIEW</i> 與 MIME <i>application/vnd.google-earth.kmz</i>。
</string> </string>
<string name="help_storage_pictures_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_track_export_title">如何匯出/分享 紀錄的蹤跡?</string> <string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
<string name="help_track_export_content"> <string name="help_track_export_content">
可以從設定匯出蹤跡為 KMZ、KML 或 GPX。 可以從設定匯出蹤跡為 KMZ、KML 或 GPX。
KMZ/KML 全部資料;GPX 只有位置。 KMZ/KML 全部資料;GPX 只有位置。
通過<i>分享圖示</i>以分享蹤跡為 KMZ。 通過<i>分享圖示</i>以分享蹤跡為 KMZ。
</string> </string>
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</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_data_google_play_services_content">否</string>
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string> <string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
<string name="help_recording_content"> <string name="help_recording_content">
OpenTracks 使用裝置的 GPS 感測器以連續紀錄位置。 OpenTracks 使用裝置的 GPS 感測器以連續紀錄位置。
如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。 如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。
</string> </string>
<string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string> <string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string>
<string name="help_data_storage_content"> <string name="help_data_storage_content">
感測器資料儲存在每個記錄的位置。 感測器資料儲存在每個記錄的位置。
就心率感測器而言,這意味著只有<i>一個</i>心率測量值被依照位置儲存。 就心率感測器而言,這意味著只有<i>一個</i>心率測量值被依照位置儲存。
如果您是站著不動或在室內,沒有感測器資料會被記錄 (但它會被顯示)。 如果您是站著不動或在室內,沒有感測器資料會被記錄 (但它會被顯示)。
</string> </string>
<string name="help_gps_troubleshooting_title">OpenTracks 沒有紀錄位置?</string> <string name="help_gps_troubleshooting_title">OpenTracks 沒有紀錄位置?</string>
<string name="help_gps_troubleshooting_content"> <string name="help_gps_troubleshooting_content">
您能做什麼:\n 您能做什麼:\n
@@ -84,7 +75,6 @@ limitations under the License.
\n \n
如果一切都沒有幫助,請回報 bug。 如果一切都沒有幫助,請回報 bug。
</string> </string>
<string name="help_support_title">我如何支持 OpenTracks?</string> <string name="help_support_title">我如何支持 OpenTracks?</string>
<string name="help_support_content"> <string name="help_support_content">
您可以:\n 您可以:\n
@@ -96,11 +86,9 @@ limitations under the License.
f) 贊助,以及\n f) 贊助,以及\n
g) 做任何您認為對 OpenTracks 有幫助的事情。 g) 做任何您認為對 OpenTracks 有幫助的事情。
</string> </string>
<string name="open_track_as_KML">開啟為 KML</string> <string name="open_track_as_KML">開啟為 KML</string>
<string name="open_track_as_GPX">開啟為 GPX</string> <string name="open_track_as_GPX">開啟為 GPX</string>
<string name="open_track_as_trackfileformat">開啟為 %1$s</string> <string name="open_track_as_trackfileformat">開啟為 %1$s</string>
<!-- Activity Type --> <!-- Activity Type -->
<string name="activity_type_airplane">飛機</string> <string name="activity_type_airplane">飛機</string>
<string name="activity_type_atv">越野車</string> <string name="activity_type_atv">越野車</string>
@@ -335,34 +323,23 @@ limitations under the License.
<string name="sensor_state_power_avg">平均功率</string> <string name="sensor_state_power_avg">平均功率</string>
<string name="sensor_could_not_scan">無法掃描藍牙裝置 (錯誤 %1$i).</string> <string name="sensor_could_not_scan">無法掃描藍牙裝置 (錯誤 %1$i).</string>
<string name="bluetooth_disabled">請啟用藍芽。</string> <string name="bluetooth_disabled">請啟用藍芽。</string>
<string name="settings_open_tracks_title">開啟蹤跡</string> <string name="settings_open_tracks_title">開啟蹤跡</string>
<string name="settings_open_tracks_summary">所有關於 OpenTracks 專案</string> <string name="settings_open_tracks_summary">所有關於 OpenTracks 專案</string>
<string name="settings_defaults_title">預設單位與活動</string> <string name="settings_defaults_title">預設單位與活動</string>
<string name="settings_defaults_summary">首選單位、預設活動、蹤跡預設名稱</string> <string name="settings_defaults_summary">首選單位、預設活動、蹤跡預設名稱</string>
<string name="settings_ui_title">使用者介面</string> <string name="settings_ui_title">使用者介面</string>
<string name="settings_ui_summary">外觀、紀錄布局、主題</string> <string name="settings_ui_summary">外觀、紀錄布局、主題</string>
<string name="settings_gps_title">GPS</string> <string name="settings_gps_title">GPS</string>
<string name="settings_gps_summary">時間間隔、距離間隔、最大距離、精度</string> <string name="settings_gps_summary">時間間隔、距離間隔、最大距離、精度</string>
<string name="settings_sensors_title">藍芽感測器</string> <string name="settings_sensors_title">藍芽感測器</string>
<string name="settings_sensors_summary">自行車感測器、輪周、跑步感測器</string> <string name="settings_sensors_summary">自行車感測器、輪周、跑步感測器</string>
<string name="settings_announcements_title">語音播報</string>
<string name="settings_announcements_title">語音通知</string>
<string name="settings_announcements_summary">時間、距離、語速</string> <string name="settings_announcements_summary">時間、距離、語速</string>
<string name="settings_import_export_title">匯入/匯出</string> <string name="settings_import_export_title">匯入/匯出</string>
<string name="settings_import_export_summary">匯入、匯出、自動匯出</string> <string name="settings_import_export_summary">匯入、匯出、自動匯出</string>
<string name="settings_units_category">單位</string> <string name="settings_units_category">單位</string>
<string name="settings_activity_defaults_category">活動的預設值</string> <string name="settings_activity_defaults_category">活動的預設值</string>
<string name="settings_reset_summary">重置設定為預設值</string> <string name="settings_reset_summary">重置設定為預設值</string>
<string name="settings_layout_reset_key" translatable="false">設定布局重置</string>
<!-- Settings Advanced --> <!-- Settings Advanced -->
<string name="settings_advanced">進階</string> <string name="settings_advanced">進階</string>
<string name="settings_default_trackfileformat">匯出/分享檔案格式</string> <string name="settings_default_trackfileformat">匯出/分享檔案格式</string>
@@ -396,14 +373,12 @@ limitations under the License.
<string name="settings_night_mode_option_system">系統</string> <string name="settings_night_mode_option_system">系統</string>
<string name="settings_night_mode_option_day">日</string> <string name="settings_night_mode_option_day">日</string>
<string name="settings_night_mode_option_night">夜</string> <string name="settings_night_mode_option_night">夜</string>
<string name="settings_announcements_totaltime_frequency">時間間隔</string> <string name="settings_announcements_totaltime_frequency">時間間隔</string>
<string name="settings_announcements_distance_frequency">距離間隔</string> <string name="settings_announcements_distance_frequency">距離間隔</string>
<string name="settings_announcements_average_heart_rate">平均心率</string> <string name="settings_announcements_average_heart_rate">平均心率</string>
<string name="settings_announcements_lap_heart_rate">單圈心率</string> <string name="settings_announcements_lap_heart_rate">單圈心率</string>
<string name="settings_announcements_average_speed_pace">平均速度/步速</string> <string name="settings_announcements_average_speed_pace">平均速度/步速</string>
<string name="settings_announcements_lap_speed_pace">單圈速度/步速</string> <string name="settings_announcements_lap_speed_pace">單圈速度/步速</string>
<!-- Custom Layout --> <!-- Custom Layout -->
<string name="custom_layout_list_title">布局</string> <string name="custom_layout_list_title">布局</string>
<string name="custom_layout_list_edit_name">輸入要新增的佈局名稱</string> <string name="custom_layout_list_edit_name">輸入要新增的佈局名稱</string>
@@ -425,24 +400,19 @@ limitations under the License.
<string name="settings_generic_sensor">通用</string> <string name="settings_generic_sensor">通用</string>
<string name="settings_cycling_sensor">騎自行車</string> <string name="settings_cycling_sensor">騎自行車</string>
<string name="settings_running_sensor">跑步</string> <string name="settings_running_sensor">跑步</string>
<string name="settings_sensor_bluetooth">藍牙</string> <string name="settings_sensor_bluetooth">藍牙</string>
<string name="settings_sensor_cycling_distance_speed">距離與速度</string> <string name="settings_sensor_cycling_distance_speed">距離與速度</string>
<string name="settings_sensor_cycling_cadence">踏頻</string> <string name="settings_sensor_cycling_cadence">踏頻</string>
<string name="settings_sensor_running_speed_and_cadence">距離、速度與踏頻</string> <string name="settings_sensor_running_speed_and_cadence">距離、速度與踏頻</string>
<string name="settings_sensor_wheel_circumference">輪周 (公釐)</string> <string name="settings_sensor_wheel_circumference">輪周 (公釐)</string>
<string name="settings_sensor_bluetooth_service_filter_enabled">僅顯示宣告所需服務的藍芽裝置</string> <string name="settings_sensor_bluetooth_service_filter_enabled">僅顯示宣告所需服務的藍芽裝置</string>
<string name="settings_sensor_heart_rate_max">區間:最大心率 (bpm)</string> <string name="settings_sensor_heart_rate_max">區間:最大心率 (bpm)</string>
<!-- Settings Stats --> <!-- Settings Stats -->
<string name="settings_stats_rate_title">偏好速率</string> <string name="settings_stats_rate_title">偏好速率</string>
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string> <string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
<string name="settings_stats_units_metric">公制 (公里、公尺)</string> <string name="settings_stats_units_metric">公制 (公里、公尺)</string>
<string name="settings_stats_units_nautical">航海 (海里、英尺)</string> <string name="settings_stats_units_nautical">航海 (海里、英尺)</string>
<string name="settings_stats_units_title">慣用單位</string> <string name="settings_stats_units_title">慣用單位</string>
<!-- Settings public api --> <!-- Settings public api -->
<string name="settings_api_title">公開 API</string> <string name="settings_api_title">公開 API</string>
<string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string> <string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string>
@@ -454,11 +424,9 @@ limitations under the License.
<string name="settings_public_api_dashboard_enabled_title">自動資料傳輸</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_on">開始紀錄的應用程式也可以存取紀錄的資料。</string>
<string name="settings_public_api_dashboard_enabled_summary_off">記錄的資料將不會自動與其他應用程式共用。</string> <string name="settings_public_api_dashboard_enabled_summary_off">記錄的資料將不會自動與其他應用程式共用。</string>
<!-- Settings import/export --> <!-- Settings import/export -->
<string name="settings_import_section">匯入</string> <string name="settings_import_section">匯入</string>
<string name="settings_export_section">匯出</string> <string name="settings_export_section">匯出</string>
<string name="settings_prevent_reimport_tracks_title">防止重新匯入蹤跡</string> <string name="settings_prevent_reimport_tracks_title">防止重新匯入蹤跡</string>
<!-- Share Track --> <!-- Share Track -->
<string name="share_track_share_file_body">你應該會喜歡這個蹤跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看蹤跡。\n\n%1$s</string> <string name="share_track_share_file_body">你應該會喜歡這個蹤跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看蹤跡。\n\n%1$s</string>
@@ -513,7 +481,7 @@ limitations under the License.
<string name="aggregated_stats_empty_message">紀錄您的第一個蹤跡以檢視綜合統計資料</string> <string name="aggregated_stats_empty_message">紀錄您的第一個蹤跡以檢視綜合統計資料</string>
<string name="aggregated_stats_filter_no_results">沒有指定篩選的活動</string> <string name="aggregated_stats_filter_no_results">沒有指定篩選的活動</string>
<!-- Interval List --> <!-- Interval List -->
<string name="interval_list_empty_message">還有有任何間格</string> <string name="interval_list_empty_message">還沒有任何間格</string>
<!-- Unit --> <!-- Unit -->
<string name="unit_feet">英尺</string> <string name="unit_feet">英尺</string>
<string name="unit_kilometer">公里</string> <string name="unit_kilometer">公里</string>
@@ -530,7 +498,6 @@ limitations under the License.
<string name="location_coordinate">%1$s°</string> <string name="location_coordinate">%1$s°</string>
<string name="location_latitude_longitude">%1$s, %2$s</string> <string name="location_latitude_longitude">%1$s, %2$s</string>
<!-- Value --> <!-- Value -->
<string name="value_integer_feet_excellent_gps">%1$d 英尺 (GPS 訊號佳)</string> <string name="value_integer_feet_excellent_gps">%1$d 英尺 (GPS 訊號佳)</string>
<string name="value_integer_feet_recommended">%1$d 英尺 (建議值)</string> <string name="value_integer_feet_recommended">%1$d 英尺 (建議值)</string>
<string name="value_integer_kilometer_hour">%1$d 公里/小時</string> <string name="value_integer_kilometer_hour">%1$d 公里/小時</string>
@@ -652,12 +619,9 @@ limitations under the License.
<!-- Share image --> <!-- Share image -->
<string name="share_image_subject">我想與你分享一張圖片</string> <string name="share_image_subject">我想與你分享一張圖片</string>
<string name="share_image_body">我認為你可能會對這張圖片感興趣。</string> <string name="share_image_body">我認為你可能會對這張圖片感興趣。</string>
<string name="hold_to_stop">按住以停止</string> <string name="hold_to_stop">按住以停止</string>
<string name="instant_export_enabled_summary">在紀錄完成後匯出蹤跡至儲存區</string> <string name="instant_export_enabled_summary">在紀錄完成後匯出蹤跡至儲存區</string>
<string name="instant_export_enabled_title">即時運動後匯出</string> <string name="instant_export_enabled_title">即時運動後匯出</string>
<string name="settings_default_export_uri_title">蹤跡匯出目錄</string> <string name="settings_default_export_uri_title">蹤跡匯出目錄</string>
<!-- Fields --> <!-- Fields -->
<string name="field_set_primary">主要的</string> <string name="field_set_primary">主要的</string>
@@ -667,12 +631,11 @@ limitations under the License.
<string name="error_crash_title">%1$s 已崩潰</string> <string name="error_crash_title">%1$s 已崩潰</string>
<string name="share_crash_report">分享</string> <string name="share_crash_report">分享</string>
<string name="report_bug">回報 bug</string> <string name="report_bug">回報 bug</string>
<string name="select_show_on_map_behavior">選擇 \"在地圖上顯示\" 行為</string> <string name="select_show_on_map_behavior">選擇 \"在地圖上顯示\" 行為</string>
<string name="show_on_map_format_ask">詢問</string> <string name="show_on_map_format_ask">詢問</string>
<string name="show_on_dashboard">OpenTracks 儀表板 API</string> <string name="show_on_dashboard">OpenTracks 儀表板 API</string>
<string name="show_on_dashboard_not_installed">沒有安裝支援 OpenTracks 儀表板 API 的應用程式。</string> <string name="show_on_dashboard_not_installed">沒有安裝支援 OpenTracks 儀表板 API 的應用程式。</string>
<string name="always">總是</string> <string name="always">總是</string>
<string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。</string> <string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。</string>
<string name="voice_on_device_speaker_title">使用裝置的揚聲器</string>
</resources> </resources>
+2
View File
@@ -62,6 +62,8 @@
<string name="settings_export" translatable="false">settingsExport</string> <string name="settings_export" translatable="false">settingsExport</string>
<!-- Stats layout customization default layout --> <!-- Stats layout customization default layout -->
<string name="settings_layout_reset_key" translatable="false">settingsLayoutReset</string>
<string name="default_activity_key" translatable="false">defaultActivity</string> <string name="default_activity_key" translatable="false">defaultActivity</string>
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string> <string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
-1
View File
@@ -361,7 +361,6 @@ limitations under the License.
<string name="settings_activity_defaults_category">Activity\'s Defaults</string> <string name="settings_activity_defaults_category">Activity\'s Defaults</string>
<string name="settings_reset_summary">Reset Settings to Default Values</string> <string name="settings_reset_summary">Reset Settings to Default Values</string>
<string name="settings_layout_reset_key" translatable="false">settingsLayoutReset</string>
<!-- Settings Advanced --> <!-- Settings Advanced -->
<string name="settings_advanced">Advanced</string> <string name="settings_advanced">Advanced</string>
-47
View File
@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="ar"/>
<locale android:name="bg"/>
<locale android:name="ca"/>
<locale android:name="cs"/>
<locale android:name="da"/>
<locale android:name="de"/>
<locale android:name="el"/>
<locale android:name="en"/>
<locale android:name="es"/>
<locale android:name="et"/>
<locale android:name="eu"/>
<locale android:name="fa"/>
<locale android:name="fi"/>
<locale android:name="fr"/>
<locale android:name="ga"/>
<locale android:name="gl"/>
<locale android:name="hi"/>
<locale android:name="hr"/>
<locale android:name="hu"/>
<locale android:name="in"/>
<locale android:name="it"/>
<locale android:name="iw"/>
<locale android:name="ja"/>
<locale android:name="ko"/>
<locale android:name="lt"/>
<locale android:name="lv"/>
<locale android:name="mo"/>
<locale android:name="ms"/>
<locale android:name="nb"/>
<locale android:name="nl"/>
<locale android:name="pl"/>
<locale android:name="pt"/>
<locale android:name="ro"/>
<locale android:name="ru"/>
<locale android:name="sk"/>
<locale android:name="sl"/>
<locale android:name="sr"/>
<locale android:name="sv"/>
<locale android:name="th"/>
<locale android:name="tl"/>
<locale android:name="tr"/>
<locale android:name="uk"/>
<locale android:name="vi"/>
<locale android:name="zh"/>
</locale-config>