forked from upstream-mirrors/OpenTracks
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 24c2c5fc1b | |||
| 8797b033a3 | |||
| b2c4cdc4aa | |||
| ae37ec9c33 | |||
| ccc18739a4 | |||
| f48c4806b5 | |||
| 3aadf6d5d8 | |||
| 2f2679c775 | |||
| 442e23b106 | |||
| 65249f11b2 | |||
| 34b5c2ab18 | |||
| 93919183be | |||
| 1915641d78 | |||
| c2c7cad3eb | |||
| e4957e45af | |||
| 77bce9a5eb | |||
| 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 |
@@ -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
|
||||
|
||||
+16
-13
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.1.0-beta03'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 5168
|
||||
versionName "v4.3.1"
|
||||
versionCode 5222
|
||||
versionName "v4.4.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -69,8 +74,6 @@ android {
|
||||
|
||||
testInstrumentationRunner "de.dennisguse.opentracks.TestRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
resConfigs "ar", "bg", "ca", "cs", "da", "de", "el", "en", "es", "et", "eu", "fa", "fi", "fr", "ga", "gl", "hi", "hr", "hu", "in", "it", "iw", "ja", "ko", "lt", "lv", "mo", "ms", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "sv", "th", "tl", "tr", "uk", "vi", "zh"
|
||||
}
|
||||
signingConfigs {
|
||||
nightly {
|
||||
@@ -108,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"
|
||||
@@ -128,17 +131,17 @@ dependencies {
|
||||
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.mediarouter:mediarouter:1.3.1'
|
||||
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:5.2.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.3.1'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
v4.3.2: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- KML header was misformed (#1522)
|
||||
|
||||
Developer:
|
||||
- Use Java 17 (multi-line text, switch)
|
||||
- API33: locale config is generated
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.3.3: OpenTracks
|
||||
|
||||
Bugfixes:
|
||||
- Hidden fields were shown (introduced in v4.2.0)
|
||||
- Speed was shown instead of pace (introduced in v4.2.0)
|
||||
@@ -0,0 +1,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)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.4.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Export all tracks as one file
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,8 @@ import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
@@ -30,7 +32,11 @@ public class EspressoDeleteTrackTest {
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
public GrantPermissionRule mGrantPermissionRuleLocation = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleBluetooth = GrantPermissionRule.grant(Manifest.permission.BLUETOOTH_CONNECT);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleNotifications = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
|
||||
|
||||
@Test
|
||||
public void espressoDeleteTrackTest() {
|
||||
|
||||
@@ -15,6 +15,8 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -38,7 +40,11 @@ public class EspressoEditTrackRecordingTest {
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
public GrantPermissionRule mGrantPermissionRuleLocation = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleBluetooth = GrantPermissionRule.grant(android.Manifest.permission.BLUETOOTH_CONNECT);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleNotifications = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
|
||||
@@ -13,6 +13,8 @@ import static org.hamcrest.Matchers.anything;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.selectTabAtIndex;
|
||||
import static de.dennisguse.opentracks.util.EspressoUtils.waitFor;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -31,7 +33,11 @@ public class EspressoUITest {
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
public GrantPermissionRule mGrantPermissionRuleLocation = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleBluetooth = GrantPermissionRule.grant(android.Manifest.permission.BLUETOOTH_CONNECT);
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRuleNotifications = GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS);
|
||||
|
||||
@LargeTest
|
||||
@Test
|
||||
|
||||
+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);
|
||||
|
||||
+10
-10
@@ -204,11 +204,11 @@ 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
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
trackExporter.writeTrack(List.of(track), context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
@@ -287,10 +287,10 @@ 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));
|
||||
trackExporter.writeTrack(List.of(track), context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
@@ -310,11 +310,11 @@ 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
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
trackExporter.writeTrack(List.of(track), context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// 2. import
|
||||
@@ -428,11 +428,11 @@ 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
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
trackExporter.writeTrack(List.of(track), context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
|
||||
// 2. import
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(tmpFileUri);
|
||||
@@ -453,11 +453,11 @@ 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
|
||||
trackExporter.writeTrack(track, context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
trackExporter.writeTrack(List.of(track), context.getContentResolver().openOutputStream(tmpFileUri));
|
||||
contentProviderUtils.deleteTrack(context, trackId);
|
||||
|
||||
// then
|
||||
|
||||
+2
-2
@@ -231,8 +231,8 @@ public class GPXTrackImporterTest {
|
||||
importTrackId = importer.importFile(inputStream).get(0);
|
||||
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
|
||||
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context);
|
||||
trackExporter.writeTrack(importedTrack, outputStream);
|
||||
TrackExporter trackExporter = TrackFileFormat.GPX.createTrackExporter(context, contentProviderUtils);
|
||||
trackExporter.writeTrack(List.of(importedTrack), outputStream);
|
||||
|
||||
// then
|
||||
String expected = new BufferedReader(
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
@@ -78,7 +79,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-14.8980, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(-14.8980, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,7 +94,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -108,7 +109,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,7 +124,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(30.15, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -138,7 +139,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-17.2260, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(-17.2260, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -153,7 +154,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-39.4865, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(-39.4865, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,7 +174,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint1.getLocation());
|
||||
|
||||
// then
|
||||
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()), altitude_egm2008.correctAltitude(trackPoint2.getLocation()), 0.0001);
|
||||
assertNotEquals(altitude_egm2008.correctAltitude(trackPoint1.getLocation()).toM(), altitude_egm2008.correctAltitude(trackPoint2.getLocation()).toM(), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -189,7 +190,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(22.99, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(22.99, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -204,7 +205,7 @@ public class EGM2008UtilsTest {
|
||||
EGM2008Utils.EGM2008Correction altitude_egm2008 = EGM2008Utils.createCorrection(context, trackPoint.getLocation());
|
||||
|
||||
// then
|
||||
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()), MAX_BILINEAR_ERROR);
|
||||
assertEquals(-85.824, altitude_egm2008.correctAltitude(trackPoint.getLocation()).toM(), MAX_BILINEAR_ERROR);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,11 +17,14 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -37,7 +40,7 @@ public class FileUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testBuildUniqueFileName_new() {
|
||||
String filename = FileUtils.buildUniqueFileName(new File("/dir"), "Filename", "ext");
|
||||
String filename = FileUtils.buildUniqueFileName(FileUtils.getPhotoDir(ApplicationProvider.getApplicationContext()), "Filename", "ext");
|
||||
assertEquals("Filename.ext", filename);
|
||||
}
|
||||
|
||||
@@ -45,9 +48,14 @@ public class FileUtilsTest {
|
||||
* Tests {@link FileUtils#buildUniqueFileName(File, String, String)} when the file exists already.
|
||||
*/
|
||||
@Test
|
||||
public void testBuildUniqueFileName_exist() {
|
||||
// Expect "/default.prop" to exist on the phone/emulator
|
||||
String filename = FileUtils.buildUniqueFileName(new File("/"), "default", "prop");
|
||||
public void testBuildUniqueFileName_exist() throws IOException {
|
||||
// given
|
||||
new File(FileUtils.getPhotoDir(ApplicationProvider.getApplicationContext()), "default.prop").createNewFile();
|
||||
|
||||
//when
|
||||
String filename = FileUtils.buildUniqueFileName(FileUtils.getPhotoDir(ApplicationProvider.getApplicationContext()), "default", "prop");
|
||||
|
||||
// then
|
||||
assertEquals("default(1).prop", filename);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -70,8 +70,7 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/ThemeCustom"
|
||||
android:localeConfig="@xml/locales_config">
|
||||
android:theme="@style/ThemeCustom">
|
||||
|
||||
<activity
|
||||
android:name=".publicapi.StartRecording"
|
||||
@@ -90,6 +89,7 @@ limitations under the License.
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- NOTE: the name of the MAIN/LAUNCHER is used by icon packs; if this is changed, icon packs break.-->
|
||||
<activity
|
||||
android:name=".introduction.IntroductionActivity"
|
||||
android:exported="true"
|
||||
@@ -349,6 +349,7 @@ limitations under the License.
|
||||
<activity android:name=".ui.aggregatedStatistics.AggregatedStatisticsActivity" />
|
||||
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserActivity" />
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$ExportDirectoryChooserOneFileActivity" />
|
||||
<activity android:name=".io.file.importer.DirectoryChooserActivity$DefaultTrackExportDirectoryChooserActivity" />
|
||||
|
||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.database.Cursor;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
@@ -246,7 +245,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
}
|
||||
|
||||
// Recording -> Stop
|
||||
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||
ActivityUtils.vibrate(this, 1000);
|
||||
updateGpsMenuItem(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this);
|
||||
viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24);
|
||||
@@ -311,9 +310,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));
|
||||
|
||||
@@ -382,6 +381,12 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
searchView.setIconified(false);
|
||||
searchMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_help) {
|
||||
startActivity(IntentUtils.newIntent(this, HelpActivity.class));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -45,6 +43,7 @@ import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
|
||||
import de.dennisguse.opentracks.ui.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
@@ -137,7 +136,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks_secondary_color));
|
||||
viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks_secondary_color));
|
||||
viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> {
|
||||
((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(1000);
|
||||
ActivityUtils.vibrate(this, 1000);
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackStoppedActivity.class)
|
||||
.putExtra(TrackStoppedActivity.EXTRA_TRACK_ID, trackId);
|
||||
@@ -333,18 +332,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 +348,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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,12 +208,12 @@ 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())) {
|
||||
// TODO handle failure (i.e., do not export an empty file)
|
||||
trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream);
|
||||
trackExporter.writeTrack(tracks, fileOutputStream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "there occurred an error while sharing a file: " + e);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ public abstract class Altitude {
|
||||
public double toM() {
|
||||
return altitude_m;
|
||||
}
|
||||
|
||||
public abstract Altitude replace(double altitude_m);
|
||||
|
||||
|
||||
public abstract int getLabelId();
|
||||
|
||||
public static class WGS84 extends Altitude {
|
||||
@@ -31,12 +29,7 @@ public abstract class Altitude {
|
||||
return R.string.wgs84;
|
||||
}
|
||||
|
||||
public static Altitude of(double altitude_m) {
|
||||
return new WGS84(altitude_m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Altitude replace(double altitude_m) {
|
||||
public static WGS84 of(double altitude_m) {
|
||||
return new WGS84(altitude_m);
|
||||
}
|
||||
}
|
||||
@@ -52,12 +45,7 @@ public abstract class Altitude {
|
||||
return R.string.egm2008;
|
||||
}
|
||||
|
||||
public static Altitude of(double altitude_m) {
|
||||
return new EGM2008(altitude_m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Altitude replace(double altitude_m) {
|
||||
public static EGM2008 of(double altitude_m) {
|
||||
return new EGM2008(altitude_m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
public class BatteryLevel {
|
||||
|
||||
public static BatteryLevel of(int percentage) {
|
||||
return new BatteryLevel(percentage);
|
||||
}
|
||||
|
||||
private final int percentage;
|
||||
|
||||
private BatteryLevel(int percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
|
||||
public int getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -29,6 +29,10 @@ public class TrackFilenameGenerator {
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
public static String format(@NonNull String name, @NonNull TrackFileFormat trackFileFormat) {
|
||||
return FileUtils.sanitizeFileName(name + "." + trackFileFormat.getExtension());
|
||||
}
|
||||
|
||||
private final String template;
|
||||
|
||||
public TrackFilenameGenerator(@NonNull String template) {
|
||||
@@ -44,7 +48,7 @@ public class TrackFilenameGenerator {
|
||||
values.put(STARTTIME_TIME_KEY, track.getStartTime().toLocalTime().toString());
|
||||
values.put(STARTTIME_DATE_KEY, track.getStartTime().toLocalDate().toString());
|
||||
|
||||
return FileUtils.sanitizeFileName(format(template, values)) + "." + trackFileFormat.getExtension();
|
||||
return format(format(template, values), trackFileFormat);
|
||||
}
|
||||
|
||||
private static String format(String template, Map<String, String> values) {
|
||||
@@ -77,10 +81,6 @@ public class TrackFilenameGenerator {
|
||||
return String.format(templateCompiled, valueList.toArray());
|
||||
}
|
||||
|
||||
public String getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
try {
|
||||
getExample();
|
||||
|
||||
@@ -23,8 +23,6 @@ import androidx.annotation.NonNull;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
@@ -87,13 +85,8 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
List<Column> columns = Collections.unmodifiableList(Arrays.asList(
|
||||
public boolean writeTrack(@NonNull List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
List<Column> columns = List.of(
|
||||
new Column("time", null),
|
||||
new Column("trackpoint_type", t -> quote(t.getType().name())),
|
||||
new Column("latitude", t -> t.hasLocation() ? COORDINATE_FORMAT.format(t.getLatitude()) : ""),
|
||||
@@ -108,8 +101,7 @@ public class CSVTrackExporter implements TrackExporter {
|
||||
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("power", t -> t.hasPower() ? ALTITUDE_FORMAT.format(t.getPower().getW()) : ""));
|
||||
|
||||
try {
|
||||
prepare(outputStream);
|
||||
@@ -144,15 +136,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 +158,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -24,6 +23,7 @@ import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -31,14 +31,15 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.tables.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.ExportActivityBinding;
|
||||
import de.dennisguse.opentracks.io.file.ErrorListDialog;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ExportUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
@@ -47,12 +48,20 @@ import de.dennisguse.opentracks.util.FileUtils;
|
||||
* An activity for saving tracks to the external storage.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
* TODO: This class needs some refactoring.
|
||||
* * It pushes each export job (usually one Track) for export; although export could be run in parallel.
|
||||
* Also this requires that the ExportActivity stays in foreground, so the user has to activily wait.
|
||||
* It would be better to let the ExportService handle this and let it report progress / conflicts to ExportActivity
|
||||
* * File name conflicts are checked in this class instead of the ExportService.
|
||||
* So, for this check actually a different file name might be used than in the ExportService.
|
||||
* * Saved state as an object instead of individual values.
|
||||
*/
|
||||
public class ExportActivity extends FragmentActivity implements ExportServiceResultReceiver.Receiver {
|
||||
|
||||
private static final String TAG = ExportActivity.class.getSimpleName();
|
||||
|
||||
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
|
||||
public static final String EXTRA_ONE_FILE_KEY = "one_file";
|
||||
public static final String EXTRA_TRACKFILEFORMAT_KEY = "trackfileformat";
|
||||
|
||||
private static final String BUNDLE_AUTO_CONFLICT = "auto_conflict";
|
||||
@@ -64,9 +73,13 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
private static final String BUNDLE_DIRECTORY_FILES = "track_directory_files";
|
||||
private static final String BUNDLE_TRACK_ERRORS = "track_errors";
|
||||
|
||||
private static final int CONFLICT_NONE = 0;
|
||||
private static final int CONFLICT_OVERWRITE = 1;
|
||||
private static final int CONFLICT_SKIP = 2;
|
||||
private static final String BUNDLE_EXPORT_TASKS = "export_tasks";
|
||||
|
||||
private enum ConflictResolutionStrategy {
|
||||
CONFLICT_NONE,
|
||||
CONFLICT_OVERWRITE,
|
||||
CONFLICT_SKIP;
|
||||
}
|
||||
|
||||
private TrackFileFormat trackFileFormat;
|
||||
private Uri directoryUri;
|
||||
@@ -87,12 +100,12 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
|
||||
private ArrayList<String> trackErrors = new ArrayList<>();
|
||||
|
||||
private int autoConflict;
|
||||
private ConflictResolutionStrategy autoConflict;
|
||||
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
// List of tracks to be exported.
|
||||
private final ArrayList<Track> tracks = new ArrayList<>();
|
||||
private ArrayList<ExportTask> exportTasks;
|
||||
|
||||
private final LinkedBlockingQueue<PendingConflict> conflictsQueue = new LinkedBlockingQueue<>();
|
||||
private final Handler conflictsHandler = new Handler();
|
||||
@@ -100,34 +113,36 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
private final Runnable conflictsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (conflictsQueue.size() > 0) {
|
||||
PendingConflict conflict = conflictsQueue.peek();
|
||||
if (conflict.resolve()) {
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
viewBinding.exportProgressLeftButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.skip();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.exportProgressRightButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.overwrite();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
if (conflictsQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingConflict conflict = conflictsQueue.peek();
|
||||
if (conflict.resolve()) {
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
viewBinding.exportProgressLeftButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.skip();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.exportProgressRightButton.setOnClickListener((view) -> {
|
||||
setConflictVisibility(View.GONE);
|
||||
conflict.overwrite();
|
||||
conflictsQueue.remove(conflict);
|
||||
if (!conflictsQueue.isEmpty()) {
|
||||
conflictsHandler.post(conflictsRunnable);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,6 +154,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
|
||||
directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
|
||||
trackFileFormat = (TrackFileFormat) getIntent().getSerializableExtra(EXTRA_TRACKFILEFORMAT_KEY);
|
||||
boolean allInOneFile = getIntent().getBooleanExtra(EXTRA_ONE_FILE_KEY, false);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(this);
|
||||
|
||||
@@ -148,14 +164,17 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
resultReceiver = new ExportServiceResultReceiver(new Handler(), this);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
autoConflict = CONFLICT_NONE;
|
||||
autoConflict = ConflictResolutionStrategy.CONFLICT_NONE;
|
||||
setProgress();
|
||||
new Thread(() -> {
|
||||
directoryFiles = ExportUtils.getAllFiles(ExportActivity.this, documentFile.getUri());
|
||||
runOnUiThread(() -> initExport(0));
|
||||
runOnUiThread(() -> {
|
||||
createExportTasks(allInOneFile);
|
||||
nextExport(null);
|
||||
});
|
||||
}).start();
|
||||
} else {
|
||||
autoConflict = savedInstanceState.getInt(BUNDLE_AUTO_CONFLICT);
|
||||
autoConflict = ConflictResolutionStrategy.valueOf(savedInstanceState.getString(BUNDLE_AUTO_CONFLICT));
|
||||
trackExportSuccessCount = savedInstanceState.getInt(BUNDLE_SUCCESS_COUNT);
|
||||
trackExportErrorCount = savedInstanceState.getInt(BUNDLE_ERROR_COUNT);
|
||||
trackExportOverwrittenCount = savedInstanceState.getInt(BUNDLE_OVERWRITTEN_COUNT);
|
||||
@@ -163,9 +182,9 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
trackExportTotalCount = savedInstanceState.getInt(BUNDLE_TOTAL_COUNT);
|
||||
directoryFiles = savedInstanceState.getStringArrayList(BUNDLE_DIRECTORY_FILES);
|
||||
trackErrors = savedInstanceState.getStringArrayList(BUNDLE_TRACK_ERRORS);
|
||||
|
||||
exportTasks = new ArrayList<>(savedInstanceState.getParcelableArrayList(BUNDLE_EXPORT_TASKS));
|
||||
setProgress();
|
||||
initExport(getTotalDone());
|
||||
nextExport(null);
|
||||
}
|
||||
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(getString(R.string.export_progress_message, directoryDisplayName));
|
||||
@@ -175,7 +194,7 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(BUNDLE_AUTO_CONFLICT, autoConflict);
|
||||
outState.putString(BUNDLE_AUTO_CONFLICT, autoConflict.name());
|
||||
outState.putInt(BUNDLE_SUCCESS_COUNT, trackExportSuccessCount);
|
||||
outState.putInt(BUNDLE_ERROR_COUNT, trackExportErrorCount);
|
||||
outState.putInt(BUNDLE_OVERWRITTEN_COUNT, trackExportOverwrittenCount);
|
||||
@@ -183,13 +202,14 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
outState.putInt(BUNDLE_TOTAL_COUNT, trackExportTotalCount);
|
||||
outState.putStringArrayList(BUNDLE_DIRECTORY_FILES, (ArrayList<String>) directoryFiles);
|
||||
outState.putStringArrayList(BUNDLE_TRACK_ERRORS, trackErrors);
|
||||
outState.putParcelableArrayList(BUNDLE_EXPORT_TASKS, exportTasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
conflictsQueue.clear();
|
||||
tracks.clear();
|
||||
exportTasks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,57 +222,50 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
this.doubleBackToCancel = true;
|
||||
Toast.makeText(this, getString(R.string.generic_click_twice_cancel), Toast.LENGTH_SHORT).show();
|
||||
|
||||
new Handler().postDelayed(() -> doubleBackToCancel=false, 2000);
|
||||
new Handler().postDelayed(() -> doubleBackToCancel = false, 2000);
|
||||
}
|
||||
|
||||
private void initExport(int from) {
|
||||
try (Cursor cursor = contentProviderUtils.getTrackCursor(null, null, TracksColumns._ID)) {
|
||||
if (cursor == null) {
|
||||
onExportEnded();
|
||||
return;
|
||||
}
|
||||
|
||||
trackExportTotalCount = cursor.getCount();
|
||||
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
|
||||
for (int i = from; i < trackExportTotalCount; i++) {
|
||||
cursor.moveToPosition(i);
|
||||
Track track = ContentProviderUtils.createTrack(cursor);
|
||||
tracks.add(track);
|
||||
}
|
||||
|
||||
if (!tracks.isEmpty()) {
|
||||
export(tracks.get(0));
|
||||
} else {
|
||||
onExportEnded();
|
||||
}
|
||||
private void createExportTasks(boolean allInOneFile) {
|
||||
List<Track> tracks = contentProviderUtils.getTracks();
|
||||
exportTasks = new ArrayList<>();
|
||||
if (allInOneFile) {
|
||||
String filename = "OpenTracks-Backup";
|
||||
exportTasks.add(new ExportTask(filename, trackFileFormat, tracks.stream().map(Track::getId).collect(Collectors.toList())));
|
||||
} else {
|
||||
exportTasks.addAll(tracks.stream().map(it -> new ExportTask(null, trackFileFormat, List.of(it.getId()))).collect(Collectors.toList()));
|
||||
}
|
||||
trackExportTotalCount = exportTasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue track identified by UUID to be exported if not exported already or there is a conflict resolution.
|
||||
*
|
||||
* @param track Track object.
|
||||
* @param conflictResolution conflict resolution to be applied if needed.
|
||||
*/
|
||||
private void export(Track track, int conflictResolution) {
|
||||
boolean fileExists = exportFileExists(track);
|
||||
private void export(ExportTask exportTask, ConflictResolutionStrategy conflictResolution) {
|
||||
boolean fileExists = exportFileExists(exportTask);
|
||||
|
||||
if (fileExists && conflictResolution == CONFLICT_NONE) {
|
||||
conflict(track);
|
||||
} else if (fileExists && conflictResolution == CONFLICT_SKIP) {
|
||||
if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_NONE) {
|
||||
conflict(exportTask);
|
||||
} else if (fileExists && conflictResolution == ConflictResolutionStrategy.CONFLICT_SKIP) {
|
||||
trackExportSkippedCount++;
|
||||
onExportCompleted(track);
|
||||
nextExport(exportTask);
|
||||
} else {
|
||||
ExportService.enqueue(this, resultReceiver, track.getId(), trackFileFormat, directoryUri);
|
||||
ExportService.enqueue(this, resultReceiver, exportTask, directoryUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void export(Track track) {
|
||||
export(track, autoConflict);
|
||||
private void export(ExportTask exportTask) {
|
||||
export(exportTask, autoConflict);
|
||||
}
|
||||
|
||||
private boolean exportFileExists(Track track) {
|
||||
String filename = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
@Deprecated //TODO Check should be done in ExportService
|
||||
private boolean exportFileExists(ExportTask exportTask) {
|
||||
String filename;
|
||||
if (exportTask.isMultiExport()) {
|
||||
filename = TrackFilenameGenerator.format(exportTask.getFilename(), exportTask.getTrackFileFormat());
|
||||
} else {
|
||||
Track track = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0));
|
||||
filename = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
}
|
||||
return directoryFiles.stream().anyMatch(filename::equals);
|
||||
}
|
||||
|
||||
@@ -269,31 +282,29 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
}
|
||||
|
||||
private void setProgress() {
|
||||
int done = getTotalDone();
|
||||
|
||||
viewBinding.exportProgressDone.setText("" + done);
|
||||
viewBinding.exportProgressDone.setText("" + getTotalDone());
|
||||
viewBinding.exportProgressTotal.setText("" + trackExportTotalCount);
|
||||
|
||||
viewBinding.exportProgressBar.setProgress((int) ((float) done / (float) trackExportTotalCount * 100f));
|
||||
viewBinding.exportProgressSummaryNew.setText(String.valueOf(trackExportSuccessCount));
|
||||
viewBinding.exportProgressSummaryOverwrite.setText(String.valueOf(trackExportOverwrittenCount));
|
||||
viewBinding.exportProgressSummarySkip.setText(String.valueOf(trackExportSkippedCount));
|
||||
viewBinding.exportProgressSummaryErrors.setText(String.valueOf(trackExportErrorCount));
|
||||
viewBinding.exportProgressBar.setProgress((int) ((float) getTotalDone() / (float) trackExportTotalCount * 100f));
|
||||
viewBinding.exportProgressSummaryNew.setText("" + trackExportSuccessCount);
|
||||
viewBinding.exportProgressSummaryOverwrite.setText("" + trackExportOverwrittenCount);
|
||||
viewBinding.exportProgressSummarySkip.setText("" + trackExportSkippedCount);
|
||||
viewBinding.exportProgressSummaryErrors.setText("" + trackExportErrorCount);
|
||||
viewBinding.exportProgressSummaryNewGroup.setVisibility(trackExportSuccessCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.exportProgressSummaryOverwriteGroup.setVisibility(trackExportOverwrittenCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.exportProgressSummarySkipGroup.setVisibility(trackExportSkippedCount > 0 ? View.VISIBLE : View.GONE);
|
||||
viewBinding.exportProgressSummaryErrorsGroup.setVisibility(trackExportErrorCount > 0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void onExportCompleted(Track track) {
|
||||
tracks.remove(track);
|
||||
private void nextExport(@Nullable ExportTask exportTask) {
|
||||
exportTasks.remove(exportTask);
|
||||
|
||||
setProgress();
|
||||
if (tracks.isEmpty()) {
|
||||
if (exportTasks.isEmpty()) {
|
||||
onExportEnded();
|
||||
return;
|
||||
}
|
||||
export(tracks.get(0));
|
||||
export(exportTasks.get(0));
|
||||
}
|
||||
|
||||
private void onExportEnded() {
|
||||
@@ -318,30 +329,32 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportSuccess(Track.Id trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
if (exportFileExists(track)) {
|
||||
public void onExportSuccess(ExportTask exportTask) {
|
||||
if (exportFileExists(exportTask)) {
|
||||
trackExportOverwrittenCount++;
|
||||
} else {
|
||||
trackExportSuccessCount++;
|
||||
}
|
||||
|
||||
onExportCompleted(track);
|
||||
nextExport(exportTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportError(Track.Id trackId) {
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
|
||||
public void onExportError(ExportTask exportTask) {
|
||||
trackExportErrorCount++;
|
||||
trackErrors.add(track.getName());
|
||||
String name;
|
||||
if (exportTask.isMultiExport()) {
|
||||
name = exportTask.getFilename();
|
||||
} else {
|
||||
name = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0)).getName();
|
||||
}
|
||||
trackErrors.add(name);
|
||||
|
||||
onExportCompleted(track);
|
||||
nextExport(exportTask);
|
||||
}
|
||||
|
||||
private void conflict(Track track) {
|
||||
PendingConflict newConflict = new PendingConflict(track);
|
||||
private void conflict(ExportTask exportTask) {
|
||||
PendingConflict newConflict = new PendingConflict(exportTask);
|
||||
conflictsQueue.add(newConflict);
|
||||
|
||||
if (conflictsQueue.size() == 1) {
|
||||
@@ -353,10 +366,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Handle conflicts (exporting file already exists).
|
||||
*/
|
||||
private class PendingConflict {
|
||||
private final Track track;
|
||||
private final ExportTask exportTask;
|
||||
|
||||
public PendingConflict(Track track) {
|
||||
this.track = track;
|
||||
public PendingConflict(ExportTask exportTask) {
|
||||
this.exportTask = exportTask;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,14 +379,20 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* @return true if it could resolve the conflict or false otherwise.
|
||||
*/
|
||||
public boolean resolve() {
|
||||
if (autoConflict == CONFLICT_NONE) {
|
||||
if (autoConflict == ConflictResolutionStrategy.CONFLICT_NONE) {
|
||||
viewBinding.exportProgressAlertIcon.setImageDrawable(ContextCompat.getDrawable(ExportActivity.this, R.drawable.ic_report_problem_24));
|
||||
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, track.getName()));
|
||||
String name;
|
||||
if (exportTask.isMultiExport()) {
|
||||
name = exportTask.getFilename();
|
||||
} else {
|
||||
name = contentProviderUtils.getTrack(exportTask.getTrackIds().get(0)).getName();
|
||||
}
|
||||
viewBinding.exportProgressAlertMsg.setText(getString(R.string.export_track_already_exists_msg, name));
|
||||
setConflictVisibility(View.VISIBLE);
|
||||
return false;
|
||||
}
|
||||
|
||||
export(track);
|
||||
export(exportTask);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -381,10 +400,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Overwrite the export file and set the autoConflict if user set the "do it for all" switch button.
|
||||
*/
|
||||
public void overwrite() {
|
||||
export(track, CONFLICT_OVERWRITE);
|
||||
export(exportTask, ConflictResolutionStrategy.CONFLICT_OVERWRITE);
|
||||
|
||||
if (viewBinding.exportProgressApplyToAll.isChecked()) {
|
||||
autoConflict = CONFLICT_OVERWRITE;
|
||||
autoConflict = ConflictResolutionStrategy.CONFLICT_OVERWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,10 +411,10 @@ public class ExportActivity extends FragmentActivity implements ExportServiceRes
|
||||
* Skip the export file and set the autoConflict if user set the "do it for all" switch button.
|
||||
*/
|
||||
public void skip() {
|
||||
export(track, CONFLICT_SKIP);
|
||||
export(exportTask, ConflictResolutionStrategy.CONFLICT_SKIP);
|
||||
|
||||
if (viewBinding.exportProgressApplyToAll.isChecked()) {
|
||||
autoConflict = CONFLICT_SKIP;
|
||||
autoConflict = ConflictResolutionStrategy.CONFLICT_SKIP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
@@ -22,16 +25,14 @@ public class ExportService extends JobIntentService {
|
||||
private static final int JOB_ID = 1;
|
||||
|
||||
private static final String EXTRA_RECEIVER = "extra_receiver";
|
||||
private static final String EXTRA_TRACK_ID = "extra_track_id";
|
||||
private static final String EXTRA_TRACK_FILE_FORMAT = "extra_track_file_format";
|
||||
private static final String EXTRA_EXPORT_TASK = "export_task";
|
||||
private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri";
|
||||
private static final String TAG = ExportService.class.getSimpleName();
|
||||
|
||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, Track.Id trackId, TrackFileFormat trackFileFormat, Uri directoryUri) {
|
||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, ExportTask exportTask, Uri directoryUri) {
|
||||
Intent intent = new Intent(context, JobService.class);
|
||||
intent.putExtra(EXTRA_RECEIVER, receiver);
|
||||
intent.putExtra(EXTRA_TRACK_ID, trackId);
|
||||
intent.putExtra(EXTRA_TRACK_FILE_FORMAT, trackFileFormat);
|
||||
intent.putExtra(EXTRA_EXPORT_TASK, exportTask);
|
||||
intent.putExtra(EXTRA_DIRECTORY_URI, directoryUri);
|
||||
enqueueWork(context, ExportService.class, JOB_ID, intent);
|
||||
}
|
||||
@@ -40,13 +41,12 @@ public class ExportService extends JobIntentService {
|
||||
protected void onHandleWork(@NonNull Intent intent) {
|
||||
// Get all data.
|
||||
ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RECEIVER);
|
||||
Track.Id trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
|
||||
TrackFileFormat trackFileFormat = (TrackFileFormat) intent.getSerializableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||
ExportTask exportTask = intent.getParcelableExtra(EXTRA_EXPORT_TASK);
|
||||
Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI);
|
||||
|
||||
// Prepare resultCode and bundle to send to the receiver.
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK, exportTask);
|
||||
|
||||
// Build directory file.
|
||||
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
@@ -57,9 +57,7 @@ public class ExportService extends JobIntentService {
|
||||
}
|
||||
|
||||
// Export.
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
boolean success = ExportUtils.exportTrack(this, trackFileFormat, directoryFile, track);
|
||||
boolean success = ExportUtils.exportTrack(this, directoryFile, exportTask);
|
||||
|
||||
// Send result to the receiver.
|
||||
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
|
||||
|
||||
+7
-12
@@ -17,7 +17,7 @@ public class ExportServiceResultReceiver extends ResultReceiver {
|
||||
public static final int RESULT_CODE_SUCCESS = 1;
|
||||
public static final int RESULT_CODE_ERROR = 0;
|
||||
|
||||
public static final String RESULT_EXTRA_TRACK_ID = "result_extra_track_id";
|
||||
public static final String RESULT_EXTRA_EXPORT_TASK = "result_extra_export_task";
|
||||
|
||||
private final Receiver receiver;
|
||||
|
||||
@@ -28,24 +28,19 @@ public class ExportServiceResultReceiver extends ResultReceiver {
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
Track.Id trackId = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID);
|
||||
ExportTask exportTask = resultData.getParcelable(ExportServiceResultReceiver.RESULT_EXTRA_EXPORT_TASK);
|
||||
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(exportTask);
|
||||
case RESULT_CODE_ERROR -> receiver.onExportError(exportTask);
|
||||
default -> throw new RuntimeException("Unknown resultCode.");
|
||||
}
|
||||
}
|
||||
|
||||
public interface Receiver {
|
||||
default void onExportSuccess(Track.Id trackId) {
|
||||
default void onExportSuccess(ExportTask exportTask) {
|
||||
}
|
||||
|
||||
default void onExportError(Track.Id trackId) {
|
||||
default void onExportError(ExportTask exportTask) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package de.dennisguse.opentracks.io.file.exporter;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
public class ExportTask implements Parcelable {
|
||||
|
||||
private final String filename; //TODO Name will only be used when exporting multiple tracks
|
||||
|
||||
private final TrackFileFormat trackFileFormat;
|
||||
|
||||
private final List<Track.Id> trackIds;
|
||||
|
||||
public ExportTask(@Nullable String filename, @NonNull TrackFileFormat trackFileFormat, @NonNull List<Track.Id> trackIds) {
|
||||
this.filename = filename;
|
||||
this.trackFileFormat = trackFileFormat;
|
||||
this.trackIds = trackIds;
|
||||
}
|
||||
|
||||
protected ExportTask(Parcel in) {
|
||||
filename = in.readString();
|
||||
trackFileFormat = TrackFileFormat.valueOf(in.readString());
|
||||
trackIds = in.createTypedArrayList(Track.Id.CREATOR);
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public TrackFileFormat getTrackFileFormat() {
|
||||
return trackFileFormat;
|
||||
}
|
||||
|
||||
public List<Track.Id> getTrackIds() {
|
||||
return trackIds;
|
||||
}
|
||||
|
||||
public boolean isMultiExport() {
|
||||
return trackIds.size() > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ExportTask that = (ExportTask) o;
|
||||
return Objects.equals(filename, that.filename) && trackFileFormat == that.trackFileFormat && Objects.equals(trackIds, that.trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(filename, trackFileFormat, trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(filename);
|
||||
dest.writeString(trackFileFormat.name());
|
||||
dest.writeTypedList(trackIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<ExportTask> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public ExportTask createFromParcel(Parcel in) {
|
||||
return new ExportTask(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExportTask[] newArray(int size) {
|
||||
return new ExportTask[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -98,12 +98,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
try {
|
||||
prepare(outputStream);
|
||||
writeHeader();
|
||||
@@ -145,37 +140,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,24 +93,20 @@ 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) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
}
|
||||
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
try {
|
||||
prepare(outputStream);
|
||||
writeHeader(tracks);
|
||||
for (Track track : tracks) {
|
||||
writeMarkers(track);
|
||||
}
|
||||
boolean hasMultipleTracks = tracks.length > 1;
|
||||
boolean hasMultipleTracks = tracks.size() > 1;
|
||||
if (hasMultipleTracks) {
|
||||
writeMultiTrackBegin();
|
||||
}
|
||||
@@ -169,30 +165,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,22 +218,26 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeader(Track[] tracks) {
|
||||
private void writeHeader(List<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>");
|
||||
|
||||
Track track = tracks[0];
|
||||
Track track = tracks.get(0);
|
||||
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
|
||||
printWriter.println("<atom:generator>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:generator>");
|
||||
|
||||
@@ -291,7 +289,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
|
||||
private void writeMultiTrackBegin() {
|
||||
if (printWriter != null) {
|
||||
printWriter.println("<Folder id=tour>");
|
||||
printWriter.println("<Folder id=\"tracks\">");
|
||||
printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>");
|
||||
printWriter.println("<open>1</open>");
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@@ -64,12 +65,7 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
|
||||
return writeTrack(new Track[]{track}, outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
|
||||
public boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream) {
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||
// Add kml file
|
||||
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
|
||||
@@ -91,7 +87,7 @@ public class KmzTrackExporter implements TrackExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void addImages(Context context, Track[] tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
|
||||
private void addImages(Context context, List<Track> tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
|
||||
for (Track track : tracks) {
|
||||
try (Cursor cursor = contentProviderUtils.getMarkerCursor(track.getId(), null, -1)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package de.dennisguse.opentracks.io.file.exporter;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
@@ -29,7 +30,5 @@ import de.dennisguse.opentracks.data.models.Track;
|
||||
*/
|
||||
public interface TrackExporter {
|
||||
|
||||
boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream);
|
||||
|
||||
boolean writeTrack(Track track, @NonNull OutputStream outputStream);
|
||||
boolean writeTrack(List<Track> tracks, @NonNull OutputStream outputStream);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,30 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExportDirectoryChooserOneFileActivity extends DirectoryChooserActivity {
|
||||
|
||||
@Override
|
||||
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
||||
super.configureDirectoryChooserIntent(intent);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
return IntentUtils.toDocumentFile(this, PreferencesUtils.getDefaultExportDirectoryUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent createNextActivityIntent(Uri directoryUri) {
|
||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||
intent.putExtra(ExportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
|
||||
intent.putExtra(ExportActivity.EXTRA_ONE_FILE_KEY, true);
|
||||
intent.putExtra(ExportActivity.EXTRA_TRACKFILEFORMAT_KEY, PreferencesUtils.getExportTrackFileFormat());
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultTrackExportDirectoryChooserActivity extends DirectoryChooserActivity {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-12
@@ -53,26 +53,22 @@ public abstract class AbstractBluetoothConnectionManager<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.BatteryLevel;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
@@ -48,6 +49,11 @@ public class BluetoothUtils {
|
||||
|
||||
public static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = new UUID(0x290200001000L, 0x800000805f9b34fbL);
|
||||
|
||||
public static final ServiceMeasurementUUID BATTERY = new ServiceMeasurementUUID(
|
||||
new UUID(0x180F00001000L, 0x800000805f9b34fbL),
|
||||
new UUID(0x2A1900001000L, 0x800000805f9b34fbL)
|
||||
);
|
||||
|
||||
public static final ServiceMeasurementUUID HEARTRATE = new ServiceMeasurementUUID(
|
||||
new UUID(0x180D00001000L, 0x800000805f9b34fbL),
|
||||
new UUID(0x2A3700001000L, 0x800000805f9b34fbL)
|
||||
@@ -102,6 +108,17 @@ public class BluetoothUtils {
|
||||
return BluetoothUtils.getAdapter(context) != null;
|
||||
}
|
||||
|
||||
public static BatteryLevel parseBatteryLevel(BluetoothGattCharacteristic characteristic) {
|
||||
// DOCUMENTATION org.bluetooth.characteristic.battery_level.xml
|
||||
byte[] raw = characteristic.getValue();
|
||||
if (raw.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int batteryLevel = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
|
||||
return BatteryLevel.of(batteryLevel);
|
||||
}
|
||||
|
||||
public static HeartRate parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||
//DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
|
||||
byte[] raw = characteristic.getValue();
|
||||
|
||||
@@ -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
-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;
|
||||
}
|
||||
|
||||
+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();
|
||||
|
||||
+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);
|
||||
|
||||
+1
-2
@@ -5,7 +5,6 @@ import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.EGM2008Utils;
|
||||
|
||||
@@ -30,6 +29,6 @@ public class EGM2008CorrectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
trackPoint.setAltitude(Altitude.EGM2008.of(egm2008Correction.correctAltitude(trackPoint.getLocation())));
|
||||
trackPoint.setAltitude(egm2008Correction.correctAltitude(trackPoint.getLocation()));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -49,11 +49,6 @@ public class MainSettingsFragment extends PreferenceFragmentCompat {
|
||||
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_api_key));
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference(getString(R.string.settings_open_tracks_key)).setOnPreferenceClickListener(preference -> {
|
||||
((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_open_tracks_key));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class OpenTracksSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.settings_open_tracks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_open_tracks_title);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,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) {
|
||||
@@ -579,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) {
|
||||
@@ -589,9 +584,8 @@ public class PreferencesUtils {
|
||||
} else {
|
||||
entries[i] = displayValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,6 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
fragment = new ImportExportSettingsFragment();
|
||||
} else if (key.equals(getString(R.string.settings_api_key))) {
|
||||
fragment = new PublicAPISettingsFragment();
|
||||
} else if (key.equals(getString(R.string.settings_open_tracks_key))) {
|
||||
fragment = new OpenTracksSettingsFragment();
|
||||
}
|
||||
|
||||
return fragment;
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
|
||||
/**
|
||||
* Converts WGS84 altitude to EGM2008 (should be close to height above sea level).
|
||||
@@ -103,7 +104,7 @@ public class EGM2008Utils {
|
||||
return indices.getAbsoluteIndex() == getIndices(location).getAbsoluteIndex();
|
||||
}
|
||||
|
||||
public double correctAltitude(@NonNull Location location) {
|
||||
public Altitude.EGM2008 correctAltitude(@NonNull Location location) {
|
||||
if (!canCorrect(location))
|
||||
throw new RuntimeException("Undulation data not loaded for this location.");
|
||||
if (!location.hasAltitude())
|
||||
@@ -134,7 +135,7 @@ public class EGM2008Utils {
|
||||
|
||||
double h = 0.003 * undulationRaw - 108;
|
||||
|
||||
return location.getAltitude() - h;
|
||||
return Altitude.EGM2008.of(location.getAltitude() - h);
|
||||
}
|
||||
|
||||
private boolean isSouthPole() {
|
||||
|
||||
@@ -17,12 +17,16 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportService;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportServiceResultReceiver;
|
||||
import de.dennisguse.opentracks.io.file.exporter.ExportTask;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
@@ -43,7 +47,7 @@ public class ExportUtils {
|
||||
|
||||
ExportServiceResultReceiver resultReceiver = new ExportServiceResultReceiver(new Handler(), new ExportServiceResultReceiver.Receiver() {
|
||||
@Override
|
||||
public void onExportError(Track.Id trackId) {
|
||||
public void onExportError(ExportTask unused) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
intent.putExtra(SettingsActivity.EXTRAS_CHECK_EXPORT_DIRECTORY, true);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
@@ -51,21 +55,29 @@ public class ExportUtils {
|
||||
}
|
||||
});
|
||||
|
||||
ExportService.enqueue(context, resultReceiver, trackId, trackFileFormat, directory.getUri());
|
||||
ExportService.enqueue(context, resultReceiver, new ExportTask(null, trackFileFormat, List.of(trackId)), directory.getUri());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean exportTrack(Context context, TrackFileFormat trackFileFormat, DocumentFile directory, Track track) {
|
||||
TrackExporter trackExporter = trackFileFormat.createTrackExporter(context);
|
||||
public static boolean exportTrack(Context context, DocumentFile directory, ExportTask exportTask) {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
|
||||
List<Track> tracks = exportTask.getTrackIds().stream().map(contentProviderUtils::getTrack).collect(Collectors.toList());
|
||||
Uri exportDocumentFileUri;
|
||||
if (tracks.size() == 1) {
|
||||
exportDocumentFileUri = getExportDocumentFileUri(context, tracks.get(0), exportTask.getTrackFileFormat(), directory);
|
||||
} else {
|
||||
String filename = TrackFilenameGenerator.format(exportTask.getFilename(), exportTask.getTrackFileFormat());
|
||||
exportDocumentFileUri = getExportDocumentFileUri(context, filename, exportTask.getTrackFileFormat(), directory);
|
||||
}
|
||||
|
||||
Uri exportDocumentFileUri = getExportDocumentFileUri(context, track, trackFileFormat, directory);
|
||||
if (exportDocumentFileUri == null) {
|
||||
Log.e(TAG, "Couldn't create document file for export");
|
||||
return false;
|
||||
}
|
||||
|
||||
TrackExporter trackExporter = exportTask.getTrackFileFormat().createTrackExporter(context, contentProviderUtils);
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(exportDocumentFileUri)) {
|
||||
if (trackExporter.writeTrack(track, outputStream)) {
|
||||
if (trackExporter.writeTrack(tracks, outputStream)) {
|
||||
return true;
|
||||
} else {
|
||||
if (!DocumentFile.fromSingleUri(context, exportDocumentFileUri).delete()) {
|
||||
@@ -101,6 +113,10 @@ public class ExportUtils {
|
||||
|
||||
private static Uri getExportDocumentFileUri(Context context, Track track, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
String exportFileName = PreferencesUtils.getTrackFileformatGenerator().format(track, trackFileFormat);
|
||||
return getExportDocumentFileUri(context, exportFileName, trackFileFormat, directory);
|
||||
}
|
||||
|
||||
private static Uri getExportDocumentFileUri(Context context, String exportFileName, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||
Uri exportDocumentFileUri = findFile(context, directory.getUri(), exportFileName);
|
||||
if (exportDocumentFileUri == null) {
|
||||
final DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), exportFileName);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,15 +65,20 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpeedOrPace extends GenericStatisticsViewHolder {
|
||||
public abstract static class SpeedOrPace extends GenericStatisticsViewHolder {
|
||||
|
||||
private final boolean reportSpeed;
|
||||
|
||||
public SpeedOrPace(boolean reportSpeed) {
|
||||
this.reportSpeed = reportSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
@@ -85,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);
|
||||
@@ -93,6 +100,20 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpeedVH extends SpeedOrPace {
|
||||
|
||||
public SpeedVH() {
|
||||
super(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaceVH extends SpeedOrPace {
|
||||
|
||||
public PaceVH() {
|
||||
super(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageMovingSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,8 +17,8 @@ public class Mapping {
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_distance_key), GenericStatisticsViewHolder.Distance::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_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);
|
||||
|
||||
@@ -42,4 +42,8 @@ limitations under the License.
|
||||
android:id="@+id/track_list_settings"
|
||||
android:icon="@drawable/ic_settings_24dp"
|
||||
android:title="@string/menu_settings" />
|
||||
<item
|
||||
android:id="@+id/track_list_help"
|
||||
android:title="@string/menu_help" />
|
||||
|
||||
</menu>
|
||||
@@ -0,0 +1 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -491,7 +491,6 @@
|
||||
<string name="settings_import_section">Importar</string>
|
||||
<string name="aggregated_stats_filter_no_results">No hay actividades para el filtro especificado</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (recomendado)</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recomendado)</string>
|
||||
<string name="generic_undo">Deshacer</string>
|
||||
|
||||
@@ -520,7 +520,6 @@ limitations under the License.
|
||||
<string name="settings_sensors_title">Bluetooth senzory</string>
|
||||
<string name="settings_gps_summary">Časový interval, Interval vzdálenosti, Maximální vzdálenost, Přesnost</string>
|
||||
<string name="settings_ui_summary">Vzhled, Rozložení záznamu, Téma</string>
|
||||
<string name="settings_open_tracks_title">O programu</string>
|
||||
<string name="settings_open_tracks_summary">Vše o projektu OpenTracks</string>
|
||||
<string name="settings_defaults_title">Výchozí jednotky a aktivity</string>
|
||||
<string name="settings_ui_title">Uživatelské rozhraní</string>
|
||||
@@ -569,4 +568,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>
|
||||
@@ -537,7 +537,6 @@ Hvis GPS-enheden rapporterer unøjagtige data (f.eks. Placering, hastighed, høj
|
||||
<string name="settings_ui_summary">Udseende, Optagelse af layout, Tema</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="generic_filter">Filter</string>
|
||||
<string name="settings_open_tracks_title">Åbne spor</string>
|
||||
<string name="custom_layout_list_edit_name">Skriv navnet på det layout, der skal tilføjes</string>
|
||||
<string name="settings_layout_reset_confirm_message">Alle layoutindstillinger vil blive tilbageført til standardværdierne. Dette vil ikke slette spor på enheden.</string>
|
||||
<string name="settings_all_reset">Nulstil</string>
|
||||
|
||||
@@ -574,6 +574,5 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
|
||||
<string name="settings_ui_summary">Erscheinungsbild, Aufzeichnungslayout, Theme</string>
|
||||
<string name="settings_gps_summary">Zeitintervall, Entfernungsintervall, Max Entfernung, Genauigkeit</string>
|
||||
<string name="description_speed_nautical">Geschwindigkeit (Knoten)</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="gps_recording_status">%1$s: %2$s</string>
|
||||
</resources>
|
||||
@@ -542,7 +542,6 @@ limitations under the License.
|
||||
<string name="settings_open_tracks_summary">Todo sobre el proyecto OpenTracks</string>
|
||||
<string name="settings_defaults_title">Unidades y Actividad predeterminadas</string>
|
||||
<string name="settings_defaults_summary">Unidades preferidas, Actividad predeterminada, Nombre predeterminado del recorrido</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="generic_undo">Deshacer</string>
|
||||
<string name="no_compatible_file_manager_installed">No hay un gestor de archivos compatible instalado</string>
|
||||
<string name="settings_public_api_disabled_toast">La API pública de OpenTracks está deshabilitada: esta se puede activar en los ajustes.</string>
|
||||
|
||||
@@ -498,7 +498,6 @@ limitations under the License.
|
||||
<string name="settings_import_export_title">Import/Eksport</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_open_tracks_summary">Kõik OpenTracks Projekti Kohta</string>
|
||||
<string name="settings_open_tracks_title">OpenTracks</string>
|
||||
<string name="settings_reset_summary">Lähtestage Seaded vaikeväärtustele</string>
|
||||
<string name="settings_recording_customize_layout_title">Kohandage oma salvestuspaigutust</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Andmeväljad rea kohta</string>
|
||||
|
||||
@@ -528,7 +528,6 @@ GPS gailuak datu okerrak salatzen baditu (adibidez, kokapena, abiadura, kota), O
|
||||
<string name="settings_defaults_summary">Unitate Hobetsia, Jarduera Lehenetsia, Pistaren Izen Lehenetsia</string>
|
||||
<string name="settings_ui_title">Interfazea</string>
|
||||
<string name="settings_ui_summary">Itxura, Grabazioaren Diseinua, Gaia</string>
|
||||
<string name="settings_open_tracks_title">Ireki Pistak</string>
|
||||
<string name="settings_cycling_sensor">Txirrindularitza</string>
|
||||
<string name="generic_filter">Iragazkia</string>
|
||||
<string name="generic_from">Nondik</string>
|
||||
|
||||
@@ -526,7 +526,6 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="settings_defaults_summary">Unités préférées, Activité par défaut, Suivi du nom par défaut</string>
|
||||
<string name="settings_defaults_title">Unités et activité par défaut</string>
|
||||
<string name="settings_open_tracks_summary">A propos du projet OpenTracks</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="generic_undo">Annuler</string>
|
||||
<string name="menu_add_custom_layout">Nouvelle présentation</string>
|
||||
<string name="generic_filter">Filtre</string>
|
||||
|
||||
@@ -208,7 +208,6 @@
|
||||
<string name="settings_ui_summary">Dealramh, Leagan Amach Taifeadta, Téama</string>
|
||||
<string name="settings_sensors_summary">Braiteoirí Rothaíochta, Imlíne Rothaí, Braiteoirí Reatha</string>
|
||||
<string name="bluetooth_disabled">Cumasaigh Bluetooth le do thoil.</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">Gach Maidir Tionscadal OpenTracks</string>
|
||||
<string name="settings_defaults_title">Aonaid agus Gníomhaíocht Réamhshocraithe</string>
|
||||
<string name="settings_ui_title">Comhéadan Úsáideora</string>
|
||||
|
||||
@@ -505,7 +505,6 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f km/h (recomendado)</string>
|
||||
<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">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>
|
||||
@@ -612,4 +611,5 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="help_data_google_play_services_content">Non</string>
|
||||
<string name="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>
|
||||
@@ -506,7 +506,6 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="settings_layout_reset_done">Tutte le impostazioni del layout sono state reimpostate ai valori predefiniti</string>
|
||||
<string name="settings_generic_sensor">Generico</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="settings_open_tracks_title">Binari aperti</string>
|
||||
<string name="settings_open_tracks_summary">Tutto a proposito del progetto OpenTracks</string>
|
||||
<string name="settings_ui_title">Interfaccia Utente</string>
|
||||
<string name="settings_ui_summary">Aspetto, Layout durante la registrazione, Tema</string>
|
||||
|
||||
@@ -498,7 +498,6 @@ limitations under the License.
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (anbefales)</string>
|
||||
<string name="stats_clock">Klokke</string>
|
||||
<string name="settings_open_tracks_title">OpenTracks</string>
|
||||
<string name="settings_defaults_summary">Foretrukne enheter, forvalgsaktivitet, forvalgt spornavn</string>
|
||||
<string name="settings_sensors_summary">Sykkelsensorer, hjulomkrets, løpesensorer</string>
|
||||
<string name="settings_open_tracks_summary">Alt om OpenTracks-prosjektet</string>
|
||||
@@ -603,6 +602,7 @@ 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_sensor_heart_rate_max">Soner: maksimum puls (slag/min)</string>
|
||||
<string name="settings_api_summary">Fjernkontroll- og statistikk-API for andre programmer</string>
|
||||
<string name="voice_on_device_speaker_title">Bruk enhetens høyttaler</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>
|
||||
@@ -549,7 +549,6 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_recording_customize_layout_select_columns">Gegevensvelden per rij</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/u</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/u (aanbevolen)</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">Alles over het OpenTracks Project</string>
|
||||
<string name="activity_type_workout">training</string>
|
||||
<string name="settings_layout_reset_confirm_title">Lay-outs terugzetten\?</string>
|
||||
@@ -586,4 +585,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>
|
||||
@@ -536,7 +536,6 @@ limitations under the License.
|
||||
<string name="field_remove_from_layout">Usuń</string>
|
||||
<string name="field_set_primary">Główny</string>
|
||||
<string name="field_set_secondary">Poboczny</string>
|
||||
<string name="settings_open_tracks_title">Otwarte tory</string>
|
||||
<string name="settings_open_tracks_summary">Wszystko o projekcie OpenTacks</string>
|
||||
<string name="settings_defaults_title">Domyślne Jednostki i Aktywności</string>
|
||||
<string name="settings_defaults_summary">Preferowane Jednostki, Domyślne Aktywności, Nazwy Treningów</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user