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
|
# These are supported funding model platforms
|
||||||
|
|
||||||
liberapay: OpenTracks
|
liberapay: OpenTracks
|
||||||
|
github: [rgmf, pstorch, dennisguse]
|
||||||
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [ 23, 26 ] #TODO Re-enable 29
|
api-level: [ 23, 30 ]
|
||||||
target: [ default ]
|
target: [ default ]
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v2.4.0
|
uses: actions/checkout@v2.4.0
|
||||||
|
|||||||
+18
-11
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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 getVersionCode = { ->
|
||||||
def date = new Date()
|
try {
|
||||||
def formattedDate = date.format('yyyyMMdd')
|
def stdout = new ByteArrayOutputStream()
|
||||||
return Integer.valueOf(formattedDate)
|
exec {
|
||||||
|
commandLine 'git', 'rev-list', 'HEAD', '--count'
|
||||||
|
standardOutput = stdout
|
||||||
|
}
|
||||||
|
return Integer.valueOf(stdout.toString().trim())
|
||||||
|
} catch (ignored) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -56,8 +63,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "de.dennisguse.opentracks"
|
applicationId "de.dennisguse.opentracks"
|
||||||
versionCode 4519
|
versionCode 4580
|
||||||
versionName "v3.22.0"
|
versionName "v3.23.0"
|
||||||
|
|
||||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||||
|
|
||||||
@@ -105,7 +112,7 @@ android {
|
|||||||
|
|
||||||
if (variant.buildType.name == 'nightly') {
|
if (variant.buildType.name == 'nightly') {
|
||||||
variant.outputs.all {
|
variant.outputs.all {
|
||||||
setVersionCodeOverride(versionCodeFromDate())
|
setVersionCodeOverride(getVersionCode())
|
||||||
setVersionNameOverride(getVersionName())
|
setVersionNameOverride(getVersionName())
|
||||||
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
outputFileName = "${applicationId}_${variant.versionCode}.apk"
|
||||||
}
|
}
|
||||||
@@ -116,7 +123,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
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.preference:preference:1.1.1'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
@@ -127,7 +134,7 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.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 and outdoor activities grants happiness, skill and confidence.
|
||||||
* Sport tracking: track your sport and outdoor activities
|
|
||||||
* Photos and markers: mark interesting locations while tracking
|
Value your health by keeping track on your training.
|
||||||
* Altitude gain and loss: via barometric sensor
|
Records as you go running or walking, and gives you a bike computer with a bigger screen for cycling.
|
||||||
* Altitude shown in EGM2008 (above mean sea level); exported as WGS84
|
Mark interesting locations along your way with pictures.
|
||||||
* Voice announcements
|
Keep recorded statistics in great detail for analysis.
|
||||||
* Supports Bluetooth LE sensors: heart rate, speed and distance (cycling), cadence (cycling), and power meter (cycling)
|
Share only the data you want others to have.
|
||||||
* Export data: export tracks either as KMZ (incl. photos), KML, or GPX
|
|
||||||
* No Internet access: Internet is not used
|
* 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:
|
Cechy:
|
||||||
* Śledzenie sportu: śledź swoją aktywność sportową i outdoorową.
|
* Śledzenie sportu: śledź swoją aktywność sportową i outdoorową.
|
||||||
* Zdjęcia i markery: zaznacz interesujące lokalizacje podczas śledzenia.
|
* 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
|
* Wysokość pokazana w EGM2008 (powyżej średniego poziomu morza); wyeksportowana jako WGS84
|
||||||
* Komunikaty głosowe
|
* Komunikaty głosowe
|
||||||
* Obsługa czujników Bluetooth LE: tętno, prędkość i dystans (kolarstwo), kadencja (kolarstwo) i miernik mocy (kolarstwo)
|
* 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
|
* 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")));
|
trackPointCreator.setClock(Clock.fixed(Instant.parse("2020-02-02T02:02:02Z"), ZoneId.of("CET")));
|
||||||
trackId = service.startNewTrack();
|
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);
|
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);
|
service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null);
|
||||||
@@ -218,12 +218,12 @@ public class ExportImportTest {
|
|||||||
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
|
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
|
||||||
|
|
||||||
// Distance
|
// Distance
|
||||||
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
|
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
|
||||||
|
|
||||||
// Speed
|
// Speed
|
||||||
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
||||||
assertEquals(Speed.of(3.75), importedTrackStatistics.getAverageSpeed());
|
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageSpeed());
|
||||||
assertEquals(Speed.of(7.5), importedTrackStatistics.getAverageMovingSpeed());
|
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
|
||||||
|
|
||||||
// Altitude
|
// Altitude
|
||||||
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
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:03Z"), importedTrackStatistics.getStartTime());
|
||||||
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
|
assertEquals(Instant.parse("2020-02-02T02:02:23Z"), importedTrackStatistics.getStopTime());
|
||||||
|
|
||||||
assertEquals(Duration.ofSeconds(20), importedTrackStatistics.getTotalTime());
|
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getTotalTime());
|
||||||
assertEquals(Duration.ofSeconds(19), importedTrackStatistics.getMovingTime());
|
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
|
||||||
|
|
||||||
// Distance
|
// Distance
|
||||||
assertEquals(Distance.of(30), importedTrackStatistics.getTotalDistance());
|
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
|
||||||
|
|
||||||
// Speed
|
// Speed
|
||||||
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
|
||||||
assertEquals(Speed.of(1.5), importedTrackStatistics.getAverageSpeed());
|
assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed());
|
||||||
assertEquals(Speed.of(1.5789473684210527), importedTrackStatistics.getAverageMovingSpeed());
|
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
|
||||||
|
|
||||||
// Altitude
|
// Altitude
|
||||||
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
|
||||||
|
|||||||
+2
-2
@@ -83,14 +83,14 @@ public class TrackPointAssert {
|
|||||||
try {
|
try {
|
||||||
Assert.assertEquals(expected.size(), actual.size());
|
Assert.assertEquals(expected.size(), actual.size());
|
||||||
} catch (AssertionError e) {
|
} 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++) {
|
for (int i = 0; i < expected.size(); i++) {
|
||||||
try {
|
try {
|
||||||
assertEquals(expected.get(i), actual.get(i));
|
assertEquals(expected.get(i), actual.get(i));
|
||||||
} catch (AssertionError e) {
|
} 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());
|
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.io.file.importer.TrackPointAssert;
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
import de.dennisguse.opentracks.services.sensors.AltitudeSumManager;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the track recording service.
|
* Tests for the track recording service.
|
||||||
@@ -451,10 +451,7 @@ public class TrackRecordingServiceTest {
|
|||||||
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
newTrackPoint(trackRecordingService, latitude, longitude, accuracy, speed, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void newTrackPoint(TrackRecordingService trackRecordingService, double latitude, double longitude, float accuracy, long speed, long time) {
|
||||||
* Inserts a location and waits for 200ms.
|
|
||||||
*/
|
|
||||||
private 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))
|
TrackPoint trackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(time))
|
||||||
.setLongitude(longitude)
|
.setLongitude(longitude)
|
||||||
.setLatitude(latitude)
|
.setLatitude(latitude)
|
||||||
|
|||||||
+57
-45
@@ -20,14 +20,11 @@ import org.junit.BeforeClass;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.internal.debugging.LocationImpl;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
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.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TestDataUtil;
|
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.000002, 35.0, 3, 15);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.000003, 35.0, 4, 15);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.000004, 35.0, 5, 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();
|
service.endCurrentTrack();
|
||||||
|
|
||||||
@@ -210,7 +207,7 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f),
|
.setAltitudeLoss(0f),
|
||||||
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||||
.setLatitude(45.0002)
|
.setLatitude(45.0005)
|
||||||
.setLongitude(35)
|
.setLongitude(35)
|
||||||
.setHorizontalAccuracy(Distance.of(6))
|
.setHorizontalAccuracy(Distance.of(6))
|
||||||
.setSpeed(Speed.of(15))
|
.setSpeed(Speed.of(15))
|
||||||
@@ -220,9 +217,6 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
), trackPoints);
|
), trackPoints);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@@ -257,6 +251,13 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(0))
|
.setSpeed(Speed.of(0))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(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)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
@@ -274,8 +275,8 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
// when
|
// when
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
|
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, 2, 0);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 3, 0); // will be ignored
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 4, 0);
|
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, 5, 0);
|
||||||
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
|
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 6, 15);
|
||||||
|
|
||||||
@@ -296,6 +297,27 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(15))
|
.setSpeed(Speed.of(15))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(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)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
@@ -350,38 +372,6 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(0f)
|
.setAltitudeLoss(0f)
|
||||||
.setHeartRate_bpm(5f),
|
.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)
|
new TrackPoint(TrackPoint.Type.TRACKPOINT, null)
|
||||||
.setLatitude(45)
|
.setLatitude(45)
|
||||||
.setLongitude(35)
|
.setLongitude(35)
|
||||||
@@ -462,12 +452,20 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setHorizontalAccuracy(Distance.of(2))
|
.setHorizontalAccuracy(Distance.of(2))
|
||||||
.setSpeed(Speed.of(5))
|
.setSpeed(Speed.of(5))
|
||||||
.setSensorDistance(Distance.of(10)),
|
.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)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setSensorDistance(Distance.of(11))
|
.setSensorDistance(Distance.of(11))
|
||||||
.setSpeed(Speed.of(5))
|
.setSpeed(Speed.of(5))
|
||||||
.setAltitudeGain(12f)
|
.setAltitudeGain(6f)
|
||||||
.setAltitudeLoss(12f)
|
.setAltitudeLoss(6f)
|
||||||
.setSensorDistance(Distance.of(4))
|
.setSensorDistance(Distance.of(2))
|
||||||
), trackPoints);
|
), trackPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,6 +509,13 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(0))
|
.setSpeed(Speed.of(0))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(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)
|
new TrackPoint(TrackPoint.Type.SEGMENT_START_AUTOMATIC, null)
|
||||||
.setLatitude(45.2)
|
.setLatitude(45.2)
|
||||||
@@ -519,6 +524,13 @@ public class TrackRecordingServiceTestLocation {
|
|||||||
.setSpeed(Speed.of(0))
|
.setSpeed(Speed.of(0))
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(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)
|
new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, null)
|
||||||
.setAltitudeGain(0f)
|
.setAltitudeGain(0f)
|
||||||
.setAltitudeLoss(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;
|
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 androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
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.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class TrackStatisticsUpdaterTest {
|
public class TrackStatisticsUpdaterTest {
|
||||||
|
|
||||||
@@ -81,18 +81,18 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
TrackStatistics statistics = subject.getTrackStatistics();
|
TrackStatistics statistics = subject.getTrackStatistics();
|
||||||
assertEquals(99.58, statistics.getTotalDistance().toM(), 0.01);
|
assertEquals(128.03, statistics.getTotalDistance().toM(), 0.01);
|
||||||
assertEquals(Duration.ofSeconds(14), statistics.getTotalTime());
|
assertEquals(Duration.ofSeconds(13), statistics.getTotalTime());
|
||||||
assertEquals(Duration.ofSeconds(7), statistics.getMovingTime());
|
assertEquals(Duration.ofSeconds(9), statistics.getMovingTime());
|
||||||
|
|
||||||
assertEquals(2.5, statistics.getMinAltitude(), 0.01);
|
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.getTotalAltitudeGain(), 0.01);
|
||||||
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
assertEquals(36, statistics.getTotalAltitudeLoss(), 0.01);
|
||||||
|
|
||||||
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
|
assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01);
|
||||||
assertEquals(14.226, statistics.getAverageMovingSpeed().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
|
@Test
|
||||||
@@ -141,9 +141,9 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
TrackPoint tp2 = new TrackPoint(0, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(2000));
|
||||||
tp2.setSpeed(Speed.of(5f));
|
tp2.setSpeed(Speed.of(5f));
|
||||||
TrackPoint tp3 = new TrackPoint(0.001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
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));
|
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));
|
tp4.setSensorDistance(Distance.of(5f));
|
||||||
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||||
tp5.setSensorDistance(Distance.of(10f));
|
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 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));
|
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));
|
TrackPoint tp3 = new TrackPoint(0.00001, 0, Altitude.WGS84.of(5.0), Instant.ofEpochMilli(3000));
|
||||||
tp3.setSensorDistance(Distance.of(5f));
|
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));
|
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));
|
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -248,8 +251,11 @@ public class TrackStatisticsUpdaterTest {
|
|||||||
|
|
||||||
TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000));
|
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));
|
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));
|
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));
|
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));
|
TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000));
|
||||||
|
|
||||||
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
subject.addTrackPoint(tp1, GPS_DISTANCE);
|
||||||
|
|||||||
@@ -43,40 +43,40 @@ public class BluetoothUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_crankOnly() {
|
public void parseCyclingSpeedCadence_crankOnly() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
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
|
// when
|
||||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
|
||||||
assertNull(sensor.getDistanceSpeed());
|
assertNull(sensor.getDistanceSpeed());
|
||||||
|
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_wheelOnly() {
|
public void parseCyclingSpeedCadence_wheelOnly() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
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
|
// when
|
||||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
assertEquals(65535 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||||
assertNull(sensor.getCadence());
|
assertNull(sensor.getCadence());
|
||||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseCyclingSpeedCadence_crankWheel() {
|
public void parseCyclingSpeedCadence_crankWheel() {
|
||||||
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, 0, 0);
|
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
|
// when
|
||||||
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
SensorDataCycling.CadenceAndSpeed sensor = BluetoothUtils.parseCyclingCrankAndWheel("address", "sensorName", characteristic);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(200, sensor.getCadence().getCrankRevolutionsCount());
|
assertEquals(200 + 16777216, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
||||||
assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount());
|
assertEquals(225, sensor.getCadence().getCrankRevolutionsCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
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.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class PreferencesUtilsTest {
|
public class PreferencesUtilsTest {
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void ExportTrackFileFormat_ok() {
|
public void ExportTrackFileFormat_ok() {
|
||||||
@@ -72,6 +77,24 @@ public class PreferencesUtilsTest {
|
|||||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
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
|
@Test
|
||||||
public void testGetCustomLayout_default() {
|
public void testGetCustomLayout_default() {
|
||||||
// given
|
// given
|
||||||
@@ -85,7 +108,7 @@ public class PreferencesUtilsTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(layout.getFields().size() > 0);
|
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));
|
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,8 +118,8 @@ public class PreferencesUtilsTest {
|
|||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
editor.putString(
|
editor.putString(
|
||||||
context.getString(R.string.stats_custom_layout_fields_key),
|
context.getString(R.string.stats_custom_layouts_key),
|
||||||
"run;"
|
"run;2;"
|
||||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
+ 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_distance_key) + ",1,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||||
@@ -108,7 +131,8 @@ public class PreferencesUtilsTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(layout.getFields().size(), 4);
|
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));
|
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||||
assertTrue(layout.getFields().get(0).isVisible());
|
assertTrue(layout.getFields().get(0).isVisible());
|
||||||
@@ -133,8 +157,8 @@ public class PreferencesUtilsTest {
|
|||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
editor.putString(
|
editor.putString(
|
||||||
context.getString(R.string.stats_custom_layout_fields_key),
|
context.getString(R.string.stats_custom_layouts_key),
|
||||||
"walking;"
|
"walking;2;"
|
||||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
+ 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_distance_key) + ",1,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
|
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
|
||||||
@@ -146,7 +170,8 @@ public class PreferencesUtilsTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(layout.getFields().size(), 4);
|
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));
|
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||||
assertTrue(layout.getFields().get(0).isVisible());
|
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));
|
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
PreferencesUtils.setCustomLayout(layoutSrc);
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
|
||||||
|
editor.commit();
|
||||||
|
|
||||||
// then
|
// 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);
|
assertNotNull(csv);
|
||||||
assertEquals(csv,
|
assertEquals(csv,
|
||||||
"road cycling;"
|
"road cycling;" + PreferencesUtils.getLayoutColumnsByDefault() + ";"
|
||||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1,0;"
|
||||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",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;"
|
+ 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;");
|
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
|
||||||
|
|
||||||
Layout layoutDst = PreferencesUtils.getCustomLayout();
|
Layout layoutDst = PreferencesUtils.getCustomLayout();
|
||||||
assertEquals(layoutSrc.getProfile(), layoutDst.getProfile());
|
assertEquals(layoutSrc.getName(), layoutDst.getName());
|
||||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
||||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
||||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
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());
|
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=".io.file.exporter.ExportActivity" />
|
||||||
|
|
||||||
<activity android:name=".settings.SettingsActivity" />
|
<activity android:name=".settings.SettingsActivity" />
|
||||||
<activity android:name=".settings.SettingsCustomLayoutActivity" />
|
<activity android:name=".settings.SettingsCustomLayoutListActivity" />
|
||||||
|
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ShowOnMapProxyActivity$KML"
|
android:name=".ShowOnMapProxyActivity$KML"
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ import android.view.View;
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
|
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
|
||||||
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.databinding.AggregatedStatsBinding;
|
import de.dennisguse.opentracks.databinding.AggregatedStatsBinding;
|
||||||
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
|
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
|
||||||
|
|
||||||
public class AggregatedStatisticsActivity extends AbstractActivity {
|
public class AggregatedStatisticsActivity extends AbstractActivity {
|
||||||
|
|
||||||
|
public static final String EXTRA_TRACK_IDS = "track_ids";
|
||||||
|
|
||||||
private AggregatedStatsBinding viewBinding;
|
private AggregatedStatsBinding viewBinding;
|
||||||
|
|
||||||
private AggregatedStatisticsAdapter adapter;
|
private AggregatedStatisticsAdapter adapter;
|
||||||
@@ -21,8 +26,10 @@ public class AggregatedStatisticsActivity extends AbstractActivity {
|
|||||||
|
|
||||||
viewBinding.aggregatedStatsList.setEmptyView(viewBinding.aggregatedStatsEmptyView);
|
viewBinding.aggregatedStatsList.setEmptyView(viewBinding.aggregatedStatsEmptyView);
|
||||||
|
|
||||||
|
List<Track.Id> trackIds = getIntent().getParcelableArrayListExtra(EXTRA_TRACK_IDS);
|
||||||
|
|
||||||
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
||||||
viewModel.getAggregatedStats().observe(this, aggregatedStatistics -> {
|
viewModel.getAggregatedStats(trackIds).observe(this, aggregatedStatistics -> {
|
||||||
if (aggregatedStatistics != null) {
|
if (aggregatedStatistics != null) {
|
||||||
adapter = new AggregatedStatisticsAdapter(this, aggregatedStatistics);
|
adapter = new AggregatedStatisticsAdapter(this, aggregatedStatistics);
|
||||||
viewBinding.aggregatedStatsList.setAdapter(adapter);
|
viewBinding.aggregatedStatsList.setAdapter(adapter);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.drawable.TransitionDrawable;
|
import android.graphics.drawable.TransitionDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -97,7 +98,10 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCallback().recordStart();
|
Callback callback = (Callback) getContext();
|
||||||
|
if (callback != null) {
|
||||||
|
callback.recordStart();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
viewBinding.controllerStop.setOnTouchListener(this);
|
viewBinding.controllerStop.setOnTouchListener(this);
|
||||||
@@ -114,6 +118,12 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
|||||||
trackRecordingServiceConnection.startConnection(getContext());
|
trackRecordingServiceConnection.startConnection(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
handlerUpdateTotalTime.removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
@@ -175,7 +185,10 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
|||||||
buttonDelay = () -> {
|
buttonDelay = () -> {
|
||||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||||
view.performClick();
|
view.performClick();
|
||||||
getCallback().recordPause();
|
Callback callback = (Callback) getContext();
|
||||||
|
if (callback != null) {
|
||||||
|
callback.recordPause();
|
||||||
|
}
|
||||||
|
|
||||||
transition.resetTransition();
|
transition.resetTransition();
|
||||||
|
|
||||||
@@ -198,8 +211,12 @@ public class ControllerFragment extends Fragment implements View.OnTouchListener
|
|||||||
|
|
||||||
buttonDelay = () -> {
|
buttonDelay = () -> {
|
||||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||||
getCallback().recordStop();
|
Context context = getContext();
|
||||||
ActivityUtils.vibrate(getContext(), 1000);
|
Callback callback = (Callback) context;
|
||||||
|
if (callback != null) {
|
||||||
|
callback.recordStop();
|
||||||
|
ActivityUtils.vibrate(context, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
transition.resetTransition();
|
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);
|
viewBinding.controllerStatus.setText(recordingStatus.isPaused() ? R.string.generic_paused : R.string.generic_recording);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Callback getCallback() {
|
|
||||||
return (Callback) getActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
void recordStart();
|
void recordStart();
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ import androidx.loader.content.CursorLoader;
|
|||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
import java.time.Duration;
|
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.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
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.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.ActivityUtils;
|
import de.dennisguse.opentracks.util.ActivityUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.util.ListItemUtils;
|
import de.dennisguse.opentracks.util.ListItemUtils;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.StringUtils;
|
import de.dennisguse.opentracks.util.StringUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
|
|
||||||
@@ -248,6 +252,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
trackRecordingServiceConnection.unbind(this);
|
trackRecordingServiceConnection.unbind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
viewBinding = null;
|
||||||
|
trackRecordingServiceConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getRootView() {
|
protected View getRootView() {
|
||||||
viewBinding = TrackListBinding.inflate(getLayoutInflater());
|
viewBinding = TrackListBinding.inflate(getLayoutInflater());
|
||||||
@@ -463,6 +474,13 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
|
|||||||
return true;
|
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) {
|
if (itemId == R.id.list_context_menu_select_all) {
|
||||||
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
for (int i = 0; i < viewBinding.trackList.getCount(); i++) {
|
||||||
viewBinding.trackList.setItemChecked(i, true);
|
viewBinding.trackList.setItemChecked(i, true);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.dennisguse.opentracks;
|
package de.dennisguse.opentracks;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
@@ -18,6 +19,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
|
|||||||
|
|
||||||
import com.google.android.material.tabs.TabLayoutMediator;
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
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.fragments.StatisticsRecordingFragment;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.settings.SettingsActivity;
|
import de.dennisguse.opentracks.settings.SettingsActivity;
|
||||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||||
import de.dennisguse.opentracks.util.IntentUtils;
|
import de.dennisguse.opentracks.util.IntentUtils;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackUtils;
|
import de.dennisguse.opentracks.util.TrackUtils;
|
||||||
|
|
||||||
@@ -232,6 +235,13 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
trackDataHub.stop();
|
trackDataHub.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
viewBinding = null;
|
||||||
|
trackRecordingServiceConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getRootView() {
|
protected View getRootView() {
|
||||||
viewBinding = TrackRecordingBinding.inflate(getLayoutInflater());
|
viewBinding = TrackRecordingBinding.inflate(getLayoutInflater());
|
||||||
@@ -256,6 +266,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
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) {
|
if (item.getItemId() == R.id.track_detail_insert_marker) {
|
||||||
Intent intent = IntentUtils
|
Intent intent = IntentUtils
|
||||||
.newIntent(this, MarkerEditActivity.class)
|
.newIntent(this, MarkerEditActivity.class)
|
||||||
@@ -264,8 +279,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
if (item.getItemId() == R.id.track_detail_menu_select_layout) {
|
||||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
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;
|
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.content.data.Layout;
|
||||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
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 Layout layout;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||||
|
|
||||||
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.itemClickListener = itemClickListener;
|
this.itemClickListener = itemClickListener;
|
||||||
this.layout = layout;
|
this.layout = layout;
|
||||||
@@ -34,12 +34,12 @@ public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerVi
|
|||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
||||||
return new SettingsCustomLayoutAdapter.ViewHolder(view);
|
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
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);
|
DataField field = layout.getFields().get(position);
|
||||||
viewHolder.itemView.setTag(field.getKey());
|
viewHolder.itemView.setTag(field.getKey());
|
||||||
viewHolder.title.setText(field.getTitle());
|
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.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
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 YES_VALUE = "1";
|
||||||
public static final String NOT_VALUE = "0";
|
public static final String NOT_VALUE = "0";
|
||||||
|
|
||||||
@@ -24,6 +26,26 @@ public class DataField {
|
|||||||
this.isWide = isWide;
|
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() {
|
public String getKey() {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
@@ -63,7 +85,8 @@ public class DataField {
|
|||||||
public String toCsv() {
|
public String toCsv() {
|
||||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
||||||
String primary = this.isPrimary ? 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) {
|
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);
|
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;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Layout {
|
import de.dennisguse.opentracks.R;
|
||||||
private final String profile;
|
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<>();
|
private final List<DataField> dataFields = new ArrayList<>();
|
||||||
|
|
||||||
public Layout(String profile) {
|
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
||||||
this.profile = profile;
|
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) {
|
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
||||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
||||||
}
|
}
|
||||||
@@ -27,6 +86,11 @@ public class Layout {
|
|||||||
dataFields.remove(dataField);
|
dataFields.remove(dataField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void replaceAllFields(List<DataField> newFields) {
|
||||||
|
dataFields.clear();
|
||||||
|
addFields(newFields);
|
||||||
|
}
|
||||||
|
|
||||||
public List<DataField> getFields() {
|
public List<DataField> getFields() {
|
||||||
return new ArrayList<>(dataFields);
|
return new ArrayList<>(dataFields);
|
||||||
}
|
}
|
||||||
@@ -36,7 +100,46 @@ public class Layout {
|
|||||||
dataFields.add(to, dataFieldToMove);
|
dataFields.add(to, dataFieldToMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProfile() {
|
public String getName() {
|
||||||
return profile;
|
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;
|
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() {
|
public boolean hasSensorData() {
|
||||||
return hasHeartRate() || hasCadence() || hasPower();
|
return hasHeartRate() || hasCadence() || hasPower();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,6 +205,26 @@ public class ContentProviderUtils {
|
|||||||
return tracks;
|
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) {
|
public Track getTrack(@NonNull Track.Id trackId) {
|
||||||
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
|
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
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.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
|
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.
|
* 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();
|
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;
|
updateUInecessary = true;
|
||||||
layout = PreferencesUtils.getCustomLayout();
|
layout = PreferencesUtils.getCustomLayout();
|
||||||
}
|
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
|
||||||
|
|
||||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_columns_key, key)) {
|
|
||||||
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key != null && updateUInecessary && isResumed()) {
|
if (key != null && updateUInecessary && isResumed()) {
|
||||||
@@ -107,13 +104,13 @@ public class StatisticsRecordingFragment extends Fragment {
|
|||||||
|
|
||||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||||
final int numColumns = PreferencesUtils.getLayoutColumns();
|
layout = PreferencesUtils.getCustomLayout();
|
||||||
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
|
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
|
||||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||||
@Override
|
@Override
|
||||||
public int getSpanSize(int position) {
|
public int getSpanSize(int position) {
|
||||||
if (statisticsAdapter.isItemWide(position)) {
|
if (statisticsAdapter.isItemWide(position)) {
|
||||||
return numColumns;
|
return layout.getColumnsPerRow();
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.ResultReceiver;
|
import android.os.ResultReceiver;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.JobIntentService;
|
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_ID = "extra_track_id";
|
||||||
private static final String EXTRA_TRACK_FILE_FORMAT = "extra_track_file_format";
|
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 EXTRA_DIRECTORY_URI = "extra_directory_uri";
|
||||||
|
private static final String TAG = ExportService.class.getSimpleName();
|
||||||
|
|
||||||
public static void enqueue(Context context, ExportServiceResultReceiver receiver, Track.Id trackId, TrackFileFormat trackFileFormat, Uri directoryUri) {
|
public static void enqueue(Context context, ExportServiceResultReceiver receiver, Track.Id trackId, TrackFileFormat trackFileFormat, Uri directoryUri) {
|
||||||
Intent intent = new Intent(context, JobService.class);
|
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);
|
TrackFileFormat trackFileFormat = (TrackFileFormat) intent.getSerializableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||||
Uri directoryUri = intent.getParcelableExtra(EXTRA_DIRECTORY_URI);
|
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.
|
// Build directory file.
|
||||||
DocumentFile directoryFile = DocumentFile.fromTreeUri(this, directoryUri);
|
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.
|
// Export.
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
|
||||||
Track track = contentProviderUtils.getTrack(trackId);
|
Track track = contentProviderUtils.getTrack(trackId);
|
||||||
boolean success = ExportUtils.exportTrack(this, trackFileFormat, directoryFile, track);
|
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.
|
// Send result to the receiver.
|
||||||
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
|
int resultCode = success ? ExportServiceResultReceiver.RESULT_CODE_SUCCESS : ExportServiceResultReceiver.RESULT_CODE_ERROR;
|
||||||
resultReceiver.send(resultCode, bundle);
|
resultReceiver.send(resultCode, bundle);
|
||||||
|
|||||||
+24
-6
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.io.file.importer;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
|
DocumentFile directoryUri = configureDirectoryChooserIntent(intent);
|
||||||
if (directoryUri == null) {
|
if (!isDirectoryValid(directoryUri)) {
|
||||||
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
|
||||||
} else {
|
} else {
|
||||||
startActivity(createNextActivityIntent(directoryUri.getUri())); //TODO Refactor to DocumentFile
|
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
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||||
super.onActivityResult(requestCode, resultCode, resultData);
|
super.onActivityResult(requestCode, resultCode, resultData);
|
||||||
@@ -83,6 +88,11 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
return PreferencesUtils.getDefaultExportDirectoryUri(this);
|
return PreferencesUtils.getDefaultExportDirectoryUri(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||||
|
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Intent createNextActivityIntent(Uri directoryUri) {
|
protected Intent createNextActivityIntent(Uri directoryUri) {
|
||||||
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
Intent intent = IntentUtils.newIntent(this, ExportActivity.class);
|
||||||
@@ -99,14 +109,15 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
|
||||||
switch (resultCode) {
|
switch (resultCode) {
|
||||||
case RESULT_OK:
|
case RESULT_OK:
|
||||||
Uri directoryUri = resultData.getData();
|
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), PreferencesUtils.getDefaultExportDirectoryUri(this));
|
||||||
|
|
||||||
|
Uri directoryUri = resultData.getData();
|
||||||
PreferencesUtils.setDefaultExportDirectoryUri(directoryUri);
|
PreferencesUtils.setDefaultExportDirectoryUri(directoryUri);
|
||||||
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri);
|
IntentUtils.persistDirectoryAccessPermission(getApplicationContext(), directoryUri, resultData.getFlags());
|
||||||
break;
|
break;
|
||||||
case RESULT_CANCELED:
|
case RESULT_CANCELED:
|
||||||
|
IntentUtils.releaseDirectoryAccessPermission(getApplicationContext(), PreferencesUtils.getDefaultExportDirectoryUri(this));
|
||||||
PreferencesUtils.setDefaultExportDirectoryUri(null);
|
PreferencesUtils.setDefaultExportDirectoryUri(null);
|
||||||
//TODO Remove stored permission contentResolver.releasePersistableUriPermission
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +125,19 @@ public abstract class DirectoryChooserActivity extends AppCompatActivity {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isDirectoryValid(final DocumentFile directoryUri) {
|
||||||
|
return super.isDirectoryValid(directoryUri) && directoryUri.canWrite();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
protected DocumentFile configureDirectoryChooserIntent(Intent intent) {
|
||||||
super.configureDirectoryChooserIntent(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)) {
|
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;
|
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.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||||
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
import de.dennisguse.opentracks.services.handlers.TrackPointCreator;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.TrackNameUtils;
|
import de.dennisguse.opentracks.util.TrackNameUtils;
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ class TrackRecordingManager {
|
|||||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||||
|
|
||||||
private boolean currentSegmentHasTrackPoint;
|
private boolean currentSegmentHasTrackPoint;
|
||||||
|
private TrackPoint lastTrackPoint;
|
||||||
private TrackPoint lastStoredTrackPoint;
|
private TrackPoint lastStoredTrackPoint;
|
||||||
|
|
||||||
TrackRecordingManager(Context context) {
|
TrackRecordingManager(Context context) {
|
||||||
@@ -80,11 +81,13 @@ class TrackRecordingManager {
|
|||||||
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
trackStatisticsUpdater = new TrackStatisticsUpdater(track.getTrackStatistics());
|
||||||
insertTrackPoint(trackId, segmentStartTrackPoint);
|
insertTrackPoint(trackId, segmentStartTrackPoint);
|
||||||
currentSegmentHasTrackPoint = false;
|
currentSegmentHasTrackPoint = false;
|
||||||
|
lastTrackPoint = null;
|
||||||
lastStoredTrackPoint = null; }
|
lastStoredTrackPoint = null; }
|
||||||
|
|
||||||
void pause(TrackPointCreator trackPointCreator) {
|
void pause(TrackPointCreator trackPointCreator) {
|
||||||
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
|
insertTrackPoint(trackId, trackPointCreator.createSegmentEnd());
|
||||||
currentSegmentHasTrackPoint = false;
|
currentSegmentHasTrackPoint = false;
|
||||||
|
lastTrackPoint = null;
|
||||||
lastStoredTrackPoint = null;
|
lastStoredTrackPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +97,7 @@ class TrackRecordingManager {
|
|||||||
|
|
||||||
trackId = null;
|
trackId = null;
|
||||||
trackStatisticsUpdater = null;
|
trackStatisticsUpdater = null;
|
||||||
|
lastTrackPoint = null;
|
||||||
lastStoredTrackPoint = null;
|
lastStoredTrackPoint = null;
|
||||||
currentSegmentHasTrackPoint = false;
|
currentSegmentHasTrackPoint = false;
|
||||||
}
|
}
|
||||||
@@ -103,7 +107,7 @@ class TrackRecordingManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
|
TrackStatisticsUpdater tmpTrackStatisticsUpdater = new TrackStatisticsUpdater(trackStatisticsUpdater);
|
||||||
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastStoredTrackPoint);
|
Pair<TrackPoint, SensorDataSet> current = trackPointCreator.createCurrentTrackPoint(lastTrackPoint);
|
||||||
|
|
||||||
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
|
tmpTrackStatisticsUpdater.addTrackPoint(current.first, recordingDistanceInterval);
|
||||||
|
|
||||||
@@ -161,13 +165,20 @@ class TrackRecordingManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasSensorData() || (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving())) {
|
if (distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval) && trackPoint.isMoving()) {
|
||||||
insertTrackPoint(trackId, trackPoint);
|
insertTrackPoint(trackId, trackPoint);
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +187,22 @@ class TrackRecordingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void insertTrackPoint(@NonNull Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
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 {
|
try {
|
||||||
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
contentProviderUtils.insertTrackPoint(trackPoint, trackId);
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||||
|
|||||||
+8
-2
@@ -69,6 +69,8 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
|
|
||||||
case BluetoothProfile.STATE_DISCONNECTED:
|
case BluetoothProfile.STATE_DISCONNECTED:
|
||||||
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
|
Log.d(TAG, "Disconnected from sensor: " + gatt.getDevice());
|
||||||
|
|
||||||
|
clearData();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,14 +132,18 @@ public abstract class BluetoothConnectionManager<DataType> {
|
|||||||
observer.onChanged(sensorData);
|
observer.onChanged(sensorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void clearData() {
|
||||||
|
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
synchronized void disconnect() {
|
synchronized void disconnect() {
|
||||||
if (bluetoothGatt == null) {
|
if (bluetoothGatt == null) {
|
||||||
Log.w(TAG, "Cannot disconnect if not connected.");
|
Log.w(TAG, "Cannot disconnect if not connected.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bluetoothGatt.close();
|
bluetoothGatt.close();
|
||||||
observer.onDisconnecting(createEmptySensorData(bluetoothGatt.getDevice().getAddress()));
|
clearData();
|
||||||
|
|
||||||
bluetoothGatt = null;
|
bluetoothGatt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,73 @@
|
|||||||
package de.dennisguse.opentracks.settings;
|
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.R;
|
||||||
|
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||||
|
|
||||||
class PreferencesOpenHelper {
|
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() {
|
static PreferencesOpenHelper newInstance(int version) {
|
||||||
return new PreferencesOpenHelper();
|
return new PreferencesOpenHelper(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkForUpgrade() {
|
void check() {
|
||||||
int lastVersion = PreferencesUtils.getInt(R.string.prefs_last_version_key, 0);
|
int lastVersion = PreferencesUtils.getInt(R.string.prefs_last_version_key, 0);
|
||||||
if (PREFERENCES_VERSION > lastVersion) {
|
if (version > lastVersion) {
|
||||||
onUpgrade();
|
onUpgrade();
|
||||||
|
} else if (version < lastVersion) {
|
||||||
|
onDowngrade();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUpgrade() {
|
private void onUpgrade() {
|
||||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, PREFERENCES_VERSION);
|
PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
|
||||||
switch (PREFERENCES_VERSION) {
|
for (int i = 1; i <= version; i++) {
|
||||||
case 1:
|
switch (i) {
|
||||||
upgradeFrom0to1();
|
case 1:
|
||||||
|
upgradeFrom0to1();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
upgradeFrom1to2();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Not implemented: upgrade to " + version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upgradeFrom0to1() {
|
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.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
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.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Layout;
|
import de.dennisguse.opentracks.content.data.Layout;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
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.TrackIconUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
@@ -57,6 +56,8 @@ public class PreferencesUtils {
|
|||||||
|
|
||||||
private final static String TAG = PreferencesUtils.class.getSimpleName();
|
private final static String TAG = PreferencesUtils.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int PREFERENCES_VERSION = 2;
|
||||||
|
|
||||||
private PreferencesUtils() {
|
private PreferencesUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ public class PreferencesUtils {
|
|||||||
PreferencesUtils.resources = resources;
|
PreferencesUtils.resources = resources;
|
||||||
PreferencesUtils.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
PreferencesUtils.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
PreferencesOpenHelper.newInstance().checkForUpgrade();
|
PreferencesOpenHelper.newInstance(PREFERENCES_VERSION).check();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener changeListener) {
|
public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener changeListener) {
|
||||||
@@ -94,7 +95,7 @@ public class PreferencesUtils {
|
|||||||
/**
|
/**
|
||||||
* Gets a preference key
|
* Gets a preference key
|
||||||
*
|
*
|
||||||
* @param keyId the key id
|
* @param keyId the key id
|
||||||
*/
|
*/
|
||||||
private static String getKey(int keyId) {
|
private static String getKey(int keyId) {
|
||||||
return resources.getString(keyId);
|
return resources.getString(keyId);
|
||||||
@@ -112,7 +113,7 @@ public class PreferencesUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isKey(int[] keyIds, String key) {
|
public static boolean isKey(int[] keyIds, String key) {
|
||||||
for(int keyId : keyIds) {
|
for (int keyId : keyIds) {
|
||||||
if (isKey(keyId, key)) {
|
if (isKey(keyId, key)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -185,7 +186,7 @@ public class PreferencesUtils {
|
|||||||
} else {
|
} else {
|
||||||
unit = resources.getString(R.string.stats_units_imperial);
|
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) {
|
public static boolean isReportSpeed(String category) {
|
||||||
@@ -546,76 +547,113 @@ public class PreferencesUtils {
|
|||||||
return getDefaultExportDirectoryUri(context) != null;
|
return getDefaultExportDirectoryUri(context) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getLayoutColumns() {
|
public static int getLayoutColumnsByDefault() {
|
||||||
return getInt(R.string.stats_custom_layout_columns_key, resources.getInteger(R.integer.stats_custom_layout_columns_default));
|
return resources.getInteger(R.integer.stats_custom_layout_columns_default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setLayoutColumns(int columns) {
|
private static List<TypedArray> getMultiTypedArray() {
|
||||||
setInt(R.string.stats_custom_layout_columns_key, columns);
|
return Stream.of(
|
||||||
}
|
R.array.stats_custom_layout_fields_default_value_0,
|
||||||
|
R.array.stats_custom_layout_fields_default_value_1,
|
||||||
private static List<TypedArray> getMultiTypedArray(String key) {
|
R.array.stats_custom_layout_fields_default_value_2,
|
||||||
List<TypedArray> typedArrays = new ArrayList<>();
|
R.array.stats_custom_layout_fields_default_value_3,
|
||||||
|
R.array.stats_custom_layout_fields_default_value_4,
|
||||||
try {
|
R.array.stats_custom_layout_fields_default_value_5,
|
||||||
Class<R.array> resource = R.array.class;
|
R.array.stats_custom_layout_fields_default_value_6,
|
||||||
Field field;
|
R.array.stats_custom_layout_fields_default_value_7,
|
||||||
int i = 0;
|
R.array.stats_custom_layout_fields_default_value_8,
|
||||||
|
R.array.stats_custom_layout_fields_default_value_9,
|
||||||
do {
|
R.array.stats_custom_layout_fields_default_value_10,
|
||||||
field = resource.getField(key + resources.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
|
R.array.stats_custom_layout_fields_default_value_11,
|
||||||
typedArrays.add(resources.obtainTypedArray(field.getInt(null)));
|
R.array.stats_custom_layout_fields_default_value_12,
|
||||||
i++;
|
R.array.stats_custom_layout_fields_default_value_13,
|
||||||
} while (field != null); //TODO Catch no such field exception instead of using an endless loop.
|
R.array.stats_custom_layout_fields_default_value_14,
|
||||||
} catch (Exception e) {
|
R.array.stats_custom_layout_fields_default_value_15,
|
||||||
e.printStackTrace();
|
R.array.stats_custom_layout_fields_default_value_16,
|
||||||
Log.e(TAG, e.getMessage());
|
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());
|
||||||
return typedArrays;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ResourceType")
|
@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() {
|
static String buildDefaultLayout() {
|
||||||
List<TypedArray> fieldsArrays = getMultiTypedArray("stats_custom_layout_fields_default_value");
|
return resources.getString(R.string.stats_custom_layout_default_layout) + CsvLayoutUtils.ITEM_SEPARATOR + getLayoutColumnsByDefault() + CsvLayoutUtils.ITEM_SEPARATOR + buildDefaultFields();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
public static Layout getCustomLayout() {
|
||||||
String csvCustomLayout = getString(R.string.stats_custom_layout_fields_key, buildDefaultLayout());
|
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||||
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
|
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||||
Layout layout = new Layout(csvParts.get(0));
|
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
|
||||||
for (int i = 1; i < csvParts.size(); i++) {
|
if (layoutSelected == null) {
|
||||||
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
|
return Layout.fromCsv(csvLines[0], resources);
|
||||||
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;
|
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) {
|
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
|
||||||
List<DataField> fields = layout.getFields();
|
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
|
||||||
if (fields.isEmpty()) {
|
}
|
||||||
return;
|
|
||||||
|
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
|
return layouts;
|
||||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
|
}
|
||||||
+ CsvConstants.LINE_SEPARATOR;
|
|
||||||
setString(R.string.stats_custom_layout_fields_key, csv);
|
public static List<String> getAllCustomLayoutNames() {
|
||||||
|
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void resetCustomLayoutPreferences() {
|
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();
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
editor.remove(resources.getString(R.string.stats_custom_layout_fields_key));
|
editor.remove(resources.getString(R.string.stats_custom_layouts_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.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
@@ -31,7 +32,8 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
fragment = (PreferenceFragmentCompat) getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_KEY);
|
fragment = (PreferenceFragmentCompat) getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_KEY);
|
||||||
} else {
|
}
|
||||||
|
if (fragment == null) {
|
||||||
fragment = new MainSettingsFragment();
|
fragment = new MainSettingsFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +57,11 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
getSupportFragmentManager().putFragment(outState, FRAGMENT_KEY, fragment);
|
if (fragment != null && fragment.isAdded()) {
|
||||||
|
getSupportFragmentManager().putFragment(outState, FRAGMENT_KEY, fragment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+20
-16
@@ -18,18 +18,20 @@ import java.util.stream.IntStream;
|
|||||||
|
|
||||||
import de.dennisguse.opentracks.AbstractActivity;
|
import de.dennisguse.opentracks.AbstractActivity;
|
||||||
import de.dennisguse.opentracks.R;
|
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.Layout;
|
||||||
import de.dennisguse.opentracks.content.data.DataField;
|
import de.dennisguse.opentracks.content.data.DataField;
|
||||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
||||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
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 ActivitySettingsCustomLayoutBinding viewBinding;
|
||||||
private GridLayoutManager gridLayoutManager;
|
private GridLayoutManager gridLayoutManager;
|
||||||
private SettingsCustomLayoutAdapter adapterFieldsVisible;
|
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
||||||
private SettingsCustomLayoutAdapter adapterFieldsHidden;
|
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
||||||
|
private String profile;
|
||||||
private Layout layoutFieldsVisible;
|
private Layout layoutFieldsVisible;
|
||||||
private Layout layoutFieldsHidden;
|
private Layout layoutFieldsHidden;
|
||||||
private int numColumns;
|
private int numColumns;
|
||||||
@@ -38,12 +40,13 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
|
||||||
// Recycler view with visible stats.
|
// Recycler view with visible stats.
|
||||||
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), true);
|
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||||
adapterFieldsVisible = new SettingsCustomLayoutAdapter(this, this, layoutFieldsVisible);
|
profile = layout.getName();
|
||||||
|
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
|
||||||
|
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
|
||||||
|
|
||||||
numColumns = PreferencesUtils.getLayoutColumns();
|
numColumns = layout.getColumnsPerRow();
|
||||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
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) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
numColumns = position + 1;
|
numColumns = position + 1;
|
||||||
gridLayoutManager.setSpanCount(numColumns);
|
gridLayoutManager.setSpanCount(numColumns);
|
||||||
PreferencesUtils.setLayoutColumns(position + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.
|
// Recycler view with not visible stats.
|
||||||
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), false);
|
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
|
||||||
adapterFieldsHidden = new SettingsCustomLayoutAdapter(this, this, layoutFieldsHidden);
|
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
|
||||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||||
|
|
||||||
|
setTitle(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
||||||
Layout newLayout = new Layout(layoutFieldsVisible.getProfile());
|
Layout layout = new Layout(profile, numColumns);
|
||||||
newLayout.addFields(layoutFieldsVisible.getFields());
|
layout.addFields(layoutFieldsVisible.getFields());
|
||||||
newLayout.addFields(layoutFieldsHidden.getFields());
|
layout.addFields(layoutFieldsHidden.getFields());
|
||||||
PreferencesUtils.setCustomLayout(newLayout);
|
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));
|
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
|
||||||
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
|
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
|
||||||
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
|
Intent intent = new Intent(getContext(), SettingsCustomLayoutListActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ public class TrackStatisticsUpdater {
|
|||||||
private final TrackStatistics currentSegment;
|
private final TrackStatistics currentSegment;
|
||||||
// Current segment's last trackPoint
|
// Current segment's last trackPoint
|
||||||
private TrackPoint lastTrackPoint;
|
private TrackPoint lastTrackPoint;
|
||||||
// Current segment's last moving trackPoint
|
|
||||||
private TrackPoint lastMovingTrackPoint;
|
|
||||||
|
|
||||||
public TrackStatisticsUpdater() {
|
public TrackStatisticsUpdater() {
|
||||||
this(new TrackStatistics());
|
this(new TrackStatistics());
|
||||||
@@ -96,7 +94,6 @@ public class TrackStatisticsUpdater {
|
|||||||
this.speedBuffer_mps = new DoubleRingBuffer(toCopy.speedBuffer_mps);
|
this.speedBuffer_mps = new DoubleRingBuffer(toCopy.speedBuffer_mps);
|
||||||
|
|
||||||
this.lastTrackPoint = toCopy.lastTrackPoint;
|
this.lastTrackPoint = toCopy.lastTrackPoint;
|
||||||
this.lastMovingTrackPoint = toCopy.lastMovingTrackPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackStatistics getTrackStatistics() {
|
public TrackStatistics getTrackStatistics() {
|
||||||
@@ -116,9 +113,8 @@ public class TrackStatisticsUpdater {
|
|||||||
* @param minGPSDistance the min recording distance
|
* @param minGPSDistance the min recording distance
|
||||||
*/
|
*/
|
||||||
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||||
if (trackPoint.getType() == TrackPoint.Type.SEGMENT_START_MANUAL) {
|
if (trackPoint.isSegmentStart()) {
|
||||||
reset(trackPoint);
|
reset(trackPoint);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentSegment.isInitialized()) {
|
if (!currentSegment.isInitialized()) {
|
||||||
@@ -129,7 +125,7 @@ public class TrackStatisticsUpdater {
|
|||||||
currentSegment.setStopTime(trackPoint.getTime());
|
currentSegment.setStopTime(trackPoint.getTime());
|
||||||
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
|
currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime()));
|
||||||
|
|
||||||
// Process sensor data
|
// Process sensor data: barometer
|
||||||
if (trackPoint.hasAltitudeGain()) {
|
if (trackPoint.hasAltitudeGain()) {
|
||||||
currentSegment.addTotalAltitudeGain(trackPoint.getAltitudeGain());
|
currentSegment.addTotalAltitudeGain(trackPoint.getAltitudeGain());
|
||||||
}
|
}
|
||||||
@@ -138,62 +134,46 @@ public class TrackStatisticsUpdater {
|
|||||||
currentSegment.addTotalAltitudeLoss(trackPoint.getAltitudeLoss());
|
currentSegment.addTotalAltitudeLoss(trackPoint.getAltitudeLoss());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasSensorDistance()) {
|
//Update absolute (GPS-based) altitude
|
||||||
currentSegment.addTotalDistance(trackPoint.getSensorDistance());
|
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()) {
|
if (trackPoint.isSegmentEnd()) {
|
||||||
reset(trackPoint);
|
reset(trackPoint);
|
||||||
return;
|
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;
|
lastTrackPoint = trackPoint;
|
||||||
lastMovingTrackPoint = trackPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset(TrackPoint trackPoint) {
|
private void reset(TrackPoint trackPoint) {
|
||||||
@@ -203,7 +183,6 @@ public class TrackStatisticsUpdater {
|
|||||||
currentSegment.reset(trackPoint.getTime());
|
currentSegment.reset(trackPoint.getTime());
|
||||||
|
|
||||||
lastTrackPoint = null;
|
lastTrackPoint = null;
|
||||||
lastMovingTrackPoint = null;
|
|
||||||
altitudeBuffer_m.reset();
|
altitudeBuffer_m.reset();
|
||||||
speedBuffer_mps.reset();
|
speedBuffer_mps.reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.dennisguse.opentracks.util;
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import static android.content.Context.VIBRATOR_SERVICE;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -14,6 +16,7 @@ import android.view.MenuItem;
|
|||||||
import android.widget.AbsListView;
|
import android.widget.AbsListView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -21,8 +24,6 @@ import java.util.ArrayList;
|
|||||||
import de.dennisguse.opentracks.ContextualActionModeCallback;
|
import de.dennisguse.opentracks.ContextualActionModeCallback;
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
|
||||||
import static android.content.Context.VIBRATOR_SERVICE;
|
|
||||||
|
|
||||||
public class ActivityUtils {
|
public class ActivityUtils {
|
||||||
|
|
||||||
private static final String TAG = ActivityUtils.class.getSimpleName();
|
private static final String TAG = ActivityUtils.class.getSimpleName();
|
||||||
@@ -75,11 +76,7 @@ public class ActivityUtils {
|
|||||||
arrayList.add(key);
|
arrayList.add(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int[] result = new int[arrayList.size()];
|
return arrayList.stream().mapToInt(i -> i).toArray();
|
||||||
for (int i = 0; i < arrayList.size(); i++) {
|
|
||||||
result[i] = arrayList.get(i);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -97,7 +94,7 @@ public class ActivityUtils {
|
|||||||
return searchView;
|
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);
|
final Vibrator vibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
|
vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||||
|
|||||||
@@ -107,9 +107,6 @@ public class BluetoothUtils {
|
|||||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, 2);
|
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) {
|
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
|
// 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;
|
int valueLength = characteristic.getValue().length;
|
||||||
@@ -118,26 +115,26 @@ public class BluetoothUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int flags = characteristic.getValue()[0];
|
int flags = characteristic.getValue()[0];
|
||||||
boolean hasCrank = (flags & 0x01) > 0;
|
boolean hasWheel = (flags & 0x01) > 0;
|
||||||
boolean hasWheel = (flags & 0x02) > 0;
|
boolean hasCrank = (flags & 0x02) > 0;
|
||||||
|
|
||||||
SensorDataCycling.Cadence cadence = null;
|
|
||||||
int index = 1;
|
int index = 1;
|
||||||
if (hasCrank && valueLength - index >= 6) {
|
SensorDataCycling.DistanceSpeed speed = null;
|
||||||
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
|
if (hasWheel && valueLength - index >= 6) {
|
||||||
|
int wheelTotalRevolutionCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, index);
|
||||||
index += 4;
|
index += 4;
|
||||||
|
int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
||||||
int crankTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s
|
speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelTotalRevolutionCount, wheelTime);
|
||||||
index += 2;
|
index += 2;
|
||||||
cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorDataCycling.DistanceSpeed speed = null;
|
SensorDataCycling.Cadence cadence = null;
|
||||||
if (hasWheel && valueLength - index >= 4) {
|
if (hasCrank && valueLength - index >= 4) {
|
||||||
int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
long crankCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index);
|
||||||
index += 2;
|
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);
|
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) {
|
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) {
|
private static Uri findFile(Context context, Uri directoryUri, String exportFileName) {
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ public class FileUtils {
|
|||||||
*
|
*
|
||||||
* @param name name
|
* @param name name
|
||||||
*/
|
*/
|
||||||
// TODO Check if this function is still needed.
|
|
||||||
public static String sanitizeFileName(String name) {
|
public static String sanitizeFileName(String name) {
|
||||||
StringBuilder builder = new StringBuilder(name.length());
|
StringBuilder builder = new StringBuilder(name.length());
|
||||||
for (int i = 0; i < name.length(); i++) {
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package de.dennisguse.opentracks.util;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.UriPermission;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -25,12 +26,14 @@ import android.util.Pair;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
import de.dennisguse.opentracks.content.DescriptionGenerator;
|
||||||
@@ -92,8 +95,9 @@ public class IntentUtils {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<Uri, String> uriTrackFile = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat());
|
String trackName = FileUtils.sanitizeFileName(track.getName());
|
||||||
Pair<Uri, String> uriSharePicture = ShareContentProvider.createURI(trackId, track.getName(), TrackFileFormat.SHARE_PICTURE_PNG);
|
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));
|
uris.addAll(Arrays.asList(uriSharePicture.first, uriTrackFile.first));
|
||||||
}
|
}
|
||||||
@@ -203,8 +207,23 @@ public class IntentUtils {
|
|||||||
return new Pair<>(intent, photoUri);
|
return new Pair<>(intent, photoUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void persistDirectoryAccessPermission(Context context, Uri directoryUri) {
|
public static void persistDirectoryAccessPermission(Context context, Uri directoryUri, int existingFlags) {
|
||||||
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
int newFlags = existingFlags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
context.getContentResolver().takePersistableUriPermission(directoryUri, flags);
|
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) {
|
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()));
|
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.viewmodels;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
@@ -20,18 +21,18 @@ public class AggregatedStatisticsModel extends AndroidViewModel {
|
|||||||
super(application);
|
super(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<AggregatedStatistics> getAggregatedStats() {
|
public LiveData<AggregatedStatistics> getAggregatedStats(@Nullable List<Track.Id> trackIds) {
|
||||||
if (aggregatedStats == null) {
|
if (aggregatedStats == null) {
|
||||||
aggregatedStats = new MutableLiveData<>();
|
aggregatedStats = new MutableLiveData<>();
|
||||||
loadAggregatedStats();
|
loadAggregatedStats(trackIds);
|
||||||
}
|
}
|
||||||
return aggregatedStats;
|
return aggregatedStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAggregatedStats() {
|
private void loadAggregatedStats(@Nullable List<Track.Id> trackIds) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
||||||
List<Track> tracks = contentProviderUtils.getTracks();
|
List<Track> tracks = contentProviderUtils.getTracks(trackIds);
|
||||||
|
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
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:icon="@drawable/ic_delete_forever_24dp"
|
||||||
android:title="@string/menu_delete"
|
android:title="@string/menu_delete"
|
||||||
app:showAsAction="ifRoom" />
|
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
|
<item
|
||||||
android:id="@+id/list_context_menu_select_all"
|
android:id="@+id/list_context_menu_select_all"
|
||||||
android:icon="@drawable/ic_select_all_24dp"
|
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"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
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
|
<item
|
||||||
android:id="@+id/track_detail_insert_marker"
|
android:id="@+id/track_detail_insert_marker"
|
||||||
android:icon="@drawable/ic_marker_add_24dp"
|
android:icon="@drawable/ic_marker_add_24dp"
|
||||||
@@ -22,10 +27,11 @@ limitations under the License.
|
|||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:title="@string/menu_insert_marker" />
|
android:title="@string/menu_insert_marker" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_detail_menu_show_on_map"
|
android:id="@+id/track_detail_menu_select_layout"
|
||||||
android:icon="@drawable/ic_map_24dp"
|
android:icon="@drawable/ic_ui"
|
||||||
|
android:orderInCategory="2"
|
||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:title="@string/menu_show_on_map" />
|
android:title="@string/menu_select_layout" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/track_detail_markers"
|
android:id="@+id/track_detail_markers"
|
||||||
android:icon="@drawable/ic_marker_show_24dp"
|
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="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">%1$.1f mi/h</string>
|
||||||
<string name="value_float_mile_hour_recommended">%1$.1f mil/h (doporučeno)</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">%1$.1f km/h</string>
|
||||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (doporučeno)</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>
|
</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_day">Claro</string>
|
||||||
<string name="settings_night_mode_option_night">Oscuro</string>
|
<string name="settings_night_mode_option_night">Oscuro</string>
|
||||||
<string name="settings_night_mode_title">Elegir un tema</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="stats_split_interval">Intervalo</string>
|
||||||
<string name="track_detail_intervals_tab">Intervalos</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>
|
<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="track_delete_progress">%1$d/%2$d</string>
|
||||||
<string name="menu_voice_rate">Velocidad de voz</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="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_running_speed_and_cadence">Distancia, velocidad y cadencia</string>
|
||||||
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distancia y velocidad</string>
|
<string name="settings_sensor_cycling_distance_speed">Distancia y velocidad</string>
|
||||||
<string name="settings_sensor_cycling_cadence">Ciclismo: cadencia</string>
|
<string name="settings_sensor_cycling_cadence">Cadencia</string>
|
||||||
<string name="settings_announcements">Avisos</string>
|
<string name="settings_announcements">Avisos</string>
|
||||||
<string name="settings_announcements_totaltime_frequency">Intervalo de tiempo</string>
|
<string name="settings_announcements_totaltime_frequency">Intervalo de tiempo</string>
|
||||||
<string name="settings_announcements_distance_frequency">Intervalo de distancia</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_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_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_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_coordinate">%1$s°</string>
|
||||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||||
<string name="field_remove_from_layout">Remover</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="stats_coordinates">Latitud/Longitud</string>
|
||||||
<string name="title_activity_settings_custom_layout">Personalizar la pantalla de grabación</string>
|
<string name="title_activity_settings_custom_layout">Personalizar la pantalla de grabación</string>
|
||||||
<string name="field_set_secondary">Secundario</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>
|
</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_heart_rate_avg">Keskmine pulsisagedus</string>
|
||||||
<string name="sensor_state_cadence_max">Max kadentsus</string>
|
<string name="sensor_state_cadence_max">Max kadentsus</string>
|
||||||
<string name="sensor_state_cadence_avg">Keskmine 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="stats_loss">Kahjum</string>
|
||||||
<string name="track_detail_intervals_tab">Intervallid</string>
|
<string name="track_detail_intervals_tab">Intervallid</string>
|
||||||
<string name="stats_split_interval">Jagatud intervall</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="track_delete_progress">%1$d/%2$d</string>
|
||||||
<string name="menu_voice_rate">Hääle kiirus</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="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_cadence">Rütm</string>
|
||||||
<string name="settings_sensor_cycling_distance_speed">Jalgrattasport: kaugus ja kiirus</string>
|
<string name="settings_sensor_cycling_distance_speed">Kaugus ja kiirus</string>
|
||||||
<string name="settings_sensor_running_speed_and_cadence">Running: kaugus, kiirus ja kadents</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>
|
</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_GPX">Ouvert en tant que GPX</string>
|
||||||
<string name="open_track_as_trackfileformat">Ouvrir comme %1$s</string>
|
<string name="open_track_as_trackfileformat">Ouvrir comme %1$s</string>
|
||||||
<string name="activity_type_escooter">éscooter</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_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_all_do_it_for_all">le faire pour tous les conflits</string>
|
||||||
<string name="export_track_errors">Traces non exportées</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="delete_service">Service de suppression</string>
|
||||||
<string name="menu_voice_rate">Vitesse vocale</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="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_distance_speed">Distance et vitesse</string>
|
||||||
<string name="settings_sensor_cycling_cadence">Vélo : cadence</string>
|
<string name="settings_sensor_cycling_cadence">Cadre</string>
|
||||||
<string name="settings_sensor_running_speed_and_cadence">Course à pied : distance, vitesse et cadence</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">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="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>
|
<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="description_speed_source_gps">GPS</string>
|
||||||
<string name="generic_choose_an_option">Choisissez une option</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_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="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="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>
|
<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_announcements_distance_frequency">Intervalle de distance</string>
|
||||||
<string name="settings_layout_reset_confirm_title">Réinitialiser vos dispositions \?</string>
|
<string name="settings_layout_reset_confirm_title">Réinitialiser vos dispositions \?</string>
|
||||||
<string name="settings_layout_reset_done">Tous les paramètres pas défaut sont revenus à leur valeur par défaut</string>
|
<string name="settings_layout_reset_done">Tous les paramètres 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_coordinate">%1$s°</string>
|
||||||
<string name="location_latitude_longitude">%1$s, %2$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="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="permission_bluetooth_failed">OpenTracks a besoin de la permission d\'utiliser le Bluetooth.</string>
|
||||||
<string name="settings_recording_customize_layout_select_columns">Champs de données pas rangée</string>
|
<string name="settings_recording_customize_layout_select_columns">Champs de données pas rangée</string>
|
||||||
<string name="field_set_primary">Principal</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>
|
</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_overwrite_msg">Rutas sobrescritas</string>
|
||||||
<string name="export_progress_summary_skip_msg">Rutas omitidas</string>
|
<string name="export_progress_summary_skip_msg">Rutas omitidas</string>
|
||||||
<string name="export_progress_summary_errors_msg">Erros</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_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_ok_msg">Ficheiros importados</string>
|
||||||
<string name="import_progress_summary_exists_msg">Xa existe</string>
|
<string name="import_progress_summary_exists_msg">Xa existe</string>
|
||||||
<string name="import_progress_summary_errors_msg">Erros</string>
|
<string name="import_progress_summary_errors_msg">Erros</string>
|
||||||
<string name="sensor_state_cadence_avg">Cadencia media</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_avg">Pulso medio</string>
|
||||||
<string name="sensor_state_heart_rate_max">Pulso máximo</string>
|
<string name="sensor_state_heart_rate_max">Pulso máximo</string>
|
||||||
<string name="sensor_state_power_avg">Potencia media</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">%1$.1f km/h</string>
|
||||||
<string name="value_float_mile_hour_recommended">%1$.1f km/h (recomendado)</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="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>
|
</resources>
|
||||||
@@ -23,15 +23,13 @@ limitations under the License.
|
|||||||
<string name="about_libraries">Librerie</string>
|
<string name="about_libraries">Librerie</string>
|
||||||
<!-- Help -->
|
<!-- Help -->
|
||||||
<string name="help_feature_title">OpenTracks ha le seguenti caratteristiche:</string>
|
<string name="help_feature_title">OpenTracks ha le seguenti caratteristiche:</string>
|
||||||
<string name="help_feature_content">
|
<string name="help_feature_content">* crea tracce GPS
|
||||||
* crea tracciamenti GPS\n
|
\n * utilizza un sensore di frequenza cardiaca (solo Bluetooth LE)
|
||||||
* utilizza un sensore di frequenza cardiaca (solo Bluetooth LE)\n
|
\n * comunicazioni vocali (per tempo o distanza)
|
||||||
* comunicazioni vocali (per tempo o distanza)\n
|
\n * crea marcatori (incl. immagini)
|
||||||
* crea marcatori (incl. immagini)\n
|
\n * funziona completamente offline
|
||||||
* funziona completamente offline\n
|
\n * mostra i tracce su una mappa (vedi sotto)</string>
|
||||||
* mostra i tracciamenti su una mappa (vedi sotto)
|
<string name="help_track_map_title">Come visualizzare una traccia su una mappa\?</string>
|
||||||
</string>
|
|
||||||
<string name="help_track_map_title">Come mostrare un tracciamento su una mappa?</string>
|
|
||||||
<string name="help_track_map_content">
|
<string name="help_track_map_content">
|
||||||
Utilizzare la piccola icona della mappa.\n
|
Utilizzare la piccola icona della mappa.\n
|
||||||
a)
|
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_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_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_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="help_support_title">Come posso supportare OpenTracks?</string>
|
||||||
<string name="open_track_as_KML">Apri come KML</string>
|
<string name="open_track_as_KML">Apri come KML</string>
|
||||||
<string name="open_track_as_GPX">Apri come GPX</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_bus">guidare autobus</string>
|
||||||
<string name="activity_type_driving_car">guidare auto</string>
|
<string name="activity_type_driving_car">guidare auto</string>
|
||||||
<string name="activity_type_ferry">traghetto</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_gliding">aliante</string>
|
||||||
<string name="activity_type_hang_gliding">deltaplano</string>
|
<string name="activity_type_hang_gliding">deltaplano</string>
|
||||||
<string name="activity_type_helicopter">elicottero</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_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_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_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_imperial">Ritmo (min/mi)</string>
|
||||||
<string name="description_pace_metric">Ritmo (min/km)</string>
|
<string name="description_pace_metric">Ritmo (min/km)</string>
|
||||||
<string name="description_recorded_time">Registrato: %1$s</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_distance">Distanza totale: %1$.2f km (%2$.1f mi)</string>
|
||||||
<string name="description_total_time">Tempo totale: %1$s</string>
|
<string name="description_total_time">Tempo totale: %1$s</string>
|
||||||
<!-- Export -->
|
<!-- Export -->
|
||||||
<string name="export_progress_message">Esportazioni su %1$s…</string>
|
<string name="export_progress_message">Esportando su %1$s…</string>
|
||||||
<!-- External Storage -->
|
<!-- External Storage -->
|
||||||
<!-- Files -->
|
<!-- Files -->
|
||||||
<plurals name="files">
|
<plurals name="files">
|
||||||
@@ -159,7 +157,7 @@ Infatti, un\'applicazione deve supportare <i>ACTION_VIEW</i> con MIME <i>applica
|
|||||||
</plurals>
|
</plurals>
|
||||||
<!-- Generic -->
|
<!-- Generic -->
|
||||||
<string name="generic_add">Aggiungi</string>
|
<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">Descrizione</string>
|
||||||
<string name="generic_description_line">Descrizione: %1$s</string>
|
<string name="generic_description_line">Descrizione: %1$s</string>
|
||||||
<string name="generic_name">Nome</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 -->
|
<!-- Import -->
|
||||||
<string name="import_progress_message">Importazione da %1$s in corso…</string>
|
<string name="import_progress_message">Importazione da %1$s in corso…</string>
|
||||||
<!-- Marker -->
|
<!-- 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_success">È stato inserito un indicatore</string>
|
||||||
<string name="marker_add_canceled">Operazione annullata, nessun indicatore aggiunto</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>
|
<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_delete">Elimina</string>
|
||||||
<string name="menu_edit">Modifica</string>
|
<string name="menu_edit">Modifica</string>
|
||||||
<string name="menu_export_all">Esporta tutto</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_import_all">Importa tutto</string>
|
||||||
<string name="menu_insert_marker">Inserisci indicatore</string>
|
<string name="menu_insert_marker">Inserisci indicatore</string>
|
||||||
<string name="menu_insert_photo">Inserisci foto</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>
|
<string name="settings_recording_track_name_title">Nome percorso predefinito</string>
|
||||||
<!-- Settings Reset -->
|
<!-- Settings Reset -->
|
||||||
<string name="settings_reset">Reimposta</string>
|
<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_confirm_title">Reimpostare ora?</string>
|
||||||
<string name="settings_reset_done">Tutte le impostazioni sono ripristinate sui valori predefiniti</string>
|
<string name="settings_reset_done">Tutte le impostazioni sono ripristinate sui valori predefiniti</string>
|
||||||
<!-- Settings Sensor -->
|
<!-- Settings Sensor -->
|
||||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||||
<!-- Settings Stats -->
|
<!-- 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_imperial">Imperiale (mi, ft)</string>
|
||||||
<string name="settings_stats_units_metric">Metrico (km, m)</string>
|
<string name="settings_stats_units_metric">Metrico (km, m)</string>
|
||||||
<string name="settings_stats_units_title">Unità di misura preferite</string>
|
<string name="settings_stats_units_title">Unità di misura preferite</string>
|
||||||
<!-- Share Track -->
|
<!-- 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
|
||||||
\n%1$s</string>
|
\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 -->
|
<!-- Stats -->
|
||||||
<string name="stats_average_moving_pace">Ritmo medio in movimento</string>
|
<string name="stats_average_moving_pace">Ritmo medio in movimento</string>
|
||||||
<string name="stats_average_moving_speed">Velocità media 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 -->
|
<!-- Track Widget -->
|
||||||
<!-- Tracks -->
|
<!-- Tracks -->
|
||||||
<!-- Unit -->
|
<!-- Unit -->
|
||||||
<string name="unit_feet">pd</string>
|
<string name="unit_feet">ft</string>
|
||||||
<string name="unit_kilometer">km</string>
|
<string name="unit_kilometer">km</string>
|
||||||
<string name="unit_kilometer_per_hour">km/h</string>
|
<string name="unit_kilometer_per_hour">km/h</string>
|
||||||
<string name="unit_meter">m</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_kilometer">min/km</string>
|
||||||
<string name="unit_minute_per_mile">min/mi</string>
|
<string name="unit_minute_per_mile">min/mi</string>
|
||||||
<!-- Value -->
|
<!-- Value -->
|
||||||
<string name="value_integer_feet">%1$d pd</string>
|
<string name="value_integer_feet">%1$d ft</string>
|
||||||
<string name="value_integer_feet_excellent_gps">%1$d pd (GPS forte)</string>
|
<string name="value_integer_feet_excellent_gps">%1$d ft (segnale GPS forte)</string>
|
||||||
<string name="value_integer_feet_recommended">%1$d pd (consigliato)</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">%1$d km</string>
|
||||||
<string name="value_integer_kilometer_hour">%1$d km/h</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">%1$d m</string>
|
||||||
<string name="value_integer_meter_excellent_gps">%1$d m (GPS forte)</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 (GPS debole)</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_meter_recommended">%1$d m (consigliato)</string>
|
||||||
<string name="value_integer_mile">%1$d mi</string>
|
<string name="value_integer_mile">%1$d mi</string>
|
||||||
<string name="value_integer_mile_hour">%1$d mi/h</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_theater">teatro</string>
|
||||||
<string name="waypoint_type_toilet">toilette</string>
|
<string name="waypoint_type_toilet">toilette</string>
|
||||||
<string name="waypoint_type_tourist_information">informazioni turistiche</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_tunnel">galleria</string>
|
||||||
<string name="waypoint_type_waterfall">cascata</string>
|
<string name="waypoint_type_waterfall">cascata</string>
|
||||||
<string name="waypoint_type_water_fountain">fontana ad acqua</string>
|
<string name="waypoint_type_water_fountain">fontana ad acqua</string>
|
||||||
<string name="waypoint_type_waypoint">tappa</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="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="stats_split_interval">Intervallo di divisione</string>
|
||||||
<string name="aggregated_stats_empty_message">Registra il tuo primo percorso per vedere le statistiche aggregate</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>
|
<item quantity="other">Completato con %1$d errori</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="generic_click_twice_cancel">Premi il tasto indietro di nuovo per annullare</string>
|
<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="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="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="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="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="export_track_errors">Percorsi non esportati</string>
|
||||||
<string name="generic_overwrite">Sovrascrivere</string>
|
<string name="generic_overwrite">Sovrascrivere</string>
|
||||||
<string name="generic_skip">Ignora</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_unsupported_format">Formato di file non supportato</string>
|
||||||
<string name="import_parser_error">Errore nel riconoscimento degli argomenti: %1$s</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="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_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_info">%1$s: %2$s</string>
|
||||||
<string name="import_error_list_dialog_title">File non importati</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="track_detail_intervals_tab">Intervalli</string>
|
||||||
<string name="interval_list_empty_message">Non c\'è ancora nessun intervallo</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="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_stop">Tieni premuto per fermare</string>
|
||||||
<string name="hold_to_pause">Tieni premuto per mettere in pausa</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="instant_export_enabled_title">Esportazione istantanea post-allenamento</string>
|
||||||
<string name="generic_open_track">Traccia aperta</string>
|
<string name="generic_open_track">Traccia aperta</string>
|
||||||
<string name="import_prevent_reimport">È stato impostato impedisci reimportazione traccia</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 directory di esportazione.</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="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_skip_msg">Tracce saltate</string>
|
||||||
<string name="export_progress_summary_overwrite_msg">Tracce sovrascritte</string>
|
<string name="export_progress_summary_overwrite_msg">Tracce sovrascritte</string>
|
||||||
<string name="export_progress_summary_new_msg">Nuove tracce esportate</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_ok_msg">File importati</string>
|
||||||
<string name="import_progress_summary_exists_msg">Esiste già</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_power_avg">Potenza media</string>
|
||||||
<string name="sensor_state_heart_rate_max">Frequenza cardiaca max</string>
|
<string name="sensor_state_heart_rate_max">Frequenza cardiaca max</string>
|
||||||
<string name="stats_loss">Discesa</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="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_max">Cadenza max</string>
|
||||||
<string name="sensor_state_cadence_avg">Cadenza media</string>
|
<string name="sensor_state_cadence_avg">Cadenza media</string>
|
||||||
<string name="track_distance_notification">Distanza: %1$s</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="delete_service">Servizio di eliminazione</string>
|
||||||
<string name="track_delete_progress">%1$d/%2$d</string>
|
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||||
<string name="menu_voice_rate">Velocità della voce</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="track_delete_not_recording">Questa traccia non può essere cancellata perché è attualmente in registrazione.</string>
|
||||||
<string name="settings_sensor_cycling_cadence">Ciclismo: cadenza</string>
|
<string name="settings_sensor_cycling_cadence">Cadenza</string>
|
||||||
<string name="settings_sensor_cycling_distance_speed">Ciclismo: distanza e velocità</string>
|
<string name="settings_sensor_cycling_distance_speed">Distanza e Velocità</string>
|
||||||
<string name="settings_sensor_running_speed_and_cadence">Come mostrare una traccia su una mappa</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>
|
</resources>
|
||||||
@@ -101,7 +101,7 @@ limitations under the License.
|
|||||||
<string name="description_speed_ms">Hastighet (m/s)</string>
|
<string name="description_speed_ms">Hastighet (m/s)</string>
|
||||||
<string name="description_sensor_cadence">Kadens (o/min)</string>
|
<string name="description_sensor_cadence">Kadens (o/min)</string>
|
||||||
<string name="description_sensor_heart_rate">Puls (slag/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_imperial">Hastighet (mi/hr)</string>
|
||||||
<string name="description_speed_metric">Hastighet (km/t)</string>
|
<string name="description_speed_metric">Hastighet (km/t)</string>
|
||||||
<string name="description_time">Tid</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_cadence_value">%1$d o/min</string>
|
||||||
<string name="sensor_state_heart_rate">Puls</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_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="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_not_while_recording">Ikke tilgjengelig mens du tar opp et spor</string>
|
||||||
<string name="settings_show">Vis</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_cadence_max">Maks kadens</string>
|
||||||
<string name="sensor_state_heart_rate_avg">Gjennomsnittlig puls</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_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="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
|
||||||
<string name="track_distance_notification">Distanse: %1$s</string>
|
<string name="track_distance_notification">Distanse: %1$s</string>
|
||||||
<string name="track_speed_notification">Hastighet: %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_layout_reset_done">Alle layoutinnstillinger er tilbakestilt til standardverdiene</string>
|
||||||
<string name="settings_all_reset">Tilbakestill</string>
|
<string name="settings_all_reset">Tilbakestill</string>
|
||||||
<string name="stats_sensors_cadence">Kadens</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_coordinate">%1$s°</string>
|
||||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||||
<string name="title_activity_settings_custom_layout">Tilpass opptakslayouten</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="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">%1$.1f mi/h</string>
|
||||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (anbefales)</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>
|
</resources>
|
||||||
@@ -239,7 +239,7 @@ limitations under the License.
|
|||||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||||
<string name="settings_sensor_connecting">Podłączony do %1$s</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_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 -->
|
<!-- Settings Stats -->
|
||||||
<string name="settings_stats_rate_title">Wybór tempo/prędkość</string>
|
<string name="settings_stats_rate_title">Wybór tempo/prędkość</string>
|
||||||
<string name="settings_stats_units_imperial">Imperialne (mile, stopy)</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>
|
<string name="generic_separator_done_total">/</string>
|
||||||
<plurals name="generic_completed_with_errors">
|
<plurals name="generic_completed_with_errors">
|
||||||
<item quantity="one">Ukończono z jednym błędem</item>
|
<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="many">Zakończono z błędami %1$d</item>
|
||||||
<item quantity="other">Zakończono z błędami %1$d</item>
|
<item quantity="other">Zakończono z błędami %1$d</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
@@ -522,7 +522,68 @@ limitations under the License.
|
|||||||
<string name="track_delete_progress">%1$d/%2$d</string>
|
<string name="track_delete_progress">%1$d/%2$d</string>
|
||||||
<string name="menu_voice_rate">Szybkość głosu</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="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_distance_speed">Dystans i Prędkość</string>
|
||||||
<string name="settings_sensor_cycling_cadence">Jazda na rowerze: kadencja</string>
|
<string name="settings_sensor_cycling_cadence">Tempo</string>
|
||||||
<string name="settings_sensor_running_speed_and_cadence">Bieganie: dystans, prędkość i kadencja</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>
|
</resources>
|
||||||
@@ -488,4 +488,31 @@
|
|||||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (Recomendado)</string>
|
<string name="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="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>
|
</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_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">%1$.1f km/h</string>
|
||||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (khuyến khích)</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>
|
</resources>
|
||||||
@@ -30,6 +30,9 @@ limitations under the License.
|
|||||||
<color name="colorAccent">@android:color/holo_orange_dark</color>
|
<color name="colorAccent">@android:color/holo_orange_dark</color>
|
||||||
<color name="colorAccentTransparent">#55FF8800</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="recording_text">#FFBE161B</color> <!-- red -->
|
||||||
<color name="stats_separator">#99999999</color> <!-- light gray -->
|
<color name="stats_separator">#99999999</color> <!-- light gray -->
|
||||||
<color name="text_gradient_center">#B2000000</color> <!-- black 70% opacity -->
|
<color name="text_gradient_center">#B2000000</color> <!-- black 70% opacity -->
|
||||||
|
|||||||
@@ -12,40 +12,22 @@
|
|||||||
|
|
||||||
<!-- Main settings -->
|
<!-- Main settings -->
|
||||||
<string name="settings_open_tracks_key" translatable="false">settingsOpenTracks</string>
|
<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_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_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_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_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_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_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 -->
|
<!-- 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_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_heart_rate_key" translatable="false">bluetoothSensor</string>
|
||||||
<string name="settings_sensor_bluetooth_cycling_cadence_key" translatable="false">bluetoothCyclingCadenceSensor</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_import" translatable="false">settingsImport</string>
|
||||||
<string name="settings_export" translatable="false">settingsExport</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_key" translatable="false">defaultActivity</string>
|
||||||
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
|
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
|
||||||
<string-array name="activity_types">
|
<string-array name="activity_types">
|
||||||
@@ -328,11 +311,11 @@
|
|||||||
<string name="stats_custom_layout_key" translatable="false">statsCustomLayoutKey</string>
|
<string name="stats_custom_layout_key" translatable="false">statsCustomLayoutKey</string>
|
||||||
|
|
||||||
<!-- Stats layout customization: number of columns per row -->
|
<!-- 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>
|
<integer name="stats_custom_layout_columns_default" translatable="false">2</integer>
|
||||||
|
|
||||||
<!-- Stats layout customization: key -->
|
<!-- 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 -->
|
<!-- Stats keys -->
|
||||||
<string name="stats_custom_layout_distance_key" translatable="false">distance</string>
|
<string name="stats_custom_layout_distance_key" translatable="false">distance</string>
|
||||||
<string name="stats_custom_layout_total_time_key" translatable="false">total_time</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_completed">Completed</string>
|
||||||
<string name="generic_click_twice_cancel">Click back again to cancel</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_choose_an_option">Choose an option</string>
|
||||||
|
<string name="generic_undo">Undo</string>
|
||||||
<!-- Gps -->
|
<!-- Gps -->
|
||||||
<string name="gps_starting">Starting GPS</string>
|
<string name="gps_starting">Starting GPS</string>
|
||||||
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</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_select_all">Select all</string>
|
||||||
<string name="menu_settings">Settings</string>
|
<string name="menu_settings">Settings</string>
|
||||||
<string name="menu_share">Share</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_show_on_map">Show on map</string>
|
||||||
<string name="menu_start_gps">Start GPS</string>
|
<string name="menu_start_gps">Start GPS</string>
|
||||||
<string name="menu_stop_gps">Stop GPS</string>
|
<string name="menu_stop_gps">Stop GPS</string>
|
||||||
<string name="menu_voice_rate">Voice speed</string>
|
<string name="menu_voice_rate">Voice speed</string>
|
||||||
<string name="menu_resume_track">Resume track</string>
|
<string name="menu_resume_track">Resume track</string>
|
||||||
|
<string name="menu_add_custom_layout">New Layout</string>
|
||||||
<!-- OpenTracks -->
|
<!-- OpenTracks -->
|
||||||
<string name="recording_service">Recording service</string>
|
<string name="recording_service">Recording service</string>
|
||||||
<string name="delete_service">Deletion 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="bluetooth_disabled">Please enable Bluetooth.</string>
|
||||||
<string name="settings_not_while_recording">Not available while recording a track</string>
|
<string name="settings_not_while_recording">Not available while recording a track</string>
|
||||||
<string name="settings_show">Show</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 -->
|
<!-- Settings Advanced -->
|
||||||
<string name="settings_advanced">Advanced</string>
|
<string name="settings_advanced">Advanced</string>
|
||||||
<string name="settings_default_trackfileformat">Export/sharing file format</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_announcements_distance_frequency">Distance interval</string>
|
||||||
|
|
||||||
<string name="settings_reset">Reset</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 -->
|
<!-- Custom Layout Reset -->
|
||||||
<string name="settings_layout_reset">Reset Custom Layout</string>
|
<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>
|
<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