forked from upstream-mirrors/OpenTracks
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 183b47a1ea | |||
| bc84e8cb9a | |||
| fb9e67ae85 | |||
| 8877f3fe14 | |||
| 963a37b877 | |||
| 3be1348df1 | |||
| 530967f4f6 | |||
| 25e590da02 | |||
| 0be4b25508 | |||
| 4c7d9384fb | |||
| 101701c6ff | |||
| 8f003e2f7f | |||
| 03d825d2ec | |||
| 0977d34889 | |||
| 6dee9884ff | |||
| 7b44b42def | |||
| ad712f28c6 | |||
| b287967bc4 | |||
| 29c72802aa | |||
| f5241e8046 | |||
| 1e720b2fa7 | |||
| 19382f19c4 | |||
| 14b8ab21db | |||
| 00b0e1face | |||
| 7f9057c519 | |||
| 02cec48ae4 | |||
| 0d45efdf07 | |||
| b6d935cf34 | |||
| 3228f58187 | |||
| 1e27542aea | |||
| fd7892bdf7 | |||
| 2ae8a753c9 | |||
| bb7efaa87c | |||
| 2eab7425f2 | |||
| a19bbd6153 | |||
| b523b8af9e | |||
| e9a71a10f6 | |||
| a0d69b589b | |||
| 71af495835 | |||
| ed4fb30b35 | |||
| 7f1c014bf8 | |||
| affa72fad7 | |||
| 14a14cd91f | |||
| ff89d2c5ec | |||
| 54f1690478 | |||
| 681058c3e6 | |||
| d93e025963 | |||
| 1f0a99c49a | |||
| 976d4f848f | |||
| 540a3340b3 | |||
| 2b4c68f3ec | |||
| 90d55e419e | |||
| ac31f4fd6a | |||
| 32a75bb479 | |||
| d32f1e05ec | |||
| defd6aed85 | |||
| 7463dc703c | |||
| 2ab04da936 | |||
| 6b84d3b9ba | |||
| 78b57cdc67 | |||
| 35de5b6b26 |
@@ -1,4 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
liberapay: OpenTracks
|
||||
github: [rgmf, pstorch, dennisguse]
|
||||
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
||||
|
||||
@@ -7,8 +7,9 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
api-level: [ 23, 26 ] #TODO Re-enable 29
|
||||
api-level: [ 23, 30 ]
|
||||
target: [ default ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2.4.0
|
||||
|
||||
+18
-11
@@ -6,7 +6,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,17 @@ def getVersionName = { ->
|
||||
}
|
||||
}
|
||||
|
||||
def versionCodeFromDate = { ->
|
||||
def date = new Date()
|
||||
def formattedDate = date.format('yyyyMMdd')
|
||||
return Integer.valueOf(formattedDate)
|
||||
def getVersionCode = { ->
|
||||
try {
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'rev-list', 'HEAD', '--count'
|
||||
standardOutput = stdout
|
||||
}
|
||||
return Integer.valueOf(stdout.toString().trim())
|
||||
} catch (ignored) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -56,8 +63,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 4519
|
||||
versionName "v3.22.0"
|
||||
versionCode 4580
|
||||
versionName "v3.23.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -105,7 +112,7 @@ android {
|
||||
|
||||
if (variant.buildType.name == 'nightly') {
|
||||
variant.outputs.all {
|
||||
setVersionCodeOverride(versionCodeFromDate())
|
||||
setVersionCodeOverride(getVersionCode())
|
||||
setVersionNameOverride(getVersionName())
|
||||
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
||||
}
|
||||
@@ -116,7 +123,7 @@ android {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
@@ -127,7 +134,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.0.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.2.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
v3.23.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Added UI profiles
|
||||
- Aggregated statistics for selected tracks
|
||||
- Re-implemented idle/non-idle TrackPoints as moving time stopped working (introduced in v3.22.0)
|
||||
|
||||
Bugfixes:
|
||||
- BLE sensors: clear data on disconnect
|
||||
- Automatic export needs to persist permission
|
||||
@@ -1,11 +1,22 @@
|
||||
OpenTracks is a sport tracking application that completely respects your privacy.
|
||||
A sport tracking buddy that respects your privacy.
|
||||
|
||||
Features:
|
||||
* Sport tracking: track your sport and outdoor activities
|
||||
* Photos and markers: mark interesting locations while tracking
|
||||
* Altitude gain and loss: via barometric sensor
|
||||
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84
|
||||
* Voice announcements
|
||||
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling)
|
||||
* Export data: export tracks either as KMZ (incl. photos), KML, or GPX
|
||||
* No Internet access: Internet is not used
|
||||
Sport and outdoor activities grants happiness, skill and confidence.
|
||||
|
||||
Value your health by keeping track on your training.
|
||||
Records as you go running or walking, and gives you a bike computer with a bigger screen for cycling.
|
||||
Mark interesting locations along your way with pictures.
|
||||
Keep recorded statistics in great detail for analysis.
|
||||
Share only the data you want others to have.
|
||||
|
||||
* Voice announcements.
|
||||
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling).
|
||||
* Altitude gain and loss: via barometric sensor.
|
||||
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84.
|
||||
* Export data as tracks either as KMZ (incl. photos), KML, or GPX.
|
||||
* No Internet access or extra permissions.
|
||||
* Dark and light theme, respecting system settings.
|
||||
* No ads.
|
||||
|
||||
Libre software / Free Software / Open Source
|
||||
meaning you can use, study, change and share the source code.
|
||||
Licensed Apache 2.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
A sport tracker that completely respects your privacy.
|
||||
A sport tracker buddy that respects your privacy.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
v3.19.1 : OpenTracks
|
||||
|
||||
Changements :
|
||||
- Traductions mises à jour.
|
||||
|
||||
Correction de bogue :
|
||||
- Correction du bug de l'exportation de toutes les pistes.
|
||||
- Correction du bogue "Reprendre une piste".
|
||||
@@ -0,0 +1,7 @@
|
||||
v3.20.0 : OpenTracks
|
||||
|
||||
Changements :
|
||||
- Pendant l'enregistrement : l'interface des statistiques est maintenant configurable
|
||||
|
||||
Correction d'un bug :
|
||||
- Le changement de nom de piste/catégorie pendant l'enregistrement était ignoré (introduit dans la v3.19.0)
|
||||
@@ -0,0 +1,6 @@
|
||||
v3.20.1 : OpenTracks
|
||||
|
||||
Changements :
|
||||
- Les annonces peuvent maintenant être utilisées à la fois par distance et par temps.
|
||||
- Les annonces émettent un bip si aucun logiciel de synthèse vocale n'est installé.
|
||||
- Suppression de la fréquence de localisation GPS adaptative
|
||||
@@ -0,0 +1,16 @@
|
||||
v3.18.6: OpenTracks
|
||||
|
||||
Zmiany:
|
||||
- "Pokaż na mapie"- Funkcjonalość wraz z KML zamiast KMZ
|
||||
- Komunikaty głosowe nie podają nazwy aplikacji
|
||||
- Podczas śledzenia: Zmiana wysokości aktualizowana co sekundę
|
||||
- Przetłumaczone opisy w sklepach aplikacji
|
||||
|
||||
Naprawione błędy:
|
||||
- Naprawiono nieprawidłowe tłumaczenia wiadomości głosowych
|
||||
|
||||
Developer:
|
||||
- Uprawnienia Android12 (BLUETOOTH_SCAN)
|
||||
- Uprawnienia Android12 (ACCESS_COARSE_LOCATION)
|
||||
- Zastosowanie TrackPoints (testowane)
|
||||
- Usunięcie niepotrzebnych tłumaczeń
|
||||
@@ -0,0 +1,11 @@
|
||||
v3.22.0: OpenTracks
|
||||
|
||||
Zmiany:
|
||||
- Lokalizacja zapisywana tylko wtedy gdy pokonany dystans jest większy od minimalnego przedziału śledzenia dystansu
|
||||
(usunięto funkcjonalność z wykrywania bezczynności)
|
||||
- UI może zostać skonfigurowane do wyświetlania zegara
|
||||
- Prędkość ruchu bez oporu (prędkość jałowa) jest teraz konfigurowalna
|
||||
|
||||
Poprawki:
|
||||
- Przedziały: awaria, gdy zapisano zwiększenie wysokości, ale nie spadek
|
||||
- Podczas nagrywania: zapobieganie awariom podczas wyłączania komunikatów głosowych
|
||||
@@ -3,9 +3,9 @@ OpenTracks jest aplikacją do śledzenia sportu, która całkowicie szanuje Twoj
|
||||
Cechy:
|
||||
* Śledzenie sportu: śledź swoją aktywność sportową i outdoorową.
|
||||
* Zdjęcia i markery: zaznacz interesujące lokalizacje podczas śledzenia.
|
||||
* Przyrost i utrata wysokości: poprzez czujnik barometryczny
|
||||
* Zmiana wysokości: poprzez czujnik barometryczny
|
||||
* Wysokość pokazana w EGM2008 (powyżej średniego poziomu morza); wyeksportowana jako WGS84
|
||||
* Komunikaty głosowe
|
||||
* Obsługa czujników Bluetooth LE: tętno, prędkość i dystans (kolarstwo), kadencja (kolarstwo) i miernik mocy (kolarstwo)
|
||||
* Eksport danych: eksport tras jako KMZ (w tym zdjęcia), KML lub GPX
|
||||
* Brak dostępu do Internetu: Internet nie jest używany
|
||||
* Dostępu do Internetu: Internet nie jest używany
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LayoutTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
|
||||
@Test
|
||||
public void testFromCsv() {
|
||||
// given a csv line
|
||||
String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "running");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(dataFieldList.size(), 4);
|
||||
assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
|
||||
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
|
||||
assertEquals(dataFieldList.get(0).getTitle(), context.getString(R.string.stats_speed));
|
||||
assertEquals(dataFieldList.get(1).getTitle(), context.getString(R.string.stats_distance));
|
||||
assertEquals(dataFieldList.get(2).getTitle(), context.getString(R.string.stats_altitude));
|
||||
assertEquals(dataFieldList.get(3).getTitle(), context.getString(R.string.stats_gain));
|
||||
assertTrue(dataFieldList.get(0).isVisible());
|
||||
assertTrue(dataFieldList.get(0).isPrimary());
|
||||
assertFalse(dataFieldList.get(0).isWide());
|
||||
assertTrue(dataFieldList.get(1).isVisible());
|
||||
assertFalse(dataFieldList.get(1).isPrimary());
|
||||
assertFalse(dataFieldList.get(1).isWide());
|
||||
assertFalse(dataFieldList.get(2).isVisible());
|
||||
assertTrue(dataFieldList.get(2).isPrimary());
|
||||
assertFalse(dataFieldList.get(2).isWide());
|
||||
assertFalse(dataFieldList.get(3).isVisible());
|
||||
assertFalse(dataFieldList.get(3).isPrimary());
|
||||
assertFalse(dataFieldList.get(3).isWide());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong1() {
|
||||
// given a csv line without number of columns
|
||||
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong2() {
|
||||
// given a csv line without any field
|
||||
String csv1 = "Layout Name;2;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong3() {
|
||||
// given a csv line with wrong fields description
|
||||
String csv1 = "Layout Name;2;speed,distance,total time;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong4() {
|
||||
// given a csv line with the first field ok but not the others
|
||||
String csv1 = "Layout Name;2;speed,1,0;distance;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToCsv() {
|
||||
// given a layout's object
|
||||
Layout layout = new Layout("Test Layout", 2);
|
||||
layout.addField("key1", "Title 1", false, false, false);
|
||||
layout.addField("key2", "Title 2", false, true, false);
|
||||
layout.addField("key3", "Title 3", true, false, false);
|
||||
layout.addField("key4", "Title 4", true, true, false);
|
||||
layout.addField("key5", "Title 5", true, true, true);
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.toCsv();
|
||||
|
||||
// then csv is well built
|
||||
assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToCsv_columnsByDefault() {
|
||||
// given a layout's object
|
||||
Layout layout = new Layout("Test Layout");
|
||||
layout.addField("key1", "Title 1", false, false, false);
|
||||
layout.addField("key2", "Title 2", false, true, false);
|
||||
layout.addField("key3", "Title 3", true, false, false);
|
||||
layout.addField("key4", "Title 4", true, true, false);
|
||||
layout.addField("key5", "Title 5", true, true, true);
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.toCsv();
|
||||
|
||||
// then csv is well built
|
||||
assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;");
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -124,7 +124,7 @@ public class ExportImportTest {
|
||||
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||
trackId = service.startNewTrack();
|
||||
|
||||
Distance sensorDistance = hasSensorDistance ? Distance.of(5) : null;
|
||||
Distance sensorDistance = hasSensorDistance ? Distance.of(10) : null; // recording distance interval
|
||||
|
||||
sendLocation(trackPointCreator, Instant.parse("2020-02-02T02:02:03Z"), 3, 14, 10, 15, 10, 1, 66, 3, 50, sensorDistance);
|
||||
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||
@@ -218,12 +218,12 @@ public class ExportImportTest {
|
||||
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
|
||||
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
|
||||
|
||||
// Speed
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
||||
assertEquals(Speed.of(3.75), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageMovingSpeed());
|
||||
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
|
||||
|
||||
// Altitude
|
||||
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
||||
@@ -312,16 +312,16 @@ public class ExportImportTest {
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:03Z"), importedTrackStatistics.getStartTime());
|
||||
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
|
||||
|
||||
assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(19), importedTrackStatistics.getMovingTime());
|
||||
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
|
||||
|
||||
// Distance
|
||||
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
|
||||
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
|
||||
|
||||
// Speed
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
||||
assertEquals(Speed.of(1.5), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(Speed.of(1.5789473684210527), importedTrackStatistics.getAverageMovingSpeed());
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed());
|
||||
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
|
||||
|
||||
// Altitude
|
||||
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
||||
|
||||
+2
-2
@@ -83,14 +83,14 @@ public class TrackPointAssert {
|
||||
try {
|
||||
Assert.assertEquals(expected.size(), actual.size());
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Expected: " + expected + "\n actual: " + actual);
|
||||
throw new AssertionError("Size difference; expected: " + expected.size() + "; actual: " + actual.size() + "\nExpected: " + expected + "\n actual: " + actual);
|
||||
}
|
||||
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
try {
|
||||
assertEquals(expected.get(i), actual.get(i));
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError("Expected: " + expected.get(i) + "\n actual: " + actual.get(i), e);
|
||||
throw new AssertionError("Expected: " + i + " to be " + expected.get(i) + "\n actual: " + actual.get(i), e);
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(expected.size(), actual.size());
|
||||
|
||||
+2
-5
@@ -64,8 +64,8 @@ import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.io.file.importer.TrackPointAssert;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Tests for the track recording service.
|
||||
@@ -451,10 +451,7 @@ public class TrackRecordingServiceTest {
|
||||
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a location and waits for 200ms.
|
||||
*/
|
||||
private static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
|
||||
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
|
||||
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(time))
|
||||
.setLongitude(longitude)
|
||||
.setLatitude(latitude)
|
||||
|
||||
+57
-45
@@ -20,14 +20,11 @@ import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.internal.debugging.LocationImpl;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Altitude;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
@@ -190,7 +187,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000002, 35.0, 3, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 6, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000005, 35.0, 6, 15);
|
||||
|
||||
service.endCurrentTrack();
|
||||
|
||||
@@ -210,7 +207,7 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.0002)
|
||||
.setLatitude(45.0005)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(15))
|
||||
@@ -220,9 +217,6 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
), trackPoints);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@MediumTest
|
||||
@@ -257,6 +251,13 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
@@ -274,8 +275,8 @@ public class TrackRecordingServiceTestLocation {
|
||||
// when
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 2, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0); // will be ignored
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0); // will be ignored
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 0);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
|
||||
|
||||
@@ -296,6 +297,27 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(6))
|
||||
.setSpeed(Speed.of(15))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
@@ -350,38 +372,6 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(3))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(4))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
.setHeartRate_bpm(5f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
@@ -462,12 +452,20 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setHorizontalAccuracy(Distance.of(2))
|
||||
.setSpeed(Speed.of(5))
|
||||
.setSensorDistance(Distance.of(10)),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(4))
|
||||
.setSpeed(Speed.of(5))
|
||||
.setAltitudeGain(6f)
|
||||
.setAltitudeLoss(6f)
|
||||
.setSensorDistance(Distance.of(2)),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setSensorDistance(Distance.of(11))
|
||||
.setSpeed(Speed.of(5))
|
||||
.setAltitudeGain(12f)
|
||||
.setAltitudeLoss(12f)
|
||||
.setSensorDistance(Distance.of(4))
|
||||
.setAltitudeGain(6f)
|
||||
.setAltitudeLoss(6f)
|
||||
.setSensorDistance(Distance.of(2))
|
||||
), trackPoints);
|
||||
}
|
||||
|
||||
@@ -511,6 +509,13 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.1)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(3))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
|
||||
.setLatitude(45.2)
|
||||
@@ -519,6 +524,13 @@ public class TrackRecordingServiceTestLocation {
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||
.setLatitude(45.2)
|
||||
.setLongitude(35)
|
||||
.setHorizontalAccuracy(Distance.of(5))
|
||||
.setSpeed(Speed.of(0))
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f),
|
||||
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||
.setAltitudeGain(0f)
|
||||
.setAltitudeLoss(0f)
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package de.dennisguse.opentracks.services;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
|
||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* Tests resulting TrackStatistics.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackRecordingServiceTestStatistics {
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = ServiceTestRule.withTimeout(5, TimeUnit.SECONDS);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private ContentProviderUtils contentProviderUtils;
|
||||
|
||||
private TrackRecordingService service;
|
||||
|
||||
private final AltitudeSumManager altitudeSumManager = new AltitudeSumManager() {
|
||||
@Override
|
||||
public void fill(@NonNull TrackPoint trackPoint) {
|
||||
trackPoint.setAltitudeGain(0f);
|
||||
trackPoint.setAltitudeLoss(0f);
|
||||
}
|
||||
};
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws TimeoutException {
|
||||
// Set up the mock content resolver
|
||||
ContentProvider customContentProvider = new CustomContentProvider() {
|
||||
};
|
||||
customContentProvider.attachInfo(context, null);
|
||||
|
||||
contentProviderUtils = new ContentProviderUtils(context);
|
||||
tearDown();
|
||||
|
||||
// Let's use default values.
|
||||
PreferencesUtils.clear();
|
||||
|
||||
service = ((TrackRecordingService.Binder) mServiceRule.bindService(TrackRecordingServiceTest.createStartIntent(context)))
|
||||
.getService();
|
||||
service.getTrackPointCreator().stopGPS();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws TimeoutException {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||
}
|
||||
|
||||
/**
|
||||
* Moving time should increase if the previous and current TrackPoint have speed > threshold by the timeDiff(previousTrackPoint, currentTrackPoint).
|
||||
*/
|
||||
@MediumTest
|
||||
@Test
|
||||
public void movingtime_with_pauses() {
|
||||
Assume.assumeTrue(
|
||||
"Test fails on API23; reproducible on CI and some machines.",
|
||||
Build.VERSION.SDK_INT > 23
|
||||
);
|
||||
|
||||
// given
|
||||
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochMilli(0), ZoneId.systemDefault()));
|
||||
Track.Id trackId = service.startNewTrack();
|
||||
service.getTrackPointCreator().setAltitudeSumManager(altitudeSumManager);
|
||||
|
||||
Function<Integer, Void> assertMovingTime = expected -> {
|
||||
Duration actual = contentProviderUtils.getTrack(trackId).getTrackStatistics().getMovingTime();
|
||||
assertEquals(Duration.ofSeconds(expected), actual);
|
||||
return null;
|
||||
};
|
||||
|
||||
Function<Integer, Void> assertTotalTime = expected -> {
|
||||
Duration actual = contentProviderUtils.getTrack(trackId).getTrackStatistics().getTotalTime();
|
||||
assertEquals(Duration.ofSeconds(expected), actual);
|
||||
return null;
|
||||
};
|
||||
|
||||
// when / then
|
||||
int movingtime_s = 0;
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15, 5 * 60000);
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0001, 35.0, 2, 15, 6 * 60000);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0002, 35.0, 2, 15, (long) (6.5 * 60000));
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0003, 35.0, 2, 15, 7 * 60000);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 15, 8 * 60000);
|
||||
movingtime_s += 3 * 60;
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 0, 9 * 60000); //will be ignored
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 0, 10 * 60000); //will be ignored
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 0, 11 * 60000);
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 15, 13 * 60000);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0004, 35.0, 2, 15, (long) (13.5 * 60000)); //will be ignored
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0005, 35.0, 2, 15, 14 * 60000);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0006, 35.0, 2, 15, 15 * 60000);
|
||||
movingtime_s += 2 * 60;
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
assertTotalTime.apply(15 * 60);
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0006, 35.0, 2, 0, 16 * 60000); //will be ignored
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0015, 35.0, 2, 0, 17 * 60000);
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0016, 35.0, 2, 15, 18 * 60000);
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0016, 35.0, 2, 0, 19 * 60000); //TODO we could ignore this TrackPoint
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
assertTotalTime.apply(19 * 60);
|
||||
|
||||
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochSecond(40 * 60), ZoneId.systemDefault()));
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
service.pauseCurrentTrack();
|
||||
assertTotalTime.apply(40 * 60);
|
||||
|
||||
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochSecond(41 * 60), ZoneId.systemDefault()));
|
||||
service.resumeCurrentTrack();
|
||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0016, 35.0, 2, 15, 42 * 60000);
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
assertTotalTime.apply(41 * 60);
|
||||
|
||||
service.getTrackPointCreator().setClock(Clock.fixed(Instant.ofEpochSecond(50 * 60), ZoneId.systemDefault()));
|
||||
service.endCurrentTrack();
|
||||
assertMovingTime.apply(movingtime_s);
|
||||
assertTotalTime.apply(49 * 60);
|
||||
|
||||
// then
|
||||
assertFalse(service.isRecording());
|
||||
|
||||
TrackStatistics trackStatistics = contentProviderUtils.getTrack(trackId).getTrackStatistics();
|
||||
|
||||
List<TrackPoint> trackPoints = TestDataUtil.getTrackPoints(contentProviderUtils, trackId);
|
||||
assertEquals(20, trackPoints.size());
|
||||
|
||||
assertEquals(Duration.ofMinutes(49), trackStatistics.getTotalTime());
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesOpenHelperTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
|
||||
@Test
|
||||
public void test_upgradeFrom0To1_withoutStatsCustomLayouts() {
|
||||
// given the version 0
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(context.getString(R.string.stats_custom_layouts_key));
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 0);
|
||||
editor.commit();
|
||||
|
||||
// when update to version 1
|
||||
PreferencesOpenHelper.newInstance(1).check();
|
||||
|
||||
// then there should be one layout with old custom layout that has the new CSV value.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_upgradeFrom1To2_withOldVersion() {
|
||||
// given the version 1
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
String oldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";distance,1,1,0;speed,1,1,0;";
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
|
||||
|
||||
editor.commit();
|
||||
|
||||
// when update to version 2
|
||||
PreferencesOpenHelper.newInstance(2).check();
|
||||
|
||||
String updatedOldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";"
|
||||
+ PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;";
|
||||
|
||||
// then there should be one layout with old custom layout that has the new CSV value.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_upgradeFrom1To2_withNewVersion() {
|
||||
// given the version 1
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
String oldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
|
||||
|
||||
editor.commit();
|
||||
|
||||
// when update to version 2
|
||||
PreferencesOpenHelper.newInstance(2).check();
|
||||
|
||||
String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
||||
|
||||
// then there should be one layout with old custom layout that has the new CSV value.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_downgrade() {
|
||||
// given version 2
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
String customLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), customLayoutCsv);
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 2);
|
||||
|
||||
editor.commit();
|
||||
|
||||
// when downgrade to version 1
|
||||
PreferencesOpenHelper.newInstance(1).check();
|
||||
|
||||
// then custom layout should be equals to default layout.
|
||||
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
||||
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(customLayout.size(), 1);
|
||||
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
|
||||
}
|
||||
}
|
||||
+16
-10
@@ -1,5 +1,8 @@
|
||||
package de.dennisguse.opentracks.stats;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Ignore;
|
||||
@@ -16,9 +19,6 @@ import de.dennisguse.opentracks.content.data.TestDataUtil;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class TrackStatisticsUpdaterTest {
|
||||
|
||||
@@ -81,18 +81,18 @@ public class TrackStatisticsUpdaterTest {
|
||||
|
||||
// then
|
||||
TrackStatistics statistics = subject.getTrackStatistics();
|
||||
assertEquals(99.58, statistics.getTotalDistance().toM(), 0.01);
|
||||
assertEquals(Duration.ofSeconds(14), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(7), statistics.getMovingTime());
|
||||
assertEquals(128.03, statistics.getTotalDistance().toM(), 0.01);
|
||||
assertEquals(Duration.ofSeconds(13), statistics.getTotalTime());
|
||||
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
|
||||
|
||||
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
|
||||
assertEquals(27.5, statistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(28.75, statistics.getMaxAltitude(), 0.01);
|
||||
assertEquals(36, statistics.getTotalAltitudeGain(), 0.01);
|
||||
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
||||
|
||||
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
|
||||
assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01);
|
||||
assertEquals(7.11, statistics.getAverageSpeed().toMPS(), 0.01);
|
||||
assertEquals(9.84, statistics.getAverageSpeed().toMPS(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -141,9 +141,9 @@ public class TrackStatisticsUpdaterTest {
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp3 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
tp3.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp4 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
tp4.setSpeed(Speed.of(5f));
|
||||
tp4.setSensorDistance(Distance.of(5f));
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
tp5.setSensorDistance(Distance.of(10f));
|
||||
@@ -200,9 +200,12 @@ public class TrackStatisticsUpdaterTest {
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
tp3.setSensorDistance(Distance.of(5f));
|
||||
tp3.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||
tp4.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
|
||||
// when
|
||||
@@ -248,8 +251,11 @@ public class TrackStatisticsUpdaterTest {
|
||||
|
||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||
tp2.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||
tp3.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp4 = new TrackPoint(0.0005, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(4000));
|
||||
tp4.setSpeed(Speed.of(5f));
|
||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||
|
||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||
|
||||
@@ -43,40 +43,40 @@ public class BluetoothUtilsTest {
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x01, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||
characteristic.setValue(new byte[]{0x02, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99});
|
||||
|
||||
// when
|
||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertNull(sensor.getDistanceSpeed());
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x02, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
characteristic.setValue(new byte[]{0x01, (byte) 0xFF, (byte) 0xFF, 0, 1, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
assertNull(sensor.getCadence());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseCyclingSpeedCadence_crankWheel() {
|
||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x00, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
characteristic.setValue(new byte[]{0x03, (byte) 0xC8, 0x00, 0x00, 0x01, 0x06, (byte) 0x99, (byte) 0xE1, 0x00, 0x45, (byte) 0x99});
|
||||
|
||||
// when
|
||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||
assertEquals(225, sensor.getCadence().getCrankRevolutionsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CsvLayoutUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testGetCsvLineParts() {
|
||||
// given
|
||||
String csvLineOk = "Layout Name;2;distance,1,1;speed,1,1;";
|
||||
String csvLineWrong1 = "Layout Name;distance,1,1;speed,1,1;";
|
||||
String csvLineWrong2 = "Layout Name;1;";
|
||||
|
||||
// when
|
||||
List<String> partsOk = CsvLayoutUtils.getCsvLineParts(csvLineOk);
|
||||
List<String> partsWrong1 = CsvLayoutUtils.getCsvLineParts(csvLineWrong1);
|
||||
List<String> partsWrong2 = CsvLayoutUtils.getCsvLineParts(csvLineWrong2);
|
||||
|
||||
// then
|
||||
assertNotNull(partsOk);
|
||||
assertEquals(partsOk.size(), 4);
|
||||
assertEquals(partsOk.get(0), "Layout Name");
|
||||
assertEquals(partsOk.get(1), "2");
|
||||
assertEquals(partsOk.get(2), "distance,1,1");
|
||||
assertEquals(partsOk.get(3), "speed,1,1");
|
||||
assertNull(partsWrong1);
|
||||
assertNull(partsWrong2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCsvFieldParts() {
|
||||
// given
|
||||
String csvFieldOk1 = "distance,0,0";
|
||||
String csvFieldOk2 = "speed,0,1";
|
||||
String csvFieldOk3 = "time,1,0";
|
||||
String csvFieldOk4 = "moving time,1,1";
|
||||
String csvFieldOk5 = ";moving time,1,1";
|
||||
String csvLineWrong1 = "";
|
||||
String csvLineWrong2 = "distance,speed,time";
|
||||
String csvLineWrong3 = "distance,0,1o";
|
||||
String csvLineWrong4 = "distance,0z,1";
|
||||
String csvLineWrong5 = "distance";
|
||||
String csvLineWrong6 = "distance,0";
|
||||
String csvLineWrong7 = "distance,1,1;";
|
||||
|
||||
// when
|
||||
String[] ok1 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk1);
|
||||
String[] ok2 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk2);
|
||||
String[] ok3 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk3);
|
||||
String[] ok4 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk4);
|
||||
String[] ok5 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk5);
|
||||
String[] wrong1 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong1);
|
||||
String[] wrong2 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong2);
|
||||
String[] wrong3 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong3);
|
||||
String[] wrong4 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong4);
|
||||
String[] wrong5 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong5);
|
||||
String[] wrong6 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong6);
|
||||
String[] wrong7 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong7);
|
||||
|
||||
// then
|
||||
assertNotNull(ok1);
|
||||
assertNotNull(ok2);
|
||||
assertNotNull(ok3);
|
||||
assertNotNull(ok4);
|
||||
assertNotNull(ok5);
|
||||
assertFieldOk(ok1, "distance", "0", "0");
|
||||
assertFieldOk(ok2, "speed", "0", "1");
|
||||
assertFieldOk(ok3, "time", "1", "0");
|
||||
assertFieldOk(ok4, "moving time", "1", "1");
|
||||
assertFieldOk(ok5, ";moving time", "1", "1");
|
||||
assertNull(wrong1);
|
||||
assertNull(wrong2);
|
||||
assertNull(wrong3);
|
||||
assertNull(wrong4);
|
||||
assertNull(wrong5);
|
||||
assertNull(wrong6);
|
||||
assertNull(wrong7);
|
||||
}
|
||||
|
||||
private void assertFieldOk(String[] ok, String name, String val1, String val2) {
|
||||
assertEquals(ok[0], name);
|
||||
assertEquals(ok[1], val1);
|
||||
assertEquals(ok[2], val2);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -21,10 +22,14 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesUtilsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
|
||||
@Test
|
||||
public void ExportTrackFileFormat_ok() {
|
||||
@@ -72,6 +77,24 @@ public class PreferencesUtilsTest {
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllCustomLayouts_default() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// then
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertTrue(layouts.get(0).getFields().size() > 0);
|
||||
assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_default() {
|
||||
// given
|
||||
@@ -85,7 +108,7 @@ public class PreferencesUtilsTest {
|
||||
|
||||
// then
|
||||
assertTrue(layout.getFields().size() > 0);
|
||||
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
|
||||
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@@ -95,8 +118,8 @@ public class PreferencesUtilsTest {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"run;"
|
||||
context.getString(R.string.stats_custom_layouts_key),
|
||||
"run;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
@@ -108,7 +131,8 @@ public class PreferencesUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "run");
|
||||
assertEquals(layout.getName(), "run");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
@@ -133,8 +157,8 @@ public class PreferencesUtilsTest {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"walking;"
|
||||
context.getString(R.string.stats_custom_layouts_key),
|
||||
"walking;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
|
||||
@@ -146,7 +170,8 @@ public class PreferencesUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "walking");
|
||||
assertEquals(layout.getName(), "walking");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
@@ -177,20 +202,22 @@ public class PreferencesUtilsTest {
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||
|
||||
// when
|
||||
PreferencesUtils.setCustomLayout(layoutSrc);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
|
||||
editor.commit();
|
||||
|
||||
// then
|
||||
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
|
||||
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layouts_key), null);
|
||||
assertNotNull(csv);
|
||||
assertEquals(csv,
|
||||
"road cycling;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
"road cycling;" + PreferencesUtils.getLayoutColumnsByDefault() + ";"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
|
||||
|
||||
Layout layoutDst = PreferencesUtils.getCustomLayout();
|
||||
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
|
||||
assertEquals(layoutSrc.getName(), layoutDst.getName());
|
||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
||||
@@ -198,4 +225,122 @@ public class PreferencesUtilsTest {
|
||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditCustomLayouts() {
|
||||
// update all custom layouts
|
||||
|
||||
// given a custom layout with two profiles
|
||||
String cyclingProfile = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
|
||||
|
||||
String runningProfile = "running;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
String cyclingProfileUpdated = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
||||
|
||||
List<Layout> layoutsToBeUpdated = new ArrayList<>();
|
||||
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
|
||||
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
|
||||
|
||||
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
|
||||
|
||||
// then only updated profile is modified in the custom layouts
|
||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(layoutsBefore.size(), 2);
|
||||
assertEquals(layoutsAfter.size(), 2);
|
||||
|
||||
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
|
||||
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditCustomLayout() {
|
||||
// Update only one custom layout
|
||||
|
||||
// given a custom layout with two profiles
|
||||
String cyclingProfile = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
|
||||
|
||||
String runningProfile = "running;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
String cyclingProfileUpdated = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;";
|
||||
Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources);
|
||||
PreferencesUtils.updateCustomLayout(layoutToBeUpdated);
|
||||
|
||||
// then only updated profile is modified in the custom layouts
|
||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(layoutsBefore.size(), 2);
|
||||
assertEquals(layoutsAfter.size(), 2);
|
||||
|
||||
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
|
||||
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_whenSelectedOneNotExists() {
|
||||
// given a custom layout with two profiles and not existing custom layout selected
|
||||
String cyclingProfile = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
|
||||
|
||||
String runningProfile = "running;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.putString(context.getString(R.string.stats_custom_layout_selected_layout_key), "Not Exists");
|
||||
editor.apply();
|
||||
|
||||
// when it gets the custom layout
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then the first one was returned
|
||||
assertEquals(layout.getName(), "cycling");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +324,8 @@ limitations under the License.
|
||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutListActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".ShowOnMapProxyActivity$KML"
|
||||
|
||||
@@ -5,12 +5,17 @@ import android.view.View;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.databinding.AggregatedStatsBinding;
|
||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
|
||||
|
||||
public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||
|
||||
public static final String EXTRA_TRACK_IDS = "track_ids";
|
||||
|
||||
private AggregatedStatsBinding viewBinding;
|
||||
|
||||
private AggregatedStatisticsAdapter adapter;
|
||||
@@ -21,8 +26,10 @@ public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||
|
||||
viewBinding.aggregatedStatsList.setEmptyView(viewBinding.aggregatedStatsEmptyView);
|
||||
|
||||
List<Track.Id> trackIds = getIntent().getParcelableArrayListExtra(EXTRA_TRACK_IDS);
|
||||
|
||||
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
||||
viewModel.getAggregatedStats().observe(this, aggregatedStatistics -> {
|
||||
viewModel.getAggregatedStats(trackIds).observe(this, aggregatedStatistics -> {
|
||||
if (aggregatedStatistics != null) {
|
||||
adapter = new AggregatedStatisticsAdapter(this, aggregatedStatistics);
|
||||
viewBinding.aggregatedStatsList.setAdapter(adapter);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -97,7 +98,10 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
||||
return;
|
||||
}
|
||||
|
||||
getCallback().recordStart();
|
||||
Callback callback = (Callback) getContext();
|
||||
if (callback != null) {
|
||||
callback.recordStart();
|
||||
}
|
||||
});
|
||||
|
||||
viewBinding.controllerStop.setOnTouchListener(this);
|
||||
@@ -114,6 +118,12 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
handlerUpdateTotalTime.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
@@ -175,7 +185,10 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
view.performClick();
|
||||
getCallback().recordPause();
|
||||
Callback callback = (Callback) getContext();
|
||||
if (callback != null) {
|
||||
callback.recordPause();
|
||||
}
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
@@ -198,8 +211,12 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
getCallback().recordStop();
|
||||
ActivityUtils.vibrate(getContext(), 1000);
|
||||
Context context = getContext();
|
||||
Callback callback = (Callback) context;
|
||||
if (callback != null) {
|
||||
callback.recordStop();
|
||||
ActivityUtils.vibrate(context, 1000);
|
||||
}
|
||||
|
||||
transition.resetTransition();
|
||||
};
|
||||
@@ -222,10 +239,6 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
||||
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
|
||||
private Callback getCallback() {
|
||||
return (Callback) getActivity();
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void recordStart();
|
||||
|
||||
|
||||
@@ -49,6 +49,10 @@ import androidx.loader.content.CursorLoader;
|
||||
import androidx.loader.content.Loader;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
@@ -58,12 +62,12 @@ import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
@@ -248,6 +252,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
trackRecordingServiceConnection.unbind(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
trackRecordingServiceConnection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackListBinding.inflate(getLayoutInflater());
|
||||
@@ -463,6 +474,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_aggregated_stats) {
|
||||
Intent intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class)
|
||||
.putParcelableArrayListExtra(AggregatedStatisticsActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.list_context_menu_select_all) {
|
||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||
viewBinding.trackList.setItemChecked(i, true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
@@ -18,6 +19,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -28,10 +31,10 @@ import de.dennisguse.opentracks.fragments.IntervalsFragment;
|
||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackUtils;
|
||||
|
||||
@@ -232,6 +235,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
trackDataHub.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
viewBinding = null;
|
||||
trackRecordingServiceConnection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = TrackRecordingBinding.inflate(getLayoutInflater());
|
||||
@@ -256,6 +266,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_insert_marker) {
|
||||
Intent intent = IntentUtils
|
||||
.newIntent(this, MarkerEditActivity.class)
|
||||
@@ -264,8 +279,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||
if (item.getItemId() == R.id.track_detail_menu_select_layout) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
List<String> layoutNames = PreferencesUtils.getAllCustomLayoutNames();
|
||||
builder.setTitle(getString(R.string.custom_layout_select_layout)).setItems(layoutNames.toArray(new String[0]), (dialog, which) -> PreferencesUtils.setDefaultLayout(layoutNames.get(which)));
|
||||
builder.create().show();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -18,13 +18,13 @@ import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private Layout layout;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
this.layout = layout;
|
||||
@@ -34,12 +34,12 @@ public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
||||
return new SettingsCustomLayoutAdapter.ViewHolder(view);
|
||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutAdapter.ViewHolder viewHolder = (SettingsCustomLayoutAdapter.ViewHolder) holder;
|
||||
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
|
||||
DataField field = layout.getFields().get(position);
|
||||
viewHolder.itemView.setTag(field.getKey());
|
||||
viewHolder.title.setText(field.getTitle());
|
||||
@@ -0,0 +1,95 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<Layout> layoutList;
|
||||
private final Context context;
|
||||
private SettingsCustomLayoutProfileClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_layout_item, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
|
||||
Layout layout = layoutList.get(position);
|
||||
viewHolder.itemView.setTag(layout.getName());
|
||||
viewHolder.title.setText(layout.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layoutList == null) {
|
||||
return 0;
|
||||
}
|
||||
return layoutList.size();
|
||||
}
|
||||
|
||||
public List<Layout> getLayouts() {
|
||||
return layoutList;
|
||||
}
|
||||
|
||||
public void reloadLayouts() {
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeLayout(int position) {
|
||||
layoutList.remove(position);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void restoreItem(Layout layout, int position) {
|
||||
layoutList.add(position, layout);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView title;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.custom_layout_title);
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String profile = (String) view.getTag();
|
||||
Optional<Layout> optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst();
|
||||
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SettingsCustomLayoutProfileClickListener {
|
||||
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.CsvConstants;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
|
||||
public class DataField {
|
||||
public class DataField implements Parcelable {
|
||||
public static final String YES_VALUE = "1";
|
||||
public static final String NOT_VALUE = "0";
|
||||
|
||||
@@ -24,6 +26,26 @@ public class DataField {
|
||||
this.isWide = isWide;
|
||||
}
|
||||
|
||||
protected DataField(Parcel in) {
|
||||
key = in.readString();
|
||||
title = in.readString();
|
||||
isVisible = in.readByte() != 0;
|
||||
isPrimary = in.readByte() != 0;
|
||||
isWide = in.readByte() != 0;
|
||||
}
|
||||
|
||||
public static final Creator<DataField> CREATOR = new Creator<DataField>() {
|
||||
@Override
|
||||
public DataField createFromParcel(Parcel in) {
|
||||
return new DataField(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataField[] newArray(int size) {
|
||||
return new DataField[size];
|
||||
}
|
||||
};
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
@@ -63,7 +85,8 @@ public class DataField {
|
||||
public String toCsv() {
|
||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
||||
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
|
||||
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
|
||||
String wide = this.isWide ? YES_VALUE : NOT_VALUE;
|
||||
return key + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
|
||||
}
|
||||
|
||||
public static String getTitleByKey(Resources resources, String key) {
|
||||
@@ -109,4 +132,18 @@ public class DataField {
|
||||
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(key);
|
||||
parcel.writeString(title);
|
||||
parcel.writeByte((byte) (isVisible ? 1 : 0));
|
||||
parcel.writeByte((byte) (isPrimary ? 1 : 0));
|
||||
parcel.writeByte((byte) (isWide ? 1 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,75 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Layout {
|
||||
private final String profile;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
|
||||
public class Layout implements Parcelable {
|
||||
private static final String TAG = Layout.class.getSimpleName();
|
||||
|
||||
// User-generated layout's name.
|
||||
private final String name;
|
||||
private int columnsPerRow;
|
||||
private final List<DataField> dataFields = new ArrayList<>();
|
||||
|
||||
public Layout(String profile) {
|
||||
this.profile = profile;
|
||||
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
||||
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
|
||||
if (csvParts == null) {
|
||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||
return new Layout(PreferencesUtils.getDefaultLayoutName());
|
||||
}
|
||||
|
||||
Layout layout = new Layout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
|
||||
for (int i = 2; i < csvParts.size(); i++) {
|
||||
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
|
||||
if (fieldParts == null) {
|
||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||
return layout;
|
||||
}
|
||||
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
public Layout(String name) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
|
||||
}
|
||||
|
||||
public Layout(String name, int columnsPerRow) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
protected Layout(Parcel in) {
|
||||
name = in.readString();
|
||||
columnsPerRow = in.readInt();
|
||||
in.readList(dataFields, DataField.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<Layout> CREATOR = new Creator<Layout>() {
|
||||
@Override
|
||||
public Layout createFromParcel(Parcel in) {
|
||||
return new Layout(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout[] newArray(int size) {
|
||||
return new Layout[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
||||
}
|
||||
@@ -27,6 +86,11 @@ public class Layout {
|
||||
dataFields.remove(dataField);
|
||||
}
|
||||
|
||||
public void replaceAllFields(List<DataField> newFields) {
|
||||
dataFields.clear();
|
||||
addFields(newFields);
|
||||
}
|
||||
|
||||
public List<DataField> getFields() {
|
||||
return new ArrayList<>(dataFields);
|
||||
}
|
||||
@@ -36,7 +100,46 @@ public class Layout {
|
||||
dataFields.add(to, dataFieldToMove);
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return profile;
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getColumnsPerRow() {
|
||||
return columnsPerRow;
|
||||
}
|
||||
|
||||
public void setColumnsPerRow(int columnsPerRow) {
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
public boolean sameName(Layout layout) {
|
||||
return this.name.equalsIgnoreCase(layout.getName());
|
||||
}
|
||||
|
||||
public boolean sameName(String name) {
|
||||
return this.name.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
public String toCsv() {
|
||||
List<DataField> fields = getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(name);
|
||||
parcel.writeInt(columnsPerRow);
|
||||
parcel.writeList(dataFields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +349,19 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
public TrackPoint minusCumulativeSensorData(@NonNull TrackPoint lastTrackPoint) {
|
||||
if (hasSensorDistance() && lastTrackPoint.hasSensorDistance()) {
|
||||
sensorDistance = sensorDistance.minus(lastTrackPoint.getSensorDistance());
|
||||
}
|
||||
if (hasAltitudeGain() && lastTrackPoint.hasAltitudeGain()) {
|
||||
altitudeGain_m -= lastTrackPoint.altitudeGain_m;
|
||||
}
|
||||
if (hasAltitudeLoss() && lastTrackPoint.hasAltitudeLoss()) {
|
||||
altitudeLoss_m -= lastTrackPoint.altitudeLoss_m;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasSensorData() {
|
||||
return hasHeartRate() || hasCadence() || hasPower();
|
||||
}
|
||||
|
||||
@@ -205,6 +205,26 @@ public class ContentProviderUtils {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public List<Track> getTracks(List<Track.Id> trackIds) {
|
||||
if (trackIds == null || trackIds.isEmpty()) {
|
||||
return getTracks();
|
||||
}
|
||||
|
||||
String selection = String.format(TracksColumns._ID + " IN (%s)", TextUtils.join(",", Collections.nCopies(trackIds.size(), "?")));
|
||||
String[] selectionArgs = trackIds.stream().map(id -> Long.toString(id.getId())).toArray(String[]::new);
|
||||
ArrayList<Track> tracks = new ArrayList<>();
|
||||
try (Cursor cursor = getTrackCursor(selection, selectionArgs, TracksColumns._ID)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
tracks.ensureCapacity(cursor.getCount());
|
||||
do {
|
||||
tracks.add(createTrack(cursor));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
public Track getTrack(@NonNull Track.Id trackId) {
|
||||
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
|
||||
/**
|
||||
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
|
||||
|
||||
@@ -65,13 +65,10 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_fields_key, key)) {
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
|
||||
updateUInecessary = true;
|
||||
layout = PreferencesUtils.getCustomLayout();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_columns_key, key)) {
|
||||
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns());
|
||||
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
@@ -107,13 +104,13 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||
final int numColumns = PreferencesUtils.getLayoutColumns();
|
||||
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
|
||||
layout = PreferencesUtils.getCustomLayout();
|
||||
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
if (statisticsAdapter.isItemWide(position)) {
|
||||
return numColumns;
|
||||
return layout.getColumnsPerRow();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.JobIntentService;
|
||||
@@ -24,6 +25,7 @@ public class ExportService extends JobIntentService {
|
||||
private static final String EXTRA_TRACK_ID = "extra_track_id";
|
||||
private static final String EXTRA_TRACK_FILE_FORMAT = "extra_track_file_format";
|
||||
private static final String EXTRA_DIRECTORY_URI = "extra_directory_uri";
|
||||
private static final String TAG = ExportService.class.getSimpleName();
|
||||
|
||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, Track.Id trackId, TrackFileFormat trackFileFormat, Uri directoryUri) {
|
||||
Intent intent = new Intent(context, JobService.class);
|
||||
@@ -42,18 +44,23 @@ public class ExportService extends JobIntentService {
|
||||
TrackFileFormat trackFileFormat = (TrackFileFormat) intent.getSerializableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||
Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI);
|
||||
|
||||
// Prepare resultCode and bundle to send to the receiver.
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
|
||||
|
||||
// Build directory file.
|
||||
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
|
||||
if (directoryFile == null || !directoryFile.canWrite()) {
|
||||
Log.e(TAG, "Can't write to directory: " + directoryFile);
|
||||
resultReceiver.send(ExportServiceResultReceiver.RESULT_CODE_ERROR, bundle);
|
||||
return;
|
||||
}
|
||||
|
||||
// Export.
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
boolean success = ExportUtils.exportTrack(this, trackFileFormat, directoryFile, track);
|
||||
|
||||
// Prepare resultCode and bundle to send to the receiver.
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ExportServiceResultReceiver.RESULT_EXTRA_TRACK_ID, trackId);
|
||||
|
||||
// Send result to the receiver.
|
||||
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
|
||||
resultReceiver.send(resultCode, bundle);
|
||||
|
||||
+24
-6
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.io.file.importer;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
|
||||
@@ -24,7 +25,7 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
|
||||
if (directoryUri == null) {
|
||||
if (!isDirectoryValid(directoryUri)) {
|
||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||
} else {
|
||||
startActivity(createNextActivityIntent(directoryUri.getUri())); //TODO Refactor to DocumentFile
|
||||
@@ -32,6 +33,10 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||
return directoryUri != null && directoryUri.isDirectory() && directoryUri.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||
super.onActivityResult(requestCode, resultCode, resultData);
|
||||
@@ -83,6 +88,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
return PreferencesUtils.getDefaultExportDirectoryUri(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent createNextActivityIntent(Uri directoryUri) {
|
||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||
@@ -99,14 +109,15 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||
switch (resultCode) {
|
||||
case RESULT_OK:
|
||||
Uri directoryUri = resultData.getData();
|
||||
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), PreferencesUtils.getDefaultExportDirectoryUri(this));
|
||||
|
||||
Uri directoryUri = resultData.getData();
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(directoryUri);
|
||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
|
||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri, resultData.getFlags());
|
||||
break;
|
||||
case RESULT_CANCELED:
|
||||
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), PreferencesUtils.getDefaultExportDirectoryUri(this));
|
||||
PreferencesUtils.setDefaultExportDirectoryUri(null);
|
||||
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -114,12 +125,19 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
||||
super.configureDirectoryChooserIntent(intent);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
if (PreferencesUtils.isDefaultExportDirectoryUri(this)) {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, PreferencesUtils.getDefaultExportDirectoryUri(this).getUri());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||
|
||||
@@ -37,6 +37,7 @@ class TrackRecordingManager {
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
|
||||
private boolean currentSegmentHasTrackPoint;
|
||||
private TrackPoint lastTrackPoint;
|
||||
private TrackPoint lastStoredTrackPoint;
|
||||
|
||||
TrackRecordingManager(Context context) {
|
||||
@@ -80,11 +81,13 @@ class TrackRecordingManager {
|
||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||
insertTrackPoint(trackId, segmentStartTrackPoint);
|
||||
currentSegmentHasTrackPoint = false;
|
||||
lastTrackPoint = null;
|
||||
lastStoredTrackPoint = null; }
|
||||
|
||||
void pause(TrackPointCreator trackPointCreator) {
|
||||
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
|
||||
currentSegmentHasTrackPoint = false;
|
||||
lastTrackPoint = null;
|
||||
lastStoredTrackPoint = null;
|
||||
}
|
||||
|
||||
@@ -94,6 +97,7 @@ class TrackRecordingManager {
|
||||
|
||||
trackId = null;
|
||||
trackStatisticsUpdater = null;
|
||||
lastTrackPoint = null;
|
||||
lastStoredTrackPoint = null;
|
||||
currentSegmentHasTrackPoint = false;
|
||||
}
|
||||
@@ -103,7 +107,7 @@ class TrackRecordingManager {
|
||||
return null;
|
||||
}
|
||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
|
||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastStoredTrackPoint);
|
||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPoint);
|
||||
|
||||
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
|
||||
|
||||
@@ -161,13 +165,20 @@ class TrackRecordingManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorData() || (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving())) {
|
||||
if (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) {
|
||||
insertTrackPoint(trackId, trackPoint);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Not recording TrackPoint, idle");
|
||||
if (lastStoredTrackPoint != null && trackPoint.isMoving() != lastStoredTrackPoint.isMoving()) {
|
||||
// Moving from non-moving to moving or vice versa; required to compute moving time correctly.
|
||||
insertTrackPoint(trackId, trackPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Not recording TrackPoint");
|
||||
lastTrackPoint = trackPoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -176,6 +187,22 @@ class TrackRecordingManager {
|
||||
}
|
||||
|
||||
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
if (lastTrackPoint != null) {
|
||||
if (lastStoredTrackPoint != null && lastTrackPoint.getTime().equals(lastStoredTrackPoint.getTime())) {
|
||||
// Do not insert if inserted already
|
||||
Log.w(TAG, "Ignore insertTrackPoint. trackPoint time same as last valid trackId point time.");
|
||||
} else {
|
||||
insertTrackPointHelper(trackId, lastTrackPoint);
|
||||
// Remove the sensorDistance from trackPoint that is already going be stored with lastTrackPoint.
|
||||
trackPoint.minusCumulativeSensorData(lastTrackPoint);
|
||||
}
|
||||
lastTrackPoint = null;
|
||||
}
|
||||
|
||||
insertTrackPointHelper(trackId, trackPoint);
|
||||
}
|
||||
|
||||
private void insertTrackPointHelper(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
try {
|
||||
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||
|
||||
+8
-2
@@ -69,6 +69,8 @@ public abstract class BluetoothConnectionManager<DataType> {
|
||||
|
||||
case BluetoothProfile.STATE_DISCONNECTED:
|
||||
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
|
||||
|
||||
clearData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -130,14 +132,18 @@ public abstract class BluetoothConnectionManager<DataType> {
|
||||
observer.onChanged(sensorData);
|
||||
}
|
||||
|
||||
private synchronized void clearData() {
|
||||
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
|
||||
}
|
||||
|
||||
|
||||
synchronized void disconnect() {
|
||||
if (bluetoothGatt == null) {
|
||||
Log.w(TAG, "Cannot disconnect if not connected.");
|
||||
return;
|
||||
}
|
||||
bluetoothGatt.close();
|
||||
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
|
||||
|
||||
clearData();
|
||||
bluetoothGatt = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +1,73 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
|
||||
class PreferencesOpenHelper {
|
||||
|
||||
private static final int PREFERENCES_VERSION = 1;
|
||||
private final int version;
|
||||
|
||||
private PreferencesOpenHelper() {
|
||||
private PreferencesOpenHelper(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
static PreferencesOpenHelper newInstance() {
|
||||
return new PreferencesOpenHelper();
|
||||
static PreferencesOpenHelper newInstance(int version) {
|
||||
return new PreferencesOpenHelper(version);
|
||||
}
|
||||
|
||||
void checkForUpgrade() {
|
||||
void check() {
|
||||
int lastVersion = PreferencesUtils.getInt(R.string.prefs_last_version_key, 0);
|
||||
if (PREFERENCES_VERSION > lastVersion) {
|
||||
if (version > lastVersion) {
|
||||
onUpgrade();
|
||||
} else if (version < lastVersion) {
|
||||
onDowngrade();
|
||||
}
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, PREFERENCES_VERSION);
|
||||
switch (PREFERENCES_VERSION) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
|
||||
for (int i = 1; i <= version; i++) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
break;
|
||||
case 2:
|
||||
upgradeFrom1to2();
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeFrom0to1() {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout());
|
||||
String preferenceValue = PreferencesUtils.getString(R.string.stats_custom_layouts_key, "");
|
||||
if (preferenceValue.isEmpty()) {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeFrom1to2() {
|
||||
String csvVersion1CustomLayout = PreferencesUtils.getString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
ArrayList<String> parts = new ArrayList<>();
|
||||
Collections.addAll(parts, csvVersion1CustomLayout.split(CsvLayoutUtils.ITEM_SEPARATOR));
|
||||
|
||||
if (parts.size() < 2) {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parts.get(1).matches("\\d+")) {
|
||||
parts.add(1, String.valueOf(PreferencesUtils.getLayoutColumnsByDefault()));
|
||||
}
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, parts.stream().collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR)));
|
||||
}
|
||||
|
||||
private void onDowngrade() {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,26 +25,25 @@ import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.CsvConstants;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -57,6 +56,8 @@ public class PreferencesUtils {
|
||||
|
||||
private final static String TAG = PreferencesUtils.class.getSimpleName();
|
||||
|
||||
private static final int PREFERENCES_VERSION = 2;
|
||||
|
||||
private PreferencesUtils() {
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ public class PreferencesUtils {
|
||||
PreferencesUtils.resources = resources;
|
||||
PreferencesUtils.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
PreferencesOpenHelper.newInstance().checkForUpgrade();
|
||||
PreferencesOpenHelper.newInstance(PREFERENCES_VERSION).check();
|
||||
}
|
||||
|
||||
public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener changeListener) {
|
||||
@@ -94,7 +95,7 @@ public class PreferencesUtils {
|
||||
/**
|
||||
* Gets a preference key
|
||||
*
|
||||
* @param keyId the key id
|
||||
* @param keyId the key id
|
||||
*/
|
||||
private static String getKey(int keyId) {
|
||||
return resources.getString(keyId);
|
||||
@@ -112,7 +113,7 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
public static boolean isKey(int[] keyIds, String key) {
|
||||
for(int keyId : keyIds) {
|
||||
for (int keyId : keyIds) {
|
||||
if (isKey(keyId, key)) {
|
||||
return true;
|
||||
}
|
||||
@@ -185,7 +186,7 @@ public class PreferencesUtils {
|
||||
} else {
|
||||
unit = resources.getString(R.string.stats_units_imperial);
|
||||
}
|
||||
setString( R.string.stats_units_key, unit);
|
||||
setString(R.string.stats_units_key, unit);
|
||||
}
|
||||
|
||||
public static boolean isReportSpeed(String category) {
|
||||
@@ -546,76 +547,113 @@ public class PreferencesUtils {
|
||||
return getDefaultExportDirectoryUri(context) != null;
|
||||
}
|
||||
|
||||
public static int getLayoutColumns() {
|
||||
return getInt(R.string.stats_custom_layout_columns_key, resources.getInteger(R.integer.stats_custom_layout_columns_default));
|
||||
public static int getLayoutColumnsByDefault() {
|
||||
return resources.getInteger(R.integer.stats_custom_layout_columns_default);
|
||||
}
|
||||
|
||||
public static void setLayoutColumns(int columns) {
|
||||
setInt(R.string.stats_custom_layout_columns_key, columns);
|
||||
}
|
||||
|
||||
private static List<TypedArray> getMultiTypedArray(String key) {
|
||||
List<TypedArray> typedArrays = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Class<R.array> resource = R.array.class;
|
||||
Field field;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
field = resource.getField(key + resources.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
|
||||
typedArrays.add(resources.obtainTypedArray(field.getInt(null)));
|
||||
i++;
|
||||
} while (field != null); //TODO Catch no such field exception instead of using an endless loop.
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
|
||||
return typedArrays;
|
||||
private static List<TypedArray> getMultiTypedArray() {
|
||||
return Stream.of(
|
||||
R.array.stats_custom_layout_fields_default_value_0,
|
||||
R.array.stats_custom_layout_fields_default_value_1,
|
||||
R.array.stats_custom_layout_fields_default_value_2,
|
||||
R.array.stats_custom_layout_fields_default_value_3,
|
||||
R.array.stats_custom_layout_fields_default_value_4,
|
||||
R.array.stats_custom_layout_fields_default_value_5,
|
||||
R.array.stats_custom_layout_fields_default_value_6,
|
||||
R.array.stats_custom_layout_fields_default_value_7,
|
||||
R.array.stats_custom_layout_fields_default_value_8,
|
||||
R.array.stats_custom_layout_fields_default_value_9,
|
||||
R.array.stats_custom_layout_fields_default_value_10,
|
||||
R.array.stats_custom_layout_fields_default_value_11,
|
||||
R.array.stats_custom_layout_fields_default_value_12,
|
||||
R.array.stats_custom_layout_fields_default_value_13,
|
||||
R.array.stats_custom_layout_fields_default_value_14,
|
||||
R.array.stats_custom_layout_fields_default_value_15,
|
||||
R.array.stats_custom_layout_fields_default_value_16,
|
||||
R.array.stats_custom_layout_fields_default_value_17,
|
||||
R.array.stats_custom_layout_fields_default_value_18
|
||||
).map(id -> resources.obtainTypedArray(id)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
private static String buildDefaultFields() {
|
||||
List<TypedArray> fieldsArrays = getMultiTypedArray();
|
||||
return fieldsArrays.stream().map(i -> i.getString(0) + CsvLayoutUtils.PROPERTY_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
||||
}
|
||||
|
||||
static String buildDefaultLayout() {
|
||||
List<TypedArray> fieldsArrays = getMultiTypedArray("stats_custom_layout_fields_default_value");
|
||||
return resources.getString(R.string.default_activity_default) + CsvConstants.LINE_SEPARATOR
|
||||
+ fieldsArrays.stream().map(i -> i.getString(0) + CsvConstants.ITEM_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
|
||||
+ CsvConstants.LINE_SEPARATOR;
|
||||
return resources.getString(R.string.stats_custom_layout_default_layout) + CsvLayoutUtils.ITEM_SEPARATOR + getLayoutColumnsByDefault() + CsvLayoutUtils.ITEM_SEPARATOR + buildDefaultFields();
|
||||
}
|
||||
|
||||
public static String getDefaultLayoutName() {
|
||||
return resources.getString(R.string.stats_custom_layout_default_layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return custom layout selected or the first one if any has been selected or the one selected is not exists anymore.
|
||||
*/
|
||||
public static Layout getCustomLayout() {
|
||||
String csvCustomLayout = getString(R.string.stats_custom_layout_fields_key, buildDefaultLayout());
|
||||
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
|
||||
Layout layout = new Layout(csvParts.get(0));
|
||||
for (int i = 1; i < csvParts.size(); i++) {
|
||||
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
|
||||
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
|
||||
if (layoutSelected == null) {
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
return layout;
|
||||
for (String line : csvLines) {
|
||||
Layout layout = Layout.fromCsv(line, resources);
|
||||
if (layout.sameName(layoutSelected)) {
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
public static void setCustomLayout(Layout layout) {
|
||||
List<DataField> fields = layout.getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
|
||||
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
|
||||
}
|
||||
|
||||
public static void updateCustomLayout(@NonNull Layout layout) {
|
||||
List<Layout> preferenceLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
Optional<Layout> layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst();
|
||||
if (layoutToBeUpdated.isPresent()) {
|
||||
layoutToBeUpdated.get().replaceAllFields(layout.getFields());
|
||||
layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow());
|
||||
PreferencesUtils.updateCustomLayouts(preferenceLayouts);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCustomLayout(@NonNull String layoutName) {
|
||||
String newLayoutCsv = layoutName + CsvLayoutUtils.ITEM_SEPARATOR + getLayoutColumnsByDefault() + CsvLayoutUtils.ITEM_SEPARATOR + buildDefaultFields();
|
||||
String customLayoutCsv = getString(R.string.stats_custom_layouts_key, buildDefaultLayout()) + CsvLayoutUtils.LINE_SEPARATOR + newLayoutCsv;
|
||||
setString(R.string.stats_custom_layouts_key, customLayoutCsv);
|
||||
}
|
||||
|
||||
public static void setDefaultLayout(String layoutName) {
|
||||
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
|
||||
}
|
||||
|
||||
public static List<Layout> getAllCustomLayouts() {
|
||||
List<Layout> layouts = new ArrayList<>();
|
||||
String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||
String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||
for (String line : csvLines) {
|
||||
layouts.add(Layout.fromCsv(line, resources));
|
||||
}
|
||||
|
||||
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
|
||||
+ CsvConstants.LINE_SEPARATOR;
|
||||
setString(R.string.stats_custom_layout_fields_key, csv);
|
||||
return layouts;
|
||||
}
|
||||
|
||||
public static List<String> getAllCustomLayoutNames() {
|
||||
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void resetCustomLayoutPreferences() {
|
||||
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layout_fields_key))) {
|
||||
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layouts_key))) {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(resources.getString(R.string.stats_custom_layout_fields_key));
|
||||
editor.commit();
|
||||
}
|
||||
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layout_columns_key))) {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(resources.getString(R.string.stats_custom_layout_columns_key));
|
||||
editor.remove(resources.getString(R.string.stats_custom_layouts_key));
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
@@ -31,7 +32,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
fragment = (PreferenceFragmentCompat) getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_KEY);
|
||||
} else {
|
||||
}
|
||||
if (fragment == null) {
|
||||
fragment = new MainSettingsFragment();
|
||||
}
|
||||
|
||||
@@ -55,9 +57,11 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
getSupportFragmentManager().putFragment(outState, FRAGMENT_KEY, fragment);
|
||||
if (fragment != null && fragment.isAdded()) {
|
||||
getSupportFragmentManager().putFragment(outState, FRAGMENT_KEY, fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+20
-16
@@ -18,18 +18,20 @@ import java.util.stream.IntStream;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutAdapter;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutEditAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutActivity extends AbstractActivity implements SettingsCustomLayoutAdapter.SettingsCustomLayoutItemClickListener {
|
||||
public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener {
|
||||
|
||||
public static final String EXTRA_LAYOUT = "extraLayout";
|
||||
private ActivitySettingsCustomLayoutBinding viewBinding;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private SettingsCustomLayoutAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutAdapter adapterFieldsHidden;
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
||||
private String profile;
|
||||
private Layout layoutFieldsVisible;
|
||||
private Layout layoutFieldsHidden;
|
||||
private int numColumns;
|
||||
@@ -38,12 +40,13 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
// Recycler view with visible stats.
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutAdapter(this, this, layoutFieldsVisible);
|
||||
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = layout.getName();
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
|
||||
|
||||
numColumns = PreferencesUtils.getLayoutColumns();
|
||||
numColumns = layout.getColumnsPerRow();
|
||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@@ -86,7 +89,6 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
numColumns = position + 1;
|
||||
gridLayoutManager.setSpanCount(numColumns);
|
||||
PreferencesUtils.setLayoutColumns(position + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,24 +96,26 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
||||
|
||||
}
|
||||
});
|
||||
viewBinding.spinnerOptions.setSelection(PreferencesUtils.getLayoutColumns() - 1);
|
||||
viewBinding.spinnerOptions.setSelection(numColumns - 1);
|
||||
|
||||
// Recycler view with not visible stats.
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutAdapter(this, this, layoutFieldsHidden);
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
|
||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||
|
||||
setTitle(profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
||||
Layout newLayout = new Layout(layoutFieldsVisible.getProfile());
|
||||
newLayout.addFields(layoutFieldsVisible.getFields());
|
||||
newLayout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.setCustomLayout(newLayout);
|
||||
Layout layout = new Layout(profile, numColumns);
|
||||
layout.addFields(layoutFieldsVisible.getFields());
|
||||
layout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.updateCustomLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutListAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding;
|
||||
import de.dennisguse.opentracks.util.RecyclerViewSwipeDeleteCallback;
|
||||
|
||||
public class SettingsCustomLayoutListActivity extends AbstractActivity implements SettingsCustomLayoutListAdapter.SettingsCustomLayoutProfileClickListener {
|
||||
|
||||
private ActivitySettingsCustomLayoutListBinding viewBinding;
|
||||
private SettingsCustomLayoutListAdapter adapter;
|
||||
private RecyclerView recyclerView;
|
||||
private View addProfileLayout;
|
||||
private TextInputEditText addProfileEditText;
|
||||
private TextInputLayout addProfileInputLayout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(getString(R.string.custom_layout_list_title));
|
||||
|
||||
adapter = new SettingsCustomLayoutListAdapter(this, this);
|
||||
recyclerView = viewBinding.recyclerView;
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
Button cancelButton = findViewById(R.id.custom_layout_list_cancel_button);
|
||||
Button okButton = findViewById(R.id.custom_layout_list_ok_button);
|
||||
|
||||
cancelButton.setOnClickListener(view -> clearAndHideEditLayout());
|
||||
|
||||
okButton.setEnabled(false);
|
||||
okButton.setOnClickListener(view -> {
|
||||
PreferencesUtils.addCustomLayout(addProfileEditText.getText().toString());
|
||||
clearAndHideEditLayout();
|
||||
adapter.reloadLayouts();
|
||||
});
|
||||
|
||||
addProfileLayout = findViewById(R.id.custom_layout_list_add_linear_layout);
|
||||
addProfileInputLayout = findViewById(R.id.custom_layout_list_input_layout);
|
||||
addProfileEditText = findViewById(R.id.custom_layout_list_edit_name);
|
||||
addProfileEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (s == null || s.toString().isEmpty()) {
|
||||
okButton.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (adapter.getLayouts().stream().anyMatch(layout -> layout.sameName(s.toString()))) {
|
||||
okButton.setEnabled(false);
|
||||
addProfileInputLayout.setError(getString(R.string.custom_layout_list_edit_already_exists));
|
||||
} else {
|
||||
okButton.setEnabled(true);
|
||||
addProfileInputLayout.setError("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
|
||||
RecyclerViewSwipeDeleteCallback recyclerViewSwipeDeleteCallback = new RecyclerViewSwipeDeleteCallback(this) {
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
// When there's only one profile it cannot be deleted (so, "disable" movements: drag flags and swipe flags).
|
||||
return adapter.getItemCount() > 1 ? makeMovementFlags(0, ItemTouchHelper.LEFT) : makeMovementFlags(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final Layout item = adapter.getLayouts().get(position);
|
||||
|
||||
adapter.removeLayout(position);
|
||||
|
||||
Snackbar snackbar = Snackbar.make(recyclerView, getString(R.string.custom_layout_list_layout_removed), Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(getString(R.string.generic_undo).toUpperCase(), view -> {
|
||||
adapter.restoreItem(item, position);
|
||||
recyclerView.scrollToPosition(position);
|
||||
});
|
||||
|
||||
snackbar.show();
|
||||
}
|
||||
};
|
||||
|
||||
ItemTouchHelper itemTouchhelper = new ItemTouchHelper(recyclerViewSwipeDeleteCallback);
|
||||
itemTouchhelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
adapter.reloadLayouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
PreferencesUtils.getCustomLayout();
|
||||
viewBinding = ActivitySettingsCustomLayoutListBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.custom_layout_edit, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.custom_layout_edit_add_profile) {
|
||||
addProfileLayout.setVisibility(View.VISIBLE);
|
||||
addProfileEditText.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(addProfileEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) {
|
||||
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
|
||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void clearAndHideEditLayout() {
|
||||
addProfileEditText.setText("");
|
||||
addProfileInputLayout.setError("");
|
||||
addProfileLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class UserInterfaceSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
|
||||
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
|
||||
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
|
||||
Intent intent = new Intent(getContext(), SettingsCustomLayoutListActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -68,8 +68,6 @@ public class TrackStatisticsUpdater {
|
||||
private final TrackStatistics currentSegment;
|
||||
// Current segment's last trackPoint
|
||||
private TrackPoint lastTrackPoint;
|
||||
// Current segment's last moving trackPoint
|
||||
private TrackPoint lastMovingTrackPoint;
|
||||
|
||||
public TrackStatisticsUpdater() {
|
||||
this(new TrackStatistics());
|
||||
@@ -96,7 +94,6 @@ public class TrackStatisticsUpdater {
|
||||
this.speedBuffer_mps = new DoubleRingBuffer(toCopy.speedBuffer_mps);
|
||||
|
||||
this.lastTrackPoint = toCopy.lastTrackPoint;
|
||||
this.lastMovingTrackPoint = toCopy.lastMovingTrackPoint;
|
||||
}
|
||||
|
||||
public TrackStatistics getTrackStatistics() {
|
||||
@@ -116,9 +113,8 @@ public class TrackStatisticsUpdater {
|
||||
* @param minGPSDistance the min recording distance
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||
if (trackPoint.getType() == TrackPoint.Type.SEGMENT_START_MANUAL) {
|
||||
if (trackPoint.isSegmentStart()) {
|
||||
reset(trackPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentSegment.isInitialized()) {
|
||||
@@ -129,7 +125,7 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.setStopTime(trackPoint.getTime());
|
||||
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
|
||||
|
||||
// Process sensor data
|
||||
// Process sensor data: barometer
|
||||
if (trackPoint.hasAltitudeGain()) {
|
||||
currentSegment.addTotalAltitudeGain(trackPoint.getAltitudeGain());
|
||||
}
|
||||
@@ -138,62 +134,46 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.addTotalAltitudeLoss(trackPoint.getAltitudeLoss());
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
|
||||
//Update absolute (GPS-based) altitude
|
||||
if (trackPoint.hasAltitude()) {
|
||||
updateAbsoluteAltitude(trackPoint.getAltitude().toM());
|
||||
}
|
||||
|
||||
// Update total distance
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
// Sensor-based distance/speed
|
||||
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
|
||||
} else if (lastTrackPoint != null && trackPoint.isMoving()) {
|
||||
// GPS-based distance/speed
|
||||
// Assumption: we ignore TrackPoints that are not moving as those are likely imprecise GPS measurements
|
||||
Distance movingDistance = trackPoint.distanceToPrevious(lastTrackPoint);
|
||||
if (movingDistance != null) {
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update moving time
|
||||
if (trackPoint.isMoving() && lastTrackPoint != null && lastTrackPoint.isMoving()) {
|
||||
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
if (movingTime.isNegative()) {
|
||||
throw new RuntimeException("Moving time cannot be negative");
|
||||
}
|
||||
currentSegment.addMovingTime(movingTime);
|
||||
|
||||
// Update max speed
|
||||
updateSpeed(trackPoint, lastTrackPoint);
|
||||
} else {
|
||||
speedBuffer_mps.reset();
|
||||
}
|
||||
|
||||
|
||||
if (trackPoint.isSegmentEnd()) {
|
||||
reset(trackPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
//Update absolute (GPS-based) altitude
|
||||
if (trackPoint.hasAltitude()) {
|
||||
updateAbsoluteAltitude(trackPoint.getAltitude().toM());
|
||||
}
|
||||
|
||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||
lastTrackPoint = trackPoint;
|
||||
lastMovingTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trackPoint.hasSensorDistance()
|
||||
&& trackPoint.hasLocation() && lastMovingTrackPoint.hasLocation()) {
|
||||
// GPS-based distance/speed
|
||||
Distance movingDistance = trackPoint.distanceToPrevious(lastMovingTrackPoint);
|
||||
if (movingDistance != null && movingDistance.lessThan(minGPSDistance) && !trackPoint.isMoving()) {
|
||||
speedBuffer_mps.reset();
|
||||
lastTrackPoint = trackPoint;
|
||||
return; //TOOD Why? Is there nothing to be done afterwards?
|
||||
}
|
||||
// Update total distance
|
||||
currentSegment.addTotalDistance(movingDistance);
|
||||
}
|
||||
|
||||
Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
if (movingTime.isNegative()) {
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update moving time
|
||||
if (lastTrackPoint.isMoving()) {
|
||||
currentSegment.addMovingTime(movingTime);
|
||||
}
|
||||
|
||||
// Update max speed
|
||||
if (trackPoint.hasSpeed() && lastTrackPoint.hasSpeed()) {
|
||||
updateSpeed(trackPoint, lastTrackPoint);
|
||||
}
|
||||
|
||||
if (trackPoint.getType() == TrackPoint.Type.SEGMENT_START_AUTOMATIC) {
|
||||
reset(trackPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
lastMovingTrackPoint = trackPoint;
|
||||
}
|
||||
|
||||
private void reset(TrackPoint trackPoint) {
|
||||
@@ -203,7 +183,6 @@ public class TrackStatisticsUpdater {
|
||||
currentSegment.reset(trackPoint.getTime());
|
||||
|
||||
lastTrackPoint = null;
|
||||
lastMovingTrackPoint = null;
|
||||
altitudeBuffer_m.reset();
|
||||
speedBuffer_mps.reset();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import static android.content.Context.VIBRATOR_SERVICE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
@@ -14,6 +16,7 @@ import android.view.MenuItem;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -21,8 +24,6 @@ import java.util.ArrayList;
|
||||
import de.dennisguse.opentracks.ContextualActionModeCallback;
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
import static android.content.Context.VIBRATOR_SERVICE;
|
||||
|
||||
public class ActivityUtils {
|
||||
|
||||
private static final String TAG = ActivityUtils.class.getSimpleName();
|
||||
@@ -75,11 +76,7 @@ public class ActivityUtils {
|
||||
arrayList.add(key);
|
||||
}
|
||||
}
|
||||
int[] result = new int[arrayList.size()];
|
||||
for (int i = 0; i < arrayList.size(); i++) {
|
||||
result[i] = arrayList.get(i);
|
||||
}
|
||||
return result;
|
||||
return arrayList.stream().mapToInt(i -> i).toArray();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -97,7 +94,7 @@ public class ActivityUtils {
|
||||
return searchView;
|
||||
}
|
||||
|
||||
public static void vibrate(Context context, int milliseconds) {
|
||||
public static void vibrate(@NonNull Context context, int milliseconds) {
|
||||
final Vibrator vibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||
|
||||
@@ -107,9 +107,6 @@ public class BluetoothUtils {
|
||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation: https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=261449
|
||||
*/
|
||||
public static SensorDataCycling.CadenceAndSpeed parseCyclingCrankAndWheel(String address, String sensorName, @NonNull BluetoothGattCharacteristic characteristic) {
|
||||
// DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml
|
||||
int valueLength = characteristic.getValue().length;
|
||||
@@ -118,26 +115,26 @@ public class BluetoothUtils {
|
||||
}
|
||||
|
||||
int flags = characteristic.getValue()[0];
|
||||
boolean hasCrank = (flags & 0x01) > 0;
|
||||
boolean hasWheel = (flags & 0x02) > 0;
|
||||
boolean hasWheel = (flags & 0x01) > 0;
|
||||
boolean hasCrank = (flags & 0x02) > 0;
|
||||
|
||||
SensorDataCycling.Cadence cadence = null;
|
||||
int index = 1;
|
||||
if (hasCrank && valueLength - index >= 6) {
|
||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
|
||||
SensorDataCycling.DistanceSpeed speed = null;
|
||||
if (hasWheel && valueLength - index >= 6) {
|
||||
int wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
|
||||
index += 4;
|
||||
|
||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelTotalRevolutionCount, wheelTime);
|
||||
index += 2;
|
||||
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
|
||||
}
|
||||
|
||||
SensorDataCycling.DistanceSpeed speed = null;
|
||||
if (hasWheel && valueLength - index >= 4) {
|
||||
int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||
SensorDataCycling.Cadence cadence = null;
|
||||
if (hasCrank && valueLength - index >= 4) {
|
||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||
index += 2;
|
||||
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelCount, wheelTime);
|
||||
|
||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
|
||||
}
|
||||
|
||||
return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
public final class CsvConstants {
|
||||
private CsvConstants() {
|
||||
|
||||
}
|
||||
|
||||
public static final String LINE_SEPARATOR = ";";
|
||||
public static final String ITEM_SEPARATOR = ",";
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.navigation.NavigationBarView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class CsvLayoutUtils {
|
||||
public static final String LINE_SEPARATOR = "\n";
|
||||
public static final String ITEM_SEPARATOR = ";";
|
||||
public static final String PROPERTY_SEPARATOR = ",";
|
||||
|
||||
private CsvLayoutUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param csvLine Layout description in a CSV format.
|
||||
* @return All CSV parts from the csvLine or null if it's malformed.
|
||||
*/
|
||||
@Nullable
|
||||
public static List<String> getCsvLineParts(String csvLine) {
|
||||
if (csvLine == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The line must have three items, at least: layout's name, number of columns and one field.
|
||||
List<String> csvParts = Arrays.asList(csvLine.split(ITEM_SEPARATOR));
|
||||
if (csvParts.size() < 3 || !hasValue(csvParts.get(0)) || !isInt(csvParts.get(1))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return csvParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param csvField Layout's field in a CSV format.
|
||||
* @return List of field parts or null if it's malformed.
|
||||
*/
|
||||
@Nullable
|
||||
public static String[] getCsvFieldParts(@Nullable String csvField) {
|
||||
if (csvField == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Field must have three items: key, is visible (0 or 1), is primary (0 or 1).
|
||||
String[] fieldParts = csvField.split(CsvLayoutUtils.PROPERTY_SEPARATOR);
|
||||
if (fieldParts.length < 3 || !hasValue(fieldParts[0]) || !hasZeroOneValue(fieldParts[1]) || !hasZeroOneValue(fieldParts[2])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldParts;
|
||||
}
|
||||
|
||||
private static boolean hasValue(String value) {
|
||||
return value != null && !value.isEmpty();
|
||||
}
|
||||
|
||||
private static boolean isInt(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasZeroOneValue(String value) {
|
||||
return isInt(value) && (Integer.parseInt(value) == 0 || Integer.parseInt(value) == 1);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class ExportUtils {
|
||||
}
|
||||
|
||||
private static String getExportFileNameForTrack(Track track, String trackFileFormatExtension) {
|
||||
return track.getUuid().toString().substring(0, 8) + "_" + track.getName() + "." + trackFileFormatExtension;
|
||||
return track.getUuid().toString().substring(0, 8) + "_" + FileUtils.sanitizeFileName(track.getName()) + "." + trackFileFormatExtension;
|
||||
}
|
||||
|
||||
private static Uri findFile(Context context, Uri directoryUri, String exportFileName) {
|
||||
|
||||
@@ -149,7 +149,6 @@ public class FileUtils {
|
||||
*
|
||||
* @param name name
|
||||
*/
|
||||
// TODO Check if this function is still needed.
|
||||
public static String sanitizeFileName(String name) {
|
||||
StringBuilder builder = new StringBuilder(name.length());
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.UriPermission;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
@@ -25,12 +26,14 @@ import android.util.Pair;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.FileProvider;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
||||
@@ -92,8 +95,9 @@ public class IntentUtils {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair<Uri, String> uriTrackFile = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat());
|
||||
Pair<Uri, String> uriSharePicture = ShareContentProvider.createURI(trackId, track.getName(), TrackFileFormat.SHARE_PICTURE_PNG);
|
||||
String trackName = FileUtils.sanitizeFileName(track.getName());
|
||||
Pair<Uri, String> uriTrackFile = ShareContentProvider.createURI(trackId, trackName, PreferencesUtils.getExportTrackFileFormat());
|
||||
Pair<Uri, String> uriSharePicture = ShareContentProvider.createURI(trackId, trackName, TrackFileFormat.SHARE_PICTURE_PNG);
|
||||
|
||||
uris.addAll(Arrays.asList(uriSharePicture.first, uriTrackFile.first));
|
||||
}
|
||||
@@ -203,8 +207,23 @@ public class IntentUtils {
|
||||
return new Pair<>(intent, photoUri);
|
||||
}
|
||||
|
||||
public static void persistDirectoryAccessPermission(Context context, Uri directoryUri) {
|
||||
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
||||
context.getContentResolver().takePersistableUriPermission(directoryUri, flags);
|
||||
public static void persistDirectoryAccessPermission(Context context, Uri directoryUri, int existingFlags) {
|
||||
int newFlags = existingFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
context.getContentResolver().takePersistableUriPermission(directoryUri, newFlags);
|
||||
}
|
||||
|
||||
public static void releaseDirectoryAccessPermission(Context context, final DocumentFile documentFile) {
|
||||
if (documentFile == null) {
|
||||
return;
|
||||
}
|
||||
final Uri documentUri = documentFile.getUri();
|
||||
final List<UriPermission> persistedUriPermissions = context.getContentResolver().getPersistedUriPermissions();
|
||||
for (final UriPermission permission : persistedUriPermissions) {
|
||||
final Uri uri = permission.getUri();
|
||||
if (uri.equals(documentUri)) {
|
||||
context.getContentResolver().releasePersistableUriPermission(uri, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
abstract public class RecyclerViewSwipeDeleteCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
private final Paint clearPaint;
|
||||
private final ColorDrawable background;
|
||||
private final int backgroundColor;
|
||||
private final Drawable deleteDrawable;
|
||||
private final int intrinsicWidth;
|
||||
private final int intrinsicHeight;
|
||||
|
||||
public RecyclerViewSwipeDeleteCallback(Context context) {
|
||||
background = new ColorDrawable();
|
||||
backgroundColor = context.getResources().getColor(R.color.colorBackgroundDelete);
|
||||
clearPaint = new Paint();
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
deleteDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_forever_24dp);
|
||||
deleteDrawable.setTint(context.getResources().getColor(R.color.colorForegroundDelete));
|
||||
intrinsicWidth = deleteDrawable.getIntrinsicWidth();
|
||||
intrinsicHeight = deleteDrawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
|
||||
View itemView = viewHolder.itemView;
|
||||
int itemHeight = itemView.getHeight();
|
||||
|
||||
boolean isCancelled = dX == 0 && !isCurrentlyActive;
|
||||
|
||||
if (isCancelled) {
|
||||
clearCanvas(c, itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
return;
|
||||
}
|
||||
|
||||
background.setColor(backgroundColor);
|
||||
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
|
||||
background.draw(c);
|
||||
|
||||
int deleteIconTop = itemView.getTop() + (itemHeight - intrinsicHeight) / 2;
|
||||
int deleteIconMargin = (itemHeight - intrinsicHeight) / 2;
|
||||
int deleteIconLeft = itemView.getRight() - deleteIconMargin - intrinsicWidth;
|
||||
int deleteIconRight = itemView.getRight() - deleteIconMargin;
|
||||
int deleteIconBottom = deleteIconTop + intrinsicHeight;
|
||||
|
||||
deleteDrawable.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom);
|
||||
deleteDrawable.draw(c);
|
||||
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
private void clearCanvas(Canvas c, Float left, Float top, Float right, Float bottom) {
|
||||
c.drawRect(left, top, right, bottom, clearPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return 0.7f;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class StatisticsUtils {
|
||||
}
|
||||
|
||||
public static Layout filterVisible(Layout layout, boolean visible) {
|
||||
Layout result = new Layout(layout.getProfile());
|
||||
Layout result = new Layout(layout.getName());
|
||||
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.viewmodels;
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
@@ -20,18 +21,18 @@ public class AggregatedStatisticsModel extends AndroidViewModel {
|
||||
super(application);
|
||||
}
|
||||
|
||||
public LiveData<AggregatedStatistics> getAggregatedStats() {
|
||||
public LiveData<AggregatedStatistics> getAggregatedStats(@Nullable List<Track.Id> trackIds) {
|
||||
if (aggregatedStats == null) {
|
||||
aggregatedStats = new MutableLiveData<>();
|
||||
loadAggregatedStats();
|
||||
loadAggregatedStats(trackIds);
|
||||
}
|
||||
return aggregatedStats;
|
||||
}
|
||||
|
||||
private void loadAggregatedStats() {
|
||||
private void loadAggregatedStats(@Nullable List<Track.Id> trackIds) {
|
||||
new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
||||
List<Track> tracks = contentProviderUtils.getTracks();
|
||||
List<Track> tracks = contentProviderUtils.getTracks(trackIds);
|
||||
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/custom_layout_list_add_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/custom_layout_list_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
android:layout_margin="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/custom_layout_list_edit_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textCapWords"
|
||||
android:imeOptions="actionDone"
|
||||
android:hint="@string/custom_layout_list_edit_name" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/custom_layout_list_bottom_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/custom_layout_list_cancel_button"
|
||||
style="@style/BottomButton"
|
||||
android:text="@android:string/cancel" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/custom_layout_list_ok_button"
|
||||
style="@style/BottomButton"
|
||||
android:text="@android:string/ok" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/HorizontalLine" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/StatsCardView">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/custom_layout_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Profile/Custom Layout Title"
|
||||
style="@style/LargeLabel"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/custom_layout_edit_add_profile"
|
||||
android:icon="@drawable/ic_baseline_add_24"
|
||||
android:orderInCategory="1"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_add_custom_layout" />
|
||||
|
||||
</menu>
|
||||
@@ -35,6 +35,11 @@ limitations under the License.
|
||||
android:icon="@drawable/ic_delete_forever_24dp"
|
||||
android:title="@string/menu_delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/list_context_menu_aggregated_stats"
|
||||
android:icon="@drawable/ic_statistics_24dp"
|
||||
android:title="@string/menu_aggregated_statistics"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/list_context_menu_select_all"
|
||||
android:icon="@drawable/ic_select_all_24dp"
|
||||
|
||||
@@ -15,6 +15,11 @@ limitations under the License.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/track_detail_menu_show_on_map"
|
||||
android:icon="@drawable/ic_map_24dp"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_show_on_map" />
|
||||
<item
|
||||
android:id="@+id/track_detail_insert_marker"
|
||||
android:icon="@drawable/ic_marker_add_24dp"
|
||||
@@ -22,10 +27,11 @@ limitations under the License.
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_insert_marker" />
|
||||
<item
|
||||
android:id="@+id/track_detail_menu_show_on_map"
|
||||
android:icon="@drawable/ic_map_24dp"
|
||||
android:id="@+id/track_detail_menu_select_layout"
|
||||
android:icon="@drawable/ic_ui"
|
||||
android:orderInCategory="2"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_show_on_map" />
|
||||
android:title="@string/menu_select_layout" />
|
||||
<item
|
||||
android:id="@+id/track_detail_markers"
|
||||
android:icon="@drawable/ic_marker_show_24dp"
|
||||
|
||||
Binary file not shown.
@@ -547,7 +547,34 @@ limitations under the License.
|
||||
<string name="permission_bluetooth_failed_rejected">V nastavení systému je třeba ručně povolit aplikaci OpenTracks přístup k blízkým zařízením.</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mil/h (doporučeno)</string>
|
||||
<string name="settings_recording_idle_speed_title">Prahová hodnota volnoběhu</string>
|
||||
<string name="settings_recording_idle_speed_title">Rychlost pro autopausu</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (doporučeno)</string>
|
||||
<string name="stats_clock">Hodiny</string>
|
||||
<string name="settings_sensors_summary">Cyklistické snímače, obvod kola, snímače běhu</string>
|
||||
<string name="settings_defaults_summary">Preferované jednotky, Výchozí aktivita, Výchozí název stopy</string>
|
||||
<string name="settings_reset_summary">Obnovení výchozích nastavení</string>
|
||||
<string name="settings_activity_defaults_category">Výchozí nastavení aktivity</string>
|
||||
<string name="settings_units_category">Jednotky</string>
|
||||
<string name="settings_import_export_summary">Import, export, automatický export</string>
|
||||
<string name="settings_import_export_title">Dovoz/vývoz</string>
|
||||
<string name="settings_announcements_summary">Čas, vzdálenost, rychlost hlasu</string>
|
||||
<string name="settings_announcements_title">Hlasová oznámení</string>
|
||||
<string name="settings_sensors_title">Bluetooth senzory</string>
|
||||
<string name="settings_gps_summary">Časový interval, Interval vzdálenosti, Maximální vzdálenost, Přesnost</string>
|
||||
<string name="settings_ui_summary">Vzhled, Rozložení záznamu, Téma</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">Vše o projektu OpenTracks</string>
|
||||
<string name="settings_defaults_title">Výchozí jednotky a aktivity</string>
|
||||
<string name="settings_ui_title">Uživatelské rozhraní</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="generic_undo">Zrušit</string>
|
||||
<string name="menu_select_layout">Vyberte rozvržení</string>
|
||||
<string name="custom_layout_list_title">Rozložení</string>
|
||||
<string name="custom_layout_select_layout">Vyberte rozvržení</string>
|
||||
<string name="menu_add_custom_layout">Nové rozvržení</string>
|
||||
<string name="custom_layout_list_edit_name">Zadejte název rozvržení, které chcete přidat</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Název rozvržení již existuje</string>
|
||||
<string name="custom_layout_list_layout_removed">Rozložení bylo odstraněno</string>
|
||||
<string name="stats_custom_layout_default_layout">Výchozí rozložení</string>
|
||||
</resources>
|
||||
@@ -456,7 +456,7 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
||||
<string name="settings_night_mode_option_day">Claro</string>
|
||||
<string name="settings_night_mode_option_night">Oscuro</string>
|
||||
<string name="settings_night_mode_title">Elegir un tema</string>
|
||||
<string name="settings_sensor_wheel_circumference">Perímetro de la rueda (mm)</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circunferencia de la rueda (mm)</string>
|
||||
<string name="stats_split_interval">Intervalo</string>
|
||||
<string name="track_detail_intervals_tab">Intervalos</string>
|
||||
<string name="aggregated_stats_empty_message">Graba tu primera pista para ver estadísticas agregadas</string>
|
||||
@@ -506,9 +506,9 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
||||
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="menu_voice_rate">Velocidad de voz</string>
|
||||
<string name="track_delete_not_recording">Esta pista no se puede borrar, ya que está grabada.</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Correr: distancia, velocidad y cadencia</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distancia y velocidad</string>
|
||||
<string name="settings_sensor_cycling_cadence">Ciclismo: cadencia</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Distancia, velocidad y cadencia</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Distancia y velocidad</string>
|
||||
<string name="settings_sensor_cycling_cadence">Cadencia</string>
|
||||
<string name="settings_announcements">Avisos</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Intervalo de tiempo</string>
|
||||
<string name="settings_announcements_distance_frequency">Intervalo de distancia</string>
|
||||
@@ -522,7 +522,7 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
||||
<string name="settings_layout_reset_confirm_message">Todas los ajustes de distribución se revertirán a los valores por defecto. Esto no eliminará ningún recorrido en el dispositivo.</string>
|
||||
<string name="settings_layout_reset_confirm_title">¿Restablecer todas las disposiciones\?</string>
|
||||
<string name="settings_layout_reset_done">Todos los ajustes de disposición se han revertido a los valores por defecto</string>
|
||||
<string name="settings_all_reset">Restablecer todos los ajustes</string>
|
||||
<string name="settings_all_reset">Reiniciar</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="field_remove_from_layout">Remover</string>
|
||||
@@ -531,4 +531,17 @@ Si el dispositivo GPS reporta datos inexactos (por ejemplo, ubicación, velocida
|
||||
<string name="stats_coordinates">Latitud/Longitud</string>
|
||||
<string name="title_activity_settings_custom_layout">Personalizar la pantalla de grabación</string>
|
||||
<string name="field_set_secondary">Secundario</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (recomendado)</string>
|
||||
<string name="settings_recording_idle_speed_title">Umbral de velocidad de ralentí</string>
|
||||
<string name="stats_clock">Reloj</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recomendado)</string>
|
||||
<string name="settings_generic_sensor">Genérico</string>
|
||||
<string name="settings_cycling_sensor">Ciclismo</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks requiere un permiso para utilizar el Bluetooth.</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Es necesario conceder manualmente a OpenTracks el acceso a los Dispositivos Cercanos en la configuración del sistema.</string>
|
||||
<string name="settings_running_sensor">Corriendo</string>
|
||||
<string name="settings_import_section">Importar</string>
|
||||
<string name="settings_export_section">Exportar</string>
|
||||
</resources>
|
||||
@@ -469,7 +469,7 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
|
||||
<string name="sensor_state_heart_rate_avg">Keskmine pulsisagedus</string>
|
||||
<string name="sensor_state_cadence_max">Max kadentsus</string>
|
||||
<string name="sensor_state_cadence_avg">Keskmine kadentsus</string>
|
||||
<string name="settings_sensor_wheel_circumference">Ratta ümbermõõt (mm)</string>
|
||||
<string name="settings_sensor_wheel_circumference">Velje ümbermõõt (mm)</string>
|
||||
<string name="stats_loss">Kahjum</string>
|
||||
<string name="track_detail_intervals_tab">Intervallid</string>
|
||||
<string name="stats_split_interval">Jagatud intervall</string>
|
||||
@@ -506,7 +506,68 @@ Kui GPS-seade teatab ebatäpsetest andmetest (nt asukoht, kiirus, kõrgus), ei s
|
||||
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="menu_voice_rate">Hääle kiirus</string>
|
||||
<string name="track_delete_not_recording">Seda pala ei saa kustutada, kuna see on praegu salvestatud.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Jalgrattasport: cadence</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Jalgrattasport: kaugus ja kiirus</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Running: kaugus, kiirus ja kadents</string>
|
||||
<string name="settings_sensor_cycling_cadence">Rütm</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Kaugus ja kiirus</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Kaugus, kiirus ja kadents</string>
|
||||
<string name="stats_clock">Kell</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (soovitatav)</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="settings_recording_idle_speed_title">Tühikäigu kiiruse lävi</string>
|
||||
<string name="settings_announcements_distance_frequency">Vahemaa intervall</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="settings_cycling_sensor">Jalgrattasõit</string>
|
||||
<string name="settings_import_section">Import</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks nõuab luba kasutada Bluetooth.</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Peate käsitsi andma Opentrackidele juurdepääsu lähedalasuvatele seadmetele süsteemi seadetes.</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="field_set_primary">Esmane</string>
|
||||
<string name="generic_choose_an_option">Valige suvand</string>
|
||||
<string name="settings_layout_reset_done">Kõik paigutusseaded on muudetud vaikeväärtusteks</string>
|
||||
<string name="settings_all_reset">Lähtestage</string>
|
||||
<string name="settings_defaults_title">Vaikeühikud ja tegevus</string>
|
||||
<string name="settings_defaults_summary">Eelistatud Üksused, Vaiketegevus, Raja Vaikenimi</string>
|
||||
<string name="settings_ui_title">kasutajaliides</string>
|
||||
<string name="settings_ui_summary">Välimus, Salvestamise Paigutus, Teema</string>
|
||||
<string name="settings_gps_summary">Ajavahemik, vahemaa intervall, maksimaalne kaugus, täpsus</string>
|
||||
<string name="settings_sensors_title">Bluetooth Andurid</string>
|
||||
<string name="settings_announcements_title">Häälteated</string>
|
||||
<string name="settings_announcements_summary">Aeg, Kaugus, Hääle Kiirus</string>
|
||||
<string name="settings_units_category">Üksus</string>
|
||||
<string name="settings_import_export_summary">Import, Eksport, Automaatne Eksport</string>
|
||||
<string name="settings_import_export_title">Import/Eksport</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_open_tracks_summary">Kõik OpenTracks Projekti Kohta</string>
|
||||
<string name="settings_open_tracks_title">Avatud Rajad</string>
|
||||
<string name="settings_reset_summary">Lähtestage Seaded vaikeväärtustele</string>
|
||||
<string name="settings_recording_customize_layout_title">Kohandage oma salvestuspaigutust</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Andmeväljad rea kohta</string>
|
||||
<string name="settings_export_section">Eksport</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Ajavahemik</string>
|
||||
<string name="settings_layout_reset">Kohandatud Paigutuse Lähtestamine</string>
|
||||
<string name="settings_layout_reset_confirm_title">Lähtestage oma paigutused\?</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (soovitatav)</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="stats_sensors_power">Võim</string>
|
||||
<string name="stats_sensors_cadence">Rütm</string>
|
||||
<string name="stats_sensors_heart_rate">Südamerütm</string>
|
||||
<string name="field_set_secondary">Teisene</string>
|
||||
<string name="title_activity_settings_custom_layout">Salvestamise Paigutuse Kohandamine</string>
|
||||
<string name="settings_sensors_summary">Rattaandurid, Ratta Ümbermõõt, Jooksuandurid</string>
|
||||
<string name="settings_activity_defaults_category">Tegevuse Vaikeväärtused</string>
|
||||
<string name="field_remove_from_layout">Eemaldama</string>
|
||||
<string name="stats_coordinates">Laiuskraad/Pikkuskraad</string>
|
||||
<string name="settings_announcements">Teade</string>
|
||||
<string name="settings_layout_reset_confirm_message">Kõik paigutuse seaded tagastatakse vaikeväärtustele. See ei kustuta seadmes olevaid lugusid.</string>
|
||||
<string name="settings_running_sensor">Töötama</string>
|
||||
<string name="settings_generic_sensor">Geneeriline</string>
|
||||
<string name="custom_layout_list_layout_removed">Paigutus eemaldati</string>
|
||||
<string name="menu_add_custom_layout">Uus kujundus</string>
|
||||
<string name="generic_undo">Tagasi</string>
|
||||
<string name="menu_select_layout">Valige paigutus</string>
|
||||
<string name="stats_custom_layout_default_layout">Vaikimisi paigutus</string>
|
||||
<string name="custom_layout_select_layout">Valige paigutus</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Paigutuse nimi on juba olemas</string>
|
||||
<string name="custom_layout_list_edit_name">Sisestage paigutuse nimi, et lisada</string>
|
||||
<string name="custom_layout_list_title">Plaanid</string>
|
||||
</resources>
|
||||
@@ -457,7 +457,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="open_track_as_GPX">Ouvert en tant que GPX</string>
|
||||
<string name="open_track_as_trackfileformat">Ouvrir comme %1$s</string>
|
||||
<string name="activity_type_escooter">éscooter</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circonférence de roue (millimètre)</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circonférence de la roue (mm)</string>
|
||||
<string name="export_error_post_workout">L\'exportation du post-travail a échoué, veuillez vérifier le répertoire d\'exportation.</string>
|
||||
<string name="export_all_do_it_for_all">le faire pour tous les conflits</string>
|
||||
<string name="export_track_errors">Traces non exportées</string>
|
||||
@@ -506,9 +506,9 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="delete_service">Service de suppression</string>
|
||||
<string name="menu_voice_rate">Vitesse vocale</string>
|
||||
<string name="track_delete_not_recording">Cette piste ne peut pas être supprimée car elle est en cours d\'enregistrement.</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Cyclisme : distance et vitesse</string>
|
||||
<string name="settings_sensor_cycling_cadence">Vélo : cadence</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Course à pied : distance, vitesse et cadence</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Distance et vitesse</string>
|
||||
<string name="settings_sensor_cycling_cadence">Cadre</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Distance, vitesse et cadence</string>
|
||||
<string name="settings_layout_reset">Réinitialiser la disposition personnalisée</string>
|
||||
<string name="settings_layout_reset_confirm_message">Tous les paramètres de disposition reviendront à leur valeur par défaut. Ceci ne supprimera aucun parcours de l\'appareil.</string>
|
||||
<string name="stats_coordinates">Latitude/Longitude</string>
|
||||
@@ -517,7 +517,7 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="generic_choose_an_option">Choisissez une option</string>
|
||||
<string name="stats_sensors_heart_rate">Fréquence cardiaque</string>
|
||||
<string name="stats_sensors_cadence">Cadence</string>
|
||||
<string name="stats_sensors_cadence">Cadences</string>
|
||||
<string name="stats_sensors_power">Énergie</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Vous devez accorder manuellement l\'accès aux appareils à proximité à OpenTracks dans les paramètres système.</string>
|
||||
<string name="settings_recording_customize_layout_title">Personnalisez votre disposition d\'enregistrement</string>
|
||||
@@ -526,11 +526,39 @@ Si le dispositif GPS signale des données inexactes (par exemple, la localisatio
|
||||
<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_all_reset">Réinitialiser tous les paramètres</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="title_activity_settings_custom_layout">Personnaliser la disposition d\'enregistrement</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="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>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (recommandé)</string>
|
||||
<string name="settings_recording_idle_speed_title">Seuil de vitesse au ralenti</string>
|
||||
<string name="stats_clock">Horloge</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="settings_generic_sensor">Générique</string>
|
||||
<string name="settings_cycling_sensor">Cyclisme</string>
|
||||
<string name="settings_running_sensor">Courant</string>
|
||||
<string name="settings_import_section">Importer</string>
|
||||
<string name="settings_export_section">Exporter</string>
|
||||
<string name="settings_reset_summary">Réinitialiser les paramètres aux valeurs par défaut</string>
|
||||
<string name="settings_activity_defaults_category">Défauts de l\'activité</string>
|
||||
<string name="settings_sensors_summary">Capteurs de cyclisme, Circonférence de roue, Capteurs de course à pied</string>
|
||||
<string name="settings_ui_summary">Apparence, Mise en page de l\'enregistrement, Thème</string>
|
||||
<string name="settings_units_category">Unités</string>
|
||||
<string name="settings_import_export_summary">Importez, Exportez, Auto Export</string>
|
||||
<string name="settings_import_export_title">Importez/Exportez</string>
|
||||
<string name="settings_announcements_summary">Temps, distance, vitesse de la voix</string>
|
||||
<string name="settings_announcements_title">Annonces vocales</string>
|
||||
<string name="settings_sensors_title">Intervalle de temps, Intervalle de distance, Distance maximale, Précision</string>
|
||||
<string name="settings_gps_summary">Intervalle de temps, Intervalle de distance, Distance maximale, Précision</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_ui_title">Interface utilisateur</string>
|
||||
<string name="settings_defaults_summary">Unités préférées, Activité par défaut, Suivi du nom par défaut</string>
|
||||
<string name="settings_defaults_title">Unités et activité par défaut</string>
|
||||
<string name="settings_open_tracks_summary">A propos du projet OpenTracks</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
</resources>
|
||||
@@ -494,13 +494,13 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
|
||||
<string name="export_progress_summary_overwrite_msg">Rutas sobrescritas</string>
|
||||
<string name="export_progress_summary_skip_msg">Rutas omitidas</string>
|
||||
<string name="export_progress_summary_errors_msg">Erros</string>
|
||||
<string name="generic_open_track">Open Track</string>
|
||||
<string name="generic_open_track">Aberto O Control</string>
|
||||
<string name="import_prevent_reimport">Estableceches previr a reimportación da ruta</string>
|
||||
<string name="import_progress_summary_ok_msg">Ficheiros importados</string>
|
||||
<string name="import_progress_summary_exists_msg">Xa existe</string>
|
||||
<string name="import_progress_summary_errors_msg">Erros</string>
|
||||
<string name="sensor_state_cadence_avg">Cadencia media</string>
|
||||
<string name="sensor_state_cadence_max">Cadencia máx.</string>
|
||||
<string name="sensor_state_cadence_max">Max Cadencia</string>
|
||||
<string name="sensor_state_heart_rate_avg">Pulso medio</string>
|
||||
<string name="sensor_state_heart_rate_max">Pulso máximo</string>
|
||||
<string name="sensor_state_power_avg">Potencia media</string>
|
||||
@@ -549,4 +549,31 @@ Se te detés ou estás en interiores, non se gardarán datos do sensor (mais ser
|
||||
<string name="value_float_mile_hour">%1$.1f km/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f km/h (recomendado)</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (recomendado)</string>
|
||||
<string name="stats_clock">Reloxo</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_defaults_summary">O Preferido Unidades, Por Defecto Actividade, Controlar O Nome Por Defecto</string>
|
||||
<string name="settings_announcements_summary">A Distancia, Tempo, Voz Velocidade</string>
|
||||
<string name="settings_open_tracks_summary">Todo Sobre OpenTracks Proxecto</string>
|
||||
<string name="settings_defaults_title">Por defecto Unidades e Actividade</string>
|
||||
<string name="settings_ui_title">Interface De Usuario</string>
|
||||
<string name="settings_ui_summary">Aparencia, Gravación, Deseño, Tema</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_gps_summary">Intervalo de tempo, Distancia intervalo, Max Distancia, Precisión</string>
|
||||
<string name="settings_sensors_title">Bluetooth Sensores</string>
|
||||
<string name="settings_sensors_summary">Ciclismo Sensores, Roda Circunferencia, Execución De Sensores</string>
|
||||
<string name="settings_announcements_title">Voz Anuncios</string>
|
||||
<string name="settings_import_export_title">Importación/Exportación</string>
|
||||
<string name="settings_import_export_summary">Importación, Exportación, Auto Exportación</string>
|
||||
<string name="settings_units_category">Unidades</string>
|
||||
<string name="settings_activity_defaults_category">Actividade de Inadimplência</string>
|
||||
<string name="settings_reset_summary">Axustar a Configuración de Valores por Defecto</string>
|
||||
<string name="menu_select_layout">Elixe disposición</string>
|
||||
<string name="custom_layout_list_title">Disposicións</string>
|
||||
<string name="custom_layout_list_edit_name">Escribe o nome da nova disposición</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Xa existe unha con ese nome</string>
|
||||
<string name="custom_layout_list_layout_removed">Disposición eliminada</string>
|
||||
<string name="generic_undo">Desfacer</string>
|
||||
<string name="menu_add_custom_layout">Nova disposición</string>
|
||||
<string name="custom_layout_select_layout">Elixe disposición</string>
|
||||
<string name="stats_custom_layout_default_layout">Disposición por defecto</string>
|
||||
</resources>
|
||||
@@ -23,15 +23,13 @@ limitations under the License.
|
||||
<string name="about_libraries">Librerie</string>
|
||||
<!-- Help -->
|
||||
<string name="help_feature_title">OpenTracks ha le seguenti caratteristiche:</string>
|
||||
<string name="help_feature_content">
|
||||
* crea tracciamenti GPS\n
|
||||
* utilizza un sensore di frequenza cardiaca (solo Bluetooth LE)\n
|
||||
* comunicazioni vocali (per tempo o distanza)\n
|
||||
* crea marcatori (incl. immagini)\n
|
||||
* funziona completamente offline\n
|
||||
* mostra i tracciamenti su una mappa (vedi sotto)
|
||||
</string>
|
||||
<string name="help_track_map_title">Come mostrare un tracciamento su una mappa?</string>
|
||||
<string name="help_feature_content">* crea tracce GPS
|
||||
\n * utilizza un sensore di frequenza cardiaca (solo Bluetooth LE)
|
||||
\n * comunicazioni vocali (per tempo o distanza)
|
||||
\n * crea marcatori (incl. immagini)
|
||||
\n * funziona completamente offline
|
||||
\n * mostra i tracce su una mappa (vedi sotto)</string>
|
||||
<string name="help_track_map_title">Come visualizzare una traccia su una mappa\?</string>
|
||||
<string name="help_track_map_content">
|
||||
Utilizzare la piccola icona della mappa.\n
|
||||
a)
|
||||
@@ -51,7 +49,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="help_data_google_play_services_title">OpenTracks richiede/utilizza Google Play Services?</string>
|
||||
<string name="help_recording_title">Come fa OpenTracks a registrare i dati?</string>
|
||||
<string name="help_data_storage_title">In che modo OpenTracks memorizza i dati dei sensori?</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks non registra i luoghi?</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks non registra la posizione\?</string>
|
||||
<string name="help_support_title">Come posso supportare OpenTracks?</string>
|
||||
<string name="open_track_as_KML">Apri come KML</string>
|
||||
<string name="open_track_as_GPX">Apri come GPX</string>
|
||||
@@ -72,7 +70,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="activity_type_driving_bus">guidare autobus</string>
|
||||
<string name="activity_type_driving_car">guidare auto</string>
|
||||
<string name="activity_type_ferry">traghetto</string>
|
||||
<string name="activity_type_frisbee">di frisbee</string>
|
||||
<string name="activity_type_frisbee">frisbee</string>
|
||||
<string name="activity_type_gliding">aliante</string>
|
||||
<string name="activity_type_hang_gliding">deltaplano</string>
|
||||
<string name="activity_type_helicopter">elicottero</string>
|
||||
@@ -136,7 +134,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="description_max_altitude">Altitudine max: %1$d m (%2$d ft)</string>
|
||||
<string name="description_max_speed">Velocità massima: %1$.2f km/h (%2$.1f mi/h)</string>
|
||||
<string name="description_min_altitude">Altitudine min: %1$d m (%2$d ft)</string>
|
||||
<string name="description_moving_time">Tempo di spostamento: %1$s</string>
|
||||
<string name="description_moving_time">Tempo in movimento: %1$s</string>
|
||||
<string name="description_pace_imperial">Ritmo (min/mi)</string>
|
||||
<string name="description_pace_metric">Ritmo (min/km)</string>
|
||||
<string name="description_recorded_time">Registrato: %1$s</string>
|
||||
@@ -150,7 +148,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="description_total_distance">Distanza totale: %1$.2f km (%2$.1f mi)</string>
|
||||
<string name="description_total_time">Tempo totale: %1$s</string>
|
||||
<!-- Export -->
|
||||
<string name="export_progress_message">Esportazioni su %1$s…</string>
|
||||
<string name="export_progress_message">Esportando su %1$s…</string>
|
||||
<!-- External Storage -->
|
||||
<!-- Files -->
|
||||
<plurals name="files">
|
||||
@@ -159,7 +157,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
</plurals>
|
||||
<!-- Generic -->
|
||||
<string name="generic_add">Aggiungi</string>
|
||||
<string name="generic_delete_selected_confirm_title">Eliminare selezione?</string>
|
||||
<string name="generic_delete_selected_confirm_title">Eliminare selezionati\?</string>
|
||||
<string name="generic_description">Descrizione</string>
|
||||
<string name="generic_description_line">Descrizione: %1$s</string>
|
||||
<string name="generic_name">Nome</string>
|
||||
@@ -184,7 +182,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<!-- Import -->
|
||||
<string name="import_progress_message">Importazione da %1$s in corso…</string>
|
||||
<!-- Marker -->
|
||||
<string name="marker_add_error">Impossibile inserire un indicatore. Nessun segnale GPS.</string>
|
||||
<string name="marker_add_error">Impossibile inserire un indicatore. Nessun segnale GPS. Riprova ancora.</string>
|
||||
<string name="marker_add_success">È stato inserito un indicatore</string>
|
||||
<string name="marker_add_canceled">Operazione annullata, nessun indicatore aggiunto</string>
|
||||
<string name="marker_delete_one_confirm_message">L\'indicatore verrà eliminato definitivamente dal dispositivo</string>
|
||||
@@ -203,7 +201,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="menu_delete">Elimina</string>
|
||||
<string name="menu_edit">Modifica</string>
|
||||
<string name="menu_export_all">Esporta tutto</string>
|
||||
<string name="menu_help">Guida</string>
|
||||
<string name="menu_help">Aiuto</string>
|
||||
<string name="menu_import_all">Importa tutto</string>
|
||||
<string name="menu_insert_marker">Inserisci indicatore</string>
|
||||
<string name="menu_insert_photo">Inserisci foto</string>
|
||||
@@ -248,21 +246,21 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="settings_recording_track_name_title">Nome percorso predefinito</string>
|
||||
<!-- Settings Reset -->
|
||||
<string name="settings_reset">Reimposta</string>
|
||||
<string name="settings_reset_confirm_message">Tutte le impostazioni verranno ripristinate sui valori predefiniti.</string>
|
||||
<string name="settings_reset_confirm_message">Tutte le impostazioni verranno ripristinate sui valori predefiniti. Questo non eliminerà alcuna traccia dal dispositivo.</string>
|
||||
<string name="settings_reset_confirm_title">Reimpostare ora?</string>
|
||||
<string name="settings_reset_done">Tutte le impostazioni sono ripristinate sui valori predefiniti</string>
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_rate_title">Velocità preferita</string>
|
||||
<string name="settings_stats_rate_title">Unità di misura preferita</string>
|
||||
<string name="settings_stats_units_imperial">Imperiale (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrico (km, m)</string>
|
||||
<string name="settings_stats_units_title">Unità di misura preferite</string>
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">Penso che potrebbe essere interessato a questa pista.
|
||||
<string name="share_track_share_file_body">Penso che potresti essere interessato a questa traccia.
|
||||
\n
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Desidero condividere un percorso con te</string>
|
||||
<string name="share_track_subject">Desidero condividere una traccia con te</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Ritmo medio in movimento</string>
|
||||
<string name="stats_average_moving_speed">Velocità media in movimento</string>
|
||||
@@ -297,7 +295,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<!-- Track Widget -->
|
||||
<!-- Tracks -->
|
||||
<!-- Unit -->
|
||||
<string name="unit_feet">pd</string>
|
||||
<string name="unit_feet">ft</string>
|
||||
<string name="unit_kilometer">km</string>
|
||||
<string name="unit_kilometer_per_hour">km/h</string>
|
||||
<string name="unit_meter">m</string>
|
||||
@@ -306,14 +304,14 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="unit_minute_per_kilometer">min/km</string>
|
||||
<string name="unit_minute_per_mile">min/mi</string>
|
||||
<!-- Value -->
|
||||
<string name="value_integer_feet">%1$d pd</string>
|
||||
<string name="value_integer_feet_excellent_gps">%1$d pd (GPS forte)</string>
|
||||
<string name="value_integer_feet_recommended">%1$d pd (consigliato)</string>
|
||||
<string name="value_integer_feet">%1$d ft</string>
|
||||
<string name="value_integer_feet_excellent_gps">%1$d ft (segnale GPS forte)</string>
|
||||
<string name="value_integer_feet_recommended">%1$d ft (consigliato)</string>
|
||||
<string name="value_integer_kilometer">%1$d km</string>
|
||||
<string name="value_integer_kilometer_hour">%1$d km/h</string>
|
||||
<string name="value_integer_meter">%1$d m</string>
|
||||
<string name="value_integer_meter_excellent_gps">%1$d m (GPS forte)</string>
|
||||
<string name="value_integer_meter_poor_gps">%1$d m (GPS debole)</string>
|
||||
<string name="value_integer_meter_excellent_gps">%1$d m (segnale GPS forte)</string>
|
||||
<string name="value_integer_meter_poor_gps">%1$d m (segnale GPS debole)</string>
|
||||
<string name="value_integer_meter_recommended">%1$d m (consigliato)</string>
|
||||
<string name="value_integer_mile">%1$d mi</string>
|
||||
<string name="value_integer_mile_hour">%1$d mi/h</string>
|
||||
@@ -401,12 +399,12 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="waypoint_type_theater">teatro</string>
|
||||
<string name="waypoint_type_toilet">toilette</string>
|
||||
<string name="waypoint_type_tourist_information">informazioni turistiche</string>
|
||||
<string name="waypoint_type_tree">.</string>
|
||||
<string name="waypoint_type_tree">albero</string>
|
||||
<string name="waypoint_type_tunnel">galleria</string>
|
||||
<string name="waypoint_type_waterfall">cascata</string>
|
||||
<string name="waypoint_type_water_fountain">fontana ad acqua</string>
|
||||
<string name="waypoint_type_waypoint">tappa</string>
|
||||
<string name="settings_night_mode_title">Tema dell\' interfaccia grafica</string>
|
||||
<string name="settings_night_mode_title">Tema dell\'interfaccia utente</string>
|
||||
<string name="settings_error_initial_values_restored">Mi dispiace, ma si è verificato un errore. Tutte le impostazioni sono state riportate ai valori predefiniti.</string>
|
||||
<string name="stats_split_interval">Intervallo di divisione</string>
|
||||
<string name="aggregated_stats_empty_message">Registra il tuo primo percorso per vedere le statistiche aggregate</string>
|
||||
@@ -440,13 +438,13 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<item quantity="other">Completato con %1$d errori</item>
|
||||
</plurals>
|
||||
<string name="generic_click_twice_cancel">Premi il tasto indietro di nuovo per annullare</string>
|
||||
<string name="gps_fixed_and_ready">GPS pronto e corretto</string>
|
||||
<string name="gps_fixed_and_ready">GPS agganciato e pronto</string>
|
||||
<string name="menu_insert_gallery_img">Inserisci un immagine dalla galleria</string>
|
||||
<string name="permission_gps_failed">OpenTracks richiede il permesso di utilizzare il GPS.</string>
|
||||
<string name="settings_default_trackfileformat">Formato file nell\' esportazione/condivisione</string>
|
||||
<string name="settings_default_trackfileformat">Formato file di esportazione/condivisione</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Durante la registrazione del percorso, mostra le statistiche senza sbloccare il dispositivo.</string>
|
||||
<string name="description_default_speed_or_pace">Per tipo di attività</string>
|
||||
<string name="export_all_do_it_for_all">Esegui ciò per tutti i conflitti</string>
|
||||
<string name="export_all_do_it_for_all">applica lo stesso per tutti i conflitti</string>
|
||||
<string name="export_track_errors">Percorsi non esportati</string>
|
||||
<string name="generic_overwrite">Sovrascrivere</string>
|
||||
<string name="generic_skip">Ignora</string>
|
||||
@@ -457,7 +455,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="import_unsupported_format">Formato di file non supportato</string>
|
||||
<string name="import_parser_error">Errore nel riconoscimento degli argomenti: %1$s</string>
|
||||
<string name="menu_resume_track">Riprendi il percorso</string>
|
||||
<string name="track_recording_notification_accuracy">Incertezza GPS: %1$s</string>
|
||||
<string name="track_recording_notification_accuracy">Precisione GPS: %1$s</string>
|
||||
<string name="import_unable_to_import_file">Non riesco ad importare il file: %1$s</string>
|
||||
<string name="import_error_info">%1$s: %2$s</string>
|
||||
<string name="import_error_list_dialog_title">File non importati</string>
|
||||
@@ -479,29 +477,29 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="track_detail_intervals_tab">Intervalli</string>
|
||||
<string name="interval_list_empty_message">Non c\'è ancora nessun intervallo</string>
|
||||
<string name="voice_pace_lap">Tempo sul giro di %1$s</string>
|
||||
<string name="share_image_body">Penso che potresti essere interessat@ in questa immagine.</string>
|
||||
<string name="share_image_body">Penso che potresti essere interessat* a questa immagine.</string>
|
||||
<string name="hold_to_stop">Tieni premuto per fermare</string>
|
||||
<string name="hold_to_pause">Tieni premuto per mettere in pausa</string>
|
||||
<string name="instant_export_enabled_summary">È stata completata l\'esportazione del percorso finito nella memoria interna del tuo dispositivo</string>
|
||||
<string name="instant_export_enabled_summary">Esporta la traccia nella memoria al termine della registrazione</string>
|
||||
<string name="instant_export_enabled_title">Esportazione istantanea post-allenamento</string>
|
||||
<string name="generic_open_track">Traccia aperta</string>
|
||||
<string name="import_prevent_reimport">È stato impostato impedisci reimportazione traccia</string>
|
||||
<string name="export_error_post_workout">Esportazione post-allenamento non riuscita, controllare la directory di esportazione.</string>
|
||||
<string name="import_prevent_reimport">Hai impostato di fare in modo da non reimportare la traccia</string>
|
||||
<string name="export_error_post_workout">Esportazione post-allenamento non riuscita, controllare la cartella di esportazione.</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Impedire la reimportazione delle tracce</string>
|
||||
<string name="export_progress_summary_errors_msg">Errore</string>
|
||||
<string name="export_progress_summary_errors_msg">Errori</string>
|
||||
<string name="export_progress_summary_skip_msg">Tracce saltate</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Tracce sovrascritte</string>
|
||||
<string name="export_progress_summary_new_msg">Nuove tracce esportate</string>
|
||||
<string name="import_no_kml_file_found">Nessun dottore.file kml trovato in KMZ</string>
|
||||
<string name="import_no_kml_file_found">Nessun file doc.kml trovato in KMZ</string>
|
||||
<string name="import_progress_summary_ok_msg">File importati</string>
|
||||
<string name="import_progress_summary_exists_msg">Esiste già</string>
|
||||
<string name="import_progress_summary_errors_msg">Errore</string>
|
||||
<string name="import_progress_summary_errors_msg">Errori</string>
|
||||
<string name="sensor_state_power_avg">Potenza media</string>
|
||||
<string name="sensor_state_heart_rate_max">Frequenza cardiaca max</string>
|
||||
<string name="stats_loss">Discesa</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circonferenza della ruota (mm)</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circonferenza della Ruota (mm)</string>
|
||||
<string name="description_altitude_loss">Perdita di quota: %1$d m (%2$d ft)</string>
|
||||
<string name="sensor_state_heart_rate_avg">Tasso cardiaco medio</string>
|
||||
<string name="sensor_state_heart_rate_avg">Battito cardiaco medio</string>
|
||||
<string name="sensor_state_cadence_max">Cadenza max</string>
|
||||
<string name="sensor_state_cadence_avg">Cadenza media</string>
|
||||
<string name="track_distance_notification">Distanza: %1$s</string>
|
||||
@@ -511,8 +509,69 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
||||
<string name="delete_service">Servizio di eliminazione</string>
|
||||
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="menu_voice_rate">Velocità della voce</string>
|
||||
<string name="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente registrata.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Ciclismo: cadenza</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distanza e velocità</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Come mostrare una traccia su una mappa</string>
|
||||
<string name="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente in registrazione.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Cadenza</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Distanza e Velocità</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Distanza, Velocità e Cadenza</string>
|
||||
<string name="stats_clock">Orologio</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Intervallo di tempo</string>
|
||||
<string name="settings_recording_idle_speed_title">Soglia velocità di movimento</string>
|
||||
<string name="settings_announcements_distance_frequency">Intervallo di distanza</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (consigliato)</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (consigliato)</string>
|
||||
<string name="settings_cycling_sensor">Ciclismo</string>
|
||||
<string name="settings_running_sensor">Corsa</string>
|
||||
<string name="settings_import_section">Importa</string>
|
||||
<string name="settings_export_section">Esporta</string>
|
||||
<string name="stats_coordinates">Latitudine/Longitudine</string>
|
||||
<string name="field_remove_from_layout">Rimuovi</string>
|
||||
<string name="field_set_primary">Primario</string>
|
||||
<string name="field_set_secondary">Secondario</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Devi concedere manualmente il permesso ad OpenTracks di accedere ai Dispositivi nelle Vicinanze nelle impostazioni di sistema.</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks richiede il permesso di utilizzare il Bluetooth.</string>
|
||||
<string name="stats_sensors_cadence">Cadenza</string>
|
||||
<string name="generic_choose_an_option">Scegli un\'opzione</string>
|
||||
<string name="settings_recording_customize_layout_title">Personalizza il layout di registrazione</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Campi dati per riga</string>
|
||||
<string name="settings_defaults_title">Unità di misura e Attività Predefinite</string>
|
||||
<string name="settings_defaults_summary">Unità di misura preferite, Attività Predefinite, Nome Traccia Predefinito</string>
|
||||
<string name="settings_sensors_summary">Sensori di Cicliaml, Circonferenza Ruota, Sensori Corsa</string>
|
||||
<string name="settings_layout_reset_done">Tutte le impostazioni del layout sono state reimpostate ai valori predefiniti</string>
|
||||
<string name="settings_generic_sensor">Generico</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="title_activity_settings_custom_layout">Personalizza Layout di Registrazione</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">Tutto a proposito del progetto OpenTracks</string>
|
||||
<string name="settings_ui_title">Interfaccia Utente</string>
|
||||
<string name="settings_ui_summary">Aspetto, Layout durante la registrazione, Tema</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_gps_summary">Intervallo di tempo, Intervallo di distanza, Massima Distanza, Precisione</string>
|
||||
<string name="settings_sensors_title">Sensori Bluetooth</string>
|
||||
<string name="settings_announcements_title">Annunci Vocali</string>
|
||||
<string name="settings_announcements_summary">Tempo, Distanza, Velocità della Voce</string>
|
||||
<string name="settings_import_export_title">Importa/Esporta</string>
|
||||
<string name="settings_import_export_summary">Importa, Esporta, Auto Esporta</string>
|
||||
<string name="settings_units_category">Unità</string>
|
||||
<string name="settings_activity_defaults_category">Predefiniti dell\'Attività</string>
|
||||
<string name="settings_reset_summary">Reimposta Impostazioni ai Valori Predefiniti</string>
|
||||
<string name="settings_announcements">Annunci</string>
|
||||
<string name="settings_layout_reset">Reimposta Layout Personalizzato</string>
|
||||
<string name="settings_layout_reset_confirm_message">Tutte le impostazioni di layout saranno reimpostate ai valori predefiniti. Questo non eliminerà alcuna traccia dal dispositivo.</string>
|
||||
<string name="settings_layout_reset_confirm_title">Rempostare i tuoi layout\?</string>
|
||||
<string name="settings_all_reset">Reimposta</string>
|
||||
<string name="stats_sensors_heart_rate">Frequenza Cardiaca</string>
|
||||
<string name="stats_sensors_power">Potenza</string>
|
||||
<string name="custom_layout_list_title">Disegni</string>
|
||||
<string name="custom_layout_list_edit_name">Digitare il nome del layout da aggiungere</string>
|
||||
<string name="generic_undo">Annulla</string>
|
||||
<string name="menu_select_layout">Seleziona il layout</string>
|
||||
<string name="menu_add_custom_layout">Nuovo layout</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Il nome del layout già esiste</string>
|
||||
<string name="custom_layout_list_layout_removed">Il layout è stato rimosso</string>
|
||||
<string name="custom_layout_select_layout">Seleziona il layout</string>
|
||||
<string name="stats_custom_layout_default_layout">Layout predefinito</string>
|
||||
</resources>
|
||||
@@ -101,7 +101,7 @@ limitations under the License.
|
||||
<string name="description_speed_ms">Hastighet (m/s)</string>
|
||||
<string name="description_sensor_cadence">Kadens (o/min)</string>
|
||||
<string name="description_sensor_heart_rate">Puls (slag/min)</string>
|
||||
<string name="description_sensor_power">Effekt (W)</string>
|
||||
<string name="description_sensor_power">Kraft (W)</string>
|
||||
<string name="description_speed_imperial">Hastighet (mi/hr)</string>
|
||||
<string name="description_speed_metric">Hastighet (km/t)</string>
|
||||
<string name="description_time">Tid</string>
|
||||
@@ -182,7 +182,7 @@ limitations under the License.
|
||||
<string name="sensor_state_cadence_value">%1$d o/min</string>
|
||||
<string name="sensor_state_heart_rate">Puls</string>
|
||||
<string name="sensor_state_heart_rate_value">%1$d slag/min</string>
|
||||
<string name="sensor_state_power">Effekt</string>
|
||||
<string name="sensor_state_power">Kraft</string>
|
||||
<string name="sensor_state_power_value">%1$d W</string>
|
||||
<string name="settings_not_while_recording">Ikke tilgjengelig mens du tar opp et spor</string>
|
||||
<string name="settings_show">Vis</string>
|
||||
@@ -490,7 +490,7 @@ limitations under the License.
|
||||
<string name="sensor_state_cadence_max">Maks kadens</string>
|
||||
<string name="sensor_state_heart_rate_avg">Gjennomsnittlig puls</string>
|
||||
<string name="sensor_state_heart_rate_max">Maks puls</string>
|
||||
<string name="sensor_state_power_avg">Gjennomsnittlig effekt</string>
|
||||
<string name="sensor_state_power_avg">Gjennomsnittlig kraft</string>
|
||||
<string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
|
||||
<string name="track_distance_notification">Distanse: %1$s</string>
|
||||
<string name="track_speed_notification">Hastighet: %1$s</string>
|
||||
@@ -515,7 +515,7 @@ limitations under the License.
|
||||
<string name="settings_layout_reset_done">Alle layoutinnstillinger er tilbakestilt til standardverdiene</string>
|
||||
<string name="settings_all_reset">Tilbakestill</string>
|
||||
<string name="stats_sensors_cadence">Kadens</string>
|
||||
<string name="stats_sensors_power">Effekt</string>
|
||||
<string name="stats_sensors_power">Kraft</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="title_activity_settings_custom_layout">Tilpass opptakslayouten</string>
|
||||
@@ -537,4 +537,31 @@ limitations under the License.
|
||||
<string name="settings_recording_idle_speed_title">Terskel for tomgangshastighet</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (anbefales)</string>
|
||||
<string name="stats_clock">Klokke</string>
|
||||
<string name="settings_open_tracks_title">OpenTracks</string>
|
||||
<string name="settings_defaults_summary">Foretrukne enheter, Standardaktivitet, Standard-spornavn</string>
|
||||
<string name="settings_sensors_summary">Sykkelsensorer, Hjulomkrets, Løpesensorer</string>
|
||||
<string name="settings_open_tracks_summary">Alt om prosjektet OpenTracks</string>
|
||||
<string name="settings_defaults_title">Standardenheter og aktivitet</string>
|
||||
<string name="settings_ui_title">Brukerflate</string>
|
||||
<string name="settings_ui_summary">Utseende, Opptakslayout, Tema</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_gps_summary">Tidsintervall, Distanseintervall, Maks distanse, Nøyaktighet</string>
|
||||
<string name="settings_sensors_title">Bluetooth-sensorer</string>
|
||||
<string name="settings_announcements_title">Talemeldinger</string>
|
||||
<string name="settings_announcements_summary">Tid, Distanse, Talehastighet</string>
|
||||
<string name="settings_import_export_title">Import/Eksport</string>
|
||||
<string name="settings_import_export_summary">Import, Eksport, Auto-eksport</string>
|
||||
<string name="settings_units_category">Enheter</string>
|
||||
<string name="settings_activity_defaults_category">Aktiviteters standardvalg</string>
|
||||
<string name="settings_reset_summary">Tilbakestill innstillinger til standardverdier</string>
|
||||
<string name="generic_undo">Angre</string>
|
||||
<string name="menu_select_layout">Velg layout</string>
|
||||
<string name="menu_add_custom_layout">Ny layout</string>
|
||||
<string name="custom_layout_list_title">Layouter</string>
|
||||
<string name="custom_layout_list_edit_name">Skriv inn navn på layout for å legge til</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Layoutens navn finnes allerede</string>
|
||||
<string name="custom_layout_list_layout_removed">Layout ble fjernet</string>
|
||||
<string name="custom_layout_select_layout">Velg layout</string>
|
||||
<string name="stats_custom_layout_default_layout">Standard layout</string>
|
||||
</resources>
|
||||
@@ -239,7 +239,7 @@ limitations under the License.
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_sensor_connecting">Podłączony do %1$s</string>
|
||||
<string name="settings_sensor_disconnected">Rozłaczony z %1$s</string>
|
||||
<string name="settings_sensor_wheel_circumference">Obwód koła (mm)</string>
|
||||
<string name="settings_sensor_wheel_circumference">Obwód Koła (mm)</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_rate_title">Wybór tempo/prędkość</string>
|
||||
<string name="settings_stats_units_imperial">Imperialne (mile, stopy)</string>
|
||||
@@ -481,7 +481,7 @@ limitations under the License.
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">Ukończono z jednym błędem</item>
|
||||
<item quantity="few">Ukończone z czyimiś błędami</item>
|
||||
<item quantity="few">Ukończone z %1$d błędami</item>
|
||||
<item quantity="many">Zakończono z błędami %1$d</item>
|
||||
<item quantity="other">Zakończono z błędami %1$d</item>
|
||||
</plurals>
|
||||
@@ -522,7 +522,68 @@ limitations under the License.
|
||||
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||
<string name="menu_voice_rate">Szybkość głosu</string>
|
||||
<string name="track_delete_not_recording">Tej ścieżki nie można usunąć, ponieważ jest ona aktualnie nagrywana.</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Kolarstwo: dystans i prędkość</string>
|
||||
<string name="settings_sensor_cycling_cadence">Jazda na rowerze: kadencja</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Bieganie: dystans, prędkość i kadencja</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Dystans i Prędkość</string>
|
||||
<string name="settings_sensor_cycling_cadence">Tempo</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Dystans, Prędkość i Tempo</string>
|
||||
<string name="settings_ui_title">Interfejs Użytkownika</string>
|
||||
<string name="settings_gps_summary">Przedział czasu, Przedział dystansu, Maksymalny dystans, Dokładność</string>
|
||||
<string name="settings_recording_customize_layout_title">Zmień swój układ ekranu podczas śledzenia</string>
|
||||
<string name="settings_announcements">Wiadomości</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Przedział czasu</string>
|
||||
<string name="stats_sensors_heart_rate">Częstość akcji serca</string>
|
||||
<string name="stats_clock">Zegar</string>
|
||||
<string name="settings_announcements_distance_frequency">Przedział dystansu</string>
|
||||
<string name="settings_recording_idle_speed_title">Próg prędkości bezobciążeń</string>
|
||||
<string name="settings_layout_reset_confirm_title">Zresetować twoje układy\?</string>
|
||||
<string name="settings_cycling_sensor">Kolarstwo</string>
|
||||
<string name="settings_running_sensor">Bieganie</string>
|
||||
<string name="settings_import_section">Import</string>
|
||||
<string name="settings_export_section">Eksport</string>
|
||||
<string name="settings_generic_sensor">Domyślny</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks wymaga uprawnień do urzycia Bluetooth.</string>
|
||||
<string name="generic_undo">Cofnij</string>
|
||||
<string name="generic_choose_an_option">Wybierz opcje</string>
|
||||
<string name="menu_select_layout">Wybierz układ</string>
|
||||
<string name="menu_add_custom_layout">Nowy układ</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Należy manualnie zezwolić OpenTracks na dostęp do Najbliższych Urządzeń w ustawieniach.</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="settings_ui_summary">Wygląd, Układ ekranu śledzenia, Motyw</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_sensors_title">Czujnik Bluetooth</string>
|
||||
<string name="settings_sensors_summary">Czujniki rowerowe, Obwód koła, Czujniki biegowe</string>
|
||||
<string name="settings_announcements_title">Wiadomości głosowe</string>
|
||||
<string name="settings_announcements_summary">Czas, Dystans, Głos prędkości</string>
|
||||
<string name="settings_import_export_title">Import/Eksport</string>
|
||||
<string name="settings_units_category">Jednostki</string>
|
||||
<string name="settings_activity_defaults_category">Domyślne działania</string>
|
||||
<string name="settings_reset_summary">Przywróć domyślne ustawienia</string>
|
||||
<string name="settings_import_export_summary">Import, Eksport, Automatyczny eksport</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Ilość pól na wiersz</string>
|
||||
<string name="custom_layout_select_layout">Wybierz układ</string>
|
||||
<string name="custom_layout_list_title">Układy</string>
|
||||
<string name="custom_layout_list_edit_name">Napisz nazwę układu, aby dodać</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Układ o takiej nazwie już istnieje</string>
|
||||
<string name="custom_layout_list_layout_removed">Układ został usunięty</string>
|
||||
<string name="stats_custom_layout_default_layout">Domyślny układ</string>
|
||||
<string name="settings_layout_reset_confirm_message">Wszystkie ustawienia zostaną zresetowane do wartości domyślnych.</string>
|
||||
<string name="settings_layout_reset_done">Wszystkie ustawienia wróciły do wartości domyślnych</string>
|
||||
<string name="settings_all_reset">Zresetuj</string>
|
||||
<string name="settings_layout_reset">Zresetuj Układ</string>
|
||||
<string name="stats_coordinates">Szerokość/długość geograficzna</string>
|
||||
<string name="stats_sensors_cadence">Tempo</string>
|
||||
<string name="stats_sensors_power">Moc</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (zalecane)</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (zalecane)</string>
|
||||
<string name="title_activity_settings_custom_layout">Zmień układ ekranu podczas śledzenia</string>
|
||||
<string name="field_remove_from_layout">Usuń</string>
|
||||
<string name="field_set_primary">Główny</string>
|
||||
<string name="field_set_secondary">Poboczny</string>
|
||||
<string name="settings_open_tracks_title">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">Wszystko o projekcie OpenTacks</string>
|
||||
<string name="settings_defaults_title">Domyślne Jednostki i Aktywności</string>
|
||||
<string name="settings_defaults_summary">Preferowane Jednostki, Domyślne Aktywności, Nazwy Treningów</string>
|
||||
</resources>
|
||||
@@ -488,4 +488,31 @@
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (Recomendado)</string>
|
||||
<string name="settings_recording_idle_speed_title">Limite de velocidade de marcha lenta</string>
|
||||
<string name="stats_clock">Relógio</string>
|
||||
<string name="settings_announcements_title">Anúncios de voz</string>
|
||||
<string name="settings_sensors_summary">Voice announcements</string>
|
||||
<string name="settings_sensors_title">Sensores Bluetooth</string>
|
||||
<string name="settings_gps_summary">Intervalo de tempo, Intervalo de distância, Distância máxima, Precisão</string>
|
||||
<string name="settings_ui_summary">Aparência, Layout de gravação, Tema</string>
|
||||
<string name="settings_ui_title">Interface do usuário</string>
|
||||
<string name="settings_defaults_title">Unidades e Atividade Default</string>
|
||||
<string name="settings_defaults_summary">Unidades Preferidas, Atividade Padrão, Nome Padrão da Pista</string>
|
||||
<string name="settings_open_tracks_title">Trilhas abertas</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_reset_summary">Redefinir configurações para valores padrão</string>
|
||||
<string name="settings_activity_defaults_category">Defaults da atividade</string>
|
||||
<string name="settings_units_category">Unidades</string>
|
||||
<string name="settings_import_export_summary">Importar, Exportar, Auto Export</string>
|
||||
<string name="settings_import_export_title">Importar/Exportar</string>
|
||||
<string name="settings_announcements_summary">Tempo, distância, velocidade da voz</string>
|
||||
<string name="settings_open_tracks_summary">Tudo sobre o Projeto OpenTracks</string>
|
||||
<string name="stats_custom_layout_default_layout">Layout padrão</string>
|
||||
<string name="custom_layout_list_edit_already_exists">O nome do layout já existe</string>
|
||||
<string name="custom_layout_list_edit_name">Digite o nome do layout a ser adicionado</string>
|
||||
<string name="menu_add_custom_layout">Novo Layout</string>
|
||||
<string name="generic_undo">Desfazer</string>
|
||||
<string name="menu_select_layout">Selecione o layout</string>
|
||||
<string name="custom_layout_list_title">Estrutura</string>
|
||||
<string name="custom_layout_select_layout">Selecionar layout</string>
|
||||
<string name="custom_layout_list_layout_removed">Layout foi removido</string>
|
||||
</resources>
|
||||
@@ -528,4 +528,31 @@ limitations under the License.
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (khuyến khích)</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (khuyến khích)</string>
|
||||
<string name="stats_clock">Cái đồng hồ</string>
|
||||
<string name="settings_open_tracks_title">Mở Bài Hát</string>
|
||||
<string name="settings_reset_summary">Thiết lập lại các thiết Lập để mặc Định giá Trị</string>
|
||||
<string name="settings_activity_defaults_category">Hoạt động của Mặc định</string>
|
||||
<string name="settings_units_category">Đơn vị</string>
|
||||
<string name="settings_gps_summary">Khoảng thời gian, khoảng Cách, Max khoảng Cách, chính Xác</string>
|
||||
<string name="settings_import_export_summary">Nhập Khẩu, Xuất Khẩu, Xuất Khẩu Tự Động</string>
|
||||
<string name="settings_open_tracks_summary">Tất Cả Về Dự Án OpenTracks</string>
|
||||
<string name="settings_defaults_title">Định Vị và các Hoạt động</string>
|
||||
<string name="settings_defaults_summary">Các Đơn Vị Ưa Thích Định Hoạt Động Theo Dõi Tên Mặc Định</string>
|
||||
<string name="settings_ui_title">Dùng Diện</string>
|
||||
<string name="settings_ui_summary">Xuất Hiện, Ghi Lại Bố Trí Chủ Đề</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_sensors_title">Cảm Biến Bluetooth</string>
|
||||
<string name="settings_sensors_summary">Đi Xe Đạp Cảm Biến, Bánh Vòng, Chạy Bộ Cảm Biến</string>
|
||||
<string name="settings_announcements_title">Giọng Nói Thông Báo</string>
|
||||
<string name="settings_announcements_summary">Thời Gian, Khoảng Cách, Tốc Độ Giọng Nói</string>
|
||||
<string name="settings_import_export_title">Xuất Nhập Khẩu</string>
|
||||
<string name="generic_undo">Hoàn tác</string>
|
||||
<string name="menu_select_layout">Chọn bố cục</string>
|
||||
<string name="menu_add_custom_layout">Bố cục mới</string>
|
||||
<string name="custom_layout_list_title">Bố cục</string>
|
||||
<string name="custom_layout_list_edit_name">Nhập tên bố cục để thêm</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Tên bố cục đã tồn tại</string>
|
||||
<string name="custom_layout_list_layout_removed">Bố cục đã bị xoá</string>
|
||||
<string name="custom_layout_select_layout">Chọn bố cục</string>
|
||||
<string name="stats_custom_layout_default_layout">Bố cục mặc định</string>
|
||||
</resources>
|
||||
@@ -30,6 +30,9 @@ limitations under the License.
|
||||
<color name="colorAccent">@android:color/holo_orange_dark</color>
|
||||
<color name="colorAccentTransparent">#55FF8800</color>
|
||||
|
||||
<color name="colorBackgroundDelete">#F44336</color>
|
||||
<color name="colorForegroundDelete">#FEFEFE</color>
|
||||
|
||||
<color name="recording_text">#FFBE161B</color> <!-- red -->
|
||||
<color name="stats_separator">#99999999</color> <!-- light gray -->
|
||||
<color name="text_gradient_center">#B2000000</color> <!-- black 70% opacity -->
|
||||
|
||||
@@ -12,40 +12,22 @@
|
||||
|
||||
<!-- Main settings -->
|
||||
<string name="settings_open_tracks_key" translatable="false">settingsOpenTracks</string>
|
||||
<string name="settings_open_tracks_title" translatable="false">Open Tracks</string>
|
||||
<string name="settings_open_tracks_summary">All About OpenTracks Project</string>
|
||||
|
||||
<string name="settings_defaults_key" translatable="false">settingsDefaults</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_key" translatable="false">settingsUI</string>
|
||||
<string name="settings_ui_title">User Interface</string>
|
||||
<string name="settings_ui_summary">Appearance, Recording Layout, Theme</string>
|
||||
|
||||
<string name="settings_gps_key" translatable="false">settingsGps</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_gps_summary">Time interval, Distance interval, Max Distance, Accuracy</string>
|
||||
|
||||
<string name="settings_sensors_key" translatable="false">settingsSensorsGps</string>
|
||||
<string name="settings_sensors_title">Bluetooth Sensors</string>
|
||||
<string name="settings_sensors_summary">Cycling Sensors, Wheel Circumference, Running Sensors</string>
|
||||
|
||||
<string name="settings_announcements_key" translatable="false">settingsAnnouncements</string>
|
||||
<string name="settings_announcements_title">Voice Announcements</string>
|
||||
<string name="settings_announcements_summary">Time, Distance, Voice Speed</string>
|
||||
|
||||
<string name="settings_import_export_key" translatable="false">settingsImportExport</string>
|
||||
<string name="settings_import_export_title">Import/Export</string>
|
||||
<string name="settings_import_export_summary">Import, Export, Auto Export</string>
|
||||
|
||||
<!-- Setting's categories -->
|
||||
<string name="settings_units_category">Units</string>
|
||||
<string name="settings_activity_defaults_category">Activity\'s Defaults</string>
|
||||
|
||||
<string name="settings_reset_key" translatable="false">settingsReset</string>
|
||||
<string name="settings_reset_summary">Reset Settings to Default Values</string>
|
||||
<string name="settings_layout_reset_key" translatable="false">settingsLayoutReset</string>
|
||||
|
||||
<string name="settings_sensor_bluetooth_heart_rate_key" translatable="false">bluetoothSensor</string>
|
||||
<string name="settings_sensor_bluetooth_cycling_cadence_key" translatable="false">bluetoothCyclingCadenceSensor</string>
|
||||
@@ -64,6 +46,7 @@
|
||||
<string name="settings_import" translatable="false">settingsImport</string>
|
||||
<string name="settings_export" translatable="false">settingsExport</string>
|
||||
|
||||
<!-- Stats layout customization default layout -->
|
||||
<string name="default_activity_key" translatable="false">defaultActivity</string>
|
||||
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
|
||||
<string-array name="activity_types">
|
||||
@@ -328,11 +311,11 @@
|
||||
<string name="stats_custom_layout_key" translatable="false">statsCustomLayoutKey</string>
|
||||
|
||||
<!-- Stats layout customization: number of columns per row -->
|
||||
<string name="stats_custom_layout_columns_key" translatable="false">statsCustomLayoutColumnsKey</string>
|
||||
<integer name="stats_custom_layout_columns_default" translatable="false">2</integer>
|
||||
|
||||
<!-- Stats layout customization: key -->
|
||||
<string name="stats_custom_layout_fields_key" translatable="false">statsCustomLayoutFieldsKey</string>
|
||||
<string name="stats_custom_layouts_key" translatable="false">statsCustomLayoutFieldsKey</string>
|
||||
<string name="stats_custom_layout_selected_layout_key" translatable="false">statsCustomSelectedLayoutKey</string>
|
||||
<!-- Stats keys -->
|
||||
<string name="stats_custom_layout_distance_key" translatable="false">distance</string>
|
||||
<string name="stats_custom_layout_total_time_key" translatable="false">total_time</string>
|
||||
|
||||
@@ -238,6 +238,7 @@ limitations under the License.
|
||||
<string name="generic_completed">Completed</string>
|
||||
<string name="generic_click_twice_cancel">Click back again to cancel</string>
|
||||
<string name="generic_choose_an_option">Choose an option</string>
|
||||
<string name="generic_undo">Undo</string>
|
||||
<!-- Gps -->
|
||||
<string name="gps_starting">Starting GPS</string>
|
||||
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
|
||||
@@ -298,11 +299,13 @@ limitations under the License.
|
||||
<string name="menu_select_all">Select all</string>
|
||||
<string name="menu_settings">Settings</string>
|
||||
<string name="menu_share">Share</string>
|
||||
<string name="menu_select_layout">Select Layout</string>
|
||||
<string name="menu_show_on_map">Show on map</string>
|
||||
<string name="menu_start_gps">Start GPS</string>
|
||||
<string name="menu_stop_gps">Stop GPS</string>
|
||||
<string name="menu_voice_rate">Voice speed</string>
|
||||
<string name="menu_resume_track">Resume track</string>
|
||||
<string name="menu_add_custom_layout">New Layout</string>
|
||||
<!-- OpenTracks -->
|
||||
<string name="recording_service">Recording service</string>
|
||||
<string name="delete_service">Deletion service</string>
|
||||
@@ -328,6 +331,34 @@ limitations under the License.
|
||||
<string name="bluetooth_disabled">Please enable Bluetooth.</string>
|
||||
<string name="settings_not_while_recording">Not available while recording a track</string>
|
||||
<string name="settings_show">Show</string>
|
||||
|
||||
<string name="settings_open_tracks_title">Open Tracks</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_title">GPS</string>
|
||||
<string name="settings_gps_summary">Time interval, Distance interval, Max Distance, Accuracy</string>
|
||||
|
||||
<string name="settings_sensors_title">Bluetooth Sensors</string>
|
||||
<string name="settings_sensors_summary">Cycling Sensors, Wheel Circumference, Running Sensors</string>
|
||||
|
||||
<string name="settings_announcements_title">Voice Announcements</string>
|
||||
<string name="settings_announcements_summary">Time, Distance, Voice Speed</string>
|
||||
|
||||
<string name="settings_import_export_title">Import/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>
|
||||
<string name="settings_layout_reset_key" translatable="false">settingsLayoutReset</string>
|
||||
|
||||
<!-- Settings Advanced -->
|
||||
<string name="settings_advanced">Advanced</string>
|
||||
<string name="settings_default_trackfileformat">Export/sharing file format</string>
|
||||
@@ -367,6 +398,13 @@ limitations under the License.
|
||||
<string name="settings_announcements_distance_frequency">Distance interval</string>
|
||||
|
||||
<string name="settings_reset">Reset</string>
|
||||
<!-- Custom Layout -->
|
||||
<string name="custom_layout_list_title">Layouts</string>
|
||||
<string name="custom_layout_list_edit_name">Type layout\'s name to add</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Layout\'s name already exists</string>
|
||||
<string name="custom_layout_list_layout_removed">Layout was removed</string>
|
||||
<string name="custom_layout_select_layout">Select Layout</string>
|
||||
<string name="stats_custom_layout_default_layout">Default Layout</string>
|
||||
<!-- Custom Layout Reset -->
|
||||
<string name="settings_layout_reset">Reset Custom Layout</string>
|
||||
<string name="settings_layout_reset_confirm_message">All layout settings will be reverted to the default values. This will not delete any tracks on the device.</string>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user