mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-23 18:55:17 +02:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 117281469d | |||
| 3782a22684 | |||
| aed2025a5a | |||
| 780be5d1a9 | |||
| a2d9b10381 | |||
| 5950c73785 | |||
| 99a9248b27 | |||
| 6bcf1c0818 | |||
| 96dd057287 | |||
| 84aeebea4a | |||
| cfb3ff9985 | |||
| 389c4bed96 | |||
| d8acf7df22 | |||
| 53ea905713 | |||
| 8cceadd93e | |||
| d8eed2ccc9 | |||
| f5190aef06 | |||
| 9834c60f1c | |||
| 6eeb9a0eb2 | |||
| ea23e25a47 | |||
| 53e97a5e4c | |||
| f1f9153ec6 | |||
| e442558eed | |||
| 382acadc6a | |||
| 1ed807054b | |||
| c96635c46b | |||
| 9b2c93f74f | |||
| e7a86b6aad | |||
| b86f8c9a20 | |||
| e9130f3402 | |||
| a08ec97c29 | |||
| a6340d13fa | |||
| dbbf6abcd6 | |||
| 683bf2d280 | |||
| 6c2377a8e8 | |||
| b6661ccafd | |||
| 932c142691 | |||
| 57918249bc | |||
| 3405eca556 | |||
| a932adde3f | |||
| a03dfd81ac | |||
| 7550525462 | |||
| 5d70381b23 | |||
| 96a4cbf70d | |||
| 959aadbb62 | |||
| fa59752c04 | |||
| 5305ec3206 | |||
| 584339b7eb | |||
| 151c3a79de | |||
| a90c7deb32 | |||
| c3601e41cd | |||
| b3d98340dc | |||
| ebbd9c1535 | |||
| 14fc605c45 | |||
| c5ca6644f4 | |||
| e15c729c00 | |||
| 93c89690c9 | |||
| f3f05512c9 | |||
| d2fc2598d1 | |||
| fd2d2c5820 | |||
| 01f10bce24 | |||
| fd963b0fdb | |||
| 0947405563 | |||
| f595bc4d1e | |||
| 4b26583c27 | |||
| 734d4e08e9 | |||
| 6615034314 | |||
| 039495d107 | |||
| 1d926d72a7 | |||
| d1af0fa072 | |||
| ef802d1948 | |||
| 322e91bc22 | |||
| 055f2beb1e | |||
| 6cfc05af82 | |||
| 70a2040142 | |||
| 181e1a503c | |||
| b08302237f | |||
| 881f68c580 | |||
| a8a5aa4268 | |||
| 9c8b9ca05d | |||
| b70241c967 | |||
| a6f3c05e82 | |||
| f9640b0fb1 | |||
| 80a48563d3 | |||
| 9e496f97c1 | |||
| 455c2fcf75 | |||
| e590e4e07e | |||
| 36474e8541 |
@@ -18,7 +18,7 @@ jobs:
|
||||
java-version: '17'
|
||||
|
||||
- name: run tests
|
||||
uses: reactivecircus/android-emulator-runner@v2.30.1
|
||||
uses: reactivecircus/android-emulator-runner@v2.32.0
|
||||
with:
|
||||
arch: x86_64
|
||||
api-level: 34
|
||||
|
||||
@@ -70,7 +70,7 @@ hosted.weblate.org</a>.
|
||||
* export automatically after each recording (e.g., to sync via [Nextcloud](https://nextcloud.com/))
|
||||
* avoid duplication: each exported file contain a random unique identifier (i.e., `opentracks:trackid`)
|
||||
* __Altitude:__
|
||||
* gain/loss via barometric sensor (if present)
|
||||
* gain/loss via barometric sensor (internal if present or via Bluetooth's Environmental Sensing Service)
|
||||
* shown in EGM2008 (above mean sea level); exported as WGS84
|
||||
* __Bluetooth LE sensors:__
|
||||
* heart rate
|
||||
@@ -133,6 +133,8 @@ The Public API is disabled by default to protect the user's privacy, but it can
|
||||
| [OsmAnd](https://github.com/osmandapp/OsmAnd) | ? | [no](https://github.com/osmandapp/OsmAnd/issues/15271) | [no](https://github.com/osmandapp/OsmAnd/issues/15271) |
|
||||
| [FitTrackee](https://github.com/SamR1/FitTrackee) | yes | n/a | n/a |
|
||||
| [SportsTracker](https://github.com/ssaring/sportstracker) | yes, [single tracks only](https://github.com/ssaring/sportstracker/issues/260) | no | no |
|
||||
| [ExifTool](https://exiftool.org) | [yes](https://exiftool.org/forum/index.php?topic=15972.0) | no | no |
|
||||
|
||||
|
||||
## Dashboard API (incl. map)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* Mi Band 3, Amazfit Band 5, Amazfit Bip. These devices are manufactured by Huami, and other Huami devices will very likely work. Theoretically, this sensor must be enabled, but testing reveals that it works even without enabling. For details on how to enable see [Gadgetbridge wiki](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Huami-Heartrate-measurement#bluetooth-heart-rate-sensor)
|
||||
* Amazfit Neo. But first you have to enable "Discoverable" and "Activity heart rate sharing" in the official Zepp app. (after pairing with OpenTracks you can disable them again)
|
||||
* Garmin HRM-Dual (Reference: 010-12883-00)
|
||||
* CYCPLUS H1
|
||||
|
||||
## 0x1816: Cycling Cadence and Speed Service
|
||||
|
||||
@@ -60,7 +61,15 @@ We do not support type of movement (i.e., walking vs running).
|
||||
* Stryd (incl. power)
|
||||
* Wahoo Tickr X (Cadence appears to be reported in SPM rather than RPM)
|
||||
|
||||
## Tested Barometric Sensor Smartphones
|
||||
## Barometer (gain/loss)
|
||||
|
||||
### 0x2A6D: Environmental Sensing Service
|
||||
Sadly, Bluetooth sensors providing _air pressure_ via Environmental Sensing Service seem to be very rare.
|
||||
|
||||
* NoTrustIssues: https://github.com/OpenTracksApp/NoTrustIssues
|
||||
An Android app that exposes the internal barometer via Environmental Sensing Service.
|
||||
|
||||
### Internal: tested Barometric Sensor Smartphones
|
||||
|
||||
OpenTracks derives the altitude gain and altitude loss using the internal barometric sensor of a device (if present).
|
||||
However, barometric sensor seem to be prone to a lot of noise.
|
||||
|
||||
+13
-13
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.3.2'
|
||||
classpath 'com.android.tools.build:gradle:8.5.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5749
|
||||
versionName "v4.12.2."
|
||||
versionCode 5837
|
||||
versionName "v4.13.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -127,22 +127,22 @@ android {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.core:core:1.12.0'
|
||||
implementation 'androidx.core:core:1.13.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation 'androidx.mediarouter:mediarouter:1.7.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.11.0'
|
||||
androidTestImplementation 'androidx.test:core:1.6.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.6.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.6.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.12.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||
androidTestUtil 'androidx.test:orchestrator:1.5.0'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.12.2.: OpenTracks
|
||||
|
||||
Oprava chyvy:
|
||||
- Umožnění spuštění senzorů před začátkem záznamu
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.3: OpenTracks
|
||||
|
||||
Opraveny chyby:
|
||||
- Používání funkcionality IDLE a GPS ukládalo souřadnice 0.0/0.0 a tím pádem přeceňovalo všechny statistiky (vzdálenost >10 000 km). (Vzniklo ve v4.12.0)
|
||||
- Nečinnost: také spustit, pokud byla obdržena pouze jedna lokace.
|
||||
|
||||
Ohledně chyby přeceňování vzdálenosti: uložená data nebudou v OpenTracks opravena.
|
||||
Doporučení: exportujte záznamy do KML/KMZ a nahraďte neplatné souřadnice.
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.4: OpenTracks
|
||||
|
||||
Změny:
|
||||
- Oznámení: jednotky lze vynechat
|
||||
- Oznámení: přidán výkon pro jízdu na kole
|
||||
|
||||
Oprava chyby:
|
||||
- Pád při odstranění značky ze seznamu
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.5: OpenTracks
|
||||
|
||||
Oprava chyby:
|
||||
- Pohyb je nyní opět počítán správně.
|
||||
Byl rozbitý od verze 4.9.0 z důvodu představení funkce přestávky. Viz #1756.
|
||||
|
||||
Změny:
|
||||
- Podpora prediktivního zadávání
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.3: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Using GPS and IDLE functionality stored coordinates 0.0/0.0 and thus overestimating all statistics (distance >10.000 km). (Introduced in v4.12.0)
|
||||
- Idle: also trigger if only one location was received.
|
||||
|
||||
Regarding the overestimating distance bug: the stored data will not be fixed within OpenTracks.
|
||||
Recommendation: export the as KML/KMZ and replace the invalid coordinates.
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.4: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Announcements: units can be omitted
|
||||
- Announcements: added lap cycling power
|
||||
|
||||
Bugfix:
|
||||
- Delete markers from list crashed
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.5: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Moving is now computed correctly again.
|
||||
Was broken since v4.9.0 due to the introduction of idle functionality. See #1756.
|
||||
|
||||
Changes:
|
||||
- Support for Predictive Back
|
||||
@@ -0,0 +1,15 @@
|
||||
v4.13.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Markers can be created via Public API
|
||||
This allows to create Markers at any location and after recording.
|
||||
Drawback: Markers are not shown in diagrams anymore.
|
||||
NOTE: OSMDashboard supports this API after version: v4.1.0
|
||||
|
||||
Bugfix:
|
||||
- Default activity setting should have been a dropdown
|
||||
- Back arrow not working in settings
|
||||
- Label for off trail hiking now shows "off trail hiking"
|
||||
|
||||
Developer:
|
||||
- Use Position instead of Android's Location
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.12.2 .: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Toestaan dat sensoren voor de opname worden gestart
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.3: OpenTracks
|
||||
|
||||
Bugfix:
|
||||
- Gebruikmakend van GPS- en IDLE-functionaliteit opgeslagen coördinaten 0.0 / 0.0 en overschat zo alle statistieken (afstand >10.000 km). (Ingevoerd in v4.12.0)
|
||||
- Idle: ook trigger als er maar één locatie is ontvangen.
|
||||
|
||||
Met betrekking tot de bug rond de overschatting van afstand: de opgeslagen gegevens worden niet binnen OpenTracks gecorrigeerd.
|
||||
Aanbeveling: exporteer de als KML / KMZ en vervang de ongeldige coördinaten.
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.4: OpenTracks
|
||||
|
||||
Veranderingen:
|
||||
- Aankondigingen: eenheden kunnen worden weggelaten
|
||||
- Aankondigingen: extra wielrenkracht
|
||||
|
||||
Bugfix:
|
||||
- Markeringen verwijderen uit lijst gecrasht
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.5: OpenTracks
|
||||
|
||||
Foutcorrectie:
|
||||
- Verplaatsing wordt weer correct berekend.
|
||||
Was defect sinds v4.9.0 t.g.v. de introductie van inactieve functionaliteit. Zie # 1756.
|
||||
|
||||
Veranderingen:
|
||||
- Ondersteuning voor Predictive Back
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.11.1: OpenTracks
|
||||
|
||||
Hata Düzeltme:
|
||||
- PublicAPI durdurulurken çöküyor
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.11.2: OpenTracks
|
||||
|
||||
Hata Düzeltme:
|
||||
- Ekran kapandığında kayıt durduruluyor (v4.11.1 versiyonundan beri)
|
||||
(geri dönülüyor da3dd29b8e44624a8222caea9067955f252ef5f8)
|
||||
@@ -0,0 +1,5 @@
|
||||
v4.12.1: OpenTracks
|
||||
|
||||
Hata Ayıklama:
|
||||
- Android 14: eksik izinleri bildir (hata yerine)
|
||||
- POST_NOTIFICATION opsiyonel
|
||||
@@ -0,0 +1,4 @@
|
||||
v4.12.2.: OpenTracks
|
||||
|
||||
Hata Ayıklama:
|
||||
- Sensörleri kayıttan önce başlatmaya izin ver
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.4: OpenTracks
|
||||
|
||||
Değişiklikler:
|
||||
- Duyurular: birimler ihmal edilebilir
|
||||
- Duyurular: tur bisikleti gücü eklendi
|
||||
|
||||
Hata Ayıklama:
|
||||
- İşaretçileri çöken listesinden sil
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.12.5: OpenTracks
|
||||
|
||||
Hata Ayıklama:
|
||||
- Hareketler artık yeniden doğru bir şekilde hesaplanıyor.
|
||||
boşta durulan vakit özelliğinin getirilmesinden dolayı v4.9.0'den beri bozuktu. #1756'ya bakınız
|
||||
|
||||
Değişiklikler:
|
||||
- Tahmine Dayalı Geri Dönüş Desteği eklendi
|
||||
@@ -0,0 +1,10 @@
|
||||
(versiyonAdı): OpenTracks
|
||||
|
||||
Değişiklikler:
|
||||
-
|
||||
|
||||
Hata Ayıklama:
|
||||
-
|
||||
|
||||
Geliştirici:
|
||||
-
|
||||
@@ -0,0 +1 @@
|
||||
OpenTracks
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Vendored
+10
-10
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
@@ -88,9 +88,9 @@ public class TestDataUtil {
|
||||
stats.setTotalDistance(Distance.of(0));
|
||||
stats.setTotalTime(Duration.ofMillis(0));
|
||||
List<Marker> markers = List.of(
|
||||
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, stats, trackPoints.get(1), null),
|
||||
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, stats, trackPoints.get(4), null),
|
||||
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, stats, trackPoints.get(5), null)
|
||||
new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, trackPoints.get(1), null),
|
||||
new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, trackPoints.get(4), null),
|
||||
new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, trackPoints.get(5), null)
|
||||
);
|
||||
|
||||
return new TrackData(track, trackPoints, markers);
|
||||
@@ -152,7 +152,7 @@ public class TestDataUtil {
|
||||
stats.setTotalDistance(Distance.of(0));
|
||||
stats.setTotalTime(Duration.ofMillis(0));
|
||||
|
||||
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, stats, trackPoint, photoUrl);
|
||||
return new Marker("Marker name", "Marker description", "Marker category", "", trackId, trackPoint, photoUrl);
|
||||
}
|
||||
|
||||
public static List<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
|
||||
|
||||
+4
-8
@@ -9,7 +9,6 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
@@ -65,7 +64,6 @@ import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingCadence;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorCyclingPower;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.AggregatorHeartRate;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.Raw;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
@@ -141,7 +139,7 @@ public class ExportImportTest {
|
||||
Distance sensorDistance = Distance.of(10); // recording distance interval
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:03Z", 3, 14, 10, 13, 15, 10, 1f);
|
||||
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||
contentProviderUtils.insertMarker(new Marker("Marker 1", "Marker 1 desc", "Marker 1 category", null, trackId, service.getLastStoredTrackPointWithLocation(), ""));
|
||||
|
||||
// A sensor-only TrackPoint
|
||||
trackPointCreator.setClock("2020-02-02T02:02:04Z");
|
||||
@@ -156,7 +154,7 @@ public class ExportImportTest {
|
||||
mockSensorData(trackPointCreator, 5f, Distance.of(2), 69f, 3f, 50f, null); // Distance will be added to next TrackPoint
|
||||
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:02:17Z", 3, 14.001, 10, 13, 15, 10, 0f);
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
contentProviderUtils.insertMarker(new Marker("Marker 2", "Marker 2 desc", "Marker 2 category", null, trackId, service.getLastStoredTrackPointWithLocation(), ""));
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
trackPointCreator.getSensorManager().sensorDataSet = new SensorDataSet();
|
||||
@@ -596,15 +594,13 @@ public class ExportImportTest {
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
location.setAccuracy(accuracy);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
location.setVerticalAccuracyMeters(verticalAccuracy);
|
||||
}
|
||||
location.setVerticalAccuracyMeters(verticalAccuracy);
|
||||
location.setSpeed(speed);
|
||||
location.setAltitude(altitude);
|
||||
|
||||
mockAltitudeChange(trackPointCreator, altitudeGain);
|
||||
|
||||
trackPointCreator.setClock(time);
|
||||
trackPointCreator.getSensorManager().onChanged(new Raw<>(location));
|
||||
trackPointCreator.getSensorManager().getGpsManager().onLocationChanged(location);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -166,7 +166,7 @@ public class KMLTrackImporterTest {
|
||||
assertEquals(ActivityType.UNKNOWN, importedTrack.getActivityType());
|
||||
|
||||
// 2. markers
|
||||
assertEquals(0, contentProviderUtils.getMarkers(importTrackId).size());
|
||||
assertEquals(1, contentProviderUtils.getMarkers(importTrackId).size());
|
||||
|
||||
// 3. trackpoints
|
||||
List<TrackPoint> importedTrackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, importTrackId);
|
||||
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceMarkerTest {
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private TrackRecordingService service;
|
||||
|
||||
@BeforeClass
|
||||
public static void preSetUp() {
|
||||
// Prepare looper for Android's message queue
|
||||
if (Looper.myLooper() == null) Looper.prepare();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void finalTearDown() {
|
||||
if (Looper.myLooper() != null) Looper.myLooper().quit();
|
||||
}
|
||||
|
||||
private TrackRecordingService startService() throws TimeoutException {
|
||||
Intent startIntent = new Intent(context, TrackRecordingService.class);
|
||||
return ((TrackRecordingService.Binder) mServiceRule.bindService(startIntent))
|
||||
.getService();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws TimeoutException {
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
service = startService();
|
||||
tearDown();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// Ensure that the database is empty after every test
|
||||
contentProviderUtils.deleteAllTracks(context);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void notRecording_testInsertMarker() {
|
||||
// given
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
// when
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
|
||||
// then
|
||||
assertNull(markerId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_noGPSfix_cannotCreateMarker() {
|
||||
// given
|
||||
service.startNewTrack();
|
||||
|
||||
// when
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
|
||||
// then
|
||||
assertNull(markerId);
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_GPSfix_createsMarker() {
|
||||
// given
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:02Z");
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.stopUpdateRecordingData();
|
||||
|
||||
assertTrue(service.isRecording());
|
||||
trackPointCreator.onNewTrackPoint(
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse("2020-02-02T02:02:03Z"))
|
||||
.setLatitude(10)
|
||||
.setLongitude(10)
|
||||
);
|
||||
|
||||
// when
|
||||
Marker.Id markerId = service.insertMarker(null, null, null, null);
|
||||
|
||||
// then
|
||||
assertNotEquals(new Marker.Id(-1L), markerId);
|
||||
Marker wpt = contentProviderUtils.getMarker(markerId);
|
||||
assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon());
|
||||
assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName());
|
||||
assertEquals(trackId, wpt.getTrackId());
|
||||
assertEquals(0.0, wpt.getLength().toM(), 0.01);
|
||||
assertNotNull(wpt.getLocation());
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:04Z");
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
}
|
||||
+47
@@ -10,6 +10,7 @@ import android.os.Looper;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.MediumTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
@@ -20,12 +21,14 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TestUtil;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||
@@ -54,6 +57,9 @@ public class TrackRecordingServiceRecordingTest {
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = TestUtil.createGrantPermissionRule();
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
@@ -127,6 +133,47 @@ public class TrackRecordingServiceRecordingTest {
|
||||
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that an IDLE event, doesn't store invalid GPS-provided data.
|
||||
*/
|
||||
@MediumTest
|
||||
@Test
|
||||
public void recording_startIdle() throws InterruptedException {
|
||||
|
||||
// given
|
||||
TrackPointCreator trackPointCreator = service.getTrackPointCreator();
|
||||
String startTime = "2020-02-02T02:02:02Z";
|
||||
trackPointCreator.setClock(startTime);
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
String gps1 = "2020-02-02T02:02:03Z";
|
||||
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps1, 45.0, 35.0, 1, 15);
|
||||
String gps2 = "2020-02-02T02:02:04Z";
|
||||
|
||||
TrackRecordingServiceTestUtils.sendGPSLocation(trackPointCreator, gps2, 45.0, 35.0, 1, 15);
|
||||
|
||||
// when
|
||||
String idleTime = "2020-02-02T02:02:17Z";
|
||||
trackPointCreator.setClock("2020-02-02T02:02:17Z");
|
||||
Thread.sleep(Duration.ofSeconds(15).toMillis());
|
||||
|
||||
// then
|
||||
new TrackPointAssert().assertEquals(List.of(
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.parse(startTime)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps1))
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(15)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.parse(gps2))
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(1))
|
||||
.setSpeed(Speed.of(15)),
|
||||
new TrackPoint(TrackPoint.Type.IDLE, Instant.parse(idleTime))
|
||||
), TestDataUtil.getTrackPoints(contentProviderUtils, trackId));
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@Test
|
||||
public void testRecording_startPauseResume() {
|
||||
|
||||
+61
-2
@@ -1,7 +1,9 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
@@ -231,7 +233,7 @@ public class TrackStatisticsUpdaterTest {
|
||||
));
|
||||
|
||||
// then
|
||||
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(35), subject.getTrackStatistics().getMovingTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -261,10 +263,67 @@ public class TrackStatisticsUpdaterTest {
|
||||
));
|
||||
|
||||
// then
|
||||
assertEquals(Duration.ofSeconds(45), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Distance.of(1040), subject.getTrackStatistics().getTotalDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addTrackPoint_idle_remain_idle() {
|
||||
TrackStatisticsUpdater subject = new TrackStatisticsUpdater();
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochSecond(0)));
|
||||
subject.addTrackPoint(
|
||||
new TrackPoint(0, 0, Altitude.WGS84.of(0), Instant.ofEpochSecond(10))
|
||||
.setSensorDistance(Distance.of(10)));
|
||||
|
||||
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.IDLE, Instant.ofEpochSecond(30)));
|
||||
// then
|
||||
assertTrue(subject.getTrackStatistics().isIdle());
|
||||
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getTotalTime());
|
||||
assertEquals(Distance.of(10), subject.getTrackStatistics().getTotalDistance());
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(40))
|
||||
.setSensorDistance(Distance.of(0)));
|
||||
// then
|
||||
assertTrue(subject.getTrackStatistics().isIdle());
|
||||
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getTotalTime());
|
||||
assertEquals(Distance.of(10), subject.getTrackStatistics().getTotalDistance());
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(45))
|
||||
.setSensorDistance(Distance.of(1)));
|
||||
// then
|
||||
assertTrue(subject.getTrackStatistics().isIdle());
|
||||
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(45), subject.getTrackStatistics().getTotalTime());
|
||||
assertEquals(Distance.of(11), subject.getTrackStatistics().getTotalDistance());
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochSecond(50))
|
||||
.setSensorDistance(Distance.of(10)));
|
||||
// then
|
||||
assertFalse(subject.getTrackStatistics().isIdle());
|
||||
assertEquals(Duration.ofSeconds(30), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(50), subject.getTrackStatistics().getTotalTime());
|
||||
assertEquals(Distance.of(21), subject.getTrackStatistics().getTotalDistance());
|
||||
|
||||
// when
|
||||
subject.addTrackPoint(new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochSecond(60)));
|
||||
|
||||
// then
|
||||
assertFalse(subject.getTrackStatistics().isIdle());
|
||||
assertEquals(Duration.ofSeconds(40), subject.getTrackStatistics().getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(60), subject.getTrackStatistics().getTotalTime());
|
||||
assertEquals(Distance.of(21), subject.getTrackStatistics().getTotalDistance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void copy_constructor() {
|
||||
// given
|
||||
|
||||
@@ -38,7 +38,6 @@ import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.TextStyle;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@@ -138,10 +137,10 @@ public class StringUtilsTest {
|
||||
ArrayList<String> shortMonths = Arrays.stream(Month.values()).map(m -> m.getDisplayName(TextStyle.SHORT, Locale.getDefault())).collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
LocalDate today = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
|
||||
LocalDate dayName = today.minus(2, ChronoUnit.DAYS);
|
||||
LocalDate thisYear = today.minus(15, ChronoUnit.DAYS);
|
||||
LocalDate aYearAgo = today.minus(400, ChronoUnit.DAYS);
|
||||
LocalDate yesterday = today.minusDays(1);
|
||||
LocalDate dayName = today.minusDays(2);
|
||||
LocalDate thisYear = today.minusDays(15);
|
||||
LocalDate aYearAgo = today.minusDays(400);
|
||||
|
||||
int offsetFromLocale = OffsetDateTime.now().getOffset().getTotalSeconds() - 28800; // -08:00
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ limitations under the License.
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.markers.MarkerDetailActivity"
|
||||
android:name=".publicapi.ShowMarkerActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="de.dennisguse.opentracks.MarkerDetails" />
|
||||
@@ -322,43 +322,24 @@ limitations under the License.
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".publicapi.CreateMarkerActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="de.dennisguse.opentracks.CreateMarker" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.markers.MarkerDetailActivity" />
|
||||
<activity android:name=".ui.markers.MarkerEditActivity" />
|
||||
<activity android:name=".ui.markers.MarkerListActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.markers.MarkerListActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/menu_markers"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".TrackListActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".TrackListActivity" />
|
||||
<activity android:name=".TrackRecordedActivity" />
|
||||
<activity android:name=".TrackStoppedActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".TrackRecordingActivity"
|
||||
android:parentActivityName=".TrackListActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".TrackListActivity" />
|
||||
</activity>
|
||||
<activity android:name=".TrackRecordingActivity" />
|
||||
|
||||
<activity android:name=".TrackEditActivity" />
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
@@ -26,7 +25,6 @@ import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -34,7 +32,6 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
@@ -129,7 +126,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
};
|
||||
|
||||
// Menu items
|
||||
private MenuItem searchMenuItem;
|
||||
|
||||
private String searchQuery;
|
||||
|
||||
@@ -249,6 +245,14 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackListBinding.inflate(getLayoutInflater());
|
||||
|
||||
viewBinding.trackListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||
searchQuery = viewBinding.trackListSearchView.getEditText().getText().toString();
|
||||
viewBinding.trackListSearchView.hide();
|
||||
loadData();
|
||||
return true;
|
||||
});
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@@ -256,9 +260,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -280,13 +281,6 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_list_search) {
|
||||
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;
|
||||
@@ -295,42 +289,9 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_SEARCH && searchMenuItem != null) {
|
||||
return true;
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
}
|
||||
|
||||
if (searchQuery != null) {
|
||||
searchQuery = null;
|
||||
loadData();
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
searchQuery = intent.getStringExtra(SearchManager.QUERY);
|
||||
} else {
|
||||
searchQuery = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
viewBinding.trackListToolbar.setText(searchQuery);
|
||||
|
||||
viewBinding.trackListToolbar.setTitle(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.app_name)));
|
||||
|
||||
Cursor tracks = new ContentProviderUtils(this).searchTracks(searchQuery);
|
||||
|
||||
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.TrackDataHub;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.TrackRecordingBinding;
|
||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||
@@ -269,9 +270,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_insert_marker) {
|
||||
TrackPoint trackPoint = trackRecordingServiceConnection.getTrackRecordingService().getLastStoredTrackPointWithLocation();
|
||||
if (trackPoint == null) {
|
||||
return true;
|
||||
}
|
||||
Intent intent = IntentUtils
|
||||
.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId)
|
||||
.putExtra(MarkerEditActivity.EXTRA_LOCATION, trackPoint.getLocation());
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -110,15 +110,6 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements
|
||||
contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isDiscarding) {
|
||||
return;
|
||||
}
|
||||
super.onBackPressed();
|
||||
resumeTrackAndFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackStoppedBinding.inflate(getLayoutInflater());
|
||||
|
||||
@@ -41,9 +41,9 @@ import androidx.core.view.GestureDetectorCompat;
|
||||
import java.text.NumberFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
@@ -152,31 +152,6 @@ public class ChartView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent event) {
|
||||
// Check if the y event is within markerHeight of the marker center
|
||||
if (Math.abs(event.getY() - topBorder - spacer - markerHeight / 2f) < markerHeight) {
|
||||
int minDistance = Integer.MAX_VALUE;
|
||||
Marker nearestMarker = null;
|
||||
synchronized (markers) {
|
||||
for (Marker marker : markers) {
|
||||
int distance = Math.abs(getX(getMarkerXValue(marker)) - (int) event.getX() - getScrollX());
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearestMarker = marker;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestMarker != null && minDistance < markerWidth) {
|
||||
Intent intent = IntentUtils.newIntent(getContext(), MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, nearestMarker.getId());
|
||||
getContext().startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
private final ScaleGestureDetector detectorZoom = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@@ -589,7 +564,6 @@ public class ChartView extends View {
|
||||
|
||||
clipToGraphArea(canvas);
|
||||
drawDataSeries(canvas);
|
||||
drawMarker(canvas);
|
||||
drawGrid(canvas);
|
||||
|
||||
canvas.restore();
|
||||
@@ -630,30 +604,6 @@ public class ChartView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
private void drawMarker(Canvas canvas) {
|
||||
synchronized (markers) {
|
||||
for (Marker marker : markers) {
|
||||
double xValue = getMarkerXValue(marker);
|
||||
double markerIconSizeInXaxisUnits = maxX*markerWidth/effectiveWidth / zoomLevel;
|
||||
if (xValue > maxX + markerIconSizeInXaxisUnits * (1-MARKER_X_ANCHOR)) {
|
||||
continue; // there is no chance that this marker will be visible
|
||||
}
|
||||
canvas.save();
|
||||
float x = getX(getMarkerXValue(marker));
|
||||
canvas.drawLine(x, topBorder + spacer + markerHeight / 2, x, topBorder + effectiveHeight, markerPaint);
|
||||
// if marker is not near the end of the track then draw it normally
|
||||
if (xValue < maxX - markerIconSizeInXaxisUnits*(1-MARKER_X_ANCHOR)) {
|
||||
canvas.translate(x - (markerWidth * MARKER_X_ANCHOR), topBorder + spacer);
|
||||
} else { // marker at the end needs to be drawn mirrored so that it is more visible
|
||||
canvas.translate(x + (markerWidth * MARKER_X_ANCHOR), topBorder + spacer);
|
||||
canvas.scale(-1, 1);
|
||||
}
|
||||
markerPin.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the grid.
|
||||
*
|
||||
@@ -679,12 +629,13 @@ public class ChartView extends View {
|
||||
private record TitlePosition(
|
||||
int line, // line number (starts at 1, top to bottom numbering)
|
||||
int xPos // x position in points (starts at 0, left to right indexing)
|
||||
) {};
|
||||
) {}
|
||||
|
||||
private record TitleDimensions(
|
||||
int lineCount, // number of lines the titles will take
|
||||
int lineHeight, // height of a line (all lines have the same height)
|
||||
List<TitlePosition> titlePositions // positions of visible titles (the order corresponds to seriesList)
|
||||
) {};
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Draws series titles.
|
||||
@@ -711,7 +662,7 @@ public class ChartView extends View {
|
||||
private TitleDimensions getTitleDimensions() {
|
||||
int lineCnt = 1;
|
||||
int lineHeight = 0;
|
||||
List<TitlePosition> tps = new ArrayList<TitlePosition>();
|
||||
List<TitlePosition> tps = new ArrayList<>();
|
||||
int xPosInLine = spacer;
|
||||
for (ChartValueSeries chartValueSeries : seriesList) {
|
||||
if (chartValueSeries.isEnabled() && chartValueSeries.hasData() || allowIfEmpty(chartValueSeries)) {
|
||||
@@ -1042,14 +993,6 @@ public class ChartView extends View {
|
||||
return topBorder + yAxisOffset + (int) ((1 - percentage) * rangeHeight);
|
||||
}
|
||||
|
||||
private double getMarkerXValue(Marker marker) {
|
||||
if (chartByDistance) {
|
||||
return marker.getLength().toKM_Miles(unitSystem);
|
||||
} else {
|
||||
return marker.getDuration().toMillis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a paint's Rect for a string.
|
||||
*
|
||||
|
||||
@@ -377,8 +377,6 @@ public class ContentProviderUtils {
|
||||
int categoryIndex = cursor.getColumnIndexOrThrow(MarkerColumns.CATEGORY);
|
||||
int iconIndex = cursor.getColumnIndexOrThrow(MarkerColumns.ICON);
|
||||
int trackIdIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TRACKID);
|
||||
int lengthIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LENGTH);
|
||||
int durationIndex = cursor.getColumnIndexOrThrow(MarkerColumns.DURATION);
|
||||
int longitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LONGITUDE);
|
||||
int latitudeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.LATITUDE);
|
||||
int timeIndex = cursor.getColumnIndexOrThrow(MarkerColumns.TIME);
|
||||
@@ -419,13 +417,6 @@ public class ContentProviderUtils {
|
||||
if (!cursor.isNull(iconIndex)) {
|
||||
marker.setIcon(cursor.getString(iconIndex));
|
||||
}
|
||||
if (!cursor.isNull(lengthIndex)) {
|
||||
marker.setLength(Distance.of(cursor.getFloat(lengthIndex)));
|
||||
}
|
||||
if (!cursor.isNull(durationIndex)) {
|
||||
marker.setDuration(Duration.ofMillis(cursor.getLong(durationIndex)));
|
||||
}
|
||||
|
||||
if (!cursor.isNull(photoUrlIndex)) {
|
||||
marker.setPhotoUrl(cursor.getString(photoUrlIndex));
|
||||
}
|
||||
@@ -542,9 +533,6 @@ public class ContentProviderUtils {
|
||||
values.put(MarkerColumns.CATEGORY, marker.getCategory());
|
||||
values.put(MarkerColumns.ICON, marker.getIcon());
|
||||
values.put(MarkerColumns.TRACKID, marker.getTrackId().id());
|
||||
values.put(MarkerColumns.LENGTH, marker.getLength().toM());
|
||||
values.put(MarkerColumns.DURATION, marker.getDuration().toMillis());
|
||||
|
||||
values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6));
|
||||
values.put(MarkerColumns.LATITUDE, (int) (marker.getLatitude() * 1E6));
|
||||
values.put(MarkerColumns.TIME, marker.getTime().toEpochMilli());
|
||||
|
||||
@@ -29,7 +29,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||
|
||||
private static final int DATABASE_VERSION = 37;
|
||||
private static final int DATABASE_VERSION = 38;
|
||||
|
||||
private final Context context;
|
||||
|
||||
@@ -81,6 +81,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 35 -> upgradeFrom34to35(db);
|
||||
case 36 -> upgradeFrom35to36(db);
|
||||
case 37 -> upgradeFrom36to37(db);
|
||||
case 38 -> upgradeFrom37to38(db);
|
||||
default -> throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||
}
|
||||
}
|
||||
@@ -105,6 +106,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
case 34 -> downgradeFrom35to34(db);
|
||||
case 35 -> downgradeFrom36to35(db);
|
||||
case 36 -> downgradeFrom37to36(db);
|
||||
case 37 -> downgradeFrom38to37(db);
|
||||
default -> throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||
}
|
||||
}
|
||||
@@ -644,4 +646,28 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void upgradeFrom37to38(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE markers RENAME TO markers_old");
|
||||
db.execSQL("CREATE TABLE markers (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, category TEXT, icon TEXT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, bearing FLOAT, photoUrl TEXT, FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
|
||||
db.execSQL("INSERT INTO markers SELECT _id, name, description, category, icon, trackid, longitude, latitude, time, elevation, accuracy, bearing, photoUrl FROM markers_old");
|
||||
db.execSQL("DROP TABLE markers_old");
|
||||
|
||||
db.execSQL("CREATE INDEX markers_trackid_index ON markers(trackid)");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
private void downgradeFrom38to37(SQLiteDatabase db) {
|
||||
db.beginTransaction();
|
||||
|
||||
db.execSQL("ALTER TABLE markers ADD COLUMN length FLOAT");
|
||||
db.execSQL("ALTER TABLE markers ADD COLUMN duration INTEGER");
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public enum ActivityType {
|
||||
MOTOR_BIKE("motor bike", R.string.activity_type_motor_bike, R.drawable.ic_activity_mtb_24dp, true),
|
||||
MOTOR_BOATING("motor boating", R.string.activity_type_motor_boating, R.drawable.ic_activity_boat_24dp, true),
|
||||
MOUNTAIN_BIKING("mountain biking", R.string.activity_type_mountain_biking, R.drawable.ic_activity_mtb_24dp, true),
|
||||
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
OFF_TRAIL_HIKING("off trail hiking", R.string.activity_type_off_trail_hiking, R.drawable.ic_activity_walk_24dp, false),
|
||||
OTHER("other", R.string.activity_type_other, ActivityIcon.ICON_UNKNOWN, true),
|
||||
PADDLING("paddling", R.string.activity_type_paddling, ActivityIcon.ICON_UNKNOWN, true),
|
||||
PARA_GLIDING("para gliding", R.string.activity_type_para_gliding, ActivityIcon.ICON_UNKNOWN, true),
|
||||
|
||||
@@ -27,8 +27,6 @@ import androidx.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
|
||||
*
|
||||
@@ -53,10 +51,6 @@ public final class Marker {
|
||||
private Altitude altitude;
|
||||
private Float bearing;
|
||||
|
||||
//TODO It is the distance from the track starting point; rename to something more meaningful
|
||||
private Distance length;
|
||||
private Duration duration;
|
||||
|
||||
@Deprecated //TODO Make an URI instead of String
|
||||
private String photoUrl = "";
|
||||
|
||||
@@ -65,7 +59,6 @@ public final class Marker {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Deprecated //TODO Marker cannot be created without length AND duration!
|
||||
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
this.trackId = trackId;
|
||||
|
||||
@@ -75,20 +68,15 @@ public final class Marker {
|
||||
throw new RuntimeException("Marker requires a trackpoint with a location.");
|
||||
|
||||
setTrackPoint(trackPoint);
|
||||
|
||||
this.length = Distance.of(0); //TODO Not cool!
|
||||
this.duration = Duration.ofMillis(0); //TODO Not cool!
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
this(trackId, trackPoint);
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
this.icon = icon;
|
||||
this.length = statistics.getTotalDistance();
|
||||
this.duration = statistics.getTotalTime();
|
||||
this.photoUrl = photoUrl;
|
||||
}
|
||||
|
||||
@@ -234,22 +222,6 @@ public final class Marker {
|
||||
this.bearing = bearing;
|
||||
}
|
||||
|
||||
public Distance getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(Distance length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(@NonNull Duration duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public String getPhotoUrl() {
|
||||
return photoUrl;
|
||||
}
|
||||
@@ -263,7 +235,7 @@ public final class Marker {
|
||||
}
|
||||
|
||||
public boolean hasPhoto() {
|
||||
return photoUrl != null && !"".equals(photoUrl);
|
||||
return photoUrl != null && !photoUrl.isEmpty();
|
||||
}
|
||||
|
||||
public record Id(long id) implements Parcelable {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
//TODO Use everywhere instead of android.location.Location.
|
||||
public record Position(
|
||||
Instant time,
|
||||
Double latitude,
|
||||
Double longitude,
|
||||
Distance horizontalAccuracy,
|
||||
Altitude altitude,
|
||||
Distance verticalAccuracy,
|
||||
Float bearing,
|
||||
Speed speed
|
||||
) {
|
||||
public static Position empty() {
|
||||
return new Position(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public static Position of(Location location) {
|
||||
return new Position(
|
||||
Instant.ofEpochMilli(location.getTime()),
|
||||
location.getLatitude(),
|
||||
location.getLongitude(),
|
||||
location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null,
|
||||
location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null,
|
||||
location.hasVerticalAccuracy() ? Distance.of(location.getVerticalAccuracyMeters()) : null,
|
||||
location.hasBearing() ? location.getBearing() : null,
|
||||
location.hasSpeed() ? Speed.of(location.getSpeed()) : null
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasLocation() {
|
||||
return latitude != null && longitude != null;
|
||||
}
|
||||
|
||||
public boolean hasHorizontalAccuracy() {
|
||||
return horizontalAccuracy != null;
|
||||
}
|
||||
|
||||
public boolean hasAltitude() {
|
||||
return altitude != null;
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
return bearing != null;
|
||||
}
|
||||
|
||||
public boolean hasSpeed() {
|
||||
return speed != null;
|
||||
}
|
||||
|
||||
public Location toLocation() {
|
||||
if (!hasLocation()) {
|
||||
throw new RuntimeException("Cannot convert to Location.");
|
||||
}
|
||||
|
||||
Location location = new Location("");
|
||||
location.setTime(time.toEpochMilli());
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
if (hasBearing()) {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
if (hasHorizontalAccuracy()) {
|
||||
location.setAccuracy((float) horizontalAccuracy.toM());
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude.toM());
|
||||
}
|
||||
if (hasSpeed()) {
|
||||
location.setSpeed((float) speed.toMPS());
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
@@ -26,6 +25,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Sensor and/or location information for a specific point in time.
|
||||
@@ -42,6 +42,7 @@ public class TrackPoint {
|
||||
@NonNull
|
||||
private final Instant time;
|
||||
|
||||
//TODO We may use Position for these items
|
||||
private Double latitude;
|
||||
private Double longitude;
|
||||
private Distance horizontalAccuracy;
|
||||
@@ -135,6 +136,10 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isIdleTriggered() {
|
||||
return type == Type.IDLE;
|
||||
}
|
||||
|
||||
public boolean isSegmentManualStart() {
|
||||
return type == Type.SEGMENT_START_MANUAL;
|
||||
}
|
||||
@@ -181,42 +186,40 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
//TODO Better return null, if no location is present aka latitude == null etc.
|
||||
@NonNull
|
||||
public Location getLocation() {
|
||||
Location location = new Location("");
|
||||
location.setTime(time.toEpochMilli());
|
||||
if (hasLocation()) {
|
||||
location.setLatitude(latitude);
|
||||
location.setLongitude(longitude);
|
||||
}
|
||||
if (hasBearing()) {
|
||||
location.setBearing(bearing);
|
||||
}
|
||||
if (hasHorizontalAccuracy()) {
|
||||
location.setAccuracy((float) horizontalAccuracy.toM());
|
||||
}
|
||||
if (hasAltitude()) {
|
||||
location.setAltitude(altitude.toM());
|
||||
}
|
||||
if (hasSpeed()) {
|
||||
location.setSpeed((float) speed.toMPS());
|
||||
}
|
||||
|
||||
return location;
|
||||
public Position getPosition() {
|
||||
return new Position(
|
||||
time,
|
||||
latitude,
|
||||
longitude,
|
||||
horizontalAccuracy,
|
||||
altitude,
|
||||
verticalAccuracy,
|
||||
bearing,
|
||||
speed
|
||||
);
|
||||
}
|
||||
|
||||
public TrackPoint setLocation(@NonNull Location location) {
|
||||
this.latitude = location.getLatitude();
|
||||
this.longitude = location.getLongitude();
|
||||
this.altitude = location.hasAltitude() ? Altitude.WGS84.of(location.getAltitude()) : null;
|
||||
this.speed = location.hasSpeed() ? Speed.of(location.getSpeed()) : null;
|
||||
this.horizontalAccuracy = location.hasAccuracy() ? Distance.of(location.getAccuracy()) : null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
this.verticalAccuracy = location.hasVerticalAccuracy() ? Distance.of(location.getVerticalAccuracyMeters()) : null;
|
||||
}
|
||||
public TrackPoint setPosition(Position location) {
|
||||
this.latitude = location.latitude();
|
||||
this.longitude = location.longitude();
|
||||
this.altitude = location.altitude();
|
||||
this.speed = location.speed();
|
||||
this.horizontalAccuracy = location.horizontalAccuracy();
|
||||
this.verticalAccuracy = location.verticalAccuracy();
|
||||
|
||||
//TODO Should we copy the bearing?
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Location getLocation() {
|
||||
return getPosition().toLocation();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public TrackPoint setLocation(@NonNull Location location) {
|
||||
setPosition(Position.of(location));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -288,6 +291,7 @@ public class TrackPoint {
|
||||
this.speed = speed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
return bearing != null;
|
||||
}
|
||||
@@ -348,13 +352,20 @@ public class TrackPoint {
|
||||
return hasHorizontalAccuracy() && horizontalAccuracy.lessThan(thresholdHorizontalAccuracy);
|
||||
}
|
||||
|
||||
public float bearingTo(@NonNull TrackPoint dest) {
|
||||
public Optional<Float> bearingTo(@NonNull TrackPoint dest) {
|
||||
if (!dest.hasLocation() || !hasLocation()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return bearingTo(dest.getLocation());
|
||||
}
|
||||
|
||||
//TODO Bearing requires a location; what do we do if we don't have any?
|
||||
public float bearingTo(@NonNull Location dest) {
|
||||
return getLocation().bearingTo(dest);
|
||||
@Deprecated
|
||||
public Optional<Float> bearingTo(@NonNull Location dest) {
|
||||
if (!hasLocation()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(getLocation().bearingTo(dest));
|
||||
}
|
||||
|
||||
// Sensor data
|
||||
|
||||
@@ -41,10 +41,6 @@ public interface MarkerColumns extends BaseColumns {
|
||||
String CATEGORY = "category"; // marker category
|
||||
String ICON = "icon"; // marker icon
|
||||
String TRACKID = "trackid"; // track id
|
||||
|
||||
String LENGTH = "length"; // length of the track (without smoothing)
|
||||
String DURATION = "duration"; // total duration of the track from the beginning until now
|
||||
|
||||
String LONGITUDE = "longitude"; // longitude
|
||||
String LATITUDE = "latitude"; // latitude
|
||||
String TIME = "time"; // time
|
||||
@@ -61,8 +57,6 @@ public interface MarkerColumns extends BaseColumns {
|
||||
+ CATEGORY + " TEXT, "
|
||||
+ ICON + " TEXT, "
|
||||
+ TRACKID + " INTEGER NOT NULL, "
|
||||
+ LENGTH + " FLOAT, "
|
||||
+ DURATION + " INTEGER, "
|
||||
+ LONGITUDE + " INTEGER, "
|
||||
+ LATITUDE + " INTEGER, "
|
||||
+ TIME + " INTEGER, "
|
||||
|
||||
@@ -310,7 +310,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
public Distance writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint, List<TrackPoint> sensorPoints, Distance trackDistance) {
|
||||
Distance cumulativeDistance = null;
|
||||
Distance cumulativeDistance;
|
||||
|
||||
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
@@ -274,7 +275,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
float heading = getHeading(marker.getTrackId(), marker.getLocation());
|
||||
writePhotoOverlay(marker, heading, zoneOffset);
|
||||
} else {
|
||||
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), zoneOffset);
|
||||
writePlacemark(marker.getName(), marker.getCategory(), marker.getDescription(), marker.getLocation(), marker.getTime(), zoneOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +369,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
|
||||
@VisibleForTesting
|
||||
void writeTrackPoint(ZoneOffset zoneOffset, TrackPoint trackPoint) {
|
||||
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getLocation()) + "</when>");
|
||||
printWriter.println("<when>" + getTime(zoneOffset, trackPoint.getTime()) + "</when>");
|
||||
|
||||
trackpointTypeList.add(trackPoint.getType());
|
||||
|
||||
@@ -411,12 +412,12 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.println("</SimpleArrayData>");
|
||||
}
|
||||
|
||||
private void writePlacemark(String name, String activityType, String description, Location location, ZoneOffset zoneOffset) {
|
||||
private void writePlacemark(String name, String activityType, String description, Location location, Instant time, ZoneOffset zoneOffset) {
|
||||
if (location != null) {
|
||||
printWriter.println("<Placemark>");
|
||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||
printWriter.println("<description>" + StringUtils.formatCData(description) + "</description>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, location) + "</when></TimeStamp>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, time) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + KMLTrackExporter.MARKER_STYLE + "</styleUrl>");
|
||||
writeTypeLocalized(activityType);
|
||||
printWriter.println("<Point>");
|
||||
@@ -437,7 +438,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
printWriter.print("<heading>" + heading + "</heading>");
|
||||
printWriter.print("<tilt>90</tilt>");
|
||||
printWriter.println("</Camera>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getLocation()) + "</when></TimeStamp>");
|
||||
printWriter.println("<TimeStamp><when>" + getTime(zoneOffset, marker.getTime()) + "</when></TimeStamp>");
|
||||
printWriter.println("<styleUrl>#" + MARKER_STYLE + "</styleUrl>");
|
||||
writeTypeLocalized(marker.getCategory());
|
||||
|
||||
@@ -460,14 +461,13 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
|
||||
/**
|
||||
* Returns the formatted time of the location; either absolute or relative depending exportTrackDetail.
|
||||
*
|
||||
* @param location the location
|
||||
*/
|
||||
private String getTime(ZoneOffset zoneOffset, Location location) {
|
||||
return StringUtils.formatDateTimeIso8601(Instant.ofEpochMilli(location.getTime()), zoneOffset);
|
||||
private String getTime(ZoneOffset zoneOffset, Instant instant) {
|
||||
return StringUtils.formatDateTimeIso8601(instant, zoneOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: check if this is a useful feature (likely not).
|
||||
* Gets the heading to a location.
|
||||
*
|
||||
* @param trackId the track id containing the location
|
||||
@@ -480,7 +480,10 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
TrackPoint viewLocation = contentProviderUtils.getLastValidTrackPoint(trackId);
|
||||
if (viewLocation != null) {
|
||||
return viewLocation.bearingTo(location);
|
||||
Optional<Float> bearing = viewLocation.bearingTo(location);
|
||||
if (bearing.isPresent()) {
|
||||
return bearing.get();
|
||||
}
|
||||
}
|
||||
|
||||
return location.getBearing();
|
||||
@@ -495,7 +498,7 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
}
|
||||
|
||||
private void writeTypeLocalized(String localizedValue) {
|
||||
if (localizedValue == null || localizedValue.equals("")) {
|
||||
if (localizedValue == null || localizedValue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
printWriter.println("<ExtendedData>");
|
||||
|
||||
@@ -414,7 +414,7 @@ public class KmlTrackImporter extends DefaultHandler implements XMLImporter.Trac
|
||||
Float value = null;
|
||||
if (content != null) {
|
||||
content = content.trim();
|
||||
if (!"".equals(content)) {
|
||||
if (!content.isEmpty()) {
|
||||
try {
|
||||
value = Float.parseFloat(content);
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
@@ -26,7 +25,6 @@ import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
@@ -158,7 +156,7 @@ public class TrackImporter {
|
||||
contentProviderUtils.bulkInsertTrackPoint(trackPoints, trackId);
|
||||
|
||||
// Store Markers
|
||||
matchMarkers2TrackPoints(trackId);
|
||||
updateMarkers(trackId);
|
||||
for (Marker marker : markers)
|
||||
marker.setTrackId(trackId); //TODO Should happen in bulkInsertMarkers
|
||||
|
||||
@@ -206,7 +204,8 @@ public class TrackImporter {
|
||||
}
|
||||
|
||||
if (!current.hasBearing()) {
|
||||
current.setBearing(previous.bearingTo(current));
|
||||
previous.bearingTo(current)
|
||||
.ifPresent(current::setBearing);
|
||||
}
|
||||
|
||||
if (current.getType().equals(TrackPoint.Type.TRACKPOINT) && distanceToPrevious.greaterThan(maxRecordingDistance)) {
|
||||
@@ -217,55 +216,16 @@ public class TrackImporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Modifies content of markers (incl. removal).
|
||||
* NOTE: Modifies content of markers.
|
||||
*/
|
||||
private void matchMarkers2TrackPoints(Track.Id trackId) {
|
||||
List<TrackPoint> trackPointsWithLocation = trackPoints.stream()
|
||||
.filter(TrackPoint::hasLocation)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Marker> todoMarkers = new LinkedList<>(markers);
|
||||
List<Marker> doneMarkers = new LinkedList<>();
|
||||
|
||||
for (final TrackPoint trackPoint : trackPointsWithLocation) {
|
||||
if (todoMarkers.isEmpty()) {
|
||||
break;
|
||||
private void updateMarkers(Track.Id trackId) {
|
||||
markers.forEach(marker -> {
|
||||
if (marker.hasPhoto()) {
|
||||
marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl()));
|
||||
}
|
||||
|
||||
TrackStatisticsUpdater updater = new TrackStatisticsUpdater();
|
||||
updater.addTrackPoint(trackPoint);
|
||||
|
||||
List<Marker> matchedMarkers = todoMarkers.stream()
|
||||
.filter(it -> trackPoint.getLatitude() == it.getLatitude()
|
||||
&& trackPoint.getLongitude() == it.getLongitude()
|
||||
&& trackPoint.getTime().equals(it.getTime())
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
TrackStatistics statistics = updater.getTrackStatistics();
|
||||
for (Marker marker : matchedMarkers) {
|
||||
if (marker.hasPhoto()) {
|
||||
marker.setPhotoUrl(getInternalPhotoUrl(trackId, marker.getPhotoUrl()));
|
||||
}
|
||||
|
||||
marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why?
|
||||
|
||||
marker.setLength(statistics.getTotalDistance());
|
||||
marker.setDuration(statistics.getTotalTime());
|
||||
|
||||
marker.setTrackPoint(trackPoint);
|
||||
}
|
||||
|
||||
todoMarkers.removeAll(matchedMarkers);
|
||||
doneMarkers.addAll(matchedMarkers);
|
||||
}
|
||||
|
||||
if (todoMarkers.isEmpty()) {
|
||||
Log.w(TAG, "Some markers could not be attached to TrackPoints; those are not imported.");
|
||||
}
|
||||
|
||||
markers.clear();
|
||||
markers.addAll(doneMarkers);
|
||||
marker.setIcon(context.getString(R.string.marker_icon_url)); //TODO Why?
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package de.dennisguse.opentracks.publicapi;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
/**
|
||||
* Public API to creates a Marker for a given track with a given location
|
||||
*/
|
||||
public class CreateMarkerActivity extends AppCompatActivity {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_LOCATION = "location";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Track.Id trackId = new Track.Id(getIntent().getLongExtra(EXTRA_TRACK_ID, 0L));
|
||||
Location location = getIntent().getParcelableExtra(EXTRA_LOCATION);
|
||||
|
||||
TrackRecordingServiceConnection.execute(this, (service, self) -> {
|
||||
Intent intent = IntentUtils
|
||||
.newIntent(this, MarkerEditActivity.class)
|
||||
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId)
|
||||
.putExtra(MarkerEditActivity.EXTRA_LOCATION, location);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.dennisguse.opentracks.publicapi;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.databinding.MarkerDetailActivityBinding;
|
||||
import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerDetailActivity;
|
||||
import de.dennisguse.opentracks.ui.markers.MarkerDetailFragment;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
|
||||
/**
|
||||
* Public api to show an existing marker
|
||||
*/
|
||||
public class ShowMarkerActivity extends AppCompatActivity {
|
||||
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
|
||||
private static final String TAG = ShowMarkerActivity.class.getSimpleName();
|
||||
|
||||
private MarkerDetailActivityBinding viewBinding;
|
||||
|
||||
private List<Marker.Id> markerIds;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
Marker.Id markerId = new Marker.Id(getIntent().getLongExtra(EXTRA_MARKER_ID, 0));
|
||||
if (markerId.id() == 0) {
|
||||
throw new IllegalStateException("No valid markerId provided");
|
||||
}
|
||||
Intent intent = IntentUtils.newIntent(this, MarkerDetailActivity.class)
|
||||
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, markerId);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,9 +20,7 @@ public class GainManager implements SensorConnector {
|
||||
|
||||
private final SensorManager.SensorDataChangedObserver listener;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
connect();
|
||||
};
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> connect();
|
||||
|
||||
private Context context;
|
||||
private Handler handler;
|
||||
|
||||
@@ -44,7 +44,7 @@ public abstract class Aggregator<Input, Output> {
|
||||
|
||||
public Output getValue() {
|
||||
if (!hasValue()) {
|
||||
return null;
|
||||
return null; //TODO Check if this is a good idea!
|
||||
}
|
||||
if (isRecent()) {
|
||||
return value;
|
||||
@@ -55,7 +55,7 @@ public abstract class Aggregator<Input, Output> {
|
||||
/**
|
||||
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
|
||||
*/
|
||||
public void reset() {};
|
||||
public void reset() {}
|
||||
|
||||
/**
|
||||
* Is the data recent considering the current time.
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class AggregatorGPS extends Aggregator<Location, Location> {
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
|
||||
public class AggregatorGPS extends Aggregator<Position, Position> {
|
||||
|
||||
|
||||
public AggregatorGPS(String sensorAddress) {
|
||||
super(sensorAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void computeValue(Raw<Location> current) {
|
||||
protected void computeValue(Raw<Position> current) {
|
||||
value = current.value();
|
||||
}
|
||||
|
||||
@@ -22,7 +23,7 @@ public class AggregatorGPS extends Aggregator<Location, Location> {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Location getNoneValue() {
|
||||
return new Location("none");
|
||||
protected Position getNoneValue() {
|
||||
return Position.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.dennisguse.opentracks.sensors.sensorData;
|
||||
|
||||
import android.location.Location;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
@@ -11,6 +10,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import de.dennisguse.opentracks.data.models.AtmosphericPressure;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.BluetoothHandlerCyclingCadence;
|
||||
@@ -127,8 +127,8 @@ public final class SensorDataSet {
|
||||
this.barometer.add((Raw<AtmosphericPressure>) data);
|
||||
return;
|
||||
}
|
||||
if (value instanceof Location) {
|
||||
this.gps.add((Raw<Location>) data);
|
||||
if (value instanceof Position) {
|
||||
this.gps.add((Raw<Position>) data);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public final class SensorDataSet {
|
||||
|
||||
public void fillTrackPoint(TrackPoint trackPoint) {
|
||||
if (gps != null && gps.hasValue()) {
|
||||
trackPoint.setLocation(gps.getValue());
|
||||
trackPoint.setPosition(gps.getValue());
|
||||
}
|
||||
|
||||
if (getHeartRate() != null) {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
@@ -19,7 +17,6 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
@@ -138,31 +135,6 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
return new Pair<>(track, current);
|
||||
}
|
||||
|
||||
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
||||
if (name == null) {
|
||||
Integer nextMarkerNumber = contentProviderUtils.getNextMarkerNumber(trackId);
|
||||
if (nextMarkerNumber == null) {
|
||||
nextMarkerNumber = 1;
|
||||
}
|
||||
name = context.getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
||||
}
|
||||
|
||||
if (lastStoredTrackPointWithLocation == null) {
|
||||
Log.i(TAG, "Could not create a marker as trackPoint is unknown.");
|
||||
return null;
|
||||
}
|
||||
|
||||
category = category != null ? category : "";
|
||||
description = description != null ? description : "";
|
||||
String icon = context.getString(R.string.marker_icon_url);
|
||||
photoUrl = photoUrl != null ? photoUrl : "";
|
||||
|
||||
// Insert marker
|
||||
Marker marker = new Marker(name, description, category, icon, trackId, getTrackStatistics(), lastStoredTrackPointWithLocation, photoUrl);
|
||||
Uri uri = contentProviderUtils.insertMarker(marker);
|
||||
return new Marker.Id(ContentUris.parseId(uri));
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public void onIdle() {
|
||||
Log.d(TAG, "Becoming idle");
|
||||
@@ -197,6 +169,9 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
|
||||
if (trackPoint.hasLocation() && lastStoredTrackPointWithLocation == null) {
|
||||
insertTrackPoint(trackPoint, true);
|
||||
|
||||
handler.removeCallbacks(ON_IDLE);
|
||||
handler.postDelayed(ON_IDLE, idleDuration.toMillis());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -244,7 +219,7 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
}
|
||||
|
||||
TrackStatistics getTrackStatistics() {
|
||||
return trackStatisticsUpdater.getTrackStatistics();
|
||||
return trackStatisticsUpdater == null ? null : trackStatisticsUpdater.getTrackStatistics();
|
||||
}
|
||||
|
||||
private void insertTrackPoint(@NonNull TrackPoint trackPoint, boolean storeLastTrackPointIfUseful) {
|
||||
@@ -305,6 +280,10 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
|
||||
}
|
||||
}
|
||||
|
||||
public TrackPoint getLastStoredTrackPointWithLocation() {
|
||||
return lastStoredTrackPointWithLocation;
|
||||
}
|
||||
|
||||
public interface IdleObserver {
|
||||
void onIdle();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import java.io.StringWriter;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
@@ -57,6 +56,10 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
public static final RecordingData NOT_RECORDING = new RecordingData(null, null, null);
|
||||
public static final GpsStatusValue STATUS_GPS_DEFAULT = GpsStatusValue.GPS_NONE;
|
||||
|
||||
public TrackPoint getLastStoredTrackPointWithLocation() {
|
||||
return trackRecordingManager.getLastStoredTrackPointWithLocation();
|
||||
}
|
||||
|
||||
public class Binder extends android.os.Binder {
|
||||
|
||||
private Binder() {
|
||||
@@ -269,14 +272,6 @@ public class TrackRecordingService extends Service implements TrackPointCreator.
|
||||
gpsStatusObservable.postValue(gpsStatusValue);
|
||||
}
|
||||
|
||||
public Marker.Id insertMarker(String name, String category, String description, String photoUrl) {
|
||||
if (!isRecording()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trackRecordingManager.insertMarker(name, category, description, photoUrl);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@VisibleForTesting
|
||||
public TrackPointCreator getTrackPointCreator() {
|
||||
|
||||
+47
-27
@@ -4,9 +4,11 @@ import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageHeartRate;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapPower;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
|
||||
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceUnit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.icu.text.MessageFormat;
|
||||
@@ -22,11 +24,13 @@ import java.util.Map;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.intervals.IntervalStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
class VoiceAnnouncementUtils {
|
||||
|
||||
@@ -44,34 +48,37 @@ class VoiceAnnouncementUtils {
|
||||
Speed averageMovingSpeed = trackStatistics.getAverageMovingSpeed();
|
||||
Speed currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed() : null;
|
||||
|
||||
int perUnitStringId;
|
||||
int distanceId;
|
||||
int speedId;
|
||||
String unitDistanceTTS;
|
||||
String unitSpeedTTS;
|
||||
switch (unitSystem) {
|
||||
case METRIC -> {
|
||||
perUnitStringId = R.string.voice_per_kilometer;
|
||||
distanceId = R.string.voiceDistanceKilometersPlural;
|
||||
speedId = R.string.voiceSpeedKilometersPerHourPlural;
|
||||
unitDistanceTTS = "kilometer";
|
||||
unitSpeedTTS = "kilometer per hour";
|
||||
int perUnitStringId = R.string.empty;
|
||||
int distanceId = R.string.voiceDistance;
|
||||
int speedId = R.string.voiceSpeed;
|
||||
String unitDistanceTTS = StringUtils.EMPTY;
|
||||
String unitSpeedTTS = StringUtils.EMPTY;
|
||||
|
||||
if(shouldVoiceAnnounceUnit()) {
|
||||
switch (unitSystem) {
|
||||
case METRIC -> {
|
||||
perUnitStringId = R.string.voice_per_kilometer;
|
||||
distanceId = R.string.voiceDistanceKilometersPlural;
|
||||
speedId = R.string.voiceSpeedKilometersPerHourPlural;
|
||||
unitDistanceTTS = "kilometer";
|
||||
unitSpeedTTS = "kilometer per hour";
|
||||
}
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> {
|
||||
perUnitStringId = R.string.voice_per_mile;
|
||||
distanceId = R.string.voiceDistanceMilesPlural;
|
||||
speedId = R.string.voiceSpeedMilesPerHourPlural;
|
||||
unitDistanceTTS = "mile";
|
||||
unitSpeedTTS = "mile per hour";
|
||||
}
|
||||
case NAUTICAL_IMPERIAL -> {
|
||||
perUnitStringId = R.string.voice_per_nautical_mile;
|
||||
distanceId = R.string.voiceDistanceNauticalMilesPlural;
|
||||
speedId = R.string.voiceSpeedMKnotsPlural;
|
||||
unitDistanceTTS = "nautical mile";
|
||||
unitSpeedTTS = "knots";
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
case IMPERIAL_FEET, IMPERIAL_METER -> {
|
||||
perUnitStringId = R.string.voice_per_mile;
|
||||
distanceId = R.string.voiceDistanceMilesPlural;
|
||||
speedId = R.string.voiceSpeedMilesPerHourPlural;
|
||||
unitDistanceTTS = "mile";
|
||||
unitSpeedTTS = "mile per hour";
|
||||
}
|
||||
case NAUTICAL_IMPERIAL -> {
|
||||
perUnitStringId = R.string.voice_per_nautical_mile;
|
||||
distanceId = R.string.voiceDistanceNauticalMilesPlural;
|
||||
speedId = R.string.voiceSpeedMKnotsPlural;
|
||||
unitDistanceTTS = "nautical mile";
|
||||
unitSpeedTTS = "knots";
|
||||
}
|
||||
default -> throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
double distanceInUnit = totalDistance.toKM_Miles(unitSystem);
|
||||
@@ -154,6 +161,19 @@ class VoiceAnnouncementUtils {
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.hasAveragePower()) {
|
||||
int currentPower = Math.round(currentInterval.getAveragePower().getW());
|
||||
if(shouldVoiceAnnounceUnit()) {
|
||||
String template = context.getResources().getString(R.string.power_x_watt);
|
||||
builder.append(" ")
|
||||
.append(MessageFormat.format(template, Map.of("x", currentPower)));
|
||||
} else {
|
||||
builder.append(" ").append(context.getString(R.string.power)).append(" ");
|
||||
builder.append(String.valueOf(currentPower));
|
||||
}
|
||||
builder.append(".");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Position;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.SensorConnector;
|
||||
import de.dennisguse.opentracks.sensors.SensorManager;
|
||||
@@ -138,7 +139,7 @@ public class GPSManager implements SensorConnector, LocationListenerCompat, GpsS
|
||||
return;
|
||||
}
|
||||
|
||||
listener.onChange(new Raw<>(location));
|
||||
listener.onChange(new Raw<>(Position.of(location)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -229,7 +229,7 @@ public class PreferencesUtils {
|
||||
|
||||
//TODO Check if actually needed or can be superseeded by a flexible default in getUnit()
|
||||
public static void applyDefaultUnit() {
|
||||
if (getString(R.string.stats_units_key, "").equals("")) {
|
||||
if (getString(R.string.stats_units_key, "").isEmpty()) {
|
||||
if (!Locale.US.equals(Locale.getDefault())) {
|
||||
setUnit(UnitSystem.METRIC);
|
||||
} else {
|
||||
@@ -333,6 +333,10 @@ public class PreferencesUtils {
|
||||
return getBoolean(R.string.voice_announce_idle_key, true);
|
||||
}
|
||||
|
||||
public static boolean shouldVoiceAnnounceUnit() {
|
||||
return getBoolean(R.string.voice_announce_unit_key, true);
|
||||
}
|
||||
|
||||
public static Duration getVoiceAnnouncementFrequency() {
|
||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.voice_announcement_frequency_default));
|
||||
int value = getInt(R.string.voice_announcement_frequency_key, DEFAULT);
|
||||
@@ -430,6 +434,10 @@ public class PreferencesUtils {
|
||||
return getBoolean(R.string.voice_announce_lap_heart_rate_key, false);
|
||||
}
|
||||
|
||||
public static boolean shouldVoiceAnnounceLapPower() {
|
||||
return getBoolean(R.string.voice_announce_lap_power_key, false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setVoiceAnnounceLapHeartRate(boolean value) {
|
||||
setBoolean(R.string.voice_announce_lap_heart_rate_key, value);
|
||||
|
||||
@@ -42,8 +42,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
fragment = new MainSettingsFragment();
|
||||
}
|
||||
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
setSupportActionBar(viewBinding.topAppBar);
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
|
||||
getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).commit();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.time.Instant;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
@@ -60,6 +61,7 @@ public class TrackStatistics {
|
||||
private Float totalAltitudeLoss_m = null;
|
||||
// The average heart rate seen on this track
|
||||
private HeartRate avgHeartRate = null;
|
||||
private Power avgPower = null;
|
||||
|
||||
private boolean isIdle;
|
||||
|
||||
@@ -83,6 +85,7 @@ public class TrackStatistics {
|
||||
totalAltitudeGain_m = other.totalAltitudeGain_m;
|
||||
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
|
||||
avgHeartRate = other.avgHeartRate;
|
||||
avgPower = other.avgPower;
|
||||
isIdle = other.isIdle;
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@ public class TrackStatistics {
|
||||
*
|
||||
* @param other another statistics data object
|
||||
*/
|
||||
//TODO Should be refactored to append only [mainly due to isIdle] (NOTE: This requires to use a custom value object for AggregatedStatistics; this is anyhow recommended).
|
||||
public void merge(TrackStatistics other) {
|
||||
if (startTime == null) {
|
||||
startTime = other.startTime;
|
||||
@@ -116,6 +120,8 @@ public class TrackStatistics {
|
||||
stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime;
|
||||
}
|
||||
|
||||
isIdle = other.isIdle; //TODO This implicitly assumes append mode.
|
||||
|
||||
if (avgHeartRate == null) {
|
||||
avgHeartRate = other.avgHeartRate;
|
||||
} else {
|
||||
@@ -129,6 +135,19 @@ public class TrackStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
if (avgPower == null) {
|
||||
avgPower = other.avgPower;
|
||||
} else {
|
||||
if (other.avgPower != null) {
|
||||
// Using total time as weights for the averaging.
|
||||
// Important to do this before total time is updated
|
||||
avgPower = Power.of(
|
||||
(totalTime.getSeconds() * avgPower.getW() + other.totalTime.getSeconds() * other.avgPower.getW())
|
||||
/ (totalTime.getSeconds() + other.totalTime.getSeconds())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
totalDistance = totalDistance.plus(other.totalDistance);
|
||||
totalTime = totalTime.plus(other.totalTime);
|
||||
movingTime = movingTime.plus(other.movingTime);
|
||||
@@ -270,6 +289,15 @@ public class TrackStatistics {
|
||||
return avgHeartRate;
|
||||
}
|
||||
|
||||
public boolean hasPower() {
|
||||
return avgPower != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Power getAveragePower() {
|
||||
return avgPower;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average speed.
|
||||
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
|
||||
@@ -333,6 +361,12 @@ public class TrackStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
public void setAveragePower(Power power) {
|
||||
if (power != null) {
|
||||
avgPower = power;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTotalAltitudeGain() {
|
||||
return totalAltitudeGain_m != null;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
/**
|
||||
* Updater for {@link TrackStatistics}.
|
||||
@@ -43,6 +45,8 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
private float averageHeartRateBPM;
|
||||
private Duration totalHeartRateDuration = Duration.ZERO;
|
||||
private float averagePowerW;
|
||||
private Duration totalPowerDuration = Duration.ZERO;
|
||||
|
||||
// The current segment's statistics
|
||||
private final TrackStatistics currentSegment;
|
||||
@@ -53,11 +57,6 @@ public class TrackStatisticsUpdater {
|
||||
this(new TrackStatistics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new{@link TrackStatisticsUpdater} with a {@link TrackStatisticsUpdater} already existed.
|
||||
*
|
||||
* @param trackStatistics a {@link TrackStatisticsUpdater}
|
||||
*/
|
||||
public TrackStatisticsUpdater(TrackStatistics trackStatistics) {
|
||||
this.trackStatistics = trackStatistics;
|
||||
this.currentSegment = new TrackStatistics();
|
||||
@@ -84,9 +83,6 @@ public class TrackStatisticsUpdater {
|
||||
trackPoints.stream().forEachOrdered(this::addTrackPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint) {
|
||||
if (trackPoint.isSegmentManualStart()) {
|
||||
reset(trackPoint);
|
||||
@@ -125,6 +121,17 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
|
||||
}
|
||||
|
||||
// Update power
|
||||
if (trackPoint.hasPower() && lastTrackPoint != null) {
|
||||
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
Duration newTotalDuration = totalPowerDuration.plus(trackPointDuration);
|
||||
|
||||
averagePowerW = (totalPowerDuration.toMillis() * averagePowerW + trackPointDuration.toMillis() * trackPoint.getPower().getW()) / newTotalDuration.toMillis();
|
||||
totalPowerDuration = newTotalDuration;
|
||||
|
||||
currentSegment.setAveragePower(Power.of(averagePowerW));
|
||||
}
|
||||
|
||||
{
|
||||
// Update total distance
|
||||
Distance movingDistance = null;
|
||||
@@ -137,18 +144,23 @@ public class TrackStatisticsUpdater {
|
||||
movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
|
||||
}
|
||||
if (movingDistance != null) {
|
||||
currentSegment.setIdle(false);
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
}
|
||||
|
||||
if (!currentSegment.isIdle() && !trackPoint.isSegmentManualStart()) {
|
||||
if (lastTrackPoint != null) {
|
||||
if (!currentSegment.isIdle()) {
|
||||
if (!trackPoint.isSegmentManualStart() && lastTrackPoint != null) {
|
||||
currentSegment.addMovingTime(trackPoint, lastTrackPoint);
|
||||
}
|
||||
}
|
||||
|
||||
if (trackPoint.getType() == TrackPoint.Type.IDLE) {
|
||||
if (trackPoint.isIdleTriggered()) {
|
||||
currentSegment.setIdle(true);
|
||||
} else if (currentSegment.isIdle()) {
|
||||
// Shall we switch to non-idle?
|
||||
if (movingDistance != null
|
||||
&& movingDistance.greaterOrEqualThan(PreferencesUtils.getRecordingDistanceInterval())) {
|
||||
currentSegment.setIdle(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
import de.dennisguse.opentracks.data.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
@@ -103,6 +104,7 @@ public class IntervalStatistics {
|
||||
private Float gain_m;
|
||||
private Float loss_m;
|
||||
private HeartRate avgHeartRate;
|
||||
private Power avgPower;
|
||||
|
||||
public Interval() {
|
||||
}
|
||||
@@ -118,6 +120,7 @@ public class IntervalStatistics {
|
||||
gain_m = i.gain_m;
|
||||
loss_m = i.loss_m;
|
||||
avgHeartRate = i.avgHeartRate;
|
||||
avgPower = i.avgPower;
|
||||
}
|
||||
|
||||
public Interval(Interval i) {
|
||||
@@ -126,6 +129,7 @@ public class IntervalStatistics {
|
||||
gain_m = i.gain_m;
|
||||
loss_m = i.loss_m;
|
||||
avgHeartRate = i.avgHeartRate;
|
||||
avgPower = i.avgPower;
|
||||
}
|
||||
|
||||
public Distance getDistance() {
|
||||
@@ -160,12 +164,21 @@ public class IntervalStatistics {
|
||||
return avgHeartRate;
|
||||
}
|
||||
|
||||
public boolean hasAveragePower() {
|
||||
return avgPower != null;
|
||||
}
|
||||
|
||||
public Power getAveragePower() {
|
||||
return avgPower;
|
||||
}
|
||||
|
||||
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
|
||||
distance = distance.plus(trackStatistics.getTotalDistance());
|
||||
time = time.plus(trackStatistics.getTotalTime());
|
||||
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
||||
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
|
||||
avgHeartRate = trackStatistics.getAverageHeartRate();
|
||||
avgPower = trackStatistics.getAveragePower();
|
||||
if (lastTrackPoint == null) {
|
||||
return;
|
||||
}
|
||||
@@ -183,6 +196,7 @@ public class IntervalStatistics {
|
||||
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
|
||||
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
|
||||
avgHeartRate = trackStatistics.getAverageHeartRate();
|
||||
avgPower = trackStatistics.getAveragePower();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,15 +59,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
|
||||
protected void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
Object bundleMarkerId = getIntent().getExtras().get(EXTRA_MARKER_ID);
|
||||
Marker.Id markerId = null;
|
||||
if (bundleMarkerId instanceof Marker.Id cast) {
|
||||
markerId = cast;
|
||||
}
|
||||
if (bundleMarkerId instanceof Long cast) {
|
||||
//Incoming Intent via Dashboard API.
|
||||
markerId = new Marker.Id(cast);
|
||||
}
|
||||
Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
|
||||
if (markerId == null) {
|
||||
Log.d(TAG, "invalid marker id");
|
||||
finish();
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
@@ -36,19 +37,19 @@ import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.PickVisualMediaRequest;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.MarkerEditBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
|
||||
/**
|
||||
* An activity to add/edit a marker.
|
||||
@@ -59,13 +60,14 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
public static final String EXTRA_MARKER_ID = "marker_id";
|
||||
public static final String EXTRA_LOCATION = "location";
|
||||
|
||||
private static final String CAMERA_PHOTO_URI_KEY = "camera_photo_uri_key";
|
||||
|
||||
private static final String NEW_MARKER_ID = "new_marker_id";
|
||||
|
||||
private static final String TAG = MarkerEditActivity.class.getSimpleName();
|
||||
private Track.Id trackId;
|
||||
private Location location;
|
||||
private Marker.Id markerId;
|
||||
private Marker marker;
|
||||
|
||||
private MenuItem insertPhotoMenuItem;
|
||||
@@ -93,16 +95,16 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
|
||||
@Nullable Marker.Id markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
|
||||
final boolean isNewMarker = markerId == null;
|
||||
location = getIntent().getParcelableExtra(EXTRA_LOCATION);
|
||||
markerId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
|
||||
if ((trackId == null || location == null) && markerId == null) {
|
||||
throw new IllegalStateException("TrackId and Location must be provided or an existing markerId");
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
cameraPhotoUri = Uri.parse(savedInstanceState.getString(CAMERA_PHOTO_URI_KEY, ""));
|
||||
Marker.Id newMarkerId = savedInstanceState.getParcelable(NEW_MARKER_ID);
|
||||
if (newMarkerId != null) {
|
||||
markerId = newMarkerId;
|
||||
}
|
||||
}
|
||||
boolean isNewMarker = markerId == null;
|
||||
|
||||
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||
|
||||
@@ -157,16 +159,8 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (markerId == null) {
|
||||
TrackRecordingServiceConnection.execute(this, (service, self) -> {
|
||||
Marker.Id newMarkerId = createNewMarker(service);
|
||||
if (newMarkerId == null) {
|
||||
finish();
|
||||
} else {
|
||||
loadMarkerData(newMarkerId);
|
||||
}
|
||||
});
|
||||
if (isNewMarker) {
|
||||
createNewMarker().observe(this, this::loadMarkerData);
|
||||
} else {
|
||||
loadMarkerData(markerId);
|
||||
}
|
||||
@@ -174,35 +168,27 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
private Marker.Id createNewMarker(TrackRecordingService trackRecordingService) {
|
||||
try {
|
||||
Marker.Id marker = trackRecordingService.insertMarker("", "", "", null);
|
||||
if (marker == null) {
|
||||
Toast.makeText(this, R.string.marker_add_error, Toast.LENGTH_LONG).show();
|
||||
return null;
|
||||
}
|
||||
|
||||
return marker;
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Unable to add marker.", e);
|
||||
return null;
|
||||
}
|
||||
private LiveData<Marker> createNewMarker() {
|
||||
TrackPoint trackPoint = new TrackPoint(location, Instant.now());
|
||||
return viewModel.createNewMarker(trackId, trackPoint);
|
||||
}
|
||||
|
||||
private void loadMarkerData(Marker.Id markerId) {
|
||||
viewModel.getMarkerData(markerId).observe(this, data -> {
|
||||
marker = data;
|
||||
viewBinding.markerEditName.setText(marker.getName());
|
||||
viewBinding.markerEditMarkerType.setText(marker.getCategory());
|
||||
viewBinding.markerEditDescription.setText(marker.getDescription());
|
||||
if (marker.hasPhoto()) {
|
||||
setMarkerImageView(marker.getPhotoURI());
|
||||
} else {
|
||||
viewBinding.markerEditPhoto.setImageDrawable(null);
|
||||
}
|
||||
viewModel.getMarkerData(markerId).observe(this, this::loadMarkerData);
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
});
|
||||
private void loadMarkerData(Marker data) {
|
||||
marker = data;
|
||||
viewBinding.markerEditName.setText(marker.getName());
|
||||
viewBinding.markerEditMarkerType.setText(marker.getCategory());
|
||||
viewBinding.markerEditDescription.setText(marker.getDescription());
|
||||
if (marker.hasPhoto()) {
|
||||
setMarkerImageView(marker.getPhotoURI());
|
||||
} else {
|
||||
viewBinding.markerEditPhoto.setImageDrawable(null);
|
||||
}
|
||||
|
||||
hideAndShowOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -210,6 +196,8 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
super.onDestroy();
|
||||
|
||||
trackId = null;
|
||||
location = null;
|
||||
markerId = null;
|
||||
viewBinding = null;
|
||||
viewModel = null;
|
||||
takePictureFromGallery = null;
|
||||
@@ -222,8 +210,6 @@ public class MarkerEditActivity extends AbstractActivity {
|
||||
if (cameraPhotoUri != null) {
|
||||
outState.putString(CAMERA_PHOTO_URI_KEY, cameraPhotoUri.toString());
|
||||
}
|
||||
|
||||
outState.putParcelable(NEW_MARKER_ID, marker.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.NoSuchElementException;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.Marker;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
public class MarkerEditViewModel extends AndroidViewModel {
|
||||
@@ -122,22 +124,40 @@ public class MarkerEditViewModel extends AndroidViewModel {
|
||||
marker.setName(name);
|
||||
marker.setCategory(category);
|
||||
marker.setDescription(description);
|
||||
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
|
||||
if (marker.getId() == null) {
|
||||
new ContentProviderUtils(getApplication()).insertMarker(marker);
|
||||
} else {
|
||||
new ContentProviderUtils(getApplication()).updateMarker(getApplication(), marker);
|
||||
}
|
||||
|
||||
if (photoOriginalUri != null && (!marker.hasPhoto() || !photoOriginalUri.equals(marker.getPhotoURI()))) {
|
||||
deletePhoto(photoOriginalUri);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Marker> createNewMarker(Track.Id trackId, TrackPoint trackPoint) {
|
||||
Integer nextMarkerNumber = new ContentProviderUtils(getApplication()).getNextMarkerNumber(trackId);
|
||||
if (nextMarkerNumber == null) {
|
||||
nextMarkerNumber = 1;
|
||||
}
|
||||
String name = getApplication().getString(R.string.marker_name_format, nextMarkerNumber + 1);
|
||||
String icon = getApplication().getString(R.string.marker_icon_url);
|
||||
|
||||
Marker marker = new Marker(name, "", "", icon, trackId, trackPoint, "");
|
||||
|
||||
if (markerData == null) {
|
||||
markerData = new MutableLiveData<>();
|
||||
}
|
||||
markerData.postValue(marker);
|
||||
return markerData;
|
||||
}
|
||||
|
||||
public void onCancel(boolean isNewMarker) {
|
||||
Marker marker = getMarker();
|
||||
if (isNewMarker) {
|
||||
// it's new marker -> clean all photos.
|
||||
deletePhoto(marker);
|
||||
deletePhoto(photoOriginalUri);
|
||||
|
||||
new ContentProviderUtils(getApplication()).deleteMarker(getApplication(), marker.getId());
|
||||
|
||||
} else if (photoOriginalUri == null || (marker.hasPhoto() && !marker.getPhotoURI().equals(photoOriginalUri))) {
|
||||
// it's an edit marker -> delete photo if it was empty or it was changed (leaving the original in that case).
|
||||
deletePhoto(marker);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package de.dennisguse.opentracks.ui.markers;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
@@ -24,7 +23,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import java.util.List;
|
||||
@@ -48,7 +47,7 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class MarkerListActivity extends AbstractActivity {
|
||||
public class MarkerListActivity extends AbstractActivity implements DeleteMarkerDialogFragment.DeleteMarkerCaller {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
|
||||
@@ -94,7 +93,6 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
}
|
||||
};
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem searchMenuItem;
|
||||
|
||||
private String searchQuery;
|
||||
|
||||
@@ -116,9 +114,7 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
setSupportActionBar(viewBinding.markerListToolbar);
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> {
|
||||
finish();
|
||||
});
|
||||
viewBinding.bottomAppBarLayout.bottomAppBar.setNavigationOnClickListener(item -> finish());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,6 +148,14 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = MarkerListBinding.inflate(getLayoutInflater());
|
||||
|
||||
viewBinding.markerListSearchView.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||
searchQuery = viewBinding.markerListSearchView.getEditText().getText().toString();
|
||||
viewBinding.markerListSearchView.hide();
|
||||
loadData();
|
||||
return true;
|
||||
});
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@@ -161,9 +165,6 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
|
||||
insertMarkerMenuItem = menu.findItem(R.id.marker_list_insert_marker);
|
||||
|
||||
searchMenuItem = menu.findItem(R.id.marker_list_search);
|
||||
ActivityUtils.configureSearchWidget(this, searchMenuItem);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@@ -235,34 +236,10 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
if (!searchView.isIconified()) {
|
||||
searchView.setIconified(true);
|
||||
}
|
||||
|
||||
if (searchQuery != null) {
|
||||
searchQuery = null;
|
||||
loadData();
|
||||
return;
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
searchQuery = intent.getStringExtra(SearchManager.QUERY);
|
||||
} else {
|
||||
searchQuery = null;
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void loadData() {
|
||||
viewBinding.markerListToolbar.setText(searchQuery);
|
||||
|
||||
viewBinding.markerListToolbar.setTitle(Objects.requireNonNullElseGet(searchQuery, () -> getString(R.string.menu_markers)));
|
||||
|
||||
List<Marker> markers = contentProviderUtils.searchMarkers(trackId, searchQuery);
|
||||
@@ -281,4 +258,9 @@ public class MarkerListActivity extends AbstractActivity {
|
||||
private void onRecordingStatusChanged(RecordingStatus status) {
|
||||
recordingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMarkerDeleted() {
|
||||
runOnUiThread(this::loadData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,13 @@ package de.dennisguse.opentracks.ui.util;
|
||||
|
||||
import static android.content.Context.VIBRATOR_SERVICE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -21,19 +16,6 @@ public class ActivityUtils {
|
||||
|
||||
private static final String TAG = ActivityUtils.class.getSimpleName();
|
||||
|
||||
public static SearchView configureSearchWidget(Activity activity, final MenuItem menuItem) {
|
||||
final SearchView searchView = (SearchView) menuItem.getActionView();
|
||||
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
||||
if (searchManager != null) {
|
||||
//NOTE: Use searchManager.getSearchableInfo(new ComponentName(activity, SearchActivity.class)) if another activity should handle the search
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
|
||||
} else {
|
||||
Log.w(TAG, "Could not retrieve SearchManager.");
|
||||
}
|
||||
searchView.setSubmitButtonEnabled(true);
|
||||
return searchView;
|
||||
}
|
||||
|
||||
public static void vibrate(@NonNull Context context, Duration duration) {
|
||||
final Vibrator vibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
@@ -57,6 +57,7 @@ import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
public class StringUtils {
|
||||
|
||||
private static final String TAG = StringUtils.class.getSimpleName();
|
||||
public static final String EMPTY = "";
|
||||
|
||||
private StringUtils() {
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="5.8208"
|
||||
android:viewportHeight="5.8208">
|
||||
<path
|
||||
|
||||
@@ -22,14 +22,24 @@ limitations under the License.
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/marker_list_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/ic_marker_show_24dp"
|
||||
app:title="@string/menu_markers" />
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior"
|
||||
app:menu="@menu/marker_list" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.search.SearchView
|
||||
android:id="@+id/marker_list_search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/menu_search"
|
||||
app:layout_anchor="@id/marker_list_toolbar">
|
||||
<!-- Search suggestions/results go here (ScrollView, RecyclerView, etc.). -->
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -30,7 +30,7 @@ limitations under the License.
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="?attr/textInputOutlinedStyle"
|
||||
style="?attr/textInputOutlinedExposedDropdownMenuStyle"
|
||||
android:imeOptions="actionNext"
|
||||
android:importantForAutofill="no"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
@@ -22,15 +22,24 @@ limitations under the License.
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/track_list_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/ic_logo_color_24dp"
|
||||
app:title="@string/app_name"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior"
|
||||
app:menu="@menu/track_list" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.search.SearchView
|
||||
android:id="@+id/track_list_search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/menu_search"
|
||||
app:layout_anchor="@id/track_list_toolbar">
|
||||
<!-- Search suggestions/results go here (ScrollView, RecyclerView, etc.). -->
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -17,12 +17,7 @@ limitations under the License.
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/marker_list_insert_marker"
|
||||
android:icon="@drawable/ic_marker_show_24dp"
|
||||
android:icon="@drawable/ic_marker_add_24dp"
|
||||
android:title="@string/menu_insert_marker"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/marker_list_search"
|
||||
android:title="@string/menu_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -15,12 +15,6 @@ limitations under the License.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<!-- TODO SearchAction is only working if it is shown as action. However, only two slots are available. See #757. -->
|
||||
<item
|
||||
android:id="@+id/track_list_search"
|
||||
android:title="@string/menu_search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/track_list_markers"
|
||||
android:icon="@drawable/ic_marker_show_24dp"
|
||||
@@ -29,7 +23,8 @@ limitations under the License.
|
||||
<item
|
||||
android:id="@+id/track_list_settings"
|
||||
android:icon="@drawable/ic_settings_24dp"
|
||||
android:title="@string/menu_settings" />
|
||||
android:title="@string/menu_settings"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/track_list_help"
|
||||
android:icon="@drawable/ic_help_24dp"
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="activity_type_airplane">aeroplane</string>
|
||||
<string name="activity_type_atv">ATV</string>
|
||||
<string name="activity_type_biking">biking</string>
|
||||
<string name="activity_type_atv">ATV 4-Wheeler</string>
|
||||
<string name="activity_type_biking">A bike ride</string>
|
||||
<string name="activity_type_blimp">airship</string>
|
||||
<string name="activity_type_boat">boat</string>
|
||||
<string name="activity_type_boat">Boating</string>
|
||||
<string name="activity_type_climbing">climbing</string>
|
||||
<string name="activity_type_commercial_airplane">commercial aeroplane</string>
|
||||
<string name="activity_type_commercial_airplane">commercial flight</string>
|
||||
<string name="activity_type_cross_country_skiing">Cross-country skiing</string>
|
||||
<string name="activity_type_cycling">cycling</string>
|
||||
<string name="activity_type_dirt_bike">dirt bike</string>
|
||||
<string name="activity_type_donkey_back_riding">donkey riding</string>
|
||||
<string name="activity_type_donkey_back_riding">Donkey ride</string>
|
||||
<string name="activity_type_driving">driving</string>
|
||||
<string name="activity_type_driving_bus">driving bus</string>
|
||||
<string name="activity_type_driving_car">driving car</string>
|
||||
<string name="activity_type_driving_car">Car driving</string>
|
||||
<string name="activity_type_ferry">ferry</string>
|
||||
<string name="activity_type_frisbee">frisbee</string>
|
||||
<string name="activity_type_gliding">gliding</string>
|
||||
|
||||
@@ -37,12 +37,12 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_title">Jak ukládá OpenTracks obrázky pořízené jako značky na cestě\?</string>
|
||||
<string name="help_storage_pictures_content">Jsou uloženy v interním úložišti aplikace (tj. přístupné pouze v rámci OpenTracks).</string>
|
||||
<string name="help_track_export_title">Jak mohu exportovat/sdílet zaznamenané stopy\?</string>
|
||||
<string name="help_track_export_content"> \u0020Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. \u0020KMZ a KML obsahuje všechna data, GPX pouze polohu. \u0020Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. \u0020</string>
|
||||
<string name="help_track_export_content"> Trasy lze exportovat z Nastavení jako KMZ, KML nebo GPX. KMZ a KML obsahuje všechna data, GPX pouze polohu. Trasy sdílené pomocí <i>ikony sdílení</i> jsou sdíleny jako KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">Vyžaduje/používá OpenTracks službu Google Play\?</string>
|
||||
<string name="help_recording_title">Jak OpenTracks zaznamenává data?</string>
|
||||
<string name="help_recording_content">OpenTracks používá GPS senzor zařízení k nepřetržitému zaznamenávání polohy. Pokud zařízení GPS hlásí nepřesná data (např. polohu, rychlost, nadmořskou výšku), OpenTracks s tím nemůže nic udělat.</string>
|
||||
<string name="help_data_storage_title">Jak OpenTracks ukládá data ze senzorů\?</string>
|
||||
<string name="help_data_storage_content"> \u0020Data senzoru se ukládají na zaznamenané místo. \u0020Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i> jedno </i> měření srdeční frekvence. \u0020Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). \u0020</string>
|
||||
<string name="help_data_storage_content"> Data senzorů se ukládají na zaznamenané místo. Pokud jde o snímač srdeční frekvence, znamená to, že na každé místo je uloženo pouze <i>jedno</i> měření srdeční frekvence. Pokud stojíte v klidu nebo jste uvnitř, nebudou zaznamenána žádná data ze senzorů (ale budou zobrazena). </string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks nezaznamenává místa?</string>
|
||||
<string name="help_gps_troubleshooting_content"> \u0020Co můžete udělat:
|
||||
\n \u0020a) zkontrolujte zda je zapnuta GPS,
|
||||
@@ -419,14 +419,14 @@ limitations under the License.
|
||||
<item quantity="other">Ukončeno s %1$d chybami</item>
|
||||
</plurals>
|
||||
<string name="import_progress_summary_errors_msg">Chyba</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Zabraňte opětovnému importu aktivit</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Zabránit opětov. importu stop</string>
|
||||
<string name="export_progress_summary_errors_msg">Chyba</string>
|
||||
<string name="export_progress_summary_skip_msg">Soubory přeskočeny</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Soubory přepsány</string>
|
||||
<string name="export_progress_summary_new_msg">Nové soubory exportovány</string>
|
||||
<string name="import_progress_summary_exists_msg">Existovat</string>
|
||||
<string name="import_progress_summary_ok_msg">Importované soubory</string>
|
||||
<string name="generic_open_track">Otevřená trať</string>
|
||||
<string name="generic_open_track">Otevřít stopu</string>
|
||||
<string name="sensor_state_power_avg">Prům. výkon</string>
|
||||
<string name="sensor_state_heart_rate_max">Maximální srdeční frekvence</string>
|
||||
<string name="sensor_state_heart_rate_avg">Avg srdeční frekvence</string>
|
||||
@@ -601,4 +601,12 @@ limitations under the License.
|
||||
<string name="settings_recording_location_distance_summary">%1$s mezi zaznamenanými polohami</string>
|
||||
<string name="settings_theme_switch_restart">Změna motivu může vyžadovat ruční restart.</string>
|
||||
<string name="settings_night_mode_option_night_oled">Noční (OLED)</string>
|
||||
<string name="settings_announcements_unit">Jednotka vzdálenosti/rychlosti</string>
|
||||
<string name="voiceSpeed">{n, plural, =1 {1} few {{n,number}} other {{n,number,#.0}} }</string>
|
||||
<string name="voiceDistance">{n, plural, =1 {1 } few {{n,number}} other {{n,number,#.0}} }</string>
|
||||
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
|
||||
<string name="power">Výkon</string>
|
||||
<string name="settings_announcements_lap_power">Výkon kola</string>
|
||||
<string name="power_x_watt">Výkon {x, plural, =1 {1 watt} few {# watty} other {# wattů} }</string>
|
||||
<string name="create_marker_error">Nepodařilo se vytvořit značku. Zkuste to znovu.</string>
|
||||
</resources>
|
||||
@@ -23,13 +23,12 @@ limitations under the License.
|
||||
<string name="about_libraries">Bibliotheken</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks hat die folgenden Funktionen:</string>
|
||||
<string name="help_feature_content">
|
||||
* GPS-Tracks erstellen\n
|
||||
* einen Herzfrequenzsensor verwenden (nur Bluetooth LE)\n
|
||||
* Sprachansagen (nach Zeit oder Entfernung)\n
|
||||
* Markierungen erstellen (inkl. Bilder)\n
|
||||
* arbeitet vollständig offline\n
|
||||
* Tracks auf einer Karte anzeigen (siehe unten)</string>
|
||||
<string name="help_feature_content">* GPS-Tracks erstellen
|
||||
\n* einen Herzfrequenzsensor verwenden (nur Bluetooth LE)
|
||||
\n* Sprachansagen (nach Zeit oder Entfernung)
|
||||
\n* Markierungen erstellen (inkl. Bilder)
|
||||
\n* arbeitet vollständig offline
|
||||
\n* Tracks auf einer Karte anzeigen (siehe unten)</string>
|
||||
<string name="help_track_map_title">Wie wird ein Track auf einer Karte angezeigt?</string>
|
||||
<string name="help_track_map_content"> Verwende das kleine Kartensymbol.
|
||||
\n a) (Live-Aktualisierungen) Installiere <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. OSMDashboard stellt Online- oder Offline-Karten über <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
@@ -638,4 +637,13 @@ Meldet das GPS-Gerät ungenaue Daten (z.B. Standort, Geschwindigkeit, Höhe), ka
|
||||
<string name="activity_type_kickscooter">Roller</string>
|
||||
<string name="settings_recording_location_distance_summary">%1$s zwischen aufgezeichneten Orten</string>
|
||||
<string name="voiceSpeedMKnotsPlural">{n, plural, =1 {1 Knoten} other {{n,number,#.0} Knoten} }</string>
|
||||
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
||||
<string name="settings_announcements_unit">Distanz/Geschwindigkeits Einheit</string>
|
||||
<string name="power_x_watt">Leistung {x, plural, =1 {1 Watt} other {# Watt} }</string>
|
||||
<string name="power">Leistung</string>
|
||||
<string name="voiceDistance">{n, plural, =1 {1 } anderes {{n,number,#.0}} }</string>
|
||||
<string name="settings_recording_min_sampling_interval_title">Abtastzeitintervall</string>
|
||||
<string name="voiceIdle">Untätig werden.</string>
|
||||
<string name="voice_announce_lap_power_key">StimmeAnkündigungLapPower</string>
|
||||
<string name="settings_announcements_lap_power">Rundenleistung</string>
|
||||
</resources>
|
||||
@@ -37,12 +37,12 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_title">¿Cómo almacena OpenTracks las imágenes que se tomaron como marcadores\?</string>
|
||||
<string name="help_storage_pictures_content">Estos se almacenan en el almacenamiento interno de la aplicación (es decir, solo se puede acceder a ellos dentro de OpenTracks).</string>
|
||||
<string name="help_track_export_title">¿Cómo puedo exportar/compartir recorridos grabados\?</string>
|
||||
<string name="help_track_export_content"> \u0020Los Tracks se pueden exportar desde la Configuración como KMZ, KML o GPX. \u0020KMZ/KML todos los datos; solo ubicaciones GPX. \u0020Las pistas compartidas a través del <i>icono de compartir</i> se comparten como KMZ. \u0020</string>
|
||||
<string name="help_track_export_content"> Las pistas se pueden exportar desde la configuración como KMZ, KML o GPX. KMZ/KML todos los datos; Ubicaciones solo GPX. Las pistas compartidas a través del <i>icono de compartir</i> se comparten como KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">¿OpenTracks requiere/usa los servicios de Google Play\?</string>
|
||||
<string name="help_recording_title">¿Cómo registra OpenTracks los datos?</string>
|
||||
<string name="help_recording_content">OpenTracks utiliza el sensor GPS del dispositivo para registrar la ubicación de forma continua. Si el dispositivo GPS informa datos inexactos (por ejemplo, ubicación, velocidad, elevación), OpenTracks no puede hacer nada al respecto.</string>
|
||||
<string name="help_data_storage_title">¿Cómo almacena OpenTracks los datos de los sensores?</string>
|
||||
<string name="help_data_storage_content"> \u0020Los datos del sensor se almacenan por ubicación registrada. \u0020En términos de un sensor de frecuencia cardiaca, esto significa que sólo se almacena <i>una</i> medición de frecuencia cardiaca por ubicación. \u0020Si está parado o se encuentra en interiores, no se registrará ningún dato del sensor (pero se mostrará). \u0020</string>
|
||||
<string name="help_data_storage_content"> Los datos del sensor se almacenan por ubicación registrada. En términos de un sensor de frecuencia cardíaca, esto significa que solo se almacena <i>una</i> medición de frecuencia cardíaca por ubicación. Si está parado o está en interiores, no se registrarán los datos del sensor (pero se mostrarán). </string>
|
||||
<string name="help_gps_troubleshooting_title">¿OpenTracks no registra ubicaciones\?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Lo que puedes hacer:
|
||||
\na) compruebe si el GPS está habilitado,
|
||||
@@ -656,4 +656,12 @@ limitations under the License.
|
||||
<string name="settings_recording_location_distance_summary">%1$s entre lugares registrados</string>
|
||||
<string name="settings_theme_switch_restart">Los cambios del tema pueden requerir un reinicio manual.</string>
|
||||
<string name="settings_night_mode_option_night_oled">Oscuro (compatible con OLED)</string>
|
||||
<string name="settings_announcements_unit">Unidad de distancia/velocidad</string>
|
||||
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
||||
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
|
||||
<string name="settings_announcements_lap_power">Potencia por vuelta</string>
|
||||
<string name="power_x_watt">Potencia {x, plural, =1 {1 vatio} otros {# vatios} }</string>
|
||||
<string name="create_marker_error">No se ha podido crear un marcador. Inténtelo de nuevo.</string>
|
||||
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
|
||||
<string name="power">Potencia</string>
|
||||
</resources>
|
||||
@@ -23,13 +23,12 @@ limitations under the License.
|
||||
<string name="about_libraries">Liburutegiak</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks ek ezaugarri hauek ditu:</string>
|
||||
<string name="help_feature_content">
|
||||
* GPS ibilbideak sortu\n
|
||||
* erabili bihotz-maiztasuneko sentsorea (Bluetooth LE soilik)\n
|
||||
* ahots iragarpenak (denboraren edo distantziaren arabera)\n
|
||||
* Sortu markatzaileak (irudiak barne)\n
|
||||
* guztiz lineaz kanpo funtzionatzen du\n
|
||||
* Erakutsi ibilbideak mapa batean (ikusi azpian)</string>
|
||||
<string name="help_feature_content">* GPS ibilbideak sortu
|
||||
\n * erabili bihotz-maiztasuneko sentsorea (Bluetooth LE soilik)
|
||||
\n * ahots iragarpenak (denboraren edo distantziaren arabera)
|
||||
\n * Sortu markatzaileak (irudiak barne)
|
||||
\n * guztiz lineaz kanpo funtzionatzen du
|
||||
\n * Erakutsi ibilbideak mapa batean (ikusi azpian)</string>
|
||||
<string name="help_track_map_title">Nola erakutsi pista batean mapa batean?</string>
|
||||
<string name="help_track_map_content"> Erabili maparen ikono txikia.
|
||||
\n a) (zuzeneko eguneratzeak) Instalatu <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Lineako edo lineaz kanpoko mapak eskaintzen ditu <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
|
||||
@@ -23,13 +23,12 @@ limitations under the License.
|
||||
<string name="about_libraries">Kirjastot</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracksissa on seuraavat ominaisuudet:</string>
|
||||
<string name="help_feature_content">
|
||||
* luo GPS-reittejä\n
|
||||
* käytä sykeanturia (vain Bluetooth LE)\n
|
||||
* ääni-ilmoitukset (ajan tai etäisyyden perusteella)\n
|
||||
* luo merkkejä (mukaan lukien kuvia)\n
|
||||
* toimii täysin ilman verkkoyhteyttä\n
|
||||
* näytä jäljet kartalla (ks. alla)</string>
|
||||
<string name="help_feature_content">* luo GPS-reittejä
|
||||
\n * käytä sykeanturia (vain Bluetooth LE)
|
||||
\n * ääni-ilmoitukset (ajan tai etäisyyden perusteella)
|
||||
\n * luo merkkejä (mukaan lukien kuvia)
|
||||
\n * toimii täysin ilman verkkoyhteyttä
|
||||
\n * näytä jäljet kartalla (ks. alla)</string>
|
||||
<string name="help_track_map_title">Miten näyttää rata kartalla\?</string>
|
||||
<string name="help_track_map_content"> Käytä pientä karttakuvaketta.
|
||||
\n a) (live-päivityksiä) Asenna <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Se tarjoaa online-tai offline-karttoja kautta <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
--><resources>
|
||||
<string name="about_preference_title">À propos d\'OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> est une application de suivi sportif qui respecte totalement votre vie privée.</string>
|
||||
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_url">Page web du projet : <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Nom de la version : %1$s</string>
|
||||
<string name="about_version_code">Code de version : %1$d</string>
|
||||
<string name="about_history">OpenTracks est une bifurcation de <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (abandonné en 2016).</string>
|
||||
@@ -30,10 +30,10 @@ limitations under the License.
|
||||
\n* Fonctionne complètement hors ligne
|
||||
\n* Affichage des traces sur une carte (voir ci-dessous)</string>
|
||||
<string name="help_track_map_title">Comment afficher un tracé sur une carte \?</string>
|
||||
<string name="help_track_map_content"> Utilisez la petite icône en forme de carte.
|
||||
\na) (Actualisation en direct) Installez <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Il fournit des cartes en ligne ou hors ligne grâce à <a href="https://www.openstreetmap.org ">OpenStreetMap</a>.
|
||||
\nOSMDashboard est en réalité une application séparée qui n\'accéder (temporairement) qu\'aux données des tracés qui vont être affichées.
|
||||
\nb) Via une application cartographique standard (p. ex. <a href="http://osmand.net/">OsmAnd</a> ou <a href="https://maps.me/">MAPS.ME</a> ). L’application doit prendre en charge le format de fichier KMZ (uniquement les lieux et les horodatages). Plus précisément, l\'application doit prendre en charge <i>ACTION_VIEW</i> avec le type MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_track_map_content"> \u0020Utilisez la petite icône en forme de carte.
|
||||
\n \u0020 \u0020a) \u0020 \u0020(Actualisation en direct) Installez <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. \u0020 \u0020Il fournit des cartes en ligne ou hors ligne grâce à <a href="https://www.openstreetmap.org ">OpenStreetMap</a>.
|
||||
\n \u0020 \u0020OSMDashboard est en réalité une application séparée qui n\'accéder (temporairement) qu\'aux données des tracés qui vont être affichées. \u0020
|
||||
\n \u0020 \u0020b) \u0020 Via une application cartographique standard (p. ex. <a href="http://osmand.net/">OsmAnd</a> ou <a href="https://maps.me/">MAPS.ME</a> ). \u0020 L’application doit prendre en charge le format de fichier KMZ (uniquement les lieux et les horodatages). \u0020 Plus précisément, l\'application doit prendre en charge <i>ACTION_VIEW</i> avec le type MIME <i>application/vnd.google-earth.kmz</i>. \u0020</string>
|
||||
<string name="help_storage_pictures_title">Comment OpenTracks stocke-t-il les photos qui ont été prises en tant que repère \?</string>
|
||||
<string name="help_storage_pictures_content">Elles sont enregistrées dans le stockage interne de l\'application (c\'est-à-dire uniquement accessible dans OpenTracks).</string>
|
||||
<string name="help_track_export_title">Comment puis-je exporter/partager des tracés enregistrées \?</string>
|
||||
@@ -143,8 +143,8 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="description_max_speed">Vitesse max. : %1$.2f km/h (%2$.1f mi/h)</string>
|
||||
<string name="description_min_altitude">Élévation min. : %1$d m (%2$d pi)</string>
|
||||
<string name="description_moving_time">Durée en déplacement : %1$s</string>
|
||||
<string name="description_pace_imperial">Vitesse (min/mi)</string>
|
||||
<string name="description_pace_metric">Vitesse (min/km)</string>
|
||||
<string name="description_pace_imperial">Allure (min/mi)</string>
|
||||
<string name="description_pace_metric">Allure (min/km)</string>
|
||||
<string name="description_recorded_time">Date d\'enregistrement : %1$s</string>
|
||||
<string name="description_speed_ms">Vitesse (m/s)</string>
|
||||
<string name="description_sensor_cadence">Cadence (tr/min)</string>
|
||||
@@ -410,7 +410,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<!-- Share image -->
|
||||
<string name="share_image_subject">J aimerais partager une image avec vous</string>
|
||||
<string name="share_image_body">Je pense que cette image pourrait vous intéresser.</string>
|
||||
<string name="track_pace_notification">Vitesse : %1$s</string>
|
||||
<string name="track_pace_notification">Allure : %1$s</string>
|
||||
<string name="track_speed_notification">Vitesse : %1$s</string>
|
||||
<string name="track_detail_intervals_tab">Intervalles</string>
|
||||
<string name="interval_list_empty_message">Il n\'y a pas encore d\'intervalle</string>
|
||||
@@ -494,12 +494,12 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="settings_announcements_totaltime_frequency">Intervalle de temps</string>
|
||||
<string name="settings_announcements_distance_frequency">Intervalle de distance</string>
|
||||
<string name="settings_layout_reset_confirm_title">Réinitialiser vos dispositions \?</string>
|
||||
<string name="settings_layout_reset_done">Tous les paramètres pas défaut sont revenus à leur valeur par défaut</string>
|
||||
<string name="settings_layout_reset_done">Tous les paramètres par défaut sont revenus à leur valeur par défaut</string>
|
||||
<string name="settings_all_reset">Réinitialiser</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks a besoin de la permission d\'utiliser le Bluetooth.</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Champs de données pas rangée</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Champs de données par rangée</string>
|
||||
<string name="field_set_primary">Principal</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recommandé)</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
@@ -637,4 +637,25 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="settings_sensor_heart_rate_max">Zones : fréquence cardiaque maximale (bpm)</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_title">Filtrer les périphériques Bluetooth</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_off">Afficher tout les périphériques Bluetooth découverts</string>
|
||||
<string name="settings_recording_location_distance_summary">%1$s entre chaque localisation enregistrée</string>
|
||||
<string name="sensor_state_pressure">Baromètre</string>
|
||||
<string name="sensor_state_power_max">Puissance maximale</string>
|
||||
<string name="sensor_state_power_unit">W</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
<string name="settings_announcements_idle_category_title">Alertes de mise en veille</string>
|
||||
<string name="settings_recording_title">Enregistrement</string>
|
||||
<string name="settings_theme_switch_restart">Le changement de thème peut nécessiter un redémarrage manuel.</string>
|
||||
<string name="settings_ui_dynamic_colors_summary">Utiliser des couleurs dynamiques définies par l\'utilisateur (Android 12+ uniquement ; requiert un redémarrage manuel)</string>
|
||||
<string name="settings_recording_idle_timeout_title">Délai avant mise en veille</string>
|
||||
<string name="settings_night_mode_option_night_oled">Sombre (compatible OLED)</string>
|
||||
<string name="settings_announcements_unit">Unité de Distance/Vitesse</string>
|
||||
<string name="value_internal_sensor">Capteur interne</string>
|
||||
<string name="value_int_seconds">%1$d s (recommandé)</string>
|
||||
<string name="voiceIdle">Mise en veille.</string>
|
||||
<string name="settings_recording_min_sampling_interval_title">Fréquence d\'échantillonnage</string>
|
||||
<string name="sensor_state_cadence_unit">rpm</string>
|
||||
<string name="sensor_state_heart_rate_unit">bpm</string>
|
||||
<string name="settings_announcements_idle_title">En veille</string>
|
||||
<string name="settings_ui_dynamic_colors_title">Couleurs dynamiques</string>
|
||||
<string name="permission_recording_failed">OpenTracks a besoin de la permission d\'accéder à votre localisation ainsi qu\'aux appareils à proximité.</string>
|
||||
</resources>
|
||||
@@ -24,11 +24,11 @@ limitations under the License.
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">Características de OpenTracks:</string>
|
||||
<string name="help_feature_content">* crear rutas GPS
|
||||
\n * usar monitor do pulso cardíaco (só Bluetooth LE)
|
||||
\n * anuncios por voz (de tempo ou distancia)
|
||||
\n * crear marcadores (incl. fotos)
|
||||
\n * funciona sen precisar conexión á rede
|
||||
\n * mostra as rutas nun mapa (ver abaixo)</string>
|
||||
\n* usar monitor do pulso cardíaco (só Bluetooth LE)
|
||||
\n* anuncios por voz (de tempo ou distancia)
|
||||
\n* crear marcadores (incl. fotos)
|
||||
\n* funciona sen precisar conexión á rede
|
||||
\n* mostra as rutas nun mapa (ver abaixo)</string>
|
||||
<string name="help_track_map_title">Como mostrar a ruta no mapa\?</string>
|
||||
<string name="help_track_map_content"> \u0020Usa a icona do mapa pequeno.
|
||||
\n a) (actualizacións en directo) Instala <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Proporciona mapas en liña ou sen conexión grazas a <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
@@ -37,14 +37,14 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_title">De que xeito garda OpenTracks as fotos tomadas como marcadores\?</string>
|
||||
<string name="help_storage_pictures_content">Gárdanse na almacenaxe interna da app (só accesible desde OpenTracks).</string>
|
||||
<string name="help_track_export_title">De que xeito exporto/importo as rutas gravadas\?</string>
|
||||
<string name="help_track_export_content"> \u0020As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. \u0020KMZ/KML inclúe todos os datos; GPX só localización. \u0020As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. \u0020</string>
|
||||
<string name="help_track_export_content"> As rutas pódense exportar desde os Axustes como KMZ, KML ou GPX. KMZ/KML inclúe todos os datos; GPX só localización. As rutas compartidas a través da <i>icona compartir</i> compártense como KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">OpenTracks precisa/utiliza Google Play Service\?</string>
|
||||
<string name="help_recording_title">Como grava OpenTracks os datos\?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks usa o sensor GPS do dispositivo para gravar a localización de xeito contínuo.
|
||||
Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, elevación), OpenTracks non pode facer nada ao respecto.</string>
|
||||
<string name="help_data_storage_title">De que xeito garda OpenTracks os datos dos sensores\?</string>
|
||||
<string name="help_data_storage_content"> \u0020Os datos dos sensores gárdanse asociados á localización. \u0020 Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. \u0020 Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). \u0020</string>
|
||||
<string name="help_data_storage_content"> Os datos dos sensores gárdanse asociados á localización. Respecto do ritmo cardíaco, significa que só se garda <i>unha medida</i> asociada a unha localización. \u0020Se te detés ou estás en interiores, non se gardarán datos do sensor (mais serán mostrados). </string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks non grava as localizacións\?</string>
|
||||
<string name="help_gps_troubleshooting_content"> \u0020Podes intentar:
|
||||
\n a) mira que o GPS esté activado,
|
||||
@@ -603,4 +603,10 @@ Se o GPS informa sobre datos pouco precisos (ex.: localización, velocidade, ele
|
||||
<string name="settings_recording_location_distance_summary">%1$s entre posicións gravadas</string>
|
||||
<string name="settings_theme_switch_restart">Os cambios de decorado poderían requerir reinicio.</string>
|
||||
<string name="settings_night_mode_option_night_oled">Noite (para OLED)</string>
|
||||
<string name="settings_announcements_unit">Unidades de distancia/velocidade</string>
|
||||
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
||||
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
|
||||
<string name="settings_announcements_lap_power">Potencia na volta</string>
|
||||
<string name="power">Potencia</string>
|
||||
<string name="power_x_watt">Potencia {x, plural, =1 {1 watio} other {# watios} }</string>
|
||||
</resources>
|
||||
@@ -596,4 +596,8 @@ limitations under the License.
|
||||
<string name="help_data_google_play_services_content">Nem</string>
|
||||
<string name="settings_theme_switch_restart">A témaváltoztatások kézi újraindítást igényelnek.</string>
|
||||
<string name="settings_night_mode_option_night_oled">Éjszaka (kímélő OLED)</string>
|
||||
<string name="settings_announcements_unit">Távolság/sebesség egység</string>
|
||||
<string name="power">Erő</string>
|
||||
<string name="voiceSpeed">{n,number,#.0}</string>
|
||||
<string name="settings_announcements_lap_power">Kör teljesítmény</string>
|
||||
</resources>
|
||||
@@ -306,10 +306,15 @@ limitations under the License.
|
||||
<string name="waypoint_type_water_fountain">噴水</string>
|
||||
<string name="waypoint_type_waypoint">地点</string>
|
||||
<string name="help_feature_content">"* GPS トラックを作成します
|
||||
\n
|
||||
\n * 心拍センサーを使用します (Bluetooth LE のみ)
|
||||
\n
|
||||
\n * 音声アナウンス(時間または距離)
|
||||
\n
|
||||
\n * マーカーの作成 (写真を含む)
|
||||
\n
|
||||
\n * 完全にオフラインで動作します
|
||||
\n
|
||||
\n * 地図上にトラックを表示します (下記を参照)"</string>
|
||||
<string name="help_data_google_play_services_content">いいえ</string>
|
||||
</resources>
|
||||
@@ -175,7 +175,7 @@ limitations under the License.
|
||||
<string name="settings_chart_by_distance">Etter avstand</string>
|
||||
<string name="settings_chart_by_time">Etter tid</string>
|
||||
<string name="settings_recording_default_activity_title">Forvalgt aktivitetstype</string>
|
||||
<string name="settings_recording_location_frequency_summary">%1$s mellom registrerte posisjoner</string>
|
||||
<string name="settings_recording_location_frequency_summary">%1$s mellom posisjonskontroller</string>
|
||||
<string name="settings_recording_max_recording_distance_summary">Det blir opprettet et nytt segment hvis avstanden mellom to posisjoner er større enn %1$s</string>
|
||||
<string name="settings_recording_max_recording_distance_title">Maks. avstand</string>
|
||||
<string name="settings_recording_min_recording_distance_title">Avstandsintervall for opptak</string>
|
||||
@@ -455,7 +455,7 @@ limitations under the License.
|
||||
<string name="field_set_secondary">Sekundær</string>
|
||||
<string name="field_remove_from_layout">Fjern</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Tidsintervall</string>
|
||||
<string name="settings_announcements_distance_frequency">Distanseintervall</string>
|
||||
<string name="settings_announcements_distance_frequency">Rundelengde</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks krever tillatelse til å bruke Blåtann.</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Du må manuelt gi OpenTracks tilgang til «enheter i nærheten» i systeminnstillingene.</string>
|
||||
<string name="settings_generic_sensor">Generisk</string>
|
||||
@@ -479,7 +479,7 @@ limitations under the License.
|
||||
<string name="settings_sensors_title">Sensorer</string>
|
||||
<string name="settings_announcements_statistics_title">Statistikkmeldinger</string>
|
||||
<string name="settings_announcements_summary">Tid, distanse, talehastighet</string>
|
||||
<string name="settings_import_export_title">Import/eksport</string>
|
||||
<string name="settings_import_export_title">Import og eksport</string>
|
||||
<string name="settings_import_export_summary">Import, eksport, auto-eksport</string>
|
||||
<string name="settings_units_category">Enheter</string>
|
||||
<string name="settings_activity_defaults_category">Aktivitetens forvalg</string>
|
||||
@@ -580,4 +580,21 @@ limitations under the License.
|
||||
<string name="settings_announcements_idle_title">Inaktivitet</string>
|
||||
<string name="settings_recording_idle_timeout_title">Inaktivitetsterskel</string>
|
||||
<string name="activity_type_kickscooter">sparkesykkel</string>
|
||||
<string name="settings_recording_title">Opptak</string>
|
||||
<string name="settings_ui_dynamic_colors_title">Dynamiske farger</string>
|
||||
<string name="settings_ui_dynamic_colors_summary">Bruk brukerdefinerte dynamiske farger (kun Android 12+; krever manuell omstart)</string>
|
||||
<string name="settings_night_mode_option_night_oled">Natt (OLED-vennlig)</string>
|
||||
<string name="settings_announcements_unit">Distanse/hastighet-enhet</string>
|
||||
<string name="settings_recording_location_distance_summary">%1$s mellom registrerte posisjoner</string>
|
||||
<string name="settings_announcements_lap_power">Runde-kraft</string>
|
||||
<string name="power_x_watt">Kraft {x, plural, =1 {1 watt} other {# watt} }</string>
|
||||
<string name="power">Kraft</string>
|
||||
<string name="settings_theme_switch_restart">Temaendringer kan kreve en manuell omstart.</string>
|
||||
<string name="value_internal_sensor">Intern sensor</string>
|
||||
<string name="sensor_state_pressure">Barometer</string>
|
||||
<string name="sensor_state_power_unit">W</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_title">Filtrer Bluetooth-enheter</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_off">Vis alle Bluetooth-enheter som kan oppdages</string>
|
||||
<string name="permission_recording_failed">OpenTracks krever tillatelse for å få tilgang til posisjonen din samt enheter i nærheten.</string>
|
||||
</resources>
|
||||
@@ -24,11 +24,11 @@ limitations under the License.
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks heeft de volgende kenmerken:</string>
|
||||
<string name="help_feature_content">* GPS-tracks aanmaken
|
||||
\n * een hartslagsensor gebruiken (alleen Bluetooth LE)
|
||||
\n * spraakaankondigingen (op tijd of afstand)
|
||||
\n * markereringen aanmaken (incl. foto\'s)
|
||||
\n * werkt volledig offline
|
||||
\n * toon tracks op een kaart (zie hieronder)</string>
|
||||
\n* een hartslagsensor gebruiken (alleen Bluetooth LE)
|
||||
\n* spraakaankondigingen (op tijd of afstand)
|
||||
\n* markereringen aanmaken (incl. foto\'s)
|
||||
\n* werkt volledig offline
|
||||
\n* toon tracks op een kaart (zie hieronder)</string>
|
||||
<string name="help_track_map_title">Hoe wordt een spoor op een kaart weergegeven\?</string>
|
||||
<string name="help_track_map_content"> \u0020Gebruik het kleine kaartpictogram.
|
||||
\n \u0020a) \u0020(live updates) Installeer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Het biedt online of offline kaarten via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
@@ -37,14 +37,14 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_title">Hoe slaat OpenTracks foto\'s op die als markeringen zijn genomen\?</string>
|
||||
<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"> \u0020Tracks kunnen worden geëxporteerd vanuit de instellingen als KMZ, KML of GPX. KMZ/KML alle gegevens; GPX alleen locaties. \u0020Tracks die worden gedeeld via het <i>share-icoontje</i> worden gedeeld als KMZ \u0020</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">Vereist/gebruikt 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.
|
||||
Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid, hoogte), kan OpenTracks daar niets aan doen.</string>
|
||||
<string name="help_data_storage_title">Hoe slaat OpenTracks sensorgegevens op?</string>
|
||||
<string name="help_data_storage_content"> \u0020De sensorgegevens worden per opgenomen locatie opgeslagen. In termen van een hartslagsensor betekent dit dat slechts <i>een</i> hartslagmeting per locatie wordt opgeslagen. Als u stilstaat of binnenshuis bent, worden er geen sensorgegevens geregistreerd (maar wel getoond) \u0020</string>
|
||||
<string name="help_data_storage_content"> De sensorgegevens worden per opgenomen locatie opgeslagen. In termen van een hartslagsensor betekent dit dat slechts <i>een</i> hartslagmeting per locatie wordt opgeslagen. Als u stilstaat of binnenshuis bent, worden er geen sensorgegevens geregistreerd (maar wel getoond) </string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks neemt geen locaties op?</string>
|
||||
<string name="help_gps_troubleshooting_content"> \u0020Wat u kunt doen:
|
||||
\n \u0020a) Controleer of GPS is ingeschakeld,
|
||||
@@ -602,4 +602,12 @@ Als het GPS-apparaat onnauwkeurige gegevens rapporteert (bijv. locatie, snelheid
|
||||
<string name="settings_recording_min_sampling_interval_title">Interval van metingen</string>
|
||||
<string name="settings_theme_switch_restart">Themawijzigingen kunnen een handmatige herstart verlangen.</string>
|
||||
<string name="settings_night_mode_option_night_oled">Nacht (OLED-vriendelijk)</string>
|
||||
<string name="settings_announcements_unit">Afstand-/snelheidseenheid</string>
|
||||
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
||||
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
|
||||
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
|
||||
<string name="power">Power</string>
|
||||
<string name="settings_announcements_lap_power">Lap power</string>
|
||||
<string name="power_x_watt">Power {x, plural, =1 {1 watt} other {# watt} }</string>
|
||||
<string name="create_marker_error">Kan geen markering maken. Probeer het opnieuw.</string>
|
||||
</resources>
|
||||
@@ -30,7 +30,7 @@ limitations under the License.
|
||||
<string name="activity_type_driving_bus">prowadzenie autobusu</string>
|
||||
<string name="activity_type_driving_car">prowadzenie samochodu</string>
|
||||
<string name="activity_type_ferry">prom</string>
|
||||
<string name="activity_type_frisbee">fisbee</string>
|
||||
<string name="activity_type_frisbee">frizbee</string>
|
||||
<string name="activity_type_gliding">szybownictwo</string>
|
||||
<string name="activity_type_hang_gliding">lotniarstwo</string>
|
||||
<string name="activity_type_helicopter">helikopter</string>
|
||||
|
||||
@@ -151,7 +151,7 @@ limitations under the License.
|
||||
<string name="menu_aggregated_statistics">Toplu istatistikler</string>
|
||||
<string name="menu_delete">Sil</string>
|
||||
<string name="menu_edit">Düzenle</string>
|
||||
<string name="menu_export_all">Tümünü dışa aktar</string>
|
||||
<string name="menu_export_all">Tümünü dışa aktar (her parça başına bir dosya)</string>
|
||||
<string name="menu_help">Yardım</string>
|
||||
<string name="menu_import_all">Tümünü içe aktar</string>
|
||||
<string name="menu_insert_marker">İşaretçi ekle</string>
|
||||
@@ -330,7 +330,7 @@ limitations under the License.
|
||||
\n Aslında OSMDashboard, sadece gösterilecek olan parçaların verilerine (geçici olarak) erişebilen ayrı bir uygulamadır.
|
||||
\n b) Normal bir harita uygulaması aracılığıyla (ör. <a href=\"http://osmand.net/\">OsmAnd</a> ve <a href=\"https://maps.me/\">MAPS.ME< /a>). Bu uygulamaların KMZ dosya biçimini (yalnızca konumlar ve zaman damgaları) desteklemesi gerekir. Aslında, bir uygulamanın MIME <i>application/vnd.google-earth.kmz</i> ile <i>ACTION_VIEW</i> özelliğini desteklemesi gerekir. </i> "</string>
|
||||
<string name="help_storage_pictures_title">OpenTracks, işaretçi olarak çekilmiş resimleri nasıl saklar\?</string>
|
||||
<string name="about_url">Proje web sayfası: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_url">Proje ağ sayfası: <a href="%1$s">%1$s</a></string>
|
||||
<string name="help_track_export_title">Kayıtlı parkurları nasıl dışa aktarabilirim/paylaşabilirim\?</string>
|
||||
<string name="about_version_code">Sürüm kodu: %1$d</string>
|
||||
<string name="generic_choose_an_option">Bir seçenek belirleyin</string>
|
||||
@@ -343,7 +343,7 @@ limitations under the License.
|
||||
<string name="export_without_photos">fotoğrafsız</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="export_error_post_workout">Antrenman sonrası dışa aktarma başarısız oldu, lütfen dışa aktarma dizinini kontrol edin.</string>
|
||||
<string name="export_progress_summary_new_msg">Yeni parçalar dışa aktarıldı</string>
|
||||
<string name="export_progress_summary_new_msg">Yeni dosyalar dışa aktarıldı</string>
|
||||
<string name="generic_today">Bugün</string>
|
||||
<string name="generic_click_twice_cancel">İptal etmek için tekrar geri düğmesine tıklayın</string>
|
||||
<string name="generic_yesterday">Dün</string>
|
||||
@@ -415,9 +415,9 @@ limitations under the License.
|
||||
<string name="help_recording_content">OpenTracks, konumu devamlı olarak kaydetmek için cihazın GPS sensörünü kullanır. GPS cihazı yanlış veriler bildirirse (ör. konum, hız, yükseklik) OpenTracks bu konuda bir şey yapamaz.</string>
|
||||
<string name="help_data_storage_title">OpenTracks sensör verilerini nasıl depolar\?</string>
|
||||
<string name="export_dir_not_writable">" (yazılabilir değil!)"</string>
|
||||
<string name="export_track_errors">Parçalar dışa aktarılmadı</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Parçaların üzerine yazıldı</string>
|
||||
<string name="export_progress_summary_skip_msg">Parçalar atlandı</string>
|
||||
<string name="export_track_errors">Dosyalar dışa aktarılmadı</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Dosyaların üzerine yazıldı</string>
|
||||
<string name="export_progress_summary_skip_msg">Dosyalar atlandı</string>
|
||||
<string name="export_progress_summary_errors_msg">Hatalar</string>
|
||||
<string name="generic_open_track">Parçayı Aç</string>
|
||||
<string name="generic_overwrite">Üzerine yaz</string>
|
||||
@@ -435,7 +435,7 @@ limitations under the License.
|
||||
<string name="settings_ui_title">Kullanıcı Arayüzü</string>
|
||||
<string name="settings_default_trackfileformat">Dosya biçimini dışa aktarma/paylaşma</string>
|
||||
<string name="settings_export_filename_title">Dosya adının biçimi</string>
|
||||
<string name="settings_import_export_title">İçe/Dışa Aktar</string>
|
||||
<string name="settings_import_export_title">İçe ve Dışa Aktar</string>
|
||||
<string name="settings_import_export_summary">İçe, Dışa, Otomatik Dışa Aktar</string>
|
||||
<string name="settings_units_category">Birimler</string>
|
||||
<string name="settings_activity_defaults_category">Aktivitenin Varsayılanları</string>
|
||||
@@ -496,7 +496,7 @@ limitations under the License.
|
||||
<string name="instant_export_enabled_summary">Kayıt tamamlandıktan sonra parçayı depolamaya aktar</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/s</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Zaman aralığı</string>
|
||||
<string name="settings_announcements_distance_frequency">Mesafe aralığı</string>
|
||||
<string name="settings_announcements_distance_frequency">Tur uzunluğu</string>
|
||||
<string name="stats_sensors_heart_rate">Kalp atış hızı</string>
|
||||
<string name="stats_clock">Saat</string>
|
||||
<string name="stats_sensors_cadence">Kadans</string>
|
||||
@@ -544,4 +544,60 @@ limitations under the License.
|
||||
<string name="voice_per_nautical_mile">deniz mili başına</string>
|
||||
<string name="settings_public_api_package_title">Genel API için paket adı</string>
|
||||
<string name="show_on_dashboard_not_installed">OpenTracks Gösterge Paneli API\'sini destekleyen hiçbir uygulama yüklü değil.</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_title">Bluetooth Cihazlarını Filtrele</string>
|
||||
<string name="settings_stats_units_imperial_meter">İngiliz Ölçü Birimi (mi, m)</string>
|
||||
<string name="settings_api_summary">Diğer uygulamalar için uzaktan kontrol ve istatistik Uygulama Programlama Arayüzü (API)</string>
|
||||
<string name="generic_from">\'den beri</string>
|
||||
<string name="image_discard">Çıkar</string>
|
||||
<string name="import_thread_interrupted">İzlek kesintiye uğradı</string>
|
||||
<string name="settings_theme_switch_restart">Tema değişiklikleri elle yeniden başlatmayı gerektirebilir.</string>
|
||||
<string name="settings_ui_dynamic_colors_summary">Kullanıcı tanımlı Dinamik Renkleri kullan (sadece Android 12+; elle yeniden başlatmayı gerektirir)</string>
|
||||
<string name="settings_night_mode_option_night_oled">Gece (OLED dostu)</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Tur kalp atışı</string>
|
||||
<string name="settings_announcements_unit">Mesafe/hız birimi</string>
|
||||
<string name="settings_sensor_heart_rate_max">Alanlar: en yüksek Kalp atış hızı (atım/dk)</string>
|
||||
<string name="track_discarding">Çıkarılan Parçalar</string>
|
||||
<string name="unit_knots">kn</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<string name="value_internal_sensor">Dâhilî duyarga (sensör)</string>
|
||||
<string name="voiceSpeed">{n, plural, =1 {1} other {{n,number,#.0}} }</string>
|
||||
<string name="voiceSpeedKilometersPerHourPlural">{n, plural, =1 {1 kilometre/saat} other {{n,number,#.0} kilometre/saat} }</string>
|
||||
<string name="voiceSpeedMilesPerHourPlural">{n, plural, =1 {1 mil/saat} other {{n,number,#.0} mil/saat} }</string>
|
||||
<string name="voice_on_device_speaker_title">Cihazın hoparlörünü kullan</string>
|
||||
<string name="voiceSpeedMKnotsPlural">{n, plural, =1 {1 not} other {{n,number,#.0} not} }</string>
|
||||
<string name="voiceDistanceKilometersPlural">{n, plural, =1 {1 kilometre} other {{n,number,#.0} kilometre} }</string>
|
||||
<string name="voiceDistanceMilesPlural">{n, plural, =1 {1 mil} other {{n,number,#.0} mil} }</string>
|
||||
<string name="voiceDistanceNauticalMilesPlural">{n, plural, =1 {1 deniz mili} other {{n,number,#.0} deniz mili} }</string>
|
||||
<string name="voiceDistance">{n, plural, =1 {1 } other {{n,number,#.0}} }</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks uygulaması harita işlevselliği sunmaz. Kayıtlarınızı bir haritada görüntülemek için lütfen OSMDashboard benzeri bir uygulama yükleyiniz.</string>
|
||||
<string name="settings_recording_min_sampling_interval_title">Örnekleme zaman aralığı</string>
|
||||
<string name="settings_recording_location_distance_summary">kayıtlı konumlar arasındaki mesafe: %1$s</string>
|
||||
<string name="no_compatible_camera_installed">Uyumlu kamera uygulaması yüklü değil</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_off">Tüm keşfedilebilir Bluetooth cihazlarını göster</string>
|
||||
<string name="sensor_state_cadence_unit">devir/dk</string>
|
||||
<string name="sensor_state_heart_rate_unit">atım/dk</string>
|
||||
<string name="voiceHoursPlural">{n, plural, =1 {1 saat} other {{n,number} saat} }</string>
|
||||
<string name="no_compatible_gallery_installed">Uyumlu galeri uygulaması yüklü değil</string>
|
||||
<string name="permission_recording_failed">OpenTracks, konumunuzun yanı sıra yakındaki cihazlara erişmek için de izin gerektirir.</string>
|
||||
<string name="generic_to">\'e</string>
|
||||
<string name="settings_recording_min_required_accuracy_summary">Eğer hassasiyet oranı %1$s\'dan büyükse Konum göz ardı edilir</string>
|
||||
<string name="settings_recording_idle_timeout_title">Boşta Durma Eşiği</string>
|
||||
<string name="value_int_seconds">%1$d s (tavsiye edilen)</string>
|
||||
<string name="sensor_state_power_max">En Yüksek Güç</string>
|
||||
<string name="sensor_state_power_unit">W</string>
|
||||
<string name="settings_announcements_idle_category_title">Boşta Durma Bildirimleri</string>
|
||||
<string name="settings_announcements_idle_title">Boşta Durma</string>
|
||||
<string name="settings_recording_title">Kaydediliyor</string>
|
||||
<string name="settings_ui_dynamic_colors_title">Dinamik Renkler</string>
|
||||
<string name="voiceMinutesPlural">{n, plural, =1 {1 dakika} other {{n,number} dakika} }</string>
|
||||
<string name="voiceSecondsPlural">{n, plural, =1 {1 saniye} other {{n,number} saniye} }</string>
|
||||
<string name="voiceIdle">Boşta durma hâline geçiliyor.</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
<string name="sensor_state_pressure">Basınçölçer</string>
|
||||
<string name="menu_export_all_one_file">Tümünü dışa aktar (tüm parçaları tek dosyada topla)</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d parçalar</string>
|
||||
<string name="settings_announcements_lap_power">Tur gücü</string>
|
||||
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
|
||||
<string name="power_x_watt">Power {x, plural, =1 {1 vat(watt)} other {# vat(watt)} }</string>
|
||||
<string name="power">Güç</string>
|
||||
</resources>
|
||||
@@ -38,11 +38,7 @@ limitations under the License.
|
||||
<string name="help_storage_pictures_title">OpenTracks 如何存儲作為標記拍攝的照片?</string>
|
||||
<string name="help_storage_pictures_content">這些儲存在應用程序內部儲存區中 (即,只能在 OpenTracks 中存取)。</string>
|
||||
<string name="help_track_export_title">如何匯出/分享 紀錄的蹤跡?</string>
|
||||
<string name="help_track_export_content">
|
||||
可以從設定匯出蹤跡為 KMZ、KML 或 GPX。
|
||||
KMZ/KML 全部資料;GPX 只有位置。
|
||||
通過<i>分享圖示</i>以分享蹤跡為 KMZ。
|
||||
</string>
|
||||
<string name="help_track_export_content"> \u0020可以從設定匯出蹤跡為 KMZ、KML 或 GPX。 \u0020KMZ/KML 全部資料;GPX 只有位置。 \u0020通過<i>分享圖示</i>以分享蹤跡為 KMZ。 \u0020</string>
|
||||
<string name="help_data_google_play_services_title">OpenTracks 是否需要/使用 Google Play 服務?</string>
|
||||
<string name="help_data_google_play_services_content">否</string>
|
||||
<string name="help_recording_title">OpenTracks 是如何記錄資料的?</string>
|
||||
@@ -205,7 +201,6 @@ limitations under the License.
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<string name="generic_show_errors">顯示錯誤</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">已完成,但有一個錯誤</item>
|
||||
<item quantity="other">已完成,但有 %1$d 個錯誤</item>
|
||||
</plurals>
|
||||
<string name="generic_completed">完成</string>
|
||||
|
||||
@@ -250,6 +250,8 @@
|
||||
<bool name="voice_announce_average_speed_pace_default" translatable="false">true</bool>
|
||||
<string name="voice_announce_lap_speed_pace_key" translatable="false">voiceAnnounceLapSpeedPace</string>
|
||||
<bool name="voice_announce_lap_speed_pace_default" translatable="false">true</bool>
|
||||
<string name="voice_announce_unit_key" translatable="false">voiceAnnounceUnit</string>
|
||||
<bool name="voice_announce_unit_default" translatable="false">true</bool>
|
||||
|
||||
<string name="voice_announce_average_heart_rate_key" translatable="false">voiceAnnounceAverageHeartRate</string>
|
||||
<bool name="voice_announce_average_heart_rate_default" translatable="false">false</bool>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,11 +13,11 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<string name="empty"/>
|
||||
<string name="about_preference_title">About OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> is a sport tracking application that completely respects your privacy.</string>
|
||||
<string name="about_url">Project web page: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_url">Project webpage: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Version name: %1$s</string>
|
||||
<string name="about_version_code">Version code: %1$d</string>
|
||||
<string name="about_history">OpenTracks is a fork of <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (discontinued in 2016).</string>
|
||||
@@ -24,15 +25,12 @@ limitations under the License.
|
||||
<string name="about_libraries">Libraries</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks has the following features:</string>
|
||||
<string name="help_feature_content">
|
||||
* create GPS tracks\n
|
||||
* use a heart rate sensor (Bluetooth LE only)\n
|
||||
* voice announcements (by time or distance)\n
|
||||
* create markers (incl. pictures)\n
|
||||
* works completely offline\n
|
||||
* show tracks on a map (see below)
|
||||
</string>
|
||||
|
||||
<string name="help_feature_content">* create GPS tracks
|
||||
\n* use a heart rate sensor (Bluetooth LE only)
|
||||
\n* voice announcements (by time or distance)
|
||||
\n* create markers (incl. pictures)
|
||||
\n* works completely offline
|
||||
\n* show tracks on a map (see below)</string>
|
||||
<string name="help_track_map_title">How to show a track on a map?</string>
|
||||
<string name="help_track_map_content">
|
||||
Use the small map icon.\n
|
||||
@@ -47,33 +45,19 @@ limitations under the License.
|
||||
These apps need to support the KMZ file format (only locations and timestamps).
|
||||
In fact, an app needs to support <i>ACTION_VIEW</i> with MIME <i>application/vnd.google-earth.kmz</i>.
|
||||
</string>
|
||||
|
||||
<string name="help_storage_pictures_title">How does OpenTracks store pictures that were taken as markers?</string>
|
||||
<string name="help_storage_pictures_content">These are stored in the app internal storage (i.e., only accessible within OpenTracks).</string>
|
||||
|
||||
<string name="help_track_export_title">How can I export/share recorded tracks?</string>
|
||||
<string name="help_track_export_content">
|
||||
Tracks can be exported from the Settings as KMZ, KML, or GPX.
|
||||
KMZ/KML all data; GPX only locations.
|
||||
Tracks shared via the <i>share icon</i> are shared as KMZ.
|
||||
</string>
|
||||
|
||||
<string name="help_track_export_content"> Tracks can be exported from the Settings as KMZ, KML, or GPX. KMZ/KML all data; GPX only locations. Tracks shared via the <i>share icon</i> are shared as KMZ. </string>
|
||||
<string name="help_data_google_play_services_title">Does OpenTracks require/use Google Play Service?</string>
|
||||
<string name="help_data_google_play_services_content">No</string>
|
||||
|
||||
<string name="help_recording_title">How does OpenTracks record data?</string>
|
||||
<string name="help_recording_content">
|
||||
OpenTracks uses the GPS sensor of the device to record the location continuously.
|
||||
If the GPS device reports inaccurate data (e.g., location, speed, elevation), OpenTracks cannot do anything about it.
|
||||
</string>
|
||||
|
||||
<string name="help_data_storage_title">How does OpenTracks store sensor data?</string>
|
||||
<string name="help_data_storage_content">
|
||||
Sensor data is stored per recorded location.
|
||||
In terms of a heart rate sensor, this means that only <i>one</i> heart rate measurement is stored per location.
|
||||
If you are standing still or are indoors, no sensor data will be recorded (but it will be shown).
|
||||
</string>
|
||||
|
||||
<string name="help_data_storage_content"> Sensor data is stored per recorded location. In terms of a heart rate sensor, this means that only <i>one</i> heart rate measurement is stored per location. If you are standing still or are indoors, no sensor data will be recorded (but it will be shown). </string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks is not recording locations?</string>
|
||||
<string name="help_gps_troubleshooting_content">
|
||||
What you can do:\n
|
||||
@@ -84,7 +68,6 @@ limitations under the License.
|
||||
\n
|
||||
If everything does not help please report a bug.
|
||||
</string>
|
||||
|
||||
<string name="help_support_title">How can I support OpenTracks?</string>
|
||||
<string name="help_support_content">
|
||||
You can:\n
|
||||
@@ -96,9 +79,7 @@ limitations under the License.
|
||||
f) donate, and\n
|
||||
g) do whatever you think helps OpenTracks.
|
||||
</string>
|
||||
|
||||
<string name="open_track_as_trackfileformat">Open as %1$s</string>
|
||||
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">airplane</string>
|
||||
<string name="activity_type_atv">ATV</string>
|
||||
@@ -208,7 +189,7 @@ limitations under the License.
|
||||
<string name="export_error_post_workout">Post-workout export failed, please check the export directory.</string>
|
||||
<string name="export_cannot_write_to_dir">Cannot write to export directory.</string>
|
||||
<string name="export_dir_not_writable">" (not writable!)"</string>
|
||||
<string name="export_progress_message">Exporting to %1$s…</string>
|
||||
<string name="export_progress_message">Exporting to %1$s…</string>
|
||||
<string name="export_track_already_exists_msg">The file %1$s already exists at the destination directory.</string>
|
||||
<string name="export_all_do_it_for_all">do it for all conflicts</string>
|
||||
<string name="export_track_errors">Files not exported</string>
|
||||
@@ -225,7 +206,7 @@ limitations under the License.
|
||||
<string name="generic_name">Name</string>
|
||||
<string name="generic_name_line">Name: %1$s</string>
|
||||
<string name="generic_open_track">Open Track</string>
|
||||
<string name="generic_recording">Recording…</string>
|
||||
<string name="generic_recording">Recording…</string>
|
||||
<string name="generic_save">Save</string>
|
||||
<string name="generic_tracks">Tracks</string>
|
||||
<string name="generic_overwrite">Overwrite</string>
|
||||
@@ -272,11 +253,12 @@ limitations under the License.
|
||||
<string name="import_error_list_dialog_title">Files not imported</string>
|
||||
<string name="import_file_imported">File %1$s imported</string>
|
||||
<string name="import_prevent_reimport">You have set prevent re-import track</string>
|
||||
<string name="import_progress_message">Importing from %1$s…</string>
|
||||
<string name="import_progress_message">Importing from %1$s…</string>
|
||||
<string name="import_progress_summary_ok_msg">Files imported</string>
|
||||
<string name="import_progress_summary_exists_msg">Already exists</string>
|
||||
<string name="import_progress_summary_errors_msg">Errors</string>
|
||||
<!-- Marker -->
|
||||
<string name="create_marker_error">Unable to create a marker. Try again.</string>
|
||||
<string name="marker_add_error">Unable to insert a marker. No GPS signal. Try again.</string>
|
||||
<string name="marker_add_success">A marker was inserted</string>
|
||||
<string name="marker_add_canceled">Canceled, no marker added</string>
|
||||
@@ -338,36 +320,24 @@ limitations under the License.
|
||||
<string name="sensor_could_not_scan">Could not scan for Bluetooth devices (error %1$i).</string>
|
||||
<string name="bluetooth_disabled">Please enable Bluetooth.</string>
|
||||
<string name="bluetooth_sensor_summary">%1$s (%2$s)</string>
|
||||
|
||||
<string name="settings_open_tracks_summary">All About OpenTracks Project</string>
|
||||
|
||||
<string name="settings_defaults_title">Default Units and Activity</string>
|
||||
<string name="settings_defaults_summary">Preferred Units, Default Activity, Track Default Name</string>
|
||||
|
||||
<string name="settings_ui_title">User Interface</string>
|
||||
<string name="settings_ui_summary">Appearance, Recording Layout, Theme</string>
|
||||
|
||||
<string name="settings_gps_sensor">GPS</string>
|
||||
<string name="settings_gps_summary">Time interval, Distance interval, Max Distance, Accuracy</string>
|
||||
|
||||
<string name="settings_sensors_title">Sensors</string>
|
||||
<string name="settings_sensors_summary">Cycling Sensors, Wheel Circumference, Running Sensors</string>
|
||||
|
||||
|
||||
<string name="settings_announcements_idle_category_title">Idle Announcements</string>
|
||||
<string name="settings_announcements_idle_title">Idle</string>
|
||||
|
||||
<string name="settings_announcements_statistics_title">Statistics Announcements</string>
|
||||
<string name="settings_announcements_summary">Time, Distance, Voice Speed</string>
|
||||
|
||||
<string name="settings_import_export_title">Import and Export</string>
|
||||
<string name="settings_import_export_summary">Import, Export, Auto Export</string>
|
||||
|
||||
<string name="settings_units_category">Units</string>
|
||||
<string name="settings_activity_defaults_category">Activity\'s Defaults</string>
|
||||
|
||||
<string name="settings_reset_summary">Reset Settings to Default Values</string>
|
||||
|
||||
<!-- Settings Advanced -->
|
||||
<string name="settings_advanced">Advanced</string>
|
||||
<string name="settings_default_trackfileformat">Export/sharing file format</string>
|
||||
@@ -406,14 +376,13 @@ limitations under the License.
|
||||
<string name="settings_night_mode_option_day">Day</string>
|
||||
<string name="settings_night_mode_option_night">Night</string>
|
||||
<string name="settings_night_mode_option_night_oled">Night (OLED friendly)</string>
|
||||
|
||||
<string name="settings_announcements_totaltime_frequency">Time interval</string>
|
||||
<string name="settings_announcements_distance_frequency">Lap length</string>
|
||||
<string name="settings_announcements_average_heart_rate">Average heart rate</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Lap heart rate</string>
|
||||
<string name="settings_announcements_average_speed_pace">Average speed/pace</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Lap speed/pace</string>
|
||||
|
||||
<string name="settings_announcements_unit">Distance/speed unit</string>
|
||||
<!-- Custom Layout -->
|
||||
|
||||
|
||||
@@ -437,7 +406,6 @@ limitations under the License.
|
||||
<string name="settings_generic_sensor">Generic</string>
|
||||
<string name="settings_cycling_sensor">Cycling</string>
|
||||
<string name="settings_running_sensor">Running</string>
|
||||
|
||||
<string name="settings_sensor_cycling_distance_speed">Distance and Speed</string>
|
||||
<string name="settings_sensor_cycling_cadence">Cadence</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Distance, Speed, and Cadence</string>
|
||||
@@ -445,9 +413,7 @@ limitations under the License.
|
||||
<string name="settings_sensor_bluetooth_service_filter_title">Filter Bluetooth Devices</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_on">Only show Bluetooth devices that announce required services</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_off">Show all discoverable Bluetooth devices</string>
|
||||
|
||||
<string name="settings_sensor_heart_rate_max">Zones: maximum Heart rate (bpm)</string>
|
||||
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_rate_title">Preferred rate</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
@@ -455,7 +421,6 @@ limitations under the License.
|
||||
<string name="settings_stats_units_metric">Metric (km, m)</string>
|
||||
<string name="settings_stats_units_nautical">Nautical (NM, ft)</string>
|
||||
<string name="settings_stats_units_title">Preferred units</string>
|
||||
|
||||
<!-- Settings public api -->
|
||||
<string name="settings_api_title">Public API</string>
|
||||
<string name="settings_api_summary">Remote control and statistics API for other apps</string>
|
||||
@@ -467,11 +432,9 @@ limitations under the License.
|
||||
<string name="settings_public_api_dashboard_enabled_title">Automatic data transfer</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_on">An application that started a recording can also access the recorded data.</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_off">Recorded data will not be shared automatically with other applications.</string>
|
||||
|
||||
<!-- Settings import/export -->
|
||||
<string name="settings_import_section">Import</string>
|
||||
<string name="settings_export_section">Export</string>
|
||||
|
||||
<string name="settings_prevent_reimport_tracks_title">Prevent re-import of tracks</string>
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">I think you might be interested in this track.\n\n%1$s</string>
|
||||
@@ -510,7 +473,7 @@ limitations under the License.
|
||||
<string name="track_delete_one_confirm_message">The track and its markers will be permanently deleted from the device.</string>
|
||||
<string name="track_delete_one_confirm_title">Delete track?</string>
|
||||
<string name="track_delete_multiple_confirm_message">The selected tracks and their markers will be permanently deleted from the device.</string>
|
||||
<string name="track_delete_progress_message">Deleting…</string>
|
||||
<string name="track_delete_progress_message">Deleting…</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d tracks</string>
|
||||
<string name="track_delete_not_recording">This track cannot be deleted as it is currently recorded.</string>
|
||||
<string name="track_discarding">Discarding Track</string>
|
||||
@@ -602,6 +565,12 @@ limitations under the License.
|
||||
other {{n,number,#.0} knots}
|
||||
}
|
||||
</string>
|
||||
<string name="voiceSpeed">
|
||||
{n, plural,
|
||||
=1 {1}
|
||||
other {{n,number,#.0}}
|
||||
}
|
||||
</string>
|
||||
<string name="voice_on_device_speaker_title">Use the device\'s speaker</string>
|
||||
<string name="voice_per_kilometer">per kilometer</string>
|
||||
<string name="voice_per_mile">per mile</string>
|
||||
@@ -629,6 +598,12 @@ limitations under the License.
|
||||
other {{n,number,#.0} nautical miles}
|
||||
}
|
||||
</string>
|
||||
<string name="voiceDistance">
|
||||
{n, plural,
|
||||
=1 {1 }
|
||||
other {{n,number,#.0}}
|
||||
}
|
||||
</string>
|
||||
<string name="average_heart_rate">Average heart rate</string>
|
||||
<string name="current_heart_rate">Current heart rate</string>
|
||||
<!-- Waypoint Type -->
|
||||
@@ -686,12 +661,9 @@ limitations under the License.
|
||||
<!-- Share image -->
|
||||
<string name="share_image_subject">I would like to share an image with you</string>
|
||||
<string name="share_image_body">I think you might be interested in this image.</string>
|
||||
|
||||
<string name="hold_to_stop">Hold to stop</string>
|
||||
|
||||
<string name="instant_export_enabled_summary">Export track to storage after recording is finished</string>
|
||||
<string name="instant_export_enabled_title">Instant post-workout export</string>
|
||||
|
||||
<string name="settings_default_export_uri_title">Track export directory</string>
|
||||
<!-- Fields -->
|
||||
<string name="field_set_primary">Primary</string>
|
||||
@@ -703,14 +675,16 @@ limitations under the License.
|
||||
<string name="error_crash_title">%1$s crashed</string>
|
||||
<string name="share_crash_report">Share crash report</string>
|
||||
<string name="report_bug">Report bug</string>
|
||||
|
||||
<string name="select_show_on_map_behavior">Select \"show on map\" behavior</string>
|
||||
<string name="show_on_map_format_ask">Ask</string>
|
||||
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
|
||||
<string name="show_on_dashboard_not_installed">No application supporting OpenTracks Dashboard API installed.</string>
|
||||
<string name="always">Always</string>
|
||||
|
||||
<string name="introduction_osm_dashboard">OpenTracks itself does not provide a map. Please install OSMDashboard to view your recordings on a map.</string>
|
||||
<string name="settings_recording_min_sampling_interval_title">Sampling time interval</string>
|
||||
<string name="settings_recording_location_distance_summary">%1$s between recorded locations</string>
|
||||
</resources>
|
||||
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
|
||||
<string name="settings_announcements_lap_power">Lap power</string>
|
||||
<string name="power_x_watt">Power {x, plural, =1 {1 watt} other {# watts} }</string>
|
||||
<string name="power">Power</string>
|
||||
</resources>
|
||||
@@ -25,6 +25,13 @@
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_announcements_statistics_title">
|
||||
<SwitchPreferenceCompat
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:defaultValue="@bool/voice_announce_unit_default"
|
||||
android:key="@string/voice_announce_unit_key"
|
||||
android:title="@string/settings_announcements_unit" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/voice_announcement_frequency_default"
|
||||
android:entryValues="@array/voice_announcement_frequency_values"
|
||||
@@ -32,13 +39,6 @@
|
||||
android:title="@string/settings_announcements_totaltime_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/voice_announcement_distance_default"
|
||||
android:entryValues="@array/voice_announcement_distance_values"
|
||||
android:key="@string/voice_announcement_distance_key"
|
||||
android:title="@string/settings_announcements_distance_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/voice_announce_total_distance_default"
|
||||
android:key="@string/voice_announce_total_distance_key"
|
||||
@@ -54,20 +54,29 @@
|
||||
android:key="@string/voice_announce_average_speed_pace_key"
|
||||
android:title="@string/settings_announcements_average_speed_pace" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/voice_announce_lap_speed_pace_default"
|
||||
android:key="@string/voice_announce_lap_speed_pace_key"
|
||||
android:title="@string/settings_announcements_lap_speed_pace" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/voice_announce_average_heart_rate_default"
|
||||
android:key="@string/voice_announce_average_heart_rate_key"
|
||||
android:title="@string/settings_announcements_average_heart_rate" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/voice_announcement_distance_default"
|
||||
android:entryValues="@array/voice_announcement_distance_values"
|
||||
android:key="@string/voice_announcement_distance_key"
|
||||
android:title="@string/settings_announcements_distance_frequency"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/voice_announce_lap_speed_pace_default"
|
||||
android:key="@string/voice_announce_lap_speed_pace_key"
|
||||
android:title="@string/settings_announcements_lap_speed_pace" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="@bool/voice_announce_lap_heart_rate_default"
|
||||
android:key="@string/voice_announce_lap_heart_rate_key"
|
||||
android:title="@string/settings_announcements_lap_heart_rate" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="@string/voice_announce_lap_power_key"
|
||||
android:title="@string/settings_announcements_lap_power" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
Reference in New Issue
Block a user