forked from upstream-mirrors/OpenTracks
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6538f0d587 | |||
| 5b0e7e5065 | |||
| d6cf8ba90e | |||
| 29568d0e3f | |||
| a571c4c93b | |||
| 073fed6c84 | |||
| 6847e1f899 | |||
| 427048ac11 | |||
| 240ea47c43 | |||
| 068d56a850 | |||
| 76d02da973 | |||
| 4664895ae3 | |||
| 5235c8cf83 | |||
| 4f040ac79f | |||
| dcad42bf4d | |||
| 2eea93a4a3 | |||
| 55e419475a | |||
| 9225d90e45 | |||
| 32dc6fef82 | |||
| 96fb65b6b0 | |||
| 936bbf9264 | |||
| 241a52ee7c | |||
| c66e285bf5 | |||
| 8c1ddc077d | |||
| 61c18af639 | |||
| b83df3ac4b | |||
| 53f1c4b7f3 | |||
| 6290bdbab0 | |||
| bd0d85cd37 | |||
| ba08417236 | |||
| 63281a3ca8 | |||
| fae0cf8454 | |||
| efddb31391 | |||
| 3a8e547e10 | |||
| fe09d18197 | |||
| 93bcf8d494 | |||
| 819f454bd8 | |||
| 7d58014b15 | |||
| 9dac95569c | |||
| 96c98ad275 | |||
| 7dcc0ec8bc | |||
| 09ecc03be5 | |||
| d7a5ffb938 | |||
| 0605dbb0ab | |||
| bf03de6be7 | |||
| d65ce361f7 | |||
| 3f600f6be7 | |||
| 9aab3c216c | |||
| 7935185812 | |||
| 4c1b1be96c | |||
| 5762830655 | |||
| 6d61206782 | |||
| b9dedef292 | |||
| f3caecd049 | |||
| e16780277f | |||
| 13484685a3 | |||
| 7b745d9837 | |||
| 52742064f6 | |||
| 2edf5582aa | |||
| 808eb92bf2 | |||
| c0b84c3d72 | |||
| e275d3e5c0 | |||
| b4ebad2c3c | |||
| 1ab76e1317 | |||
| 3d1104dd0b | |||
| 2df45d31de | |||
| 5436981e0d | |||
| fd2b1bec7d | |||
| bb1eaa5b56 |
@@ -13,7 +13,7 @@ jobs:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
|
||||
- name: Assemble APK
|
||||
shell: bash
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
|
||||
- run: ./gradlew lint
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
_OpenTracks_ is a sport tracking application that completely respects your privacy.
|
||||
|
||||
[](https://github.com/humanetech-community/awesome-humane-tech)
|
||||
[](https://delightful.club/delightful-humane-design/#health)
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -40,8 +40,10 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
</tr>
|
||||
</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/">
|
||||
<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>
|
||||
|
||||
## Screenshots
|
||||
@@ -150,7 +152,7 @@ The developer documentation is in [README_API.md](README_API.md).
|
||||
## Project history
|
||||
|
||||
_OpenTracks_ is based upon Google _My Tracks app_ ([code](https://code.google.com/archive/p/mytracks/)).
|
||||
Initially, _My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
||||
_My Tracks_ was initially released by Google in 2010 as [open-source software](http://google-latlong.blogspot.fr/2010/05/code-for-my-tracks-is-now-yours.html).
|
||||
In 2016, [Google decided to discontinue](https://support.google.com/maps/answer/6333516) _My Tracks_ and stopped distributing it via the Google Play store in April 2016.
|
||||
Then [Plonk42](https://github.com/plonk42) conducted some maintenance work until 2016, so _My Tracks_ could still be used (based upon version _Google's MyTracks_ version 2.0.6).
|
||||
Plonk42's version is available [here](https://github.com/Plonk42/mytracks).
|
||||
|
||||
+18
-12
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
classpath 'com.android.tools.build:gradle:8.1.0-beta02'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,20 +47,25 @@ android {
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
compileSdk 33
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5141
|
||||
versionName "v4.2.2"
|
||||
versionCode 5211
|
||||
versionName "v4.3.5"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -106,11 +111,11 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
applicationVariants.configureEach { variant ->
|
||||
variant.resValue "string", "applicationId", variant.applicationId
|
||||
|
||||
if (variant.buildType.name == 'nightly') {
|
||||
variant.outputs.all {
|
||||
variant.outputs.configureEach {
|
||||
setVersionCodeOverride(getVersionCode())
|
||||
setVersionNameOverride(getVersionName())
|
||||
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
||||
@@ -120,22 +125,23 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
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.4.0'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.11.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.3.1'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.3.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Allow backups (incl. encrypted to the Google)
|
||||
- Android 13: allow app-specific language settings
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.3.1: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Setting to skip voice announcements on device speaker
|
||||
|
||||
Bugfix:
|
||||
- GPS disabled warning was falsely shown
|
||||
|
||||
@@ -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,4 @@
|
||||
v4.3.4: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Android 13: regional locales could not be selected (introduced in v4.3.0)
|
||||
@@ -0,0 +1,6 @@
|
||||
v4.3.5: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Sensor parsing had a overflow one off bug (#1559)
|
||||
- Insert marker: don't crash if no camera app is installed (#1561)
|
||||
|
||||
@@ -2,13 +2,13 @@ v3.19.0: OpenTracks
|
||||
|
||||
Wijzigingen:
|
||||
- Ondersteuning voor Running Speed en Cadence (RCS) sensoren
|
||||
- Bij het delen van een track wordt een afbeelding gemaakt
|
||||
- Bij delen van een track: maakt een deel-afbeelding
|
||||
- Niet trillen bij starten van opnemen
|
||||
- Bij opnemen: UI toont recentst ontvangen hoogte, snelheid en richting (ook al is deze niet opgeslagen)
|
||||
- Bij opnemen: UI toont meest recente hoogte, snelheid en richting (zelfs als niet opgeslagen)
|
||||
|
||||
Bugfix:
|
||||
- Berekening van totale tijd mag geen pauze-tijd bevatten (vereist opnieuw importeren)
|
||||
- (opnieuw) Gebruik aangepaste overzichten indien aanwezig
|
||||
- Berekening totale tijd mag geen pauze-tijd bevatten (vereist opnieuw importeren)
|
||||
- (opnieuw) Gebruik aanwezige aangepaste overzichten
|
||||
|
||||
Ontwikkelaar:
|
||||
- Upgrade naar SDK31 en AGP7.0.0 (vereist JDK11)
|
||||
|
||||
@@ -5,4 +5,4 @@ Wijzigingen:
|
||||
- Gesproken meldingen van hartslag
|
||||
|
||||
Bugfix:
|
||||
- Crash due to race condition
|
||||
- Vastloper door race condities
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
一個尊重您的隱私的運動蹤跡紀錄器。
|
||||
|
||||
運動與戶外活動賦予幸福、技能與自信。
|
||||
|
||||
由保持追蹤您的訓練來重視您的健康。
|
||||
紀錄您的跑步或健行,並且在您騎自行車時給您一台具有大螢幕的自行車電腦。
|
||||
用照片標記您一路上有趣的地點。
|
||||
保持紀錄非常詳細的統計資料以供分析。
|
||||
僅分享資料給您想要分享的其他人。
|
||||
|
||||
* 語音播報。
|
||||
* 支援藍芽 LE 感測器:心律、速度與距離 (騎自行車)、踏頻 (騎自行車)、功率計 (騎自行車)。
|
||||
* 海拔上升與下降:通過氣壓感測器。
|
||||
* EGM2008 海拔顯示 (平均海平面以上);匯出為 WGS84。
|
||||
* 匯出蹤跡資料為 KMZ (包含照片)、KML、GPX。
|
||||
* 沒有網路存取或額外的權限需求。
|
||||
* 深色與淺色主題,取決於系統設定。
|
||||
* 沒有廣告。
|
||||
|
||||
此為 自由軟體 / 免費軟體 / 開放原始碼
|
||||
這意味您可以使用、學習、修改與分享此專案的原始碼。
|
||||
授權條款:Apache 2.0
|
||||
@@ -0,0 +1 @@
|
||||
一個尊重您的隱私的運動蹤跡紀錄器。
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -205,6 +205,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+8
-6
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
+12
-10
@@ -27,21 +27,23 @@ public class KmlTrackExporterTest {
|
||||
*/
|
||||
@Test
|
||||
public void writeCloseSegment_only_write_sensordata_if_present() {
|
||||
String expected = "<when>1970-01-01T00:00:00Z</when>\n" +
|
||||
"<coord/>\n" +
|
||||
"<when>1970-01-01T01:00:00+01:00</when>\n" +
|
||||
"<coord/>\n" +
|
||||
"<ExtendedData>\n" +
|
||||
"<SchemaData schemaUrl=\"#schema\">\n" +
|
||||
"</SchemaData>\n" +
|
||||
"</ExtendedData>\n" +
|
||||
"</Track>\n";
|
||||
String expected = """
|
||||
<when>1970-01-01T00:00:00Z</when>
|
||||
<coord/>
|
||||
<when>1970-01-01T01:00:00+01:00</when>
|
||||
<coord/>
|
||||
<ExtendedData>
|
||||
<SchemaData schemaUrl="#schema">
|
||||
</SchemaData>
|
||||
</ExtendedData>
|
||||
</Track>
|
||||
""";
|
||||
|
||||
// given
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0));
|
||||
|
||||
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.writeTrackPoint(ZoneOffset.UTC, trackPoint);
|
||||
|
||||
+9
-9
@@ -166,7 +166,7 @@ public class ExportImportTest {
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
|
||||
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||
@@ -179,7 +179,7 @@ public class ExportImportTest {
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:24Z");
|
||||
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
|
||||
trackPointCreator.getSensorManager().setBluetoothSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -204,7 +204,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
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
|
||||
// 1. export
|
||||
@@ -287,7 +287,7 @@ public class ExportImportTest {
|
||||
editor.commit();
|
||||
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
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
@@ -310,7 +310,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -428,7 +428,7 @@ public class ExportImportTest {
|
||||
editor.commit();
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -453,7 +453,7 @@ public class ExportImportTest {
|
||||
// given
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.CSV.createTrackExporter(context, contentProviderUtils);
|
||||
|
||||
// when
|
||||
// 1. export
|
||||
@@ -521,12 +521,12 @@ public class ExportImportTest {
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
});
|
||||
trackPointCreator.setRemoteSensorManager(remoteSensorManager);
|
||||
trackPointCreator.getSensorManager().setBluetoothSensorManager(remoteSensorManager);
|
||||
trackPointCreator.onChange(new SensorDataSet());
|
||||
}
|
||||
|
||||
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
|
||||
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
|
||||
AltitudeSumManager altitudeSumManager = trackPointCreator.getSensorManager().getAltitudeSumManager();
|
||||
altitudeSumManager.setAltitudeGain_m(altitudeGain);
|
||||
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
|
||||
}
|
||||
|
||||
+1
-1
@@ -231,7 +231,7 @@ public class GPXTrackImporterTest {
|
||||
importTrackId = importer.importFile(inputStream).get(0);
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||
trackExporter.writeTrack(importedTrack, outputStream);
|
||||
|
||||
// then
|
||||
|
||||
@@ -11,13 +11,44 @@ public class UintUtilsTest {
|
||||
assertEquals(0, UintUtils.diff(1, 1, UintUtils.UINT16_MAX));
|
||||
assertEquals(1, UintUtils.diff(2, 1, UintUtils.UINT16_MAX));
|
||||
assertEquals(3, UintUtils.diff(5, 2, UintUtils.UINT16_MAX));
|
||||
assertEquals(65534, UintUtils.diff(1, 2, UintUtils.UINT16_MAX));
|
||||
|
||||
assertEquals(65535, UintUtils.diff(1, 2, UintUtils.UINT16_MAX)); /* unsigned 16 arithmetic is modulo UINT16_MAX + 1, not modulo UINT16_MAX */
|
||||
assertEquals(65530, UintUtils.diff(UintUtils.UINT16_MAX, 5, UintUtils.UINT16_MAX));
|
||||
|
||||
/* Test modulo arithmetic for arguments that are out of range */
|
||||
|
||||
if (false) { /* false means UintUtils.diff() throws if arguments out of range */
|
||||
assertEquals(0, UintUtils.diff(65537, 1, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(1, 65537, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(65537, 65537, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(-65535, 1, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(1, -65535, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(-65535, -65535, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(-65535, 65537, UintUtils.UINT16_MAX));
|
||||
assertEquals(0, UintUtils.diff(65537, -65535, UintUtils.UINT16_MAX));
|
||||
}
|
||||
|
||||
/* The following tests are the above, but for 32-bit unsigned. */
|
||||
|
||||
assertEquals(0, UintUtils.diff(1, 1, UintUtils.UINT32_MAX));
|
||||
assertEquals(1, UintUtils.diff(2, 1, UintUtils.UINT32_MAX));
|
||||
assertEquals(3, UintUtils.diff(5, 2, UintUtils.UINT32_MAX));
|
||||
assertEquals(4294967295L, UintUtils.diff(1, 2, UintUtils.UINT32_MAX));
|
||||
assertEquals(4294967290L, UintUtils.diff(UintUtils.UINT32_MAX, 5, UintUtils.UINT32_MAX));
|
||||
|
||||
if (false) { /* false means UintUtils.diff() throws if arguments out of range */
|
||||
assertEquals(0, UintUtils.diff(4294967297L, 1, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(1, 4294967297L, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(4294967297L, 4294967297L, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(-4294967295L, 1, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(1, -4294967295L, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(-4294967295L, -4294967295L, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(-4294967295L, 4294967297L, UintUtils.UINT32_MAX));
|
||||
assertEquals(0, UintUtils.diff(65537, -4294967297L, UintUtils.UINT32_MAX));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void realData() {
|
||||
assertEquals(1, UintUtils.diff(381616, 381615, UintUtils.UINT32_MAX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-17
@@ -45,6 +45,8 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Tests insert location.
|
||||
* Note: on API23, the TrackRecordingService may not be stopped properly before the next test.
|
||||
* So, if something fails, subsequent tests may be affected.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceTestRecording {
|
||||
@@ -112,7 +114,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
String startTime = "2020-02-02T02:02:02Z";
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// then
|
||||
assertEquals(new TrackStatistics(startTime, startTime, 0, 0, 0, 0, null, null)
|
||||
@@ -149,7 +151,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String pauseTime = "2020-02-02T02:02:03Z";
|
||||
@@ -195,7 +197,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
String starTime = "2020-02-02T02:02:02Z";
|
||||
trackPointCreator.setClock(starTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
String pauseTime = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(pauseTime);
|
||||
@@ -227,7 +229,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
String stopTime = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(stopTime);
|
||||
@@ -237,7 +239,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
String resumeTime = "2020-02-02T02:02:04Z";
|
||||
trackPointCreator.setClock(resumeTime);
|
||||
service.resumeTrack(trackId);
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// then
|
||||
new TrackPointAssert().assertEquals(List.of(
|
||||
@@ -258,12 +260,13 @@ public class TrackRecordingServiceTestRecording {
|
||||
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
|
||||
|
||||
// when
|
||||
String sensor1 = "2020-02-02T02:02:03Z";
|
||||
trackPointCreator.setClock(sensor1);
|
||||
|
||||
remoteSensorManager.onChanged(new SensorDataHeartRate("", "", HeartRate.of(5))); //Should be ignored
|
||||
|
||||
String sensor3 = "2020-02-02T02:02:13Z";
|
||||
@@ -297,7 +300,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
|
||||
// when
|
||||
@@ -371,7 +374,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -436,7 +439,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -500,7 +503,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -564,7 +567,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -652,7 +655,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -702,7 +705,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -748,7 +751,7 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
// when
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
@@ -812,8 +815,8 @@ public class TrackRecordingServiceTestRecording {
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
trackPointCreator.setAltitudeSumManager(altitudeSumManager);
|
||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getRemoteSensorManager();
|
||||
trackPointCreator.getSensorManager().setAltitudeSumManager(altitudeSumManager);
|
||||
BluetoothRemoteSensorManager remoteSensorManager = trackPointCreator.getSensorManager().getBluetoothSensorManager();
|
||||
|
||||
// when
|
||||
String sensor1 = "2020-02-02T02:02:03Z";
|
||||
|
||||
-14
@@ -1,17 +1,12 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@@ -24,15 +19,6 @@ public class TrackRecordingServiceTestUtils {
|
||||
public static void resetService(ServiceTestRule mServiceRule, Context context) throws TimeoutException {
|
||||
// Let's use default values.
|
||||
PreferencesUtils.clear();
|
||||
|
||||
// Reset service (if some previous test failed)
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
|
||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
static void sendGPSLocation(TrackPointCreator trackPointCreator, String time, double latitude, double longitude, float accuracy, long speed) {
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GPSHandlerTest {
|
||||
public class GPSManagerTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@@ -34,7 +34,7 @@ public class GPSHandlerTest {
|
||||
private TrackPointCreator trackPointCreator;
|
||||
|
||||
@InjectMocks
|
||||
private GPSHandler locationHandler;
|
||||
private GPSManager locationHandler;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
@@ -50,7 +50,7 @@ public class GPSHandlerTest {
|
||||
Mockito.when(trackPointCreator.createNow())
|
||||
.thenReturn(Instant.now());
|
||||
|
||||
locationHandler.onStart(context, new Handler());
|
||||
locationHandler.start(context, new Handler());
|
||||
}
|
||||
|
||||
/**
|
||||
+1
-1
@@ -25,7 +25,7 @@ public class TrackPointCreatorTest {
|
||||
private TrackPointCreator.Callback server;
|
||||
|
||||
@Mock
|
||||
private GPSHandler locationHandler;
|
||||
private GPSManager locationHandler;
|
||||
|
||||
private TrackPointCreator subject;
|
||||
|
||||
|
||||
@@ -34,23 +34,24 @@ public class EGM2008UtilsTest {
|
||||
int expectedLength = 18671444;
|
||||
int expectedHeaderLength = 404;
|
||||
|
||||
String expectedHeader = "P5\n" +
|
||||
"# Geoid file in PGM format for the GeographicLib::Geoid class\n" +
|
||||
"# Description WGS84 EGM2008, 5-minute grid\n" +
|
||||
"# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008\n" +
|
||||
"# DateTime 2009-08-29 18:45:00\n" +
|
||||
"# MaxBilinearError 0.478\n" +
|
||||
"# RMSBilinearError 0.012\n" +
|
||||
"# MaxCubicError 0.294\n" +
|
||||
"# RMSCubicError 0.005\n" +
|
||||
"# Offset -108\n" +
|
||||
"# Scale 0.003\n" +
|
||||
"# Origin 90N 0E\n" +
|
||||
"# AREA_OR_POINT Point\n" +
|
||||
"# Vertical_Datum WGS84\n" +
|
||||
"4320 2161\n" +
|
||||
"65535" +
|
||||
"\n";
|
||||
String expectedHeader = """
|
||||
P5
|
||||
# Geoid file in PGM format for the GeographicLib::Geoid class
|
||||
# Description WGS84 EGM2008, 5-minute grid
|
||||
# URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm2008
|
||||
# DateTime 2009-08-29 18:45:00
|
||||
# MaxBilinearError 0.478
|
||||
# RMSBilinearError 0.012
|
||||
# MaxCubicError 0.294
|
||||
# RMSCubicError 0.005
|
||||
# Offset -108
|
||||
# Scale 0.003
|
||||
# Origin 90N 0E
|
||||
# AREA_OR_POINT Point
|
||||
# Vertical_Datum WGS84
|
||||
4320 2161
|
||||
65535
|
||||
""";
|
||||
|
||||
// when
|
||||
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));
|
||||
|
||||
// when
|
||||
RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||
RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||
RecordingLayout resultTrue = recordingLayout.toRecordingLayout(true);
|
||||
RecordingLayout resultFalse = recordingLayout.toRecordingLayout(false);
|
||||
|
||||
// then
|
||||
assertEquals(resultTrue.getFields().size(), 3);
|
||||
|
||||
@@ -62,7 +62,9 @@ limitations under the License.
|
||||
|
||||
<application
|
||||
android:name=".Startup"
|
||||
android:allowBackup="false"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_rules_30_lower"
|
||||
android:dataExtractionRules="@xml/backup_rules_31_higher"
|
||||
android:hardwareAccelerated="true"
|
||||
android:hasFragileUserData="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
|
||||
@@ -311,9 +311,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||
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.setTextColor(ContextCompat.getColor(this, android.R.color.white));
|
||||
|
||||
|
||||
@@ -274,18 +274,14 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return StatisticsRecordedFragment.newInstance(trackId);
|
||||
case 1:
|
||||
return IntervalsFragment.newInstance(trackId, true);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 3:
|
||||
return ChartFragment.newInstance(true);
|
||||
default:
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
}
|
||||
return switch (position) {
|
||||
case 0 -> StatisticsRecordedFragment.newInstance(trackId);
|
||||
case 1 -> IntervalsFragment.newInstance(trackId, true);
|
||||
case 2 -> ChartFragment.newInstance(false);
|
||||
case 3 -> ChartFragment.newInstance(true);
|
||||
default ->
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,18 +290,14 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement
|
||||
}
|
||||
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return getString(R.string.track_detail_stats_tab);
|
||||
case 1:
|
||||
return getString(R.string.track_detail_intervals_tab);
|
||||
case 2:
|
||||
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);
|
||||
}
|
||||
return switch (position) {
|
||||
case 0 -> getString(R.string.track_detail_stats_tab);
|
||||
case 1 -> getString(R.string.track_detail_intervals_tab);
|
||||
case 2 -> getString(R.string.settings_chart_by_time);
|
||||
case 3 -> getString(R.string.settings_chart_by_distance);
|
||||
default ->
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -333,18 +333,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return StatisticsRecordingFragment.newInstance();
|
||||
case 1:
|
||||
return IntervalsFragment.newInstance(trackId, false);
|
||||
case 2:
|
||||
return ChartFragment.newInstance(false);
|
||||
case 3:
|
||||
return ChartFragment.newInstance(true);
|
||||
default:
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
}
|
||||
return switch (position) {
|
||||
case 0 -> StatisticsRecordingFragment.newInstance();
|
||||
case 1 -> IntervalsFragment.newInstance(trackId, false);
|
||||
case 2 -> ChartFragment.newInstance(false);
|
||||
case 3 -> ChartFragment.newInstance(true);
|
||||
default ->
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -353,18 +349,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
public CharSequence getPageTitle(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return getString(R.string.track_detail_stats_tab);
|
||||
case 1:
|
||||
return getString(R.string.track_detail_intervals_tab);
|
||||
case 2:
|
||||
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);
|
||||
}
|
||||
return switch (position) {
|
||||
case 0 -> getString(R.string.track_detail_stats_tab);
|
||||
case 1 -> getString(R.string.track_detail_intervals_tab);
|
||||
case 2 -> getString(R.string.settings_chart_by_time);
|
||||
case 3 -> getString(R.string.settings_chart_by_distance);
|
||||
default ->
|
||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,13 +373,18 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
private void onGpsStatusChanged(GpsStatusValue gpsStatusValue) {
|
||||
if (gpsStatusValue.isGpsStarted() && snackbar != null && snackbar.isShown()) {
|
||||
snackbar.dismiss();
|
||||
boolean snackbarShowing = snackbar != null && snackbar.isShown();
|
||||
if (gpsStatusValue.isGpsStarted()) {
|
||||
if (snackbarShowing) {
|
||||
snackbar.dismiss();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (gpsStatusValue != GpsStatusValue.GPS_DISABLED) {
|
||||
|
||||
if (snackbarShowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
snackbar = Snackbar
|
||||
.make(viewBinding.trackRecordingCoordinatorLayout,
|
||||
getString(R.string.gps_recording_status, getString(gpsStatusValue.message), getString(R.string.gps_recording_without_signal)),
|
||||
|
||||
@@ -210,16 +210,11 @@ abstract class ChartValueSeries {
|
||||
}
|
||||
|
||||
int getTitleId(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return metricTitleId;
|
||||
case IMPERIAL:
|
||||
return imperialTitleId;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return nauticalTitleId;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> metricTitleId;
|
||||
case IMPERIAL -> imperialTitleId;
|
||||
case NAUTICAL_IMPERIAL -> nauticalTitleId;
|
||||
};
|
||||
}
|
||||
|
||||
Paint getTitlePaint() {
|
||||
|
||||
@@ -733,16 +733,11 @@ public class ChartView extends View {
|
||||
private String getXAxisLabel() {
|
||||
Context context = getContext();
|
||||
if (chartByDistance) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return context.getString(R.string.unit_kilometer);
|
||||
case IMPERIAL:
|
||||
return context.getString(R.string.unit_mile);
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return context.getString(R.string.unit_nautical_mile);
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> context.getString(R.string.unit_kilometer);
|
||||
case IMPERIAL -> context.getString(R.string.unit_mile);
|
||||
case NAUTICAL_IMPERIAL -> context.getString(R.string.unit_nautical_mile);
|
||||
};
|
||||
} else {
|
||||
return context.getString(R.string.description_time);
|
||||
}
|
||||
|
||||
@@ -133,20 +133,12 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri url, String where, String[] selectionArgs) {
|
||||
String table;
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS:
|
||||
table = TrackPointsColumns.TABLE_NAME;
|
||||
break;
|
||||
case TRACKS:
|
||||
table = TracksColumns.TABLE_NAME;
|
||||
break;
|
||||
case MARKERS:
|
||||
table = MarkerColumns.TABLE_NAME;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URL " + url);
|
||||
}
|
||||
String table = switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> TrackPointsColumns.TABLE_NAME;
|
||||
case TRACKS -> TracksColumns.TABLE_NAME;
|
||||
case MARKERS -> MarkerColumns.TABLE_NAME;
|
||||
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||
};
|
||||
|
||||
Log.w(TAG, "Deleting from table " + table);
|
||||
int totalChangesBefore = getTotalChanges();
|
||||
@@ -186,24 +178,15 @@ public class CustomContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri url) {
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS:
|
||||
return TrackPointsColumns.CONTENT_TYPE;
|
||||
case TRACKPOINTS_BY_ID:
|
||||
case TRACKPOINTS_BY_TRACKID:
|
||||
return TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||
case TRACKS:
|
||||
return TracksColumns.CONTENT_TYPE;
|
||||
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);
|
||||
}
|
||||
return switch (getUrlType(url)) {
|
||||
case TRACKPOINTS -> TrackPointsColumns.CONTENT_TYPE;
|
||||
case TRACKPOINTS_BY_ID, TRACKPOINTS_BY_TRACKID -> TrackPointsColumns.CONTENT_ITEMTYPE;
|
||||
case TRACKS -> TracksColumns.CONTENT_TYPE;
|
||||
case TRACKS_BY_ID -> TracksColumns.CONTENT_ITEMTYPE;
|
||||
case MARKERS -> MarkerColumns.CONTENT_TYPE;
|
||||
case MARKERS_BY_ID, MARKERS_BY_TRACKID -> MarkerColumns.CONTENT_ITEMTYPE;
|
||||
default -> throw new IllegalArgumentException("Unknown URL " + url);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -251,47 +234,47 @@ public class CustomContentProvider extends ContentProvider {
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
String sortOrder = null;
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS:
|
||||
case TRACKPOINTS -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : TrackPointsColumns.DEFAULT_SORT_ORDER;
|
||||
break;
|
||||
case TRACKPOINTS_BY_ID:
|
||||
}
|
||||
case TRACKPOINTS_BY_ID -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(TrackPointsColumns._ID + "=" + ContentUris.parseId(url));
|
||||
break;
|
||||
case TRACKPOINTS_BY_TRACKID:
|
||||
}
|
||||
case TRACKPOINTS_BY_TRACKID -> {
|
||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||
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)) {
|
||||
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 {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
}
|
||||
sortOrder = sort != null ? sort : TracksColumns.DEFAULT_SORT_ORDER;
|
||||
break;
|
||||
case TRACKS_BY_ID:
|
||||
}
|
||||
case TRACKS_BY_ID -> {
|
||||
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||
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);
|
||||
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
||||
case MARKERS:
|
||||
}
|
||||
case MARKERS -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||
break;
|
||||
case MARKERS_BY_ID:
|
||||
}
|
||||
case MARKERS_BY_ID -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(MarkerColumns._ID + "=" + ContentUris.parseId(url));
|
||||
break;
|
||||
case MARKERS_BY_TRACKID:
|
||||
}
|
||||
case MARKERS_BY_TRACKID -> {
|
||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||
queryBuilder.appendWhere(MarkerColumns.TRACKID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), url);
|
||||
@@ -304,41 +287,40 @@ public class CustomContentProvider extends ContentProvider {
|
||||
String table;
|
||||
String whereClause;
|
||||
switch (getUrlType(url)) {
|
||||
case TRACKPOINTS:
|
||||
case TRACKPOINTS -> {
|
||||
table = TrackPointsColumns.TABLE_NAME;
|
||||
whereClause = where;
|
||||
break;
|
||||
case TRACKPOINTS_BY_ID:
|
||||
}
|
||||
case TRACKPOINTS_BY_ID -> {
|
||||
table = TrackPointsColumns.TABLE_NAME;
|
||||
whereClause = TrackPointsColumns._ID + "=" + ContentUris.parseId(url);
|
||||
if (!TextUtils.isEmpty(where)) {
|
||||
whereClause += " AND (" + where + ")";
|
||||
}
|
||||
break;
|
||||
case TRACKS:
|
||||
}
|
||||
case TRACKS -> {
|
||||
table = TracksColumns.TABLE_NAME;
|
||||
whereClause = where;
|
||||
break;
|
||||
case TRACKS_BY_ID:
|
||||
}
|
||||
case TRACKS_BY_ID -> {
|
||||
table = TracksColumns.TABLE_NAME;
|
||||
whereClause = TracksColumns._ID + "=" + ContentUris.parseId(url);
|
||||
if (!TextUtils.isEmpty(where)) {
|
||||
whereClause += " AND (" + where + ")";
|
||||
}
|
||||
break;
|
||||
case MARKERS:
|
||||
}
|
||||
case MARKERS -> {
|
||||
table = MarkerColumns.TABLE_NAME;
|
||||
whereClause = where;
|
||||
break;
|
||||
case MARKERS_BY_ID:
|
||||
}
|
||||
case MARKERS_BY_ID -> {
|
||||
table = MarkerColumns.TABLE_NAME;
|
||||
whereClause = MarkerColumns._ID + "=" + ContentUris.parseId(url);
|
||||
if (!TextUtils.isEmpty(where)) {
|
||||
whereClause += " AND (" + where + ")";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
int count;
|
||||
try {
|
||||
@@ -371,16 +353,12 @@ public class CustomContentProvider extends ContentProvider {
|
||||
* @param contentValues the content values
|
||||
*/
|
||||
private Uri insertContentValues(Uri url, UrlType urlType, ContentValues contentValues) {
|
||||
switch (urlType) {
|
||||
case TRACKPOINTS:
|
||||
return insertTrackPoint(url, contentValues);
|
||||
case TRACKS:
|
||||
return insertTrack(url, contentValues);
|
||||
case MARKERS:
|
||||
return insertMarker(url, contentValues);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown url " + url);
|
||||
}
|
||||
return switch (urlType) {
|
||||
case TRACKPOINTS -> insertTrackPoint(url, contentValues);
|
||||
case TRACKS -> insertTrack(url, contentValues);
|
||||
case MARKERS -> insertMarker(url, contentValues);
|
||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||
};
|
||||
}
|
||||
|
||||
private Uri insertTrackPoint(Uri url, ContentValues values) {
|
||||
|
||||
@@ -61,42 +61,18 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
for (int toVersion = oldVersion + 1; toVersion <= newVersion; toVersion++) {
|
||||
Log.i(TAG, "Upgrade from " + oldVersion + " to " + toVersion);
|
||||
switch (toVersion) {
|
||||
case 24:
|
||||
upgradeFrom23to24(db);
|
||||
break;
|
||||
case 25:
|
||||
upgradeFrom24to25(db);
|
||||
break;
|
||||
case 26:
|
||||
upgradeFrom25to26(db);
|
||||
break;
|
||||
case 27:
|
||||
upgradeFrom26to27(db);
|
||||
break;
|
||||
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);
|
||||
case 24 -> upgradeFrom23to24(db);
|
||||
case 25 -> upgradeFrom24to25(db);
|
||||
case 26 -> upgradeFrom25to26(db);
|
||||
case 27 -> upgradeFrom26to27(db);
|
||||
case 28 -> upgradeFrom27to28(db);
|
||||
case 29 -> upgradeFrom28to29(db);
|
||||
case 30 -> upgradeFrom29to30(db);
|
||||
case 31 -> upgradeFrom30to31(db);
|
||||
case 32 -> upgradeFrom31to32(db);
|
||||
case 33 -> upgradeFrom32to33(db);
|
||||
case 34 -> upgradeFrom33to34(db);
|
||||
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--) {
|
||||
Log.i(TAG, "Downgrade from " + oldVersion + " to " + toVersion);
|
||||
switch (toVersion) {
|
||||
case 23:
|
||||
downgradeFrom24to23(db);
|
||||
break;
|
||||
case 24:
|
||||
downgradeFrom25to24(db);
|
||||
break;
|
||||
case 25:
|
||||
downgradeFrom26to25(db);
|
||||
break;
|
||||
case 26:
|
||||
downgradeFrom27to26(db);
|
||||
break;
|
||||
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);
|
||||
case 23 -> downgradeFrom24to23(db);
|
||||
case 24 -> downgradeFrom25to24(db);
|
||||
case 25 -> downgradeFrom26to25(db);
|
||||
case 26 -> downgradeFrom27to26(db);
|
||||
case 27 -> downgradeFrom28to27(db);
|
||||
case 28 -> downgradeFrom29to28(db);
|
||||
case 29 -> downgradeFrom30to29(db);
|
||||
case 30 -> downgradeFrom31to30(db);
|
||||
case 31 -> downgradeFrom32to31(db);
|
||||
case 32 -> downgradeFrom33to32(db);
|
||||
case 33 -> downgradeFrom34to33(db);
|
||||
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) {
|
||||
switch (uriMatcher.match(uri)) {
|
||||
case URI_GPX:
|
||||
return TrackFileFormat.GPX;
|
||||
|
||||
case URI_KML_WITH_TRACKDETAIL_SENSORDATA:
|
||||
return TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||
|
||||
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA:
|
||||
return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES:
|
||||
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);
|
||||
}
|
||||
return switch (uriMatcher.match(uri)) {
|
||||
case URI_GPX -> TrackFileFormat.GPX;
|
||||
case URI_KML_WITH_TRACKDETAIL_SENSORDATA ->
|
||||
TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||
case URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA ->
|
||||
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA;
|
||||
case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES ->
|
||||
TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES;
|
||||
case URI_CSV -> TrackFileFormat.CSV;
|
||||
default ->
|
||||
throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri);
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -170,14 +164,14 @@ public class ShareContentProvider extends CustomContentProvider {
|
||||
int i = 0;
|
||||
for (String col : projection) {
|
||||
switch (col) {
|
||||
case OpenableColumns.DISPLAY_NAME:
|
||||
case OpenableColumns.DISPLAY_NAME -> {
|
||||
cols[i] = OpenableColumns.DISPLAY_NAME;
|
||||
values[i++] = uri.getLastPathSegment();
|
||||
break;
|
||||
case OpenableColumns.SIZE:
|
||||
}
|
||||
case 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.
|
||||
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) -> {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
|
||||
|
||||
@@ -58,16 +58,11 @@ public class Distance {
|
||||
}
|
||||
|
||||
public static Distance one(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return Distance.ofKilometer(1);
|
||||
case IMPERIAL:
|
||||
return Distance.ofMile(1);
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return Distance.ofNauticalMile(1);
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> Distance.ofKilometer(1);
|
||||
case IMPERIAL -> Distance.ofMile(1);
|
||||
case NAUTICAL_IMPERIAL -> Distance.ofNauticalMile(1);
|
||||
};
|
||||
}
|
||||
|
||||
private final double distance_m;
|
||||
@@ -133,28 +128,18 @@ public class Distance {
|
||||
}
|
||||
|
||||
public double toKM_Miles(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toKM();
|
||||
case IMPERIAL:
|
||||
return toMI();
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return toNauticalMiles();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> toKM();
|
||||
case IMPERIAL -> toMI();
|
||||
case NAUTICAL_IMPERIAL -> toNauticalMiles();
|
||||
};
|
||||
}
|
||||
|
||||
public double toM_FT(UnitSystem unitSystem) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toM();
|
||||
case NAUTICAL_IMPERIAL:
|
||||
case IMPERIAL:
|
||||
return toFT();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> toM();
|
||||
case NAUTICAL_IMPERIAL, IMPERIAL -> toFT();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -46,38 +46,36 @@ public class DistanceFormatter {
|
||||
public Pair<String, String> getDistanceParts(Distance distance) {
|
||||
if (distance.isInvalid()) {
|
||||
String valueUnknown = resources.getString(R.string.value_unknown);
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> new Pair<>(valueUnknown, resources.getString(R.string.unit_meter));
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL ->
|
||||
new Pair<>(valueUnknown, resources.getString(R.string.unit_feet));
|
||||
};
|
||||
}
|
||||
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
case METRIC -> {
|
||||
if (distance.greaterThan(Distance.ofKilometer(threshold))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer));
|
||||
} else {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
|
||||
}
|
||||
case IMPERIAL:
|
||||
}
|
||||
case IMPERIAL -> {
|
||||
if (distance.greaterThan(Distance.ofMile(threshold))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
|
||||
} else {
|
||||
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))) {
|
||||
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
|
||||
} else {
|
||||
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) {
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
return toKMH();
|
||||
case IMPERIAL:
|
||||
return toMPH();
|
||||
case NAUTICAL_IMPERIAL:
|
||||
return toKnots();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
return switch (unitSystem) {
|
||||
case METRIC -> toKMH();
|
||||
case IMPERIAL -> toMPH();
|
||||
case NAUTICAL_IMPERIAL -> toKnots();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -39,20 +39,14 @@ public class SpeedFormatter {
|
||||
* @return the formatted speed (or null) and it's unit as {@link Pair}
|
||||
*/
|
||||
public Pair<String, String> getSpeedParts(Speed speed) {
|
||||
int unitId;
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
unitId = reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
||||
break;
|
||||
case IMPERIAL:
|
||||
unitId = reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_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");
|
||||
}
|
||||
int unitId = switch (unitSystem) {
|
||||
case METRIC ->
|
||||
reportSpeedOrPace ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
||||
case IMPERIAL ->
|
||||
reportSpeedOrPace ? R.string.unit_mile_per_hour : R.string.unit_minute_per_mile;
|
||||
case NAUTICAL_IMPERIAL ->
|
||||
reportSpeedOrPace ? R.string.unit_knots : R.string.unit_minute_per_nautical_mile;
|
||||
};
|
||||
|
||||
String unitString = resources.getString(unitId);
|
||||
|
||||
|
||||
@@ -122,17 +122,15 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
}
|
||||
recordingLayout = newRecordingLayout;
|
||||
|
||||
viewBinding.statsLayout.setColumnCount(recordingLayout.getColumnsPerRow());
|
||||
|
||||
viewBinding.statsLayout.removeAllViews(); //Let's start from scratch
|
||||
viewBinding.statsLayout.setColumnCount(recordingLayout.getColumnsPerRow());
|
||||
viewHolders.clear();
|
||||
|
||||
Map<String, Callable<StatisticViewHolder<?>>> m = Mapping.create(getContext());
|
||||
|
||||
int rowIndex = 0;
|
||||
int columnIndex = 0;
|
||||
for (int i = 0; i < recordingLayout.getFields().size(); i++) {
|
||||
DataField dataField = recordingLayout.getFields().get(i);
|
||||
for (DataField dataField : recordingLayout.toRecordingLayout(true).getFields()) {
|
||||
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
|
||||
param.setGravity(Gravity.FILL_HORIZONTAL);
|
||||
param.width = 0;
|
||||
@@ -162,7 +160,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
viewBinding.statsLayout.addView(viewHolder.getView(), param);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new RuntimeException("Could not add " + dataField.getKey(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package de.dennisguse.opentracks.io.file;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
@@ -25,8 +27,8 @@ public enum TrackFileFormat {
|
||||
|
||||
KML_WITH_TRACKDETAIL_AND_SENSORDATA("KML_WITH_TRACKDETAIL_AND_SENSORDATA") {
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
return new KMLTrackExporter(context, false);
|
||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||
return new KMLTrackExporter(context, contentProviderUtils, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,9 +47,9 @@ public enum TrackFileFormat {
|
||||
private static final boolean exportPhotos = false;
|
||||
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
KMLTrackExporter exporter = new KMLTrackExporter(context, exportPhotos);
|
||||
return new KmzTrackExporter(context, new ContentProviderUtils(context), exporter, exportPhotos);
|
||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,9 +72,9 @@ public enum TrackFileFormat {
|
||||
private static final boolean exportPhotos = true;
|
||||
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
KMLTrackExporter exporter = new KMLTrackExporter(context, exportPhotos);
|
||||
return new KmzTrackExporter(context, new ContentProviderUtils(context), exporter, exportPhotos);
|
||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||
KMLTrackExporter exporter = new KMLTrackExporter(context, contentProviderUtils, exportPhotos);
|
||||
return new KmzTrackExporter(context, contentProviderUtils, exporter, exportPhotos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,8 +95,8 @@ public enum TrackFileFormat {
|
||||
|
||||
GPX("GPX") {
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
return new GPXTrackExporter(new ContentProviderUtils(context), context.getString(R.string.app_name));
|
||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||
return new GPXTrackExporter(contentProviderUtils, context.getString(R.string.app_name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,8 +111,8 @@ public enum TrackFileFormat {
|
||||
|
||||
CSV("CSV") {
|
||||
@Override
|
||||
public TrackExporter createTrackExporter(Context context) {
|
||||
return new CSVTrackExporter(new ContentProviderUtils(context));
|
||||
public TrackExporter createTrackExporter(@NonNull Context context, @NonNull ContentProviderUtils contentProviderUtils) {
|
||||
return new CSVTrackExporter(contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,7 +163,7 @@ public enum TrackFileFormat {
|
||||
*
|
||||
* @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.
|
||||
|
||||
@@ -93,23 +93,9 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
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()) : ""),
|
||||
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("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("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()) : "")));
|
||||
|
||||
try {
|
||||
prepare(outputStream);
|
||||
@@ -144,15 +130,10 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
TrackPoint trackPoint = trackPointIterator.next();
|
||||
|
||||
switch (trackPoint.getType()) {
|
||||
case SEGMENT_START_MANUAL:
|
||||
case SEGMENT_END_MANUAL:
|
||||
case SEGMENT_START_AUTOMATIC:
|
||||
case SENSORPOINT:
|
||||
case TRACKPOINT:
|
||||
writeTrackPoint(columns, trackPoint);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||
case SEGMENT_START_MANUAL, SEGMENT_END_MANUAL, SEGMENT_START_AUTOMATIC, SENSORPOINT, TRACKPOINT ->
|
||||
writeTrackPoint(columns, trackPoint);
|
||||
default ->
|
||||
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) {
|
||||
if (printWriter != null) {
|
||||
String columnNames = columns.stream().map(c -> c.columnName)
|
||||
.reduce((s, s2) -> s + "," + s2)
|
||||
.orElseThrow(() -> new RuntimeException("No columns defined"));
|
||||
String columnNames = columns.stream().map(c -> c.columnName).reduce((s, s2) -> s + "," + s2).orElseThrow(() -> new RuntimeException("No columns defined"));
|
||||
printWriter.println("#" + columnNames);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTrackPoint(List<Column> columns, TrackPoint trackPoint) {
|
||||
if (printWriter != null) {
|
||||
String columnNames = columns.stream().map(c -> c.extractor.apply(trackPoint))
|
||||
.reduce((s, s2) -> s + "," + s2)
|
||||
.orElseThrow(() -> new RuntimeException("No columns defined"));
|
||||
String columnNames = columns.stream().map(c -> c.extractor.apply(trackPoint)).reduce((s, s2) -> s + "," + s2).orElseThrow(() -> new RuntimeException("No columns defined"));
|
||||
printWriter.println(columnNames);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-8
@@ -30,14 +30,9 @@ public class ExportServiceResultReceiver extends ResultReceiver {
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||
switch (resultCode) {
|
||||
case RESULT_CODE_SUCCESS:
|
||||
receiver.onExportSuccess(trackId);
|
||||
break;
|
||||
case RESULT_CODE_ERROR:
|
||||
receiver.onExportError(trackId);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unknown resultCode.");
|
||||
case RESULT_CODE_SUCCESS -> receiver.onExportSuccess(trackId);
|
||||
case RESULT_CODE_ERROR -> receiver.onExportError(trackId);
|
||||
default -> throw new RuntimeException("Unknown resultCode.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,37 +145,32 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
switch (trackPoint.getType()) {
|
||||
case SEGMENT_START_MANUAL:
|
||||
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
|
||||
break;
|
||||
case SEGMENT_END_MANUAL:
|
||||
case SEGMENT_START_MANUAL ->
|
||||
Log.i(TAG, "Exporting " + TrackPoint.Type.SEGMENT_START_MANUAL.name() + " is not supported.");
|
||||
case SEGMENT_END_MANUAL -> {
|
||||
if (wroteSegment) writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
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();
|
||||
writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
|
||||
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
|
||||
sensorPoints.clear();
|
||||
break;
|
||||
case SENSORPOINT:
|
||||
sensorPoints.add(trackPoint);
|
||||
break;
|
||||
case TRACKPOINT:
|
||||
}
|
||||
case SENSORPOINT -> sensorPoints.add(trackPoint);
|
||||
case TRACKPOINT -> {
|
||||
if (!wroteSegment) {
|
||||
// Might happen for older data (pre v3.15.0)
|
||||
writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
}
|
||||
|
||||
trackDistance = trackDistance.plus(writeTrackPoint(track.getZoneOffset(), trackPoint, sensorPoints, trackDistance));
|
||||
sensorPoints.clear();
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||
}
|
||||
default ->
|
||||
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> accuracyVertical = new ArrayList<>();
|
||||
|
||||
public KMLTrackExporter(Context context, boolean exportPhotos) {
|
||||
public KMLTrackExporter(Context context, ContentProviderUtils contentProviderUtils, boolean exportPhotos) {
|
||||
this.context = context;
|
||||
this.exportPhotos = exportPhotos;
|
||||
this.contentProviderUtils = new ContentProviderUtils(context);
|
||||
this.contentProviderUtils = contentProviderUtils;
|
||||
}
|
||||
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
@@ -169,30 +169,28 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
switch (trackPoint.getType()) {
|
||||
case SEGMENT_START_MANUAL:
|
||||
case SEGMENT_START_AUTOMATIC:
|
||||
case SEGMENT_START_MANUAL, SEGMENT_START_AUTOMATIC -> {
|
||||
if (wroteSegment) writeCloseSegment();
|
||||
writeOpenSegment();
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||
wroteSegment = true;
|
||||
break;
|
||||
case SEGMENT_END_MANUAL:
|
||||
}
|
||||
case SEGMENT_END_MANUAL -> {
|
||||
if (!wroteSegment) writeOpenSegment();
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||
writeCloseSegment();
|
||||
wroteSegment = false;
|
||||
break;
|
||||
case SENSORPOINT:
|
||||
case TRACKPOINT:
|
||||
}
|
||||
case SENSORPOINT, TRACKPOINT -> {
|
||||
if (!wroteSegment) {
|
||||
// Might happen for older data (pre v3.15.0)
|
||||
writeOpenSegment();
|
||||
wroteSegment = true;
|
||||
}
|
||||
writeTrackPoint(track.getZoneOffset(), trackPoint);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
|
||||
}
|
||||
default ->
|
||||
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) {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.3\"");
|
||||
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
|
||||
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\">");
|
||||
//TODO ADD xsi:schemaLocation for atom
|
||||
printWriter.println("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\">");
|
||||
|
||||
printWriter.println(
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
""");
|
||||
printWriter.println(
|
||||
"""
|
||||
<kml xmlns="http://www.opengis.net/kml/2.3"
|
||||
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("<open>1</open>");
|
||||
printWriter.println("<visibility>1</visibility>");
|
||||
|
||||
@@ -156,103 +156,94 @@ public class GpxTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) {
|
||||
switch (tag) {
|
||||
case TAG_GPX:
|
||||
onFileEnd();
|
||||
break;
|
||||
case TAG_MARKER:
|
||||
onMarkerEnd();
|
||||
break;
|
||||
case TAG_TRACK:
|
||||
case TAG_GPX -> onFileEnd();
|
||||
case TAG_MARKER -> onMarkerEnd();
|
||||
case TAG_TRACK -> {
|
||||
trackImporter.setTrack(context, name, uuid, description, category, null, zoneOffset);
|
||||
zoneOffset = null;
|
||||
break;
|
||||
case TAG_TRACK_SEGMENT:
|
||||
onTrackSegmentEnd();
|
||||
break;
|
||||
case TAG_TRACK_POINT:
|
||||
currentSegment.add(createTrackPoint());
|
||||
break;
|
||||
case TAG_NAME:
|
||||
}
|
||||
case TAG_TRACK_SEGMENT -> onTrackSegmentEnd();
|
||||
case TAG_TRACK_POINT -> currentSegment.add(createTrackPoint());
|
||||
case TAG_NAME -> {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_DESCRIPTION:
|
||||
}
|
||||
case TAG_DESCRIPTION -> {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_TYPE:
|
||||
}
|
||||
case TAG_TYPE -> {
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_TIME:
|
||||
}
|
||||
case TAG_TIME -> {
|
||||
if (content != null) {
|
||||
time = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ALTITUDE:
|
||||
}
|
||||
case TAG_ALTITUDE -> {
|
||||
if (content != null) {
|
||||
altitude = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_COMMENT:
|
||||
}
|
||||
case TAG_COMMENT -> {
|
||||
if (content != null) {
|
||||
markerType = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_SPEED:
|
||||
case TAG_EXTENSION_SPEED_COMPAT:
|
||||
}
|
||||
case TAG_EXTENSION_SPEED, TAG_EXTENSION_SPEED_COMPAT -> {
|
||||
if (content != null) {
|
||||
speed = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_HEARTRATE:
|
||||
}
|
||||
case TAG_EXTENSION_HEARTRATE -> {
|
||||
if (content != null) {
|
||||
heartrate = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_CADENCE:
|
||||
}
|
||||
case TAG_EXTENSION_CADENCE -> {
|
||||
if (content != null) {
|
||||
cadence = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_POWER:
|
||||
}
|
||||
case TAG_EXTENSION_POWER -> {
|
||||
if (content != null) {
|
||||
power = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ID:
|
||||
}
|
||||
case TAG_ID -> {
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_GAIN:
|
||||
}
|
||||
case TAG_EXTENSION_GAIN -> {
|
||||
if (content != null) {
|
||||
gain = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_LOSS:
|
||||
}
|
||||
case TAG_EXTENSION_LOSS -> {
|
||||
if (content != null) {
|
||||
loss = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_DISTANCE:
|
||||
}
|
||||
case TAG_EXTENSION_DISTANCE -> {
|
||||
if (content != null) {
|
||||
sensorDistance = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_ACCURACY_HORIZONTAL:
|
||||
}
|
||||
case TAG_EXTENSION_ACCURACY_HORIZONTAL -> {
|
||||
if (content != null) {
|
||||
accuracyHorizontal = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_EXTENSION_ACCURACY_VERTICAL:
|
||||
}
|
||||
case TAG_EXTENSION_ACCURACY_VERTICAL -> {
|
||||
if (content != null) {
|
||||
accuracyVertical = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
content = "";
|
||||
|
||||
@@ -79,19 +79,17 @@ public class ImportViewModel extends AndroidViewModel implements ImportServiceRe
|
||||
String message = resultData.getString(ImportServiceResultReceiver.RESULT_EXTRA_MESSAGE);
|
||||
|
||||
switch (resultCode) {
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ERROR:
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ERROR -> {
|
||||
summary.errorCount++;
|
||||
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.successCount++;
|
||||
break;
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS:
|
||||
summary.existsCount++;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode);
|
||||
}
|
||||
case ImportServiceResultReceiver.RESULT_CODE_ALREADY_EXISTS -> summary.existsCount++;
|
||||
default ->
|
||||
throw new RuntimeException(TAG + ": import service result code invalid: " + resultCode);
|
||||
}
|
||||
|
||||
importData.postValue(summary);
|
||||
|
||||
@@ -136,27 +136,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_PLACEMARK:
|
||||
case TAG_PHOTO_OVERLAY:
|
||||
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||
// Note that a track is contained in a Placemark, calling onMarkerStart will clear various track variables like name, category, and description.
|
||||
onMarkerStart();
|
||||
break;
|
||||
case TAG_MULTI_TRACK:
|
||||
case TAG_KML22_MULTI_TRACK:
|
||||
trackImporter.newTrack();
|
||||
break;
|
||||
case TAG_TRACK:
|
||||
case TAG_KML22_TRACK:
|
||||
onMarkerStart();
|
||||
case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> trackImporter.newTrack();
|
||||
case TAG_TRACK, TAG_KML22_TRACK -> {
|
||||
if (trackImporter == null) {
|
||||
throw new SAXException("Missing " + TAG_MULTI_TRACK);
|
||||
}
|
||||
onTrackSegmentStart();
|
||||
break;
|
||||
case TAG_DATA_CATEGORY:
|
||||
case TAG_SIMPLE_ARRAY_DATA:
|
||||
case TAG_KML22_SIMPLE_ARRAY_DATA:
|
||||
dataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
break;
|
||||
}
|
||||
case TAG_DATA_CATEGORY, TAG_SIMPLE_ARRAY_DATA, TAG_KML22_SIMPLE_ARRAY_DATA ->
|
||||
dataType = attributes.getValue(ATTRIBUTE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,32 +159,18 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||
switch (tag) {
|
||||
case TAG_KML:
|
||||
onFileEnd();
|
||||
break;
|
||||
case TAG_PLACEMARK:
|
||||
case TAG_PHOTO_OVERLAY:
|
||||
case TAG_KML -> onFileEnd();
|
||||
case TAG_PLACEMARK, TAG_PHOTO_OVERLAY ->
|
||||
// Note that a track is contained in a Placemark, calling onMarkerEnd is save since markerType is not set for a track.
|
||||
onMarkerEnd();
|
||||
break;
|
||||
case TAG_COORDINATES:
|
||||
onMarkerLocationEnd();
|
||||
break;
|
||||
case TAG_MULTI_TRACK:
|
||||
case TAG_KML22_MULTI_TRACK:
|
||||
onMarkerEnd();
|
||||
case TAG_COORDINATES -> onMarkerLocationEnd();
|
||||
case TAG_MULTI_TRACK, TAG_KML22_MULTI_TRACK -> {
|
||||
trackImporter.setTrack(context, name, uuid, description, category, icon, zoneOffset);
|
||||
zoneOffset = null;
|
||||
break;
|
||||
case TAG_TRACK:
|
||||
case TAG_KML22_TRACK:
|
||||
onTrackSegmentEnd();
|
||||
break;
|
||||
case TAG_COORD:
|
||||
case TAG_KML22_COORD:
|
||||
onCoordEnded();
|
||||
break;
|
||||
case TAG_VALUE:
|
||||
case TAG_KML22_VALUE:
|
||||
}
|
||||
case TAG_TRACK, TAG_KML22_TRACK -> onTrackSegmentEnd();
|
||||
case TAG_COORD, TAG_KML22_COORD -> onCoordEnded();
|
||||
case TAG_VALUE, TAG_KML22_VALUE -> {
|
||||
if (KMLTrackExporter.EXTENDED_DATA_TYPE_CATEGORY.equals(dataType)) {
|
||||
if (content != null) {
|
||||
category = content.trim();
|
||||
@@ -201,28 +178,28 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
} else {
|
||||
onExtendedDataValueEnd();
|
||||
}
|
||||
break;
|
||||
case TAG_NAME:
|
||||
}
|
||||
case TAG_NAME -> {
|
||||
if (content != null) {
|
||||
name = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_UUID:
|
||||
}
|
||||
case TAG_UUID -> {
|
||||
if (content != null) {
|
||||
uuid = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_DESCRIPTION:
|
||||
}
|
||||
case TAG_DESCRIPTION -> {
|
||||
if (content != null) {
|
||||
description = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_ICON:
|
||||
}
|
||||
case TAG_ICON -> {
|
||||
if (content != null) {
|
||||
icon = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_WHEN:
|
||||
}
|
||||
case TAG_WHEN -> {
|
||||
if (content != null) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case TAG_STYLE_URL:
|
||||
}
|
||||
case TAG_STYLE_URL -> {
|
||||
if (content != null) {
|
||||
markerType = content.trim();
|
||||
}
|
||||
break;
|
||||
case TAG_HREF:
|
||||
}
|
||||
case TAG_HREF -> {
|
||||
if (content != null) {
|
||||
photoUrl = content.trim();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset element content
|
||||
@@ -436,35 +412,19 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
}
|
||||
}
|
||||
switch (dataType) {
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED:
|
||||
sensorSpeedList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE:
|
||||
sensorDistanceList.add(value);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER:
|
||||
sensorPowerList.add(value);
|
||||
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);
|
||||
break;
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL:
|
||||
accuracyVertical.add(value);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED -> sensorSpeedList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE -> sensorDistanceList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER -> sensorPowerList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_HEART_RATE -> sensorHeartRateList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_CADENCE -> sensorCadenceList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_GAIN -> altitudeGainList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ALTITUDE_LOSS -> altitudeLossList.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_HORIZONTAL ->
|
||||
accuracyHorizontal.add(value);
|
||||
case KMLTrackExporter.EXTENDED_DATA_TYPE_ACCURACY_VERTICAL ->
|
||||
accuracyVertical.add(value);
|
||||
default ->
|
||||
Log.w(TAG, "Data from extended data " + dataType + " is not (yet) supported.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-16
@@ -40,9 +40,9 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||
* Manages connection to a Bluetooth LE sensor and subscribes for onChange-notifications.
|
||||
* Also parses the transferred data into {@link SensorDataObserver}.
|
||||
*/
|
||||
public abstract class BluetoothConnectionManager<DataType> {
|
||||
public abstract class AbstractBluetoothConnectionManager<DataType> {
|
||||
|
||||
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
|
||||
private static final String TAG = AbstractBluetoothConnectionManager.class.getSimpleName();
|
||||
|
||||
private final SensorDataObserver observer;
|
||||
|
||||
@@ -53,26 +53,22 @@ public abstract class BluetoothConnectionManager<DataType> {
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
||||
switch (newState) {
|
||||
case BluetoothProfile.STATE_CONNECTING:
|
||||
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
|
||||
break;
|
||||
case BluetoothProfile.STATE_CONNECTED:
|
||||
case BluetoothProfile.STATE_CONNECTING ->
|
||||
Log.i(TAG, "Connecting to sensor: " + gatt.getDevice());
|
||||
case BluetoothProfile.STATE_CONNECTED -> {
|
||||
Log.i(TAG, "Connected to sensor: " + gatt.getDevice() + "; discovering services.");
|
||||
|
||||
gatt.discoverServices();
|
||||
break;
|
||||
case BluetoothProfile.STATE_DISCONNECTING:
|
||||
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
||||
break;
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
}
|
||||
case BluetoothProfile.STATE_DISCONNECTING ->
|
||||
Log.i(TAG, "Disconnecting from sensor: " + gatt.getDevice());
|
||||
case BluetoothProfile.STATE_DISCONNECTED -> {
|
||||
//This is also triggered, if no connection was established (ca. 30s)
|
||||
Log.i(TAG, "Disconnected from sensor: " + gatt.getDevice() + "; trying to reconnect");
|
||||
if (gatt.connect()) {
|
||||
Log.e(TAG, "Could not trigger reconnect for sensor: " + gatt.getDevice());
|
||||
}
|
||||
clearData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,12 +132,12 @@ public abstract class BluetoothConnectionManager<DataType> {
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
|
||||
AbstractBluetoothConnectionManager(ServiceMeasurementUUID serviceUUUID, SensorDataObserver observer) {
|
||||
this.serviceMeasurementUUIDs = List.of(serviceUUUID);
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
BluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
|
||||
AbstractBluetoothConnectionManager(List<ServiceMeasurementUUID> serviceUUUID, SensorDataObserver observer) {
|
||||
this.serviceMeasurementUUIDs = serviceUUUID;
|
||||
this.observer = observer;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
/**
|
||||
* Estimates the altitude gain and altitude loss using the device's pressure sensor (i.e., barometer).
|
||||
*/
|
||||
public class AltitudeSumManager implements SensorEventListener {
|
||||
public class AltitudeSumManager implements SensorConnector, SensorEventListener {
|
||||
|
||||
private static final String TAG = AltitudeSumManager.class.getSimpleName();
|
||||
|
||||
|
||||
+3
-3
@@ -8,11 +8,11 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||
|
||||
public class BluetoothRemoteSensorManagerCyclingCadence extends BluetoothConnectionManager<Cadence> {
|
||||
public class BluetoothConnectionManagerCyclingCadence extends AbstractBluetoothConnectionManager<Cadence> {
|
||||
|
||||
private static final String TAG = BluetoothRemoteSensorManagerCyclingCadence.class.getSimpleName();
|
||||
private static final String TAG = BluetoothConnectionManagerCyclingCadence.class.getSimpleName();
|
||||
|
||||
BluetoothRemoteSensorManagerCyclingCadence(SensorDataObserver observer) {
|
||||
BluetoothConnectionManagerCyclingCadence(SensorDataObserver observer) {
|
||||
super(BluetoothUtils.CYCLING_CADENCE, observer);
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingDistanceSpeed;
|
||||
|
||||
public class BluetoothRemoteSensorManagerCyclingDistanceSpeed extends BluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
|
||||
public class BluetoothConnectionManagerCyclingDistanceSpeed extends AbstractBluetoothConnectionManager<SensorDataCyclingDistanceSpeed.Data> {
|
||||
|
||||
BluetoothRemoteSensorManagerCyclingDistanceSpeed(SensorDataObserver observer) {
|
||||
BluetoothConnectionManagerCyclingDistanceSpeed(SensorDataObserver observer) {
|
||||
super(BluetoothUtils.CYCLING_SPEED_CADENCE, observer);
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||
|
||||
public class BluetoothRemoteSensorManagerCyclingPower extends BluetoothConnectionManager<Power> {
|
||||
public class BluetoothConnectionManagerCyclingPower extends AbstractBluetoothConnectionManager<Power> {
|
||||
|
||||
BluetoothRemoteSensorManagerCyclingPower(@NonNull SensorDataObserver observer) {
|
||||
BluetoothConnectionManagerCyclingPower(@NonNull SensorDataObserver observer) {
|
||||
super(BluetoothUtils.CYCLING_POWER, observer);
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataHeartRate;
|
||||
|
||||
public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionManager<HeartRate> {
|
||||
public class BluetoothConnectionManagerHeartRate extends AbstractBluetoothConnectionManager<HeartRate> {
|
||||
|
||||
BluetoothRemoteSensorManagerHeartRate(@NonNull SensorDataObserver observer) {
|
||||
BluetoothConnectionManagerHeartRate(@NonNull SensorDataObserver observer) {
|
||||
super(BluetoothUtils.HEARTRATE, observer);
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataRunning;
|
||||
|
||||
public class BluetoothRemoteSensorManagerRunningSpeedAndCadence extends BluetoothConnectionManager<SensorDataRunning.Data> {
|
||||
public class BluetoothConnectionRunningSpeedAndCadence extends AbstractBluetoothConnectionManager<SensorDataRunning.Data> {
|
||||
|
||||
BluetoothRemoteSensorManagerRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
|
||||
BluetoothConnectionRunningSpeedAndCadence(@NonNull SensorDataObserver observer) {
|
||||
super(BluetoothUtils.RUNNING_SPEED_CADENCE, observer);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.SensorDataObserver {
|
||||
public class BluetoothRemoteSensorManager implements SensorConnector, AbstractBluetoothConnectionManager.SensorDataObserver {
|
||||
|
||||
private static final String TAG = BluetoothRemoteSensorManager.class.getSimpleName();
|
||||
|
||||
@@ -65,15 +65,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
|
||||
private Distance preferenceWheelCircumference;
|
||||
|
||||
private final BluetoothRemoteSensorManagerHeartRate heartRate = new BluetoothRemoteSensorManagerHeartRate(this);
|
||||
private final BluetoothRemoteSensorManagerCyclingCadence cyclingCadence = new BluetoothRemoteSensorManagerCyclingCadence(this);
|
||||
private final BluetoothRemoteSensorManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothRemoteSensorManagerCyclingDistanceSpeed(this);
|
||||
private final BluetoothRemoteSensorManagerCyclingPower cyclingPower = new BluetoothRemoteSensorManagerCyclingPower(this);
|
||||
private final BluetoothRemoteSensorManagerRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothRemoteSensorManagerRunningSpeedAndCadence(this);
|
||||
private final BluetoothConnectionManagerHeartRate heartRate = new BluetoothConnectionManagerHeartRate(this);
|
||||
private final BluetoothConnectionManagerCyclingCadence cyclingCadence = new BluetoothConnectionManagerCyclingCadence(this);
|
||||
private final BluetoothConnectionManagerCyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManagerCyclingDistanceSpeed(this);
|
||||
private final BluetoothConnectionManagerCyclingPower cyclingPower = new BluetoothConnectionManagerCyclingPower(this);
|
||||
private final BluetoothConnectionRunningSpeedAndCadence runningSpeedAndCadence = new BluetoothConnectionRunningSpeedAndCadence(this);
|
||||
|
||||
private final SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
|
||||
private final SensorDataSetChangeObserver observer;
|
||||
private final SensorManager.SensorDataSetChangeObserver observer;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
@@ -114,21 +114,23 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
}
|
||||
};
|
||||
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorDataSetChangeObserver observer) {
|
||||
public BluetoothRemoteSensorManager(@NonNull Context context, @NonNull Handler handler, @NonNull SensorManager.SensorDataSetChangeObserver observer) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
this.observer = observer;
|
||||
bluetoothAdapter = BluetoothUtils.getAdapter(context);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@Override
|
||||
public void start(Context context, Handler handler) {
|
||||
started = true;
|
||||
|
||||
//Registering triggers connection startup
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
@Override
|
||||
public synchronized void stop(Context context) {
|
||||
heartRate.disconnect();
|
||||
cyclingCadence.disconnect();
|
||||
cyclingSpeed.disconnect();
|
||||
@@ -145,7 +147,7 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
|
||||
}
|
||||
|
||||
private synchronized void connect(BluetoothConnectionManager<?> connectionManager, String address) {
|
||||
private synchronized void connect(AbstractBluetoothConnectionManager<?> connectionManager, String address) {
|
||||
if (!isEnabled()) {
|
||||
Log.w(TAG, "Bluetooth not enabled.");
|
||||
return;
|
||||
@@ -230,8 +232,4 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager.
|
||||
public Handler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public interface SensorDataSetChangeObserver {
|
||||
void onChange(SensorDataSet sensorDataSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
public interface SensorConnector {
|
||||
|
||||
void start(Context context, Handler handler);
|
||||
|
||||
void stop(Context context);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package de.dennisguse.opentracks.sensors;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorData;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
|
||||
public class SensorManager {
|
||||
|
||||
private static final String TAG = SensorManager.class.getSimpleName();
|
||||
|
||||
private BluetoothRemoteSensorManager bluetoothSensorManager;
|
||||
|
||||
private AltitudeSumManager altitudeSumManager;
|
||||
|
||||
public SensorManager(Context context, Handler handler, SensorDataSetChangeObserver observer) {
|
||||
bluetoothSensorManager = new BluetoothRemoteSensorManager(context, handler, observer);
|
||||
altitudeSumManager = new AltitudeSumManager();
|
||||
}
|
||||
|
||||
public void start(Context context, Handler handler) {
|
||||
bluetoothSensorManager.start(context, handler);
|
||||
altitudeSumManager.start(context, handler);
|
||||
}
|
||||
|
||||
public void stop(Context context) {
|
||||
if (bluetoothSensorManager != null) {
|
||||
bluetoothSensorManager.stop(context);
|
||||
bluetoothSensorManager = null;
|
||||
}
|
||||
|
||||
if (altitudeSumManager != null) {
|
||||
altitudeSumManager.stop(context);
|
||||
altitudeSumManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
public SensorDataSet fill(TrackPoint trackPoint) {
|
||||
altitudeSumManager.fill(trackPoint);
|
||||
return bluetoothSensorManager.fill(trackPoint);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (bluetoothSensorManager == null || altitudeSumManager == null) {
|
||||
Log.d(TAG, "No recording running and no reset necessary.");
|
||||
return;
|
||||
}
|
||||
bluetoothSensorManager.reset();
|
||||
altitudeSumManager.reset();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public BluetoothRemoteSensorManager getBluetoothSensorManager() {
|
||||
return bluetoothSensorManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setBluetoothSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
this.bluetoothSensorManager = remoteSensorManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public AltitudeSumManager getAltitudeSumManager() {
|
||||
return altitudeSumManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
|
||||
this.altitudeSumManager = altitudeSumManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public interface SensorDataSetChangeObserver {
|
||||
void onChange(SensorDataSet sensorDataSet);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public class UintUtils {
|
||||
* @return diff
|
||||
*/
|
||||
public static long diff(long a, long b, final long UINT_MAX) {
|
||||
|
||||
if (a < 0 || b < 0) {
|
||||
throw new RuntimeException("a or b cannot be less than zero.");
|
||||
}
|
||||
@@ -25,6 +26,6 @@ public class UintUtils {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
return (UINT_MAX - b) + a;
|
||||
return (UINT_MAX + 1 - b) + a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -32,16 +31,6 @@ public abstract class SensorData<T> {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getSensorAddress() {
|
||||
return sensorAddress;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSensorName() {
|
||||
return sensorName;
|
||||
}
|
||||
|
||||
public String getSensorNameOrAddress() {
|
||||
return sensorName != null ? sensorName : sensorAddress;
|
||||
}
|
||||
|
||||
+1
-2
@@ -76,9 +76,8 @@ public class SensorDataCyclingCadence extends SensorData<Cadence> {
|
||||
|
||||
@Override
|
||||
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()) {
|
||||
return getCrankRevolutionsCount() == comp.getCrankRevolutionsCount() && getCrankRevolutionsTime() == comp.getCrankRevolutionsTime();
|
||||
} else {
|
||||
|
||||
+1
-2
@@ -93,9 +93,8 @@ public class SensorDataCyclingDistanceSpeed extends SensorData<SensorDataCycling
|
||||
|
||||
@Override
|
||||
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())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
egm2008CorrectionManager = new EGM2008CorrectionManager();
|
||||
trackRecordingManager = new TrackRecordingManager(this);
|
||||
trackRecordingManager.start();
|
||||
trackPointCreator = new TrackPointCreator(this);
|
||||
trackPointCreator = new TrackPointCreator(this, this, handler);
|
||||
|
||||
voiceAnnouncementManager = new VoiceAnnouncementManager(this);
|
||||
notificationManager = new TrackRecordingServiceNotificationManager(this);
|
||||
@@ -177,7 +177,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
}
|
||||
|
||||
public void resumeTrack(Track.Id trackId) {
|
||||
trackPointCreator.reset();
|
||||
if (!trackRecordingManager.resumeExistingTrack(trackId, trackPointCreator)) {
|
||||
Log.w(TAG, "Cannot resume a non-existing track.");
|
||||
return;
|
||||
@@ -328,7 +327,7 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
TrackPoint trackPoint = data.second.first;
|
||||
egm2008CorrectionManager.correctAltitude(this, trackPoint);
|
||||
|
||||
localVoiceAnnouncementManager.update(data.first);
|
||||
localVoiceAnnouncementManager.update(this, data.first);
|
||||
|
||||
recordingDataObservable.postValue(new RecordingData(data.first, trackPoint, data.second.second));
|
||||
}
|
||||
|
||||
+3
-11
@@ -28,6 +28,7 @@ import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
@@ -67,17 +68,8 @@ public class VoiceAnnouncement {
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
Log.d(TAG, "Audio focus changed to " + focusChange);
|
||||
|
||||
boolean stop = false;
|
||||
switch (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;
|
||||
}
|
||||
boolean stop = List.of(AudioManager.AUDIOFOCUS_LOSS, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)
|
||||
.contains(focusChange);
|
||||
|
||||
if (stop && tts != null && tts.isSpeaking()) {
|
||||
tts.stop();
|
||||
|
||||
+11
-1
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
package de.dennisguse.opentracks.services.announcement;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.mediarouter.media.MediaRouter;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -73,12 +75,20 @@ public class VoiceAnnouncementManager implements SharedPreferences.OnSharedPrefe
|
||||
updateNextTaskDistance();
|
||||
}
|
||||
|
||||
public void update(@NonNull Track track) {
|
||||
public void update(@NonNull Context context, @NonNull Track track) {
|
||||
if (voiceAnnouncement == null) {
|
||||
Log.e(TAG, "Cannot update when in status shutdown.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PreferencesUtils.shouldVoiceAnnouncementOnDeviceSpeaker()
|
||||
&& MediaRouter.getInstance(context)
|
||||
.getSelectedRoute()
|
||||
.isDeviceSpeaker()) {
|
||||
Log.i(TAG, "No voice announcement on device speaker.");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean announce = false;
|
||||
this.trackStatistics = track.getTrackStatistics();
|
||||
if (trackStatistics.getTotalDistance().greaterThan(nextTotalDistance)) {
|
||||
|
||||
+7
-8
@@ -43,29 +43,28 @@ class VoiceAnnouncementUtils {
|
||||
String unitDistanceTTS;
|
||||
String unitSpeedTTS;
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
case METRIC -> {
|
||||
perUnitStringId = R.string.voice_per_kilometer;
|
||||
distanceId = R.plurals.voiceDistanceKilometers;
|
||||
speedId = R.plurals.voiceSpeedKilometersPerHour;
|
||||
unitDistanceTTS = "kilometer";
|
||||
unitSpeedTTS = "kilometer per hour";
|
||||
break;
|
||||
case IMPERIAL:
|
||||
}
|
||||
case IMPERIAL -> {
|
||||
perUnitStringId = R.string.voice_per_mile;
|
||||
distanceId = R.plurals.voiceDistanceMiles;
|
||||
speedId = R.plurals.voiceSpeedMilesPerHour;
|
||||
unitDistanceTTS = "mile";
|
||||
unitSpeedTTS = "mile per hour";
|
||||
break;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
}
|
||||
case NAUTICAL_IMPERIAL -> {
|
||||
perUnitStringId = R.string.voice_per_nautical_mile;
|
||||
distanceId = R.plurals.voiceDistanceNauticalMiles;
|
||||
speedId = R.plurals.voiceSpeedMKnots;
|
||||
unitDistanceTTS = "nautical mile";
|
||||
unitSpeedTTS = "knots";
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
double distanceInUnit = totalDistance.toKM_Miles(unitSystem);
|
||||
|
||||
+6
-5
@@ -20,14 +20,15 @@ import java.time.Instant;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.SensorConnector;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
import de.dennisguse.opentracks.util.PermissionRequester;
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public class GPSManager implements SensorConnector, LocationListenerCompat, GpsStatus.GpsStatusListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private final String TAG = GPSHandler.class.getSimpleName();
|
||||
private final String TAG = GPSManager.class.getSimpleName();
|
||||
|
||||
public static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
|
||||
|
||||
@@ -40,11 +41,11 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
|
||||
private Duration gpsInterval;
|
||||
private Distance thresholdHorizontalAccuracy;
|
||||
|
||||
public GPSHandler(TrackPointCreator trackPointCreator) {
|
||||
public GPSManager(TrackPointCreator trackPointCreator) {
|
||||
this.trackPointCreator = trackPointCreator;
|
||||
}
|
||||
|
||||
public void onStart(@NonNull Context context, @NonNull Handler handler) {
|
||||
public void start(@NonNull Context context, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
this.handler = handler;
|
||||
|
||||
@@ -61,7 +62,7 @@ public class GPSHandler implements LocationListenerCompat, GpsStatus.GpsStatusLi
|
||||
|
||||
@SuppressWarnings({"MissingPermission"})
|
||||
//TODO upgrade to AGP7.0.0/API31 started complaining about removeUpdates.
|
||||
public void onStop() {
|
||||
public void stop(Context context) {
|
||||
if (locationManager != null && context != null) {
|
||||
if (PermissionRequester.GPS.hasPermission(context)) {
|
||||
LocationManagerCompat.removeUpdates(locationManager, this);
|
||||
@@ -16,14 +16,13 @@ import java.time.ZoneId;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothRemoteSensorManager;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
|
||||
/**
|
||||
* Creates TrackPoints while recording by fusing data from different sensors (e.g., GNSS, barometer, BLE sensors).
|
||||
*/
|
||||
public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDataSetChangeObserver {
|
||||
public class TrackPointCreator implements SensorManager.SensorDataSetChangeObserver {
|
||||
|
||||
private static final String TAG = TrackPointCreator.class.getSimpleName();
|
||||
|
||||
@@ -34,45 +33,34 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
@NonNull
|
||||
private Clock clock = new MonotonicClock();
|
||||
|
||||
private final GPSHandler gpsHandler;
|
||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||
private AltitudeSumManager altitudeSumManager;
|
||||
private final GPSManager gpsManager;
|
||||
private SensorManager sensorManager;
|
||||
|
||||
public TrackPointCreator(Callback service) {
|
||||
public TrackPointCreator(Callback service, Context context, Handler handler) {
|
||||
this.service = service;
|
||||
this.gpsHandler = new GPSHandler(this);
|
||||
this.gpsManager = new GPSManager(this);
|
||||
this.sensorManager = new SensorManager(context, handler, this);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TrackPointCreator(GPSHandler gpsHandler, Callback service) {
|
||||
TrackPointCreator(GPSManager gpsManager, Callback service) {
|
||||
this.service = service;
|
||||
this.gpsHandler = gpsHandler;
|
||||
this.gpsManager = gpsManager;
|
||||
}
|
||||
|
||||
public synchronized void start(@NonNull Context context, @NonNull Handler handler) {
|
||||
this.context = context;
|
||||
|
||||
gpsHandler.onStart(context, handler);
|
||||
|
||||
remoteSensorManager = new BluetoothRemoteSensorManager(context, handler, this);
|
||||
altitudeSumManager = new AltitudeSumManager();
|
||||
|
||||
remoteSensorManager.start();
|
||||
altitudeSumManager.start(context, handler);
|
||||
|
||||
gpsManager.start(context, handler);
|
||||
sensorManager.start(context, handler);
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
return context != null;
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
if (remoteSensorManager == null || altitudeSumManager == null) {
|
||||
Log.d(TAG, "No recording running and no reset necessary.");
|
||||
return;
|
||||
}
|
||||
remoteSensorManager.reset();
|
||||
altitudeSumManager.reset();
|
||||
private synchronized void reset() {
|
||||
sensorManager.reset();
|
||||
}
|
||||
|
||||
private SensorDataSet addSensorData(TrackPoint trackPoint) {
|
||||
@@ -80,31 +68,12 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
Log.w(TAG, "Not started, should not be called.");
|
||||
return null;
|
||||
}
|
||||
SensorDataSet sensorDataSet = null;
|
||||
BluetoothRemoteSensorManager localRemoteSensorManager = remoteSensorManager;
|
||||
if (localRemoteSensorManager != null) {
|
||||
sensorDataSet = localRemoteSensorManager.fill(trackPoint);
|
||||
}
|
||||
AltitudeSumManager localAltitudeSumManager = altitudeSumManager;
|
||||
if (localAltitudeSumManager != null) {
|
||||
localAltitudeSumManager.fill(trackPoint);
|
||||
}
|
||||
|
||||
return sensorDataSet;
|
||||
return sensorManager.fill(trackPoint);
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
gpsHandler.onStop();
|
||||
|
||||
if (remoteSensorManager != null) {
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
|
||||
if (altitudeSumManager != null) {
|
||||
altitudeSumManager.stop(context);
|
||||
altitudeSumManager = null;
|
||||
}
|
||||
gpsManager.stop(context);
|
||||
|
||||
this.context = null;
|
||||
}
|
||||
@@ -125,7 +94,7 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
public void onNewTrackPoint(@NonNull TrackPoint trackPoint) {
|
||||
addSensorData(trackPoint);
|
||||
|
||||
boolean stored = service.newTrackPoint(trackPoint, gpsHandler.getThresholdHorizontalAccuracy());
|
||||
boolean stored = service.newTrackPoint(trackPoint, gpsManager.getThresholdHorizontalAccuracy());
|
||||
if (stored) {
|
||||
reset();
|
||||
}
|
||||
@@ -169,26 +138,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public AltitudeSumManager getAltitudeSumManager() {
|
||||
return altitudeSumManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setAltitudeSumManager(AltitudeSumManager altitudeSumManager) {
|
||||
this.altitudeSumManager = altitudeSumManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public BluetoothRemoteSensorManager getRemoteSensorManager() {
|
||||
return remoteSensorManager;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
this.remoteSensorManager = remoteSensorManager;
|
||||
public SensorManager getSensorManager() {
|
||||
return sensorManager;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -202,8 +153,8 @@ public class TrackPointCreator implements BluetoothRemoteSensorManager.SensorDat
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public GPSHandler getGpsHandler() {
|
||||
return gpsHandler;
|
||||
public GPSManager getGpsHandler() {
|
||||
return gpsManager;
|
||||
}
|
||||
|
||||
void sendGpsStatus(GpsStatusValue gpsStatusValue) {
|
||||
|
||||
@@ -68,18 +68,10 @@ public class DefaultsSettingsFragment extends PreferenceFragmentCompat implement
|
||||
|
||||
ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key));
|
||||
|
||||
int entriesId;
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
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");
|
||||
}
|
||||
int entriesId = switch (unitSystem) {
|
||||
case METRIC -> R.array.stats_rate_metric_options;
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> R.array.stats_rate_imperial_options;
|
||||
};
|
||||
|
||||
String[] entries = getResources().getStringArray(entriesId);
|
||||
statsRatePreferences.setEntries(entries);
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ImportExportSettingsFragment extends PreferenceFragmentCompat {
|
||||
DocumentFile directory = IntentUtils.toDocumentFile(getContext(), directoryUri);
|
||||
//Use same value for not set as Androidx ListPreference and EditTextPreference
|
||||
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));
|
||||
|
||||
@@ -32,14 +32,9 @@ class PreferencesOpenHelper {
|
||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
|
||||
for (int i = 1; i <= version; i++) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
break;
|
||||
case 2:
|
||||
upgradeFrom1to2();
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + version);
|
||||
case 1 -> upgradeFrom0to1();
|
||||
case 2 -> upgradeFrom1to2();
|
||||
default -> throw new RuntimeException("Not implemented: upgrade to " + version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,11 @@ public class PreferencesUtils {
|
||||
return getBoolean(R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean shouldVoiceAnnouncementOnDeviceSpeaker() {
|
||||
final boolean DEFAULT = resources.getBoolean(R.bool.voice_on_device_speaker_default);
|
||||
return getBoolean(R.string.voice_on_device_speaker_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static Duration getVoiceAnnouncementFrequency() {
|
||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
|
||||
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
|
||||
@@ -443,23 +448,21 @@ public class PreferencesUtils {
|
||||
|
||||
String displayValue = formatter.formatDistance(distance);
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
case METRIC -> {
|
||||
if (value == recordingDistanceIntervalDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
}
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||
if (value == recordingDistanceIntervalDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,23 +492,21 @@ public class PreferencesUtils {
|
||||
|
||||
String displayValue = formatter.formatDistance(distance);
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
case METRIC -> {
|
||||
if (value == maxRecordingDistanceDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
}
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||
if (value == maxRecordingDistanceDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,7 +565,7 @@ public class PreferencesUtils {
|
||||
|
||||
String displayValue = formatter.formatDistance(distance);
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
case METRIC -> {
|
||||
if (value == recordingGPSAccuracyDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_meter_recommended, value);
|
||||
} else if (value == recordingGPSAccuracyExcellent) {
|
||||
@@ -574,9 +575,8 @@ public class PreferencesUtils {
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
case NAUTICAL_IMPERIAL:
|
||||
}
|
||||
case IMPERIAL, NAUTICAL_IMPERIAL -> {
|
||||
if (value == recordingGPSAccuracyDefault) {
|
||||
entries[i] = resources.getString(R.string.value_integer_feet_recommended, (int) distance.toFT());
|
||||
} else if (value == recordingGPSAccuracyExcellent) {
|
||||
@@ -584,9 +584,8 @@ public class PreferencesUtils {
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,32 +611,30 @@ public class PreferencesUtils {
|
||||
float value = Float.parseFloat(entryValues[i]);
|
||||
|
||||
switch (unitSystem) {
|
||||
case METRIC:
|
||||
case METRIC -> {
|
||||
if (value == idleSpeedDefault) {
|
||||
entries[i] = resources.getString(R.string.value_float_kilometer_hour_recommended, value);
|
||||
} else {
|
||||
entries[i] = resources.getString(R.string.value_float_kilometer_hour, value);
|
||||
}
|
||||
break;
|
||||
case IMPERIAL:
|
||||
}
|
||||
case IMPERIAL -> {
|
||||
double valueMPH = Speed.ofKMH(value).toMPH();
|
||||
|
||||
if (value == idleSpeedDefault) {
|
||||
entries[i] = resources.getString(R.string.value_float_mile_hour_recommended, valueMPH);
|
||||
} else {
|
||||
entries[i] = resources.getString(R.string.value_float_mile_hour, valueMPH);
|
||||
}
|
||||
break;
|
||||
case NAUTICAL_IMPERIAL:
|
||||
}
|
||||
case NAUTICAL_IMPERIAL -> {
|
||||
double valueKnots = Speed.ofKMH(value).toKnots();
|
||||
if (value == idleSpeedDefault) {
|
||||
entries[i] = resources.getString(R.string.value_float_knots_recommended, valueKnots);
|
||||
} else {
|
||||
entries[i] = resources.getString(R.string.value_float_knots, valueKnots);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -20,7 +20,6 @@ import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter;
|
||||
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener {
|
||||
|
||||
@@ -41,7 +40,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
// Recycler view with visible stats.
|
||||
RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = recordingLayout.getName();
|
||||
recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||
recordingLayoutFieldsVisible = recordingLayout.toRecordingLayout(true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible);
|
||||
|
||||
numColumns = recordingLayout.getColumnsPerRow();
|
||||
@@ -90,7 +89,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
|
||||
|
||||
// Recycler view with not visible stats.
|
||||
recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||
recordingLayoutFieldsHidden = recordingLayout.toRecordingLayout(false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden);
|
||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
@@ -96,8 +96,7 @@ public class BluetoothLeAdapter extends BaseAdapter {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Device)) return false;
|
||||
Device device = (Device) o;
|
||||
if (!(o instanceof Device device)) return false;
|
||||
return address.equals(device.address);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,4 +87,14 @@ public class DataField implements Parcelable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, isVisible, isPrimary, isWide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DataField{" +
|
||||
"key='" + key + '\'' +
|
||||
", isVisible=" + isVisible +
|
||||
", isPrimary=" + isPrimary +
|
||||
", isWide=" + isWide +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ public class RecordingLayout implements Parcelable {
|
||||
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) {
|
||||
DataField dataFieldToMove = dataFields.remove(from);
|
||||
dataFields.add(to, dataFieldToMove);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package de.dennisguse.opentracks.ui.markers;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -244,7 +245,12 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
private void createMarkerWithPicture() {
|
||||
Pair<Intent, Uri> intentAndPhotoUri = MarkerUtils.createTakePictureIntent(this, getTrackId());
|
||||
cameraPhotoUri = intentAndPhotoUri.second;
|
||||
takePictureFromCamera.launch(intentAndPhotoUri.first);
|
||||
|
||||
try {
|
||||
takePictureFromCamera.launch(intentAndPhotoUri.first);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(this, R.string.no_compatible_camera_installed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void createMarkerWithGalleryImage() {
|
||||
@@ -256,6 +262,10 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
}
|
||||
intent.setType("image/*");
|
||||
takePictureFromGallery.launch(intent);
|
||||
try {
|
||||
takePictureFromGallery.launch(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(this, R.string.no_compatible_gallery_installed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
@@ -309,11 +311,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
|
||||
public void setSearch(String searchQuery) {
|
||||
this.searchQuery = searchQuery;
|
||||
restart();
|
||||
if (searchQuery != null) {
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(searchQuery);
|
||||
} else {
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.menu_markers));
|
||||
}
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.menu_markers)));
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
|
||||
@@ -7,8 +7,6 @@ import android.util.TypedValue;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class ThemeUtils {
|
||||
|
||||
private ThemeUtils() {
|
||||
@@ -42,14 +40,14 @@ public class ThemeUtils {
|
||||
}
|
||||
|
||||
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);
|
||||
typedArray.recycle();
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
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);
|
||||
typedArray.recycle();
|
||||
return fontSize;
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
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) {
|
||||
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context);
|
||||
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context, new ContentProviderUtils(context));
|
||||
|
||||
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
|
||||
if (exportDocumentFileUri == null) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
@@ -162,34 +163,8 @@ public class FileUtils {
|
||||
* @param character the character
|
||||
*/
|
||||
private static boolean isSpecialFat32(char character) {
|
||||
switch (character) {
|
||||
case '$':
|
||||
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;
|
||||
}
|
||||
return List.of('$', '%', '\'', '-', '_', '@', '~', '`', '!', '(', ')', '{', '}', '^', '#', '&', '+', ',', ';', '=', '[', ']', ' ')
|
||||
.contains(character);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,11 +4,8 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
|
||||
public class StatisticsUtils {
|
||||
|
||||
@@ -24,11 +21,4 @@ public class StatisticsUtils {
|
||||
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)
|
||||
).findFirst();
|
||||
|
||||
if (found.isEmpty()) {
|
||||
return ActivityType.UNKNOWN.getIconId();
|
||||
}
|
||||
return found.map(ActivityType::getIconId)
|
||||
.orElseGet(ActivityType.UNKNOWN::getIconId);
|
||||
|
||||
return found.get().getIconId();
|
||||
}
|
||||
|
||||
public static int getIconActivityType(String activityTypeId) {
|
||||
@@ -52,11 +50,9 @@ public class TrackIconUtils {
|
||||
it -> it.getId().equals(activityTypeId)
|
||||
).findFirst();
|
||||
|
||||
if (found.isEmpty()) {
|
||||
return ActivityType.UNKNOWN.getFirstLocalizedStringId();
|
||||
}
|
||||
return found.map(ActivityType::getFirstLocalizedStringId)
|
||||
.orElseGet(ActivityType.UNKNOWN::getFirstLocalizedStringId);
|
||||
|
||||
return found.get().getFirstLocalizedStringId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,10 +92,8 @@ public class TrackIconUtils {
|
||||
)
|
||||
.findFirst();
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return selected.map(ActivityType::isShowSpeedPreferred)
|
||||
.orElse(false);
|
||||
|
||||
return selected.get().isShowSpeedPreferred();
|
||||
}
|
||||
}
|
||||
|
||||
+26
-88
@@ -2,18 +2,15 @@ package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -68,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
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
// TODO Pace wont work for now
|
||||
boolean reportSpeed = true;
|
||||
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(reportSpeed).build(getContext());
|
||||
.setReportSpeedOrPace(reportSpeed)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
|
||||
@@ -88,7 +90,9 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
} else {
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.description_speed_source_gps));
|
||||
|
||||
String title = reportSpeed ? getContext().getString(R.string.stats_speed) : getContext().getString(R.string.stats_pace);
|
||||
getBinding().statsDescriptionMain.setText(title);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
@@ -96,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 {
|
||||
|
||||
@Override
|
||||
@@ -256,84 +274,4 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorHeartRate extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
//TODO Loads preference every time
|
||||
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
|
||||
int textColor;
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
|
||||
sensorName = sensorDataSet.getHeartRate().second;
|
||||
textColor = zones.getTextColorForZone(getContext(), sensorDataSet.getHeartRate().first);
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
|
||||
textColor = zones.getTextColorForZone(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
|
||||
getBinding().statsValue.setTextColor(textColor);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorCadence extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
|
||||
sensorName = sensorDataSet.getCadence().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorPower extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
|
||||
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
|
||||
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_speed_key), GenericStatisticsViewHolder.SpeedOrPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.SpeedOrPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_speed_key), GenericStatisticsViewHolder.SpeedVH::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_speed_key), GenericStatisticsViewHolder.AverageSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_max_speed_key), GenericStatisticsViewHolder.MaxSpeed::new);
|
||||
@@ -31,9 +31,9 @@ public class Mapping {
|
||||
m.put(context.getString(R.string.stats_custom_layout_loss_key), GenericStatisticsViewHolder.Loss::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), GenericStatisticsViewHolder.SensorHeartRate::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_cadence_key), GenericStatisticsViewHolder.SensorCadence::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_power_key), GenericStatisticsViewHolder.SensorPower::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), SensorStatisticsViewHolder.SensorHeartRate::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_cadence_key), SensorStatisticsViewHolder.SensorCadence::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_power_key), SensorStatisticsViewHolder.SensorPower::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
|
||||
|
||||
return m;
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.databinding.StatsSensorItemBinding;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public abstract class SensorStatisticsViewHolder extends StatisticViewHolder<StatsSensorItemBinding> {
|
||||
|
||||
@Override
|
||||
protected StatsSensorItemBinding createViewBinding(LayoutInflater inflater) {
|
||||
return StatsSensorItemBinding.inflate(inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureUI(DataField dataField) {
|
||||
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
||||
}
|
||||
|
||||
public static class SensorHeartRate extends SensorStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
|
||||
sensorName = sensorDataSet.getHeartRate().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
|
||||
}
|
||||
|
||||
//TODO Loads preference every time
|
||||
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
|
||||
int textColor;
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
textColor = zones.getTextColorForZone(getContext(), sensorDataSet.getHeartRate().first);
|
||||
} else {
|
||||
textColor = zones.getTextColorForZone(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
|
||||
getBinding().statsValue.setTextColor(textColor);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorCadence extends SensorStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
|
||||
sensorName = sensorDataSet.getCadence().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorPower extends SensorStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
|
||||
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
|
||||
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_value"
|
||||
style="@style/TextAppearance.OpenTracks.PrimaryValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:value="@string/value_unknown"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_unit"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="90" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_unit"
|
||||
style="@style/TextAppearance.OpenTracks.PrimaryUnit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@id/stats_value"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_value"
|
||||
tools:text="@string/sensor_unit_beats_per_minute" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_description_main"
|
||||
style="@style/TextAppearance.OpenTracks.PrimaryHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_value"
|
||||
tools:text="@string/sensor_state_heart_rate" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_description_secondary"
|
||||
style="@style/TextAppearance.OpenTracks.SecondaryHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_description_main"
|
||||
tools:text="90" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -569,4 +569,54 @@ limitations under the License.
|
||||
<string name="settings_public_api_dashboard_enabled_summary_off">Zaznamenaná data nebudou automaticky sdílena s jinými aplikacemi.</string>
|
||||
<string name="pace">Tempo</string>
|
||||
<string name="track_discarding">Mazání trasy</string>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">1 uzel</item>
|
||||
<item quantity="few">%1$.1f uzly</item>
|
||||
<item quantity="other">%1$.1f uzlů</item>
|
||||
</plurals>
|
||||
<string name="error_crash_title">%1$s selhal</string>
|
||||
<string name="share_crash_report">Sdílet hlášení o selhání</string>
|
||||
<string name="voice_on_device_speaker_title">Použít reproduktor zařízení</string>
|
||||
<string name="description_pace_nautical">Tempo (min/NM)</string>
|
||||
<string name="voice_per_nautical_mile">za námořní míli</string>
|
||||
<string name="settings_announcements_average_speed_pace">Průměrná rychlost/tempo</string>
|
||||
<string name="settings_announcements_average_heart_rate">Průměrný srdeční tep</string>
|
||||
<string name="value_float_knots">%1$.1f uzlů</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d stop</string>
|
||||
<string name="image_discard">Vyřadit</string>
|
||||
<string name="show_on_map_format_ask">Zeptat se</string>
|
||||
<string name="settings_stats_units_nautical">Námořní (NM, ft)</string>
|
||||
<string name="report_bug">Nahlásit chybu</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Srdeční tep kola</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks sám o sobě mapu neposkytuje. Pro zobrazení záznamů na mapě si nainstalujte OSMDashboard.</string>
|
||||
<string name="select_show_on_map_behavior">Zvolte chování \"zobrazit na mapě\"</string>
|
||||
<plurals name="voiceDistanceMiles">
|
||||
<item quantity="one">1 míle</item>
|
||||
<item quantity="few">%1$.1f míle</item>
|
||||
<item quantity="other">%1$.1f mil</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceKilometers">
|
||||
<item quantity="one">1 kilometr</item>
|
||||
<item quantity="few">%1$.1f kilometry</item>
|
||||
<item quantity="other">%1$.1f kilometrů</item>
|
||||
</plurals>
|
||||
<string name="always">Vždy</string>
|
||||
<string name="help_data_google_play_services_content">Ne</string>
|
||||
<string name="description_speed_nautical">Rychlost (uzly)</string>
|
||||
<string name="settings_export_filename_title">Formát názvu souboru</string>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">1 námořní míle</item>
|
||||
<item quantity="few">%1$.1f námořní míle</item>
|
||||
<item quantity="other">%1$.1f námořních mil</item>
|
||||
</plurals>
|
||||
<string name="show_on_dashboard_not_installed">Není nainstalována žádná aplikace podporující OpenTracks Dashboard API.</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Rychlost/tempo kola</string>
|
||||
<string name="settings_sensor_heart_rate_max">Zóny: maximální srdeční tep (bpm)</string>
|
||||
<string name="settings_api_summary">Vzdálené ovládání a rozhraní API pro statistiky pro jiné aplikace</string>
|
||||
<string name="settings_public_api_package_title">Název balíčku pro veřejné rozhraní API</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<string name="unit_knots">kn</string>
|
||||
<string name="unit_minute_per_nautical_mile">min/NM</string>
|
||||
<string name="value_integer_knots">%1$d uzlů</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f uzlů (doporučeno)</string>
|
||||
</resources>
|
||||
@@ -14,40 +14,38 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--><resources>
|
||||
<string name="about_preference_title">Acerca de OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> é unha aplicación para rexistrar as túas actividades deportivas que respecta a túa privacidade.</string>
|
||||
<string name="about_preference_title">Sobre OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> é unha aplicación para rexistrar actividades deportivas que respecta a túa privacidade.</string>
|
||||
<string name="about_url">Web do proxecto: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Nome da versión: %1$s</string>
|
||||
<string name="about_version_code">Código da versión: %1$d</string>
|
||||
<string name="about_history">OpenTracks é un fork de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a>(abandonado en 2016).</string>
|
||||
<string name="about_history">OpenTracks é unha bifurcación de <a href="https://code.google.com/archive/p/mytracks/"><i>MyTracks de Google</i></a> (abandonado en 2016).</string>
|
||||
<string name="about_libraries">Bibliotecas</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks ten as seguintes funcións:</string>
|
||||
<string name="help_feature_content">
|
||||
* crea rutas GPS\n
|
||||
* usa monitor do pulso cardíaco (só Bluetooth LE)\n
|
||||
* anuncios por voz (de tempo ou distancia)\n
|
||||
* crea marcadores (incl. fotos)\n
|
||||
* funciona sen precisar conexión a rede\n
|
||||
* mostra as rutas nun mapa (ver abaixo)
|
||||
</string>
|
||||
<string name="help_track_map_title">¿Como mostrar a ruta nun mapa?</string>
|
||||
<string name="help_feature_title">Características de OpenTracks:</string>
|
||||
<string name="help_feature_content">* crear rutas GPS
|
||||
\n * usar monitor do pulso cardíaco (só Bluetooth LE)
|
||||
\n * anuncios por voz (de tempo ou distancia)
|
||||
\n * crear marcadores (incl. fotos)
|
||||
\n * funciona sen precisar conexión á rede
|
||||
\n * mostra as rutas nun mapa (ver abaixo)</string>
|
||||
<string name="help_track_map_title">Como mostrar a ruta no mapa\?</string>
|
||||
<string name="help_track_map_content"> Usa a icona do mapa pequeno.
|
||||
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard"> OSMDashboard </a>. Ofrece mapas en liña ou fóra de liña a través de <a href="https://www.openstreetmap.org"> OpenStreetMap </a>.
|
||||
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Proporciona mapas en liña ou sen conexión grazas a <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n En realidade, OSMDashboard é unha aplicación independente que só pode acceder (temporalmente) aos datos das pistas que se amosarán.
|
||||
\n b) A través dunha aplicación de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicacións precisan admitir o formato de ficheiro KMZ (só localizacións e marcas de tempo). De feito, unha aplicación ten que admitir <i> ACTION_VIEW </i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_storage_pictures_title">¿De que xeito garda OpenTracks as fotos tomadas como marcadores?</string>
|
||||
<string name="help_storage_pictures_content">Gárdanse no almacenaxe interno da app (só accesible desde OpenTracks).</string>
|
||||
<string name="help_track_export_title">¿De que xeito exporto/importo as rutas gravadas?</string>
|
||||
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As ruts compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">¿OpenTracks precisa/utiliza Google Play Service?</string>
|
||||
<string name="help_recording_title">¿Como grava OpenTracks os datos?</string>
|
||||
\n b) A través dunha aplicación de mapa normal (por exemplo, <a href="http://osmand.net/">OsmAnd</a> e <a href="https://maps.me/">MAPS.ME</a>). Estas aplicacións precisan admitir o formato de ficheiro KMZ (só localizacións e marcas de tempo). De feito, unha aplicación ten que admitir <i>ACTION_VIEW</i> con MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_storage_pictures_title">De que xeito garda OpenTracks as fotos tomadas como marcadores\?</string>
|
||||
<string name="help_storage_pictures_content">Gárdanse na almacenaxe interna da app (só accesible desde OpenTracks).</string>
|
||||
<string name="help_track_export_title">De que xeito exporto/importo as rutas gravadas\?</string>
|
||||
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">OpenTracks precisa/utiliza Google Play Service\?</string>
|
||||
<string name="help_recording_title">Como grava OpenTracks os datos\?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks usa o sensor GPS do dispositivo para gravar a localización de xeito contínuo.
|
||||
Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, elevación), OpenTracks non pode facer nada ao respecto.</string>
|
||||
<string name="help_data_storage_title">De que xeito garda OpenTracks os datos dos sensores\?</string>
|
||||
<string name="help_data_storage_content"> Os datos dos sensores gárdanse asociados á localización. Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). </string>
|
||||
<string name="help_gps_troubleshooting_title">¿OpenTracks non grava as localizacións?</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks non grava as localizacións\?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Podes intentar:
|
||||
\n a) mira que o GPS esté activado,
|
||||
\n b) mellora a recepción GPS (ex. sae ao exterior),
|
||||
@@ -55,7 +53,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
\n d) desactiva o aforro de enerxía.
|
||||
\n
|
||||
\n Se isto non axuda, infórmanos do problema. </string>
|
||||
<string name="help_support_title">¿Como podo axudar a OpenTracks?</string>
|
||||
<string name="help_support_title">Como podo axudar a OpenTracks\?</string>
|
||||
<string name="help_support_content"> Podes:
|
||||
\n a) falar de OpenTracks coas túas amizades,
|
||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">propoñer novas funcións</a>,
|
||||
@@ -154,7 +152,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="description_recorded_time">Hora da gravación: %1$s</string>
|
||||
<string name="description_speed_ms">Velocidade (m/s)</string>
|
||||
<string name="description_sensor_cadence">Cadencia (rpm)</string>
|
||||
<string name="description_sensor_heart_rate">Frecuencia cardíaca (pulsacións por minuto)</string>
|
||||
<string name="description_sensor_heart_rate">Pulso cardíaco (latexos por minuto)</string>
|
||||
<string name="description_sensor_power">Potencia (W)</string>
|
||||
<string name="description_default_speed_or_pace">Por tipo de actividade</string>
|
||||
<string name="description_speed_imperial">Velocidade (mi/h)</string>
|
||||
@@ -233,8 +231,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="permission_gps_failed">OpenTracks precisa permiso para usar o GPS.</string>
|
||||
<!-- Search -->
|
||||
<!-- Sensor State -->
|
||||
<string name="sensor_state_heart_rate">Frecuencia cardíaca</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
||||
<string name="sensor_state_heart_rate">Pulso cardíaco</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d lpm</string>
|
||||
<string name="sensor_state_power">Potencia</string>
|
||||
<string name="sensor_could_not_scan">Non se escanearon dispositivos Bluetooth (fallo %1$i).</string>
|
||||
<string name="bluetooth_disabled">Activa o Bluetooth.</string>
|
||||
@@ -508,10 +506,10 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recomendado)</string>
|
||||
<string name="stats_clock">Reloxo</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_defaults_summary">O Preferido Unidades, Por Defecto Actividade, Controlar O Nome Por Defecto</string>
|
||||
<string name="settings_defaults_summary">Unidades preferidas, Actividade por defecto, Nome da ruta</string>
|
||||
<string name="settings_announcements_summary">A Distancia, Tempo, Voz Velocidade</string>
|
||||
<string name="settings_open_tracks_summary">Todo Sobre OpenTracks Proxecto</string>
|
||||
<string name="settings_defaults_title">Por defecto Unidades e Actividade</string>
|
||||
<string name="settings_defaults_title">Unidades e Actividade por defecto</string>
|
||||
<string name="settings_ui_title">Interface da app</string>
|
||||
<string name="settings_ui_summary">Aparencia, Gravación, Deseño, Tema</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
@@ -523,7 +521,7 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="settings_import_export_summary">Importación, Exportación, Auto Exportación</string>
|
||||
<string name="settings_units_category">Unidades</string>
|
||||
<string name="settings_activity_defaults_category">Valores por defecto</string>
|
||||
<string name="settings_reset_summary">Axustar a Configuración de Valores por Defecto</string>
|
||||
<string name="settings_reset_summary">Restablecer Axustes dos Valores</string>
|
||||
<string name="menu_select_layout">Elixe disposición</string>
|
||||
<string name="custom_layout_list_title">Disposicións</string>
|
||||
<string name="custom_layout_list_edit_name">Escribe o nome da nova disposición</string>
|
||||
@@ -576,8 +574,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="gps_recording_status">%1$s: %2$s</string>
|
||||
<string name="gps_recording_without_signal">OpenTracks está gravando sen datos GPS.</string>
|
||||
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Ritmo cardíaco na volta</string>
|
||||
<string name="settings_announcements_average_heart_rate">Ritmo cardíaco medio</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Pulso durante a volta</string>
|
||||
<string name="settings_announcements_average_heart_rate">Pulso cardíaco medio</string>
|
||||
<string name="image_discard">Descartar</string>
|
||||
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo medio</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo da volta</string>
|
||||
@@ -611,4 +609,8 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="settings_public_api_package_title">Nome do paquete para Public API</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d rutas</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks non proporciona un mapa. Instala OSMDashboard para ver as gravacións nun mapa.</string>
|
||||
<string name="help_data_google_play_services_content">Non</string>
|
||||
<string name="settings_sensor_heart_rate_max">Zonas: Pulso máximo (lpm)</string>
|
||||
<string name="settings_api_summary">Control remoto e API de estatísticas para outras apps</string>
|
||||
<string name="voice_on_device_speaker_title">Usar o altofalante do dispositivo</string>
|
||||
</resources>
|
||||
@@ -603,4 +603,6 @@ limitations under the License.
|
||||
<string name="settings_public_api_package_title">Pakkenavn for offentlig API</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks sørger ikke selv for kart. Installer OSMDashboard for å vise opptakene dine på et kart.</string>
|
||||
<string name="help_data_google_play_services_content">Nei</string>
|
||||
<string name="settings_sensor_heart_rate_max">Soner: Makspuls (bpm)</string>
|
||||
<string name="settings_api_summary">Fjernkontroll- og statistikk-API for andre programmer</string>
|
||||
</resources>
|
||||
@@ -38,7 +38,7 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_content">Deze worden opgeslagen in de interne opslag van de app (d.w.z. alleen toegankelijk binnen OpenTracks).</string>
|
||||
<string name="help_track_export_title">Hoe kan ik opgenomen tracks exporteren/delen?</string>
|
||||
<string name="help_track_export_content"> Tracks kunnen worden geëxporteerd vanuit de instellingen als KMZ, KML of GPX. KMZ/KML alle gegevens; GPX alleen locaties. Tracks die worden gedeeld via het <i>share-icoontje</i> worden gedeeld als KMZ </string>
|
||||
<string name="help_data_google_play_services_title">Heeft OpenTracks Google Play Service nodig/gebruikt?</string>
|
||||
<string name="help_data_google_play_services_title">Gebruikt/vereist OpenTracks Google Play Service\?</string>
|
||||
<string name="help_recording_title">Hoe legt OpenTracks gegevens vast?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks gebruikt de GPS-sensor van het apparaat om de locatie continu te registreren.
|
||||
@@ -264,18 +264,18 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_stats_units_metric">Metrisch (km, m)</string>
|
||||
<string name="settings_stats_units_title">Voorkeurseenheden</string>
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">Ik denk dat u misschien wel geïnteresseerd bent in deze route.
|
||||
<string name="share_track_share_file_body">Misschien ben je geïnteresseerd in deze route.
|
||||
\n
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Ik wil graag een route met u delen</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Gem. verplaatsingstempo</string>
|
||||
<string name="stats_average_moving_speed">Gem. verplaatsingssnelheid</string>
|
||||
<string name="stats_average_moving_pace">Gem. bewegingstempo</string>
|
||||
<string name="stats_average_moving_speed">Gem. bewegingssnelheid</string>
|
||||
<string name="stats_average_pace">Gem. tempo</string>
|
||||
<string name="stats_average_speed">Gem. snelheid</string>
|
||||
<string name="stats_altitude">Hoogte</string>
|
||||
<string name="stats_fastest_pace">Snelste tempo</string>
|
||||
<string name="stats_gain">Stijging</string>
|
||||
<string name="stats_gain">Gestegen</string>
|
||||
<string name="stats_latitude">Breedtegraad</string>
|
||||
<string name="stats_longitude">Lengtegraad</string>
|
||||
<string name="stats_max_speed">Maximum snelheid</string>
|
||||
@@ -287,7 +287,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<!-- Track -->
|
||||
<string name="track_name_format">Route %1$d</string>
|
||||
<string name="track_recording_notification_accuracy">Locatie nauwkeurigheid: %1$s</string>
|
||||
<string name="track_markers">Markeringen voor %1$s</string>
|
||||
<string name="track_markers">%1$s Markeringen</string>
|
||||
<!-- Track Delete -->
|
||||
<string name="track_delete_one_confirm_message">De route en de bijbehorende markeringen worden definitief verwijderd van het apparaat.</string>
|
||||
<string name="track_delete_one_confirm_title">Route verwijderen?</string>
|
||||
@@ -361,7 +361,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="waypoint_type_drinking_water">drinkwater</string>
|
||||
<string name="waypoint_type_falling_rocks">vallende stenen</string>
|
||||
<string name="waypoint_type_ferry">veerboot</string>
|
||||
<string name="waypoint_type_fire_place">open haard</string>
|
||||
<string name="waypoint_type_fire_place">vuurplaats</string>
|
||||
<string name="waypoint_type_fire_station">brandweerkazerne</string>
|
||||
<string name="waypoint_type_fishing">vissen</string>
|
||||
<string name="waypoint_type_gas_station">tankstation</string>
|
||||
@@ -369,7 +369,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="waypoint_type_grocery_store">kruidenier</string>
|
||||
<string name="waypoint_type_hair_dresser">kapper</string>
|
||||
<string name="waypoint_type_harbor">haven</string>
|
||||
<string name="waypoint_type_hiking_path">trektochtpad</string>
|
||||
<string name="waypoint_type_hiking_path">wandelroute</string>
|
||||
<string name="waypoint_type_hotel">hotel</string>
|
||||
<string name="waypoint_type_house">huis</string>
|
||||
<string name="waypoint_type_information">informatie</string>
|
||||
@@ -390,10 +390,10 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="waypoint_type_swimming_pool">zwembad</string>
|
||||
<string name="waypoint_type_telephone">telefoon</string>
|
||||
<string name="waypoint_type_theater">schouwburg</string>
|
||||
<string name="waypoint_type_toilet">restroom</string>
|
||||
<string name="waypoint_type_toilet">toilet</string>
|
||||
<string name="waypoint_type_tourist_information">toeristische informatie</string>
|
||||
<string name="waypoint_type_tree">boom</string>
|
||||
<string name="waypoint_type_tunnel">schacht</string>
|
||||
<string name="waypoint_type_tunnel">tunnel</string>
|
||||
<string name="waypoint_type_waterfall">waterval</string>
|
||||
<string name="waypoint_type_water_fountain">waterfontein</string>
|
||||
<string name="waypoint_type_waypoint">routepunt</string>
|
||||
@@ -416,7 +416,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="import_no_kml_file_found">Geen doc.kml bestand gevonden in KMZ</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">Volledig scherm</string>
|
||||
<string name="gps_fixed_and_ready">GPS vast en klaar</string>
|
||||
<string name="aggregated_stats_empty_message">Neem uw eerste track op om geaggregeerde statistieken te zien</string>
|
||||
<string name="aggregated_stats_empty_message">Neem uw eerste route op om geaggregeerde statistieken te zien</string>
|
||||
<string name="track_detail_intervals_tab">Intervallen</string>
|
||||
<string name="settings_sensor_wheel_circumference">Wielomtrek (mm)</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Voorkom herinvoer van tracks</string>
|
||||
@@ -435,7 +435,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_night_mode_option_day">Dag</string>
|
||||
<string name="settings_night_mode_option_system">Systeem</string>
|
||||
<string name="settings_night_mode_title">UI Thema</string>
|
||||
<string name="stats_loss">Verlies</string>
|
||||
<string name="stats_loss">Gedaald</string>
|
||||
<string name="interval_list_empty_message">Er zijn nog geen intervallen</string>
|
||||
<string name="stats_split_interval">Gesplitst interval</string>
|
||||
<string name="instant_export_enabled_title">Directe uitvoer na de training</string>
|
||||
@@ -460,7 +460,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="import_unable_to_import_file">Kan bestand niet importeren: %1$s</string>
|
||||
<string name="import_error_info">%1$s: %2$s</string>
|
||||
<string name="import_file_imported">Bestand %1$s geimporteerd</string>
|
||||
<string name="hold_to_stop">Hou vast om te stoppen</string>
|
||||
<string name="hold_to_stop">Indrukken om te stoppen</string>
|
||||
<string name="track_speed_notification">Snelheid: %1$s</string>
|
||||
<string name="track_distance_notification">Afstand: %1$s</string>
|
||||
<string name="track_pace_notification">Tempo: %1$s</string>
|
||||
@@ -478,7 +478,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_announcements_summary">Tijd, afstand, spraaksnelheid</string>
|
||||
<string name="generic_yesterday">Gisteren</string>
|
||||
<string name="settings_ui_title">User Interface</string>
|
||||
<string name="stats_coordinates">Breedte- /Lengtegraad</string>
|
||||
<string name="stats_coordinates">Coördinaten</string>
|
||||
<string name="settings_announcements_distance_frequency">Afstandsinterval</string>
|
||||
<string name="settings_recording_idle_speed_title">Drempelwaarde stationair</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/u</string>
|
||||
@@ -494,9 +494,9 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_announcements_totaltime_frequency">Tijdsinterval</string>
|
||||
<string name="field_set_secondary">Secondair</string>
|
||||
<string name="field_remove_from_layout">Verwijderen</string>
|
||||
<string name="settings_public_api_dashboard_enabled_title">Automatische overdracht van gegevens</string>
|
||||
<string name="settings_public_api_dashboard_enabled_title">Automatische data overdracht</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_on">Een app die een opname start, heeft ook toegang tot de opgenomen gegevens.</string>
|
||||
<string name="stats_sensors_cadence">Cadans</string>
|
||||
<string name="stats_sensors_cadence">Ritme</string>
|
||||
<string name="stats_sensors_heart_rate">Hartslag</string>
|
||||
<string name="settings_layout_reset">Aangepaste lay-out terugzetten</string>
|
||||
<string name="settings_layout_reset_confirm_message">Alle lay-out instellingen worden teruggezet naar de standaardwaarden. Tracks worden niet gewist.</string>
|
||||
@@ -538,7 +538,7 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_public_api_disabled_toast">OpenTracks Publieke API is uitgeschakeld: inschakelen kan onder instellingen.</string>
|
||||
<string name="stats_clock">Klok</string>
|
||||
<string name="aggregated_stats_filter_no_results">Er zijn geen activiteiten voor het aangegeven filter</string>
|
||||
<string name="no_compatible_file_manager_installed">Geen ondersteunde bestandsbeheerder geïnstalleerd</string>
|
||||
<string name="no_compatible_file_manager_installed">Geen geschikte bestandsbeheerder geïnstalleerd</string>
|
||||
<string name="generic_undo">Terugzetten</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_enabled">Alleen Bluetooth-apparaten tonen die gevraagde diensten melden</string>
|
||||
<string name="settings_activity_defaults_category">Standaardinstellingen van een activiteit</string>
|
||||
@@ -586,4 +586,27 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="always">Altijd</string>
|
||||
<string name="show_on_map_format_ask">Vragen</string>
|
||||
<string name="select_show_on_map_behavior">Selecteer \"toon op kaart\"-gedrag</string>
|
||||
<string name="show_on_dashboard_not_installed">Er is geen app geïnstalleerd die OpenTracks Dashboard API ondersteunt.</string>
|
||||
<string name="help_data_google_play_services_content">Nee</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks zelf biedt geen kaart. Installeer OSMDashboard om uw opnames op een kaart te bekijken.</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">1 knoop</item>
|
||||
<item quantity="other">%1$.1f knopen</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">1 zeemijl</item>
|
||||
<item quantity="other">%1$.1f zeemijlen</item>
|
||||
</plurals>
|
||||
<string name="value_integer_knots">%1$d knopen</string>
|
||||
<string name="voice_per_nautical_mile">per zeemijl</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d routes</string>
|
||||
<string name="voice_on_device_speaker_title">Gebruik toestelspeaker</string>
|
||||
<string name="settings_public_api_package_title">Pakketnaam voor openbare API</string>
|
||||
<string name="settings_stats_units_nautical">Nautisch (NM, ft)</string>
|
||||
<string name="settings_api_summary">Afstandsbediening en statistieken API voor andere apps</string>
|
||||
<string name="settings_sensor_heart_rate_max">Zones: maximum Hartslag (bpm)</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f knopen (aanbevolen)</string>
|
||||
<string name="value_float_knots">%1$.1f knopen</string>
|
||||
<string name="description_speed_nautical">Snelheid (knopen)</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--><resources>
|
||||
<string name="about_preference_title">關於 OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> 是一個完全尊重您的隱私的運動蹤跡應用程式。</string>
|
||||
<string name="about_url">專案網頁:<a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">版本名稱:%1$s</string>
|
||||
<string name="about_version_code">版本代號:%1$d</string>
|
||||
<string name="about_history">OpenTracks 是一個 <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> 的分支 (於 2016 年停止維護)。</string>
|
||||
<string name="about_third_party_data">第三方資料</string>
|
||||
<string name="about_libraries">函式庫</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks 擁有以下功能:</string>
|
||||
<string name="help_feature_content">* 建立 GPS 蹤跡
|
||||
\n * 使用一個心率感測器 (僅限藍牙 LE)
|
||||
\n * 語音播報 (依時間或距離)
|
||||
\n * 建立標記 (包含圖片)
|
||||
\n * 完全離線工作
|
||||
\n * 在地圖上顯示蹤跡 (見下文)</string>
|
||||
<string name="help_track_map_title">如何在地圖顯示一個蹤跡?</string>
|
||||
<string name="help_track_map_content">
|
||||
使用小的地圖圖示。\n
|
||||
a)
|
||||
(即時更新) 安裝 <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>.
|
||||
透過 <a href="https://www.openstreetmap.org">OpenStreetMap</a> 提供線上或離線地圖。\n
|
||||
實際上,OSMDashboard 是一個單獨的應用程式,只能 (暫時) 存取將要顯示的蹤跡資料。
|
||||
|
||||
\n
|
||||
b)
|
||||
透過一個 "普通的" 地圖應用程式 (例如,<a href="http://osmand.net/">OsmAnd</a> 與 <a href="https://maps.me/">MAPS.ME</a>)。
|
||||
這些 APP 需要支援 KMZ 檔案格式 (只有位置與時間戳記)。
|
||||
事實上,APP 需要支援 <i>ACTION_VIEW</i> 與 MIME <i>application/vnd.google-earth.kmz</i>。
|
||||
</string>
|
||||
<string name="help_storage_pictures_title">OpenTracks 如何存儲作為標記拍攝的照片?</string>
|
||||
<string name="help_storage_pictures_content">這些儲存在應用程序內部儲存區中 (即,只能在 OpenTracks 中存取)。</string>
|
||||
<string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
|
||||
<string name="help_track_export_content">
|
||||
可以從設定匯出蹤跡為 KMZ、KML 或 GPX。
|
||||
KMZ/KML 全部資料;GPX 只有位置。
|
||||
通過<i>分享圖示</i>以分享蹤跡為 KMZ。
|
||||
</string>
|
||||
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</string>
|
||||
<string name="help_data_google_play_services_content">否</string>
|
||||
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks 使用裝置的 GPS 感測器以連續紀錄位置。
|
||||
如果 GPS 裝置回報的資料不准確 (例如,位置、速度、海拔),OpenTracks 對此無能為力。
|
||||
</string>
|
||||
<string name="help_data_storage_title">OpenTracks 如何儲存感測器資料?</string>
|
||||
<string name="help_data_storage_content">
|
||||
感測器資料儲存在每個記錄的位置。
|
||||
就心率感測器而言,這意味著只有<i>一個</i>心率測量值被依照位置儲存。
|
||||
如果您是站著不動或在室內,沒有感測器資料會被記錄 (但它會被顯示)。
|
||||
</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks 沒有紀錄位置?</string>
|
||||
<string name="help_gps_troubleshooting_content">
|
||||
您能做什麼:\n
|
||||
a) 檢查 GPS 是否啟用,\n
|
||||
b) 改善 GPS 收訊 (例如,前往戶外),\n
|
||||
c) 使用 <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> app 檢查,以及\n
|
||||
d) 禁用省電。\n
|
||||
\n
|
||||
如果一切都沒有幫助,請回報 bug。
|
||||
</string>
|
||||
<string name="help_support_title">我如何支持 OpenTracks?</string>
|
||||
<string name="help_support_content">
|
||||
您可以:\n
|
||||
a) 推廣 OpenTracks,\n
|
||||
b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">提出建議</a>,\n
|
||||
c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">回報 bugs</a>,\n
|
||||
d) 幫助軟體開發,\n
|
||||
e) 在 <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play 商店</a> 購買它,\n
|
||||
f) 贊助,以及\n
|
||||
g) 做任何您認為對 OpenTracks 有幫助的事情。
|
||||
</string>
|
||||
<string name="open_track_as_KML">開啟為 KML</string>
|
||||
<string name="open_track_as_GPX">開啟為 GPX</string>
|
||||
<string name="open_track_as_trackfileformat">開啟為 %1$s</string>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">飛機</string>
|
||||
<string name="activity_type_atv">越野車</string>
|
||||
<string name="activity_type_biking">騎自行車</string>
|
||||
@@ -14,6 +104,7 @@
|
||||
<string name="activity_type_driving">正在開車</string>
|
||||
<string name="activity_type_driving_bus">駕駛公車</string>
|
||||
<string name="activity_type_driving_car">駕車</string>
|
||||
<string name="activity_type_escooter">電動滑板車</string>
|
||||
<string name="activity_type_ferry">渡輪碼頭</string>
|
||||
<string name="activity_type_frisbee">飛盤</string>
|
||||
<string name="activity_type_gliding">滑翔</string>
|
||||
@@ -66,61 +157,131 @@
|
||||
<string name="activity_type_walking">步行</string>
|
||||
<string name="activity_type_water_skiing">滑水</string>
|
||||
<string name="activity_type_wind_surfing">風帆</string>
|
||||
<string name="activity_type_swimming">游泳</string>
|
||||
<string name="activity_type_swimming_open">開放水域游泳</string>
|
||||
<string name="activity_type_workout">訓練</string>
|
||||
<!-- Description -->
|
||||
<string name="description_activity_type">活動類型:%1$s</string>
|
||||
<string name="description_average_moving_pace_in_minute">平均移動速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
|
||||
<string name="description_average_moving_pace_in_minute">平均移動步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
|
||||
<string name="description_average_moving_speed">平均移動速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
|
||||
<string name="description_average_pace_in_minute">平均速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
|
||||
<string name="description_average_pace_in_minute">平均步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
|
||||
<string name="description_average_speed">平均速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
|
||||
<string name="description_altitude_gain">爬升高度:%1$d 公尺 (%2$d 英尺)</string>
|
||||
<string name="description_altitude_loss">下降高度:%1$d m (%2$d ft)</string>
|
||||
<string name="description_altitude_imperial">海拔高度 (英尺)</string>
|
||||
<string name="description_altitude_metric">海拔高度 (公尺)</string>
|
||||
<string name="description_fastest_pace_in_minute">最快速度:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
|
||||
<string name="description_fastest_pace_in_minute">最高步速:%1$s 分鐘/公里 (%2$s 分鐘/英里)</string>
|
||||
<string name="description_max_altitude">最高海拔:%1$d 公尺 (%2$d 英尺)</string>
|
||||
<string name="description_max_speed">最快速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
|
||||
<string name="description_max_speed">最高速度:%1$.2f 公里/小時 (%2$.1f 英里/小時)</string>
|
||||
<string name="description_min_altitude">最低海拔:%1$d 公尺 (%2$d 英尺)</string>
|
||||
<string name="description_moving_time">移動時間:%1$s</string>
|
||||
<string name="description_pace_imperial">速度 (分鐘/英里)</string>
|
||||
<string name="description_pace_metric">速度 (分鐘/公里)</string>
|
||||
<string name="description_pace_imperial">步速 (分鐘/英里)</string>
|
||||
<string name="description_pace_nautical">步速 (分鐘/海里)</string>
|
||||
<string name="description_pace_metric">步速 (分鐘/公里)</string>
|
||||
<string name="description_recorded_time">記錄時間:%1$s</string>
|
||||
<string name="description_sensor_cadence">轉速 (rpm)</string>
|
||||
<string name="description_speed_ms">速度 (公尺/秒)</string>
|
||||
<string name="description_sensor_cadence">踏頻 (rpm)</string>
|
||||
<string name="description_sensor_heart_rate">心率 (bpm)</string>
|
||||
<string name="description_sensor_power">功率 (W)</string>
|
||||
<string name="description_default_speed_or_pace">依照活動類型</string>
|
||||
<string name="description_speed_imperial">速度 (英里/小時)</string>
|
||||
<string name="description_speed_nautical">速度 (節)</string>
|
||||
<string name="description_speed_metric">速度 (公里/小時)</string>
|
||||
<string name="description_time">時間</string>
|
||||
<string name="description_total_distance">總距離:%1$.2f 公里 (%2$.1f 英里)</string>
|
||||
<string name="description_total_time">總時間:%1$s</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<!-- Export -->
|
||||
<string name="export_with_photos">包含照片</string>
|
||||
<string name="export_without_photos">不包含照片</string>
|
||||
<string name="export_error_post_workout">運動後匯出失敗,請檢查匯出目錄。</string>
|
||||
<string name="export_cannot_write_to_dir">無法寫入匯出目錄。</string>
|
||||
<string name="export_dir_not_writable">" (不可寫入!)"</string>
|
||||
<string name="export_progress_message">正在匯出至 %1$s…</string>
|
||||
<string name="export_track_already_exists_msg">蹤跡 %1$s 已存在於目標目錄之中。</string>
|
||||
<string name="export_all_do_it_for_all">對所有衝突這麼做</string>
|
||||
<string name="export_track_errors">蹤跡未匯出</string>
|
||||
<string name="export_progress_summary_new_msg">新蹤跡已匯出</string>
|
||||
<string name="export_progress_summary_overwrite_msg">蹤跡覆蓋</string>
|
||||
<string name="export_progress_summary_skip_msg">蹤跡已略過</string>
|
||||
<string name="export_progress_summary_errors_msg">錯誤</string>
|
||||
<!-- External Storage -->
|
||||
<!-- Generic -->
|
||||
<string name="generic_add">新增</string>
|
||||
<string name="generic_delete_selected_confirm_title">確定要刪除所選項目?</string>
|
||||
<string name="generic_description">內容描述</string>
|
||||
<string name="generic_description_line">內容描述:%1$s</string>
|
||||
<string name="generic_name">名稱</string>
|
||||
<string name="generic_name_line">名稱:%1$s</string>
|
||||
<string name="generic_open_track">開啟蹤跡</string>
|
||||
<string name="generic_recording">正在記錄…</string>
|
||||
<string name="generic_save">儲存</string>
|
||||
<string name="generic_tracks">足跡</string>
|
||||
<string name="generic_tracks">蹤跡</string>
|
||||
<string name="generic_overwrite">覆蓋</string>
|
||||
<string name="generic_skip">略過</string>
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<string name="generic_show_errors">顯示錯誤</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">已完成,但有一個錯誤</item>
|
||||
<item quantity="other">已完成,但有 %1$d 個錯誤</item>
|
||||
</plurals>
|
||||
<string name="generic_completed">完成</string>
|
||||
<string name="generic_click_twice_cancel">再次點擊返回以取消</string>
|
||||
<string name="generic_choose_an_option">選擇一個選項</string>
|
||||
<string name="generic_undo">撤消</string>
|
||||
<string name="generic_filter">篩選</string>
|
||||
<string name="generic_from">從</string>
|
||||
<string name="generic_to">至</string>
|
||||
<string name="generic_today">今日</string>
|
||||
<string name="generic_yesterday">昨日</string>
|
||||
<string name="generic_dismiss">Dismiss</string>
|
||||
<!-- Gps -->
|
||||
<string name="gps_starting">正在啟用 GPS</string>
|
||||
<string name="gps_wait_for_better_signal">正在等待更清晰的 GPS 訊號</string>
|
||||
<string name="gps_wait_for_signal">正在等待 GPS 訊號</string>
|
||||
<string name="gps_disabled_msg">GPS 禁用</string>
|
||||
<string name="gps_fixed_and_ready">GPS 已修復並準備就緒</string>
|
||||
<string name="gps_recording_status">%1$s:%2$s</string>
|
||||
<string name="gps_recording_without_signal">OpenTracks 紀錄中,不包含 GPS 資料。</string>
|
||||
<!-- Image -->
|
||||
<string name="image_arrow">箭頭</string>
|
||||
<string name="image_marker">標記</string>
|
||||
<string name="image_marker_count">標記數</string>
|
||||
<string name="image_photo_marker">相片標記</string>
|
||||
<string name="image_record">記錄</string>
|
||||
<string name="image_stop">停止</string>
|
||||
<string name="image_track">足跡</string>
|
||||
<string name="image_discard">Discard</string>
|
||||
<string name="image_track">蹤跡</string>
|
||||
<!-- Import -->
|
||||
<string name="import_thread_interrupted">執行緒中斷</string>
|
||||
<string name="import_unsupported_format">未支援的檔案格式</string>
|
||||
<string name="import_parser_error">分析器錯誤:%1$s</string>
|
||||
<string name="import_unable_to_import_file">無法匯入檔案:%1$s</string>
|
||||
<string name="import_no_kml_file_found">在 KMZ 中沒有發現 doc.kml 檔案</string>
|
||||
<string name="import_error_info">%1$s:%2$s</string>
|
||||
<string name="import_error_list_dialog_title">檔案未匯入</string>
|
||||
<string name="import_file_imported">檔案 %1$s 已匯入</string>
|
||||
<string name="import_prevent_reimport">您已設定防止重新匯入蹤跡</string>
|
||||
<string name="import_progress_message">正在從 %1$s 匯入檔案…</string>
|
||||
<string name="import_progress_summary_ok_msg">檔案已匯入</string>
|
||||
<string name="import_progress_summary_exists_msg">已經存在</string>
|
||||
<string name="import_progress_summary_errors_msg">錯誤</string>
|
||||
<!-- Marker -->
|
||||
<string name="marker_add_error">無法插入標記,因為無法取得 GPS 訊號。請再試一次。</string>
|
||||
<string name="marker_add_success">標記已插入</string>
|
||||
<string name="marker_add_canceled">已取消,未新增任何標記</string>
|
||||
<string name="marker_add_photo_canceled">已取消,未加入照片標記</string>
|
||||
<string name="marker_delete_one_confirm_message">系統將永久刪除裝置上的這個標記</string>
|
||||
<string name="marker_delete_one_confirm_title">確定要刪除標記?</string>
|
||||
<string name="marker_delete_multiple_confirm_message">系統將永久刪除裝置上您所選取的這些標記</string>
|
||||
<string name="marker_name_format">標記 %1$d</string>
|
||||
<string name="marker_title">%1$d 個位置 (共 %2$d 個)</string>
|
||||
<!-- Marker Detail -->
|
||||
<!-- Marker Edit -->
|
||||
<string name="marker_edit_marker_type">標記類型 (例如瀑布)</string>
|
||||
<!-- Marker List -->
|
||||
<string name="marker_list_empty_message">您沒有任何標記</string>
|
||||
<!-- Menu -->
|
||||
<string name="menu_aggregated_statistics">彙總的統計資料</string>
|
||||
<string name="menu_delete">刪除</string>
|
||||
<string name="menu_edit">編輯</string>
|
||||
@@ -129,21 +290,72 @@
|
||||
<string name="menu_import_all">全部匯入</string>
|
||||
<string name="menu_insert_marker">插入標記</string>
|
||||
<string name="menu_insert_photo">插入相片</string>
|
||||
<string name="menu_insert_gallery_img">插入圖片庫圖片</string>
|
||||
<string name="menu_markers">標記</string>
|
||||
<string name="menu_search">搜尋</string>
|
||||
<string name="menu_select_all">全選</string>
|
||||
<string name="menu_settings">設定</string>
|
||||
<string name="menu_share">分享</string>
|
||||
<string name="menu_select_layout">選擇布局</string>
|
||||
<string name="menu_show_on_map">在地圖上顯示</string>
|
||||
<string name="menu_start_gps">啟動 GPS</string>
|
||||
<string name="menu_stop_gps">關閉 GPS</string>
|
||||
<string name="menu_voice_rate">語速</string>
|
||||
<string name="menu_resume_track">還原蹤跡</string>
|
||||
<string name="menu_add_custom_layout">新布局</string>
|
||||
<string name="menu_filter">篩選</string>
|
||||
<!-- OpenTracks -->
|
||||
<string name="recording_service">記錄服務</string>
|
||||
<string name="delete_service">刪除服務</string>
|
||||
<!-- Permission -->
|
||||
<string name="permission_gps_failed">OpenTracks 需要授權使用 GPS。</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks 需要授權使用藍芽。</string>
|
||||
<string name="permission_bluetooth_failed_rejected">您需要手動在系統設定中授權 OpenTracks 存取附近裝置。</string>
|
||||
<!-- Search -->
|
||||
<!-- Sensor State -->
|
||||
<string name="sensor_state_cadence_avg">平均踏頻</string>
|
||||
<string name="sensor_state_cadence_max">最大踏頻</string>
|
||||
<string name="sensor_state_heart_rate">心率</string>
|
||||
<string name="sensor_state_heart_rate_avg">平均心率</string>
|
||||
<string name="sensor_state_heart_rate_max">最大心率</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d bpm</string>
|
||||
<string name="sensor_state_power">電源</string>
|
||||
<string name="sensor_state_power">功率</string>
|
||||
<string name="sensor_state_power_avg">平均功率</string>
|
||||
<string name="sensor_could_not_scan">無法掃描藍牙裝置 (錯誤 %1$i).</string>
|
||||
<string name="bluetooth_disabled">請啟用藍芽。</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">所有關於 OpenTracks 專案</string>
|
||||
<string name="settings_defaults_title">預設單位與活動</string>
|
||||
<string name="settings_defaults_summary">首選單位、預設活動、蹤跡預設名稱</string>
|
||||
<string name="settings_ui_title">使用者介面</string>
|
||||
<string name="settings_ui_summary">外觀、紀錄布局、主題</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_gps_summary">時間間隔、距離間隔、最大距離、精度</string>
|
||||
<string name="settings_sensors_title">藍芽感測器</string>
|
||||
<string name="settings_sensors_summary">自行車感測器、輪周、跑步感測器</string>
|
||||
<string name="settings_announcements_title">語音播報</string>
|
||||
<string name="settings_announcements_summary">時間、距離、語速</string>
|
||||
<string name="settings_import_export_title">匯入/匯出</string>
|
||||
<string name="settings_import_export_summary">匯入、匯出、自動匯出</string>
|
||||
<string name="settings_units_category">單位</string>
|
||||
<string name="settings_activity_defaults_category">活動的預設值</string>
|
||||
<string name="settings_reset_summary">重置設定為預設值</string>
|
||||
<!-- Settings Advanced -->
|
||||
<string name="settings_advanced">進階</string>
|
||||
<string name="settings_default_trackfileformat">匯出/分享檔案格式</string>
|
||||
<string name="settings_export_filename_title">檔名的格式</string>
|
||||
<!-- Settings Chart -->
|
||||
<string name="settings_chart_by_distance">依距離</string>
|
||||
<string name="settings_chart_by_time">依時間</string>
|
||||
<!-- Settings Recording -->
|
||||
<string name="settings_recording_customize_layout_title">自訂您的紀錄布局</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">每列資料欄位</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">在鎖定畫面顯示統計資料</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">當記錄時,在不解鎖裝置的情況下顯示統計資料。</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">維持螢幕開啟</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">當記錄時,維持螢幕開啟。</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">全螢幕</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">當記錄時,在全螢幕中呈現。</string>
|
||||
<string name="settings_recording_default_activity_title">預設活動類型</string>
|
||||
<string name="settings_recording_location_frequency_summary">每 %1$s記錄一次位置資訊</string>
|
||||
<string name="settings_recording_max_recording_distance_summary">系統會在兩個地點之間的距離大於 %1$s時建立新的區段</string>
|
||||
@@ -155,77 +367,204 @@
|
||||
<string name="settings_recording_track_name_date_iso_8601_option">日期 (ISO 8601)</string>
|
||||
<string name="settings_recording_track_name_date_local_option">日期 (當地)</string>
|
||||
<string name="settings_recording_track_name_number_option">數字</string>
|
||||
<string name="settings_recording_track_name_title">預設足跡名稱</string>
|
||||
<string name="settings_recording_track_name_title">預設蹤跡名稱</string>
|
||||
<string name="settings_recording_idle_speed_title">怠速閾值</string>
|
||||
<string name="settings_night_mode_title">界面主題</string>
|
||||
<string name="settings_night_mode_option_system">系統</string>
|
||||
<string name="settings_night_mode_option_day">日</string>
|
||||
<string name="settings_night_mode_option_night">夜</string>
|
||||
<string name="settings_announcements_totaltime_frequency">時間間隔</string>
|
||||
<string name="settings_announcements_distance_frequency">距離間隔</string>
|
||||
<string name="settings_announcements_average_heart_rate">平均心率</string>
|
||||
<string name="settings_announcements_lap_heart_rate">單圈心率</string>
|
||||
<string name="settings_announcements_average_speed_pace">平均速度/步速</string>
|
||||
<string name="settings_announcements_lap_speed_pace">單圈速度/步速</string>
|
||||
<!-- Custom Layout -->
|
||||
<string name="custom_layout_list_title">布局</string>
|
||||
<string name="custom_layout_list_edit_name">輸入要新增的佈局名稱</string>
|
||||
<string name="custom_layout_list_edit_already_exists">布局的名稱已經存在</string>
|
||||
<string name="custom_layout_list_layout_removed">佈局已移除</string>
|
||||
<string name="custom_layout_select_layout">選擇布局</string>
|
||||
<string name="stats_custom_layout_default_layout">預設布局</string>
|
||||
<!-- Custom Layout Reset -->
|
||||
<string name="settings_layout_reset">重置自訂布局</string>
|
||||
<string name="settings_layout_reset_confirm_message">所有的布局設定將被還原為預設值。這不會刪除任何裝置上的蹤跡。</string>
|
||||
<string name="settings_layout_reset_confirm_title">重置您的布局?</string>
|
||||
<string name="settings_layout_reset_done">所有的布局設定已被還原為預設值</string>
|
||||
<!-- Settings Reset -->
|
||||
<string name="settings_all_reset">重置</string>
|
||||
<string name="settings_reset_confirm_message">系統會將所有設定還原為預設值。</string>
|
||||
<string name="settings_reset_confirm_title">確定要立即重設?</string>
|
||||
<string name="settings_reset_done">所有設定已還原為預設值</string>
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_generic_sensor">通用</string>
|
||||
<string name="settings_cycling_sensor">騎自行車</string>
|
||||
<string name="settings_running_sensor">跑步</string>
|
||||
<string name="settings_sensor_bluetooth">藍牙</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">距離與速度</string>
|
||||
<string name="settings_sensor_cycling_cadence">踏頻</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">距離、速度與踏頻</string>
|
||||
<string name="settings_sensor_wheel_circumference">輪周 (公釐)</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_enabled">僅顯示宣告所需服務的藍芽裝置</string>
|
||||
<string name="settings_sensor_heart_rate_max">區間:最大心率 (bpm)</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_rate_title">偏好速率</string>
|
||||
<string name="settings_stats_units_imperial">英制 (英里、英尺)</string>
|
||||
<string name="settings_stats_units_metric">公制 (公里、公尺)</string>
|
||||
<string name="settings_stats_units_nautical">航海 (海里、英尺)</string>
|
||||
<string name="settings_stats_units_title">慣用單位</string>
|
||||
<string name="share_track_share_file_body">你應該會喜歡這個足跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看足跡。\n\n%1$s</string>
|
||||
<string name="share_track_subject">我想跟你分享足跡資訊。</string>
|
||||
<string name="stats_average_moving_pace">平均移動速度</string>
|
||||
<!-- Settings public api -->
|
||||
<string name="settings_api_title">公開 API</string>
|
||||
<string name="settings_api_summary">其他 APP 的遠端控制與統計資料 API</string>
|
||||
<string name="settings_public_api_package_title">公開 API 的套件名稱</string>
|
||||
<string name="settings_public_api_enabled_title">公開 API</string>
|
||||
<string name="settings_public_api_enabled_summary_on">其他已安裝的應用程式可以開始或停止紀錄。</string>
|
||||
<string name="settings_public_api_enabled_summary_off">只有 OpenTracks 可以開始或停止紀錄。</string>
|
||||
<string name="settings_public_api_disabled_toast">OpenTracks 公開 AIP 被禁用:它可以在設定中被啟用。</string>
|
||||
<string name="settings_public_api_dashboard_enabled_title">自動資料傳輸</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_on">開始紀錄的應用程式也可以存取紀錄的資料。</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_off">記錄的資料將不會自動與其他應用程式共用。</string>
|
||||
<!-- Settings import/export -->
|
||||
<string name="settings_import_section">匯入</string>
|
||||
<string name="settings_export_section">匯出</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">防止重新匯入蹤跡</string>
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">你應該會喜歡這個蹤跡。\n\n請使用 Google 地球或其他地圖繪製軟體查看蹤跡。\n\n%1$s</string>
|
||||
<string name="share_track_subject">我想跟你分享蹤跡資訊。</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">平均移動步速</string>
|
||||
<string name="stats_average_moving_speed">平均移動速度</string>
|
||||
<string name="stats_average_pace">平均速度</string>
|
||||
<string name="stats_average_pace">平均步速</string>
|
||||
<string name="stats_average_speed">平均速度</string>
|
||||
<string name="stats_altitude">海拔高度</string>
|
||||
<string name="stats_fastest_pace">最快速度</string>
|
||||
<string name="stats_fastest_pace">最高步速</string>
|
||||
<string name="stats_gain">增加</string>
|
||||
<string name="stats_loss">減少</string>
|
||||
<string name="stats_coordinates">緯度/經度</string>
|
||||
<string name="stats_latitude">緯度</string>
|
||||
<string name="stats_longitude">經度</string>
|
||||
<string name="stats_max_speed">速限</string>
|
||||
<string name="stats_max_speed">最高速度</string>
|
||||
<string name="stats_moving_time">移動時間</string>
|
||||
<string name="stats_pace">速度</string>
|
||||
<string name="stats_pace">步速</string>
|
||||
<string name="stats_speed">速度</string>
|
||||
<string name="stats_distance">距離</string>
|
||||
<string name="stats_total_time">總時間</string>
|
||||
<string name="track_name_format">%1$d 號足跡</string>
|
||||
<string name="stats_split_interval">分段間隔</string>
|
||||
<string name="stats_sensors_heart_rate">心率</string>
|
||||
<string name="stats_sensors_cadence">踏頻</string>
|
||||
<string name="stats_sensors_power">功率</string>
|
||||
<string name="stats_clock">時鐘</string>
|
||||
<!-- Track -->
|
||||
<string name="track_name_format">%1$d 號蹤跡</string>
|
||||
<string name="track_recording_notification_accuracy">定位精度:%1$s</string>
|
||||
<string name="track_distance_notification">距離:%1$s</string>
|
||||
<string name="track_speed_notification">速度:%1$s</string>
|
||||
<string name="track_pace_notification">步速:%1$s</string>
|
||||
<string name="track_markers">「%1$s」標記</string>
|
||||
<string name="track_delete_one_confirm_message">系統將永久刪除裝置上儲存的這個足跡與其中的標記。</string>
|
||||
<string name="track_delete_one_confirm_title">確定要刪除足跡?</string>
|
||||
<string name="track_delete_multiple_confirm_message">系統將永久刪除您在裝置上所選的足跡與其中的標記。</string>
|
||||
<!-- Track Delete -->
|
||||
<string name="track_delete_one_confirm_message">系統將永久刪除裝置上儲存的這個蹤跡與其中的標記。</string>
|
||||
<string name="track_delete_one_confirm_title">確定要刪除蹤跡?</string>
|
||||
<string name="track_delete_multiple_confirm_message">系統將永久刪除您在裝置上所選的蹤跡與其中的標記。</string>
|
||||
<string name="track_delete_progress_message">刪除中…</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d 蹤跡</string>
|
||||
<string name="track_delete_not_recording">此蹤跡無法被刪除,因為它目前正在錄製。</string>
|
||||
<string name="track_discarding">廢棄蹤跡</string>
|
||||
<!-- Track Detail -->
|
||||
<string name="track_detail_stats_tab">統計資料</string>
|
||||
<string name="track_detail_intervals_tab">間隔</string>
|
||||
<!-- Track Edit -->
|
||||
<string name="track_edit_activity_type">活動類型 (例如健行)</string>
|
||||
<string name="track_edit_activity_type_hint">活動類型</string>
|
||||
<!-- Track List -->
|
||||
<string name="track_list_empty_message">按這裡開始記錄新的冒險</string>
|
||||
<!-- Aggregated Statistics -->
|
||||
<string name="aggregated_stats_empty_message">紀錄您的第一個蹤跡以檢視綜合統計資料</string>
|
||||
<string name="aggregated_stats_filter_no_results">沒有指定篩選的活動</string>
|
||||
<!-- Interval List -->
|
||||
<string name="interval_list_empty_message">還沒有任何間格</string>
|
||||
<!-- Unit -->
|
||||
<string name="unit_feet">英尺</string>
|
||||
<string name="unit_kilometer">公里</string>
|
||||
<string name="unit_kilometer_per_hour">公里/小時</string>
|
||||
<string name="unit_meter">公尺</string>
|
||||
<string name="unit_mile">英里</string>
|
||||
<string name="unit_mile_per_hour">英里/小時</string>
|
||||
<string name="unit_nautical_mile">海里</string>
|
||||
<string name="unit_knots">節</string>
|
||||
<string name="unit_minute_per_kilometer">分鐘/公里</string>
|
||||
<string name="unit_minute_per_mile">分鐘/英里</string>
|
||||
<string name="unit_minute_per_nautical_mile">分鐘/海里</string>
|
||||
<!-- Location Coordinates -->
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<!-- Value -->
|
||||
<string name="value_integer_feet_excellent_gps">%1$d 英尺 (GPS 訊號佳)</string>
|
||||
<string name="value_integer_feet_recommended">%1$d 英尺 (建議值)</string>
|
||||
<string name="value_integer_kilometer_hour">%1$d 公里/小時</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f 公里/小時</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f 公里/小時 (建議值)</string>
|
||||
<string name="value_integer_meter_excellent_gps">%1$d 公尺 (GPS 訊號佳)</string>
|
||||
<string name="value_integer_meter_poor_gps">%1$d 公尺 (GPS 訊號差)</string>
|
||||
<string name="value_integer_meter_recommended">%1$d 公尺 (建議值)</string>
|
||||
<string name="value_integer_mile_hour">%1$d 英里/小時</string>
|
||||
<string name="value_integer_knots">%1$d 節</string>
|
||||
<string name="value_float_mile_hour">%1$.1f 英里/小時</string>
|
||||
<string name="value_float_knots">%1$.1f 節</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f 英里/小時 (建議值)</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f 節 (建議值)</string>
|
||||
<string name="value_integer_minute">%1$d 分鐘</string>
|
||||
<string name="value_integer_second">%1$d 秒</string>
|
||||
<string name="value_none">無</string>
|
||||
<string name="value_off">關閉</string>
|
||||
<string name="value_smallest_recommended">最小 (建議值)</string>
|
||||
<!-- Voice -->
|
||||
<plurals name="voiceHours">
|
||||
<item quantity="one">1 小時</item>
|
||||
<item quantity="other">%1$d 小時</item>
|
||||
</plurals>
|
||||
<plurals name="voiceMinutes">
|
||||
<item quantity="one">1 分鐘</item>
|
||||
<item quantity="other">%1$d 分鐘</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSeconds">
|
||||
<item quantity="one">1 秒</item>
|
||||
<item quantity="other">%1$d 秒</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSpeedKilometersPerHour">
|
||||
<item quantity="other">每小時 %1$.1f 公里</item>
|
||||
<item quantity="one">1 公里每小時</item>
|
||||
<item quantity="other">%1$.1f 公里每小時</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSpeedMilesPerHour">
|
||||
<item quantity="other">每小時 %1$.1f 公里</item>
|
||||
<item quantity="one">1 英里每小時</item>
|
||||
<item quantity="other">%1$.1f 英里每小時</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">1 節</item>
|
||||
<item quantity="other">%1$.1f 節</item>
|
||||
</plurals>
|
||||
<string name="voice_per_kilometer">每公里</string>
|
||||
<string name="voice_per_mile">每英里</string>
|
||||
<string name="voice_per_nautical_mile">每海里</string>
|
||||
<string name="lap_time">單圈時間</string>
|
||||
<string name="pace">步速</string>
|
||||
<string name="lap_speed">單圈速度</string>
|
||||
<string name="speed">平均移動速度</string>
|
||||
<string name="total_distance">總距離</string>
|
||||
<plurals name="voiceDistanceKilometers">
|
||||
<item quantity="one">1 公里</item>
|
||||
<item quantity="other">%1$.1f 公里</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceMiles">
|
||||
<item quantity="one">1 英里</item>
|
||||
<item quantity="other">%1$.1f 英里</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">1 海里</item>
|
||||
<item quantity="other">%1$.1f 海里</item>
|
||||
</plurals>
|
||||
<string name="average_heart_rate">平均心率</string>
|
||||
<string name="current_heart_rate">目前心率</string>
|
||||
<!-- Waypoint Type -->
|
||||
<string name="waypoint_type_atm">提款機</string>
|
||||
<string name="waypoint_type_bank">銀行</string>
|
||||
<string name="waypoint_type_bar">酒吧</string>
|
||||
@@ -277,4 +616,26 @@
|
||||
<string name="waypoint_type_waterfall">瀑布</string>
|
||||
<string name="waypoint_type_water_fountain">噴泉</string>
|
||||
<string name="waypoint_type_waypoint">途經點</string>
|
||||
</resources>
|
||||
<!-- Share image -->
|
||||
<string name="share_image_subject">我想與你分享一張圖片</string>
|
||||
<string name="share_image_body">我認為你可能會對這張圖片感興趣。</string>
|
||||
<string name="hold_to_stop">按住以停止</string>
|
||||
<string name="instant_export_enabled_summary">在紀錄完成後匯出蹤跡至儲存區</string>
|
||||
<string name="instant_export_enabled_title">即時運動後匯出</string>
|
||||
<string name="settings_default_export_uri_title">蹤跡匯出目錄</string>
|
||||
<!-- Fields -->
|
||||
<string name="field_set_primary">主要的</string>
|
||||
<string name="field_set_secondary">次要的</string>
|
||||
<string name="field_remove_from_layout">移除</string>
|
||||
<string name="no_compatible_file_manager_installed">沒有安裝相容的檔案管理器</string>
|
||||
<string name="error_crash_title">%1$s 已崩潰</string>
|
||||
<string name="share_crash_report">分享</string>
|
||||
<string name="report_bug">回報 bug</string>
|
||||
<string name="select_show_on_map_behavior">選擇 \"在地圖上顯示\" 行為</string>
|
||||
<string name="show_on_map_format_ask">詢問</string>
|
||||
<string name="show_on_dashboard">OpenTracks 儀表板 API</string>
|
||||
<string name="show_on_dashboard_not_installed">沒有安裝支援 OpenTracks 儀表板 API 的應用程式。</string>
|
||||
<string name="always">總是</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks 本身不提供地圖。請安裝 OSMDashboard 以在地圖上檢視您的紀錄。</string>
|
||||
<string name="voice_on_device_speaker_title">使用裝置的揚聲器</string>
|
||||
</resources>
|
||||
@@ -62,6 +62,8 @@
|
||||
<string name="settings_export" translatable="false">settingsExport</string>
|
||||
|
||||
<!-- 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_default" translatable="false">@string/activity_type_unknown</string>
|
||||
|
||||
@@ -235,6 +237,8 @@
|
||||
<item>1.2</item>
|
||||
<item>1.3</item>
|
||||
</string-array>
|
||||
<string name="voice_on_device_speaker_key" translatable="false">voiceOnDeviceSpeaker</string>
|
||||
<bool name="voice_on_device_speaker_default" translatable="false">true</bool>
|
||||
<string name="voice_announce_total_distance_key" translatable="false">voiceAnnounceTotalDistance</string>
|
||||
<string name="voice_announce_moving_time_key" translatable="false">voiceAnnounceMovingTime</string>
|
||||
<string name="voice_announce_average_speed_pace_key" translatable="false">voiceAnnounceAverageSpeedPace</string>
|
||||
|
||||
@@ -361,7 +361,6 @@ limitations under the License.
|
||||
<string name="settings_activity_defaults_category">Activity\'s Defaults</string>
|
||||
|
||||
<string name="settings_reset_summary">Reset Settings to Default Values</string>
|
||||
<string name="settings_layout_reset_key" translatable="false">settingsLayoutReset</string>
|
||||
|
||||
<!-- Settings Advanced -->
|
||||
<string name="settings_advanced">Advanced</string>
|
||||
@@ -575,6 +574,7 @@ limitations under the License.
|
||||
<item quantity="one">1 knot</item>
|
||||
<item quantity="other">%1$.1f knots</item>
|
||||
</plurals>
|
||||
<string name="voice_on_device_speaker_title">Use the device\'s speaker</string>
|
||||
<string name="voice_per_kilometer">per kilometer</string>
|
||||
<string name="voice_per_mile">per mile</string>
|
||||
<string name="voice_per_nautical_mile">per nautical mile</string>
|
||||
@@ -664,6 +664,8 @@ limitations under the License.
|
||||
<string name="field_set_secondary">Secondary</string>
|
||||
<string name="field_remove_from_layout">Remove</string>
|
||||
<string name="no_compatible_file_manager_installed">No compatible file manager installed</string>
|
||||
<string name="no_compatible_camera_installed">No compatible camera application installed</string>
|
||||
<string name="no_compatible_gallery_installed">No compatible image gallery application installed</string>
|
||||
<string name="error_crash_title">%1$s crashed</string>
|
||||
<string name="share_crash_report">Share crash report</string>
|
||||
<string name="report_bug">Report bug</string>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<include domain="sharedpref" path="." requireFlags="clientSideEncryption"/>
|
||||
<include domain="database" path="." requireFlags="clientSideEncryption"/>
|
||||
<include domain="external" path="." requireFlags="clientSideEncryption"/>
|
||||
</full-backup-content>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user