forked from upstream-mirrors/OpenTracks
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 220c7b1443 | |||
| 33bb63976d | |||
| 8db8f68d69 | |||
| 99b417217e | |||
| ac80386b8a | |||
| b96441a964 | |||
| 207f9563df | |||
| 7b84bf448b | |||
| 41a62fd412 | |||
| 1087dbf387 | |||
| e16d1d53c6 | |||
| 00c9ba6595 | |||
| 7c1995c67f | |||
| d2e0ae8b11 | |||
| f0aa49537d | |||
| 14ace294f3 | |||
| b53ef455a6 | |||
| 3dc6c6607d | |||
| dd91e05fb6 | |||
| 19fe5275b9 | |||
| 1aeb9ca332 | |||
| 520c240a19 | |||
| 48aaca13d0 | |||
| 88a31f4f39 | |||
| 5ce9e6606b | |||
| 4ea70b15e7 | |||
| d3cf2957cc | |||
| dbac595754 | |||
| d70e7b8788 | |||
| f6835852b6 | |||
| d5864726f6 | |||
| 3cc3271726 | |||
| 8aef9c8d28 | |||
| bcca00d110 | |||
| aa929161b4 | |||
| 70cb9cbf51 | |||
| fef3b97661 | |||
| 945f366195 | |||
| 8c0a851d87 |
@@ -40,8 +40,8 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<a href="https://weblate.bubu1.eu/engage/OpenTracks/">
|
||||
<img src="https://weblate.bubu1.eu/widgets/OpenTracks/-/opentracks/horizontal-auto.svg" alt="Translation status" />
|
||||
<a href="https://hosted.weblate.org/engage/opentracks/">
|
||||
<img src="https://hosted.weblate.org/widgets/opentracks/-/horizontal-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## Screenshots
|
||||
|
||||
+4
-2
@@ -20,7 +20,7 @@ Usually, it takes two days before the update is published (build, signed, and pu
|
||||
The recent build activity can be found [here](https://f-droid.org/wiki/index.php?title=Special:RecentChanges&days=7&from=&hidebots=0&hideanons=1&hideliu=1&limit=500)
|
||||
|
||||
## Google Play Store
|
||||
Application id: de.dennisguse.opentracks.playstore
|
||||
Application id: `de.dennisguse.opentracks.playstore`
|
||||
|
||||
Requirement: `gem install fastlane`
|
||||
|
||||
@@ -35,4 +35,6 @@ Supported languages: https://support.google.com/googleplay/android-developer/tab
|
||||
|
||||
## Translations (localization)
|
||||
|
||||
Translations of the OpenTracks are handled in Weblate: [https://weblate.bubu1.eu/projects/OpenTracks/](https://weblate.bubu1.eu/projects/OpenTracks/).
|
||||
Translations of the OpenTracks are handled in
|
||||
Weblate: [https://hosted.weblate.org/projects/opentracks/](https://hosted.weblate.org/projects/opentracks)
|
||||
.
|
||||
+10
-9
@@ -59,8 +59,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "de.dennisguse.opentracks"
|
||||
versionCode 5080
|
||||
versionName "v4.1.2"
|
||||
versionCode 5119
|
||||
versionName "v4.2.0"
|
||||
|
||||
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
|
||||
|
||||
@@ -125,16 +125,17 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.4.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.8.1'
|
||||
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
|
||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:4.11.0'
|
||||
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||
androidTestUtil 'androidx.test:orchestrator:1.4.2'
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ Changes:
|
||||
Bugfix:
|
||||
- Speech announcements use MAY_DUCK (again)
|
||||
- ChartView shows markers (again)
|
||||
- Fixed landscape layout of TrackListActivity
|
||||
- Fixed landscape recordingLayout of TrackListActivity
|
||||
- Request legacy file API behavior (necessary for Android 10)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
v4.2.0: OpenTracks
|
||||
|
||||
Changes:
|
||||
- Show heartrate zone information
|
||||
|
||||
Developer:
|
||||
- Migrated to hosted.weblate.org
|
||||
- Recording UI uses Views directly
|
||||
+25
-24
@@ -12,7 +12,6 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -166,7 +165,8 @@ public class ExportImportTest {
|
||||
service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:02:18Z");
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||
BluetoothRemoteSensorManager mockRemoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:20Z");
|
||||
@@ -179,7 +179,7 @@ public class ExportImportTest {
|
||||
sendLocation(trackPointCreator, "2020-02-02T02:03:23Z", 3, 16.001, 10, 27, 15, 10, 0);
|
||||
|
||||
trackPointCreator.setClock("2020-02-02T02:03:24Z");
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator));
|
||||
trackPointCreator.setRemoteSensorManager(mockRemoteSensorManager);
|
||||
service.endCurrentTrack();
|
||||
|
||||
Track track = contentProviderUtils.getTrack(trackId);
|
||||
@@ -495,32 +495,33 @@ public class ExportImportTest {
|
||||
}
|
||||
|
||||
private void mockBLESensorData(TrackPointCreator trackPointCreator, Float speed, Distance distance, float heartRate, float cadence, Float power) {
|
||||
trackPointCreator.setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, trackPointCreator) {
|
||||
@Override
|
||||
public SensorDataSet fill(@NonNull TrackPoint trackPoint) {
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
||||
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
Mockito.when(remoteSensorManager.fill(Mockito.any())).thenAnswer(invocation -> {
|
||||
TrackPoint trackPoint = invocation.getArgument(0);
|
||||
|
||||
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
|
||||
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
|
||||
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
||||
sensorDataSet.set(cyclingCadence);
|
||||
SensorDataSet sensorDataSet = new SensorDataSet();
|
||||
sensorDataSet.set(new SensorDataCyclingPower("power", "power", Power.of(power)));
|
||||
sensorDataSet.set(new SensorDataHeartRate("heartRate", "heartRate", HeartRate.of(heartRate)));
|
||||
|
||||
if (distance != null && speed != null) {
|
||||
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
}
|
||||
SensorDataCyclingCadence cyclingCadence = Mockito.mock(SensorDataCyclingCadence.class);
|
||||
Mockito.when(cyclingCadence.hasValue()).thenReturn(true);
|
||||
Mockito.when(cyclingCadence.getValue()).thenReturn(Cadence.of(cadence));
|
||||
sensorDataSet.set(cyclingCadence);
|
||||
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
if (distance != null && speed != null) {
|
||||
SensorDataCyclingDistanceSpeed.Data distanceSpeedData = Mockito.mock(SensorDataCyclingDistanceSpeed.Data.class);
|
||||
Mockito.when(distanceSpeedData.getDistanceOverall()).thenReturn(distance);
|
||||
Mockito.when(distanceSpeedData.getSpeed()).thenReturn(Speed.of(speed));
|
||||
SensorDataCyclingDistanceSpeed distanceSpeed = Mockito.mock(SensorDataCyclingDistanceSpeed.class);
|
||||
Mockito.when(distanceSpeed.hasValue()).thenReturn(true);
|
||||
Mockito.when(distanceSpeed.getValue()).thenReturn(distanceSpeedData);
|
||||
sensorDataSet.set(distanceSpeed);
|
||||
}
|
||||
|
||||
sensorDataSet.fillTrackPoint(trackPoint);
|
||||
return sensorDataSet;
|
||||
});
|
||||
trackPointCreator.setRemoteSensorManager(remoteSensorManager);
|
||||
trackPointCreator.onChange(new SensorDataSet());
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.junit.Test;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadenceAndDistanceSpeed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingPower;
|
||||
@@ -23,10 +24,10 @@ public class BluetoothUtilsTest {
|
||||
characteristic.setValue(new byte[]{0x02, 0x3C});
|
||||
|
||||
// when
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(60, heartRate);
|
||||
assertEquals(HeartRate.of(60), heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -36,10 +37,10 @@ public class BluetoothUtilsTest {
|
||||
characteristic.setValue(new byte[]{0x01, 0x01, 0x01});
|
||||
|
||||
// when
|
||||
int heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
// then
|
||||
assertEquals(257, heartRate);
|
||||
assertEquals(HeartRate.of(257), heartRate);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+4
-1
@@ -6,6 +6,8 @@ import android.location.Location;
|
||||
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@@ -27,7 +29,8 @@ public class TrackRecordingServiceTestUtils {
|
||||
TrackRecordingService service = ((TrackRecordingService.Binder) mServiceRule.bindService(new Intent(context, TrackRecordingService.class)))
|
||||
.getService();
|
||||
|
||||
service.getTrackPointCreator().setRemoteSensorManager(new BluetoothRemoteSensorManager(context, null, service.getTrackPointCreator()));
|
||||
BluetoothRemoteSensorManager remoteSensorManager = Mockito.mock(BluetoothRemoteSensorManager.class);
|
||||
service.getTrackPointCreator().setRemoteSensorManager(remoteSensorManager);
|
||||
service.getTrackPointCreator().setClock(Clock.systemUTC());
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
+18
-17
@@ -17,7 +17,8 @@ import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesOpenHelperTest {
|
||||
@@ -38,10 +39,10 @@ public class PreferencesOpenHelperTest {
|
||||
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());
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(recordingLayouts);
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertEquals(recordingLayouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,10 +64,10 @@ public class PreferencesOpenHelperTest {
|
||||
+ 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);
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(recordingLayouts);
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,10 +88,10 @@ public class PreferencesOpenHelperTest {
|
||||
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);
|
||||
List<RecordingLayout> recordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(recordingLayouts);
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,10 +110,10 @@ public class PreferencesOpenHelperTest {
|
||||
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();
|
||||
RecordingLayout defaultRecordingLayout = RecordingLayoutIO.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
||||
List<RecordingLayout> customRecordingLayout = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(customLayout.size(), 1);
|
||||
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
|
||||
assertEquals(customRecordingLayout.size(), 1);
|
||||
assertEquals(defaultRecordingLayout.toCsv(), customRecordingLayout.get(0).toCsv());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import java.util.List;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesUtilsTest {
|
||||
@@ -85,13 +86,13 @@ public class PreferencesUtilsTest {
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> recordingLayouts = 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));
|
||||
assertEquals(recordingLayouts.size(), 1);
|
||||
assertTrue(recordingLayouts.get(0).getFields().size() > 0);
|
||||
assertEquals(recordingLayouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(recordingLayouts.get(0).getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,12 +104,12 @@ public class PreferencesUtilsTest {
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then
|
||||
assertTrue(layout.getFields().size() > 0);
|
||||
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
assertTrue(recordingLayout.getFields().size() > 0);
|
||||
assertEquals(recordingLayout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(recordingLayout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,28 +127,28 @@ public class PreferencesUtilsTest {
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getName(), "run");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 4);
|
||||
assertEquals(recordingLayout.getName(), "run");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
|
||||
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).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(recordingLayout.getFields().get(0).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(recordingLayout.getFields().get(1).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key));
|
||||
assertFalse(recordingLayout.getFields().get(2).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(2).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(recordingLayout.getFields().get(3).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -165,44 +166,44 @@ public class PreferencesUtilsTest {
|
||||
editor.apply();
|
||||
|
||||
// when
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getName(), "walking");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 4);
|
||||
assertEquals(recordingLayout.getName(), "walking");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
|
||||
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).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(recordingLayout.getFields().get(0).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(0).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(layout.getFields().get(1).isVisible());
|
||||
assertFalse(layout.getFields().get(1).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
|
||||
assertTrue(recordingLayout.getFields().get(1).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(1).isPrimary());
|
||||
|
||||
assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||
assertFalse(layout.getFields().get(2).isVisible());
|
||||
assertTrue(layout.getFields().get(2).isPrimary());
|
||||
assertTrue(layout.getFields().get(2).isWide());
|
||||
assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key));
|
||||
assertFalse(recordingLayout.getFields().get(2).isVisible());
|
||||
assertTrue(recordingLayout.getFields().get(2).isPrimary());
|
||||
assertTrue(recordingLayout.getFields().get(2).isWide());
|
||||
|
||||
assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(layout.getFields().get(3).isVisible());
|
||||
assertFalse(layout.getFields().get(3).isPrimary());
|
||||
assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key));
|
||||
assertFalse(recordingLayout.getFields().get(3).isVisible());
|
||||
assertFalse(recordingLayout.getFields().get(3).isPrimary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCustomLayout() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
Layout layoutSrc = new Layout("road cycling");
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||
RecordingLayout recordingLayoutSrc = new RecordingLayout("road cycling");
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), true, true, false));
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), true, false, false));
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), false, true, false));
|
||||
recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), false, false, false));
|
||||
|
||||
// when
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), recordingLayoutSrc.toCsv());
|
||||
editor.commit();
|
||||
|
||||
// then
|
||||
@@ -215,13 +216,13 @@ public class PreferencesUtilsTest {
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;");
|
||||
|
||||
Layout layoutDst = PreferencesUtils.getCustomLayout();
|
||||
assertEquals(layoutSrc.getName(), layoutDst.getName());
|
||||
assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size());
|
||||
for (int i = 0; i < layoutSrc.getFields().size(); i++) {
|
||||
assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey());
|
||||
assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible());
|
||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
||||
RecordingLayout recordingLayoutDst = PreferencesUtils.getCustomLayout();
|
||||
assertEquals(recordingLayoutSrc.getName(), recordingLayoutDst.getName());
|
||||
assertEquals(recordingLayoutSrc.getFields().size(), recordingLayoutDst.getFields().size());
|
||||
for (int i = 0; i < recordingLayoutSrc.getFields().size(); i++) {
|
||||
assertEquals(recordingLayoutSrc.getFields().get(i).getKey(), recordingLayoutDst.getFields().get(i).getKey());
|
||||
assertEquals(recordingLayoutSrc.getFields().get(i).isVisible(), recordingLayoutDst.getFields().get(i).isVisible());
|
||||
assertEquals(recordingLayoutSrc.getFields().get(i).isPrimary(), recordingLayoutDst.getFields().get(i).isPrimary());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +248,7 @@ public class PreferencesUtilsTest {
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
String cyclingProfileUpdated = "cycling;2;"
|
||||
@@ -256,14 +257,14 @@ public class PreferencesUtilsTest {
|
||||
+ 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));
|
||||
List<RecordingLayout> layoutsToBeUpdated = new ArrayList<>();
|
||||
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources));
|
||||
layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(runningProfile, resources));
|
||||
|
||||
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
|
||||
|
||||
// then only updated profile is modified in the custom layouts
|
||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(layoutsBefore.size(), 2);
|
||||
assertEquals(layoutsAfter.size(), 2);
|
||||
@@ -294,7 +295,7 @@ public class PreferencesUtilsTest {
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
String cyclingProfileUpdated = "cycling;2;"
|
||||
@@ -302,11 +303,11 @@ public class PreferencesUtilsTest {
|
||||
+ 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);
|
||||
RecordingLayout recordingLayoutToBeUpdated = RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources);
|
||||
PreferencesUtils.updateCustomLayout(recordingLayoutToBeUpdated);
|
||||
|
||||
// then only updated profile is modified in the custom layouts
|
||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
List<RecordingLayout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(layoutsBefore.size(), 2);
|
||||
assertEquals(layoutsAfter.size(), 2);
|
||||
@@ -337,9 +338,9 @@ public class PreferencesUtilsTest {
|
||||
editor.apply();
|
||||
|
||||
// when it gets the custom layout
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then the first one was returned
|
||||
assertEquals(layout.getName(), "cycling");
|
||||
assertEquals(recordingLayout.getName(), "cycling");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import org.junit.runner.RunWith;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CsvLayoutUtilsTest extends TestCase {
|
||||
public class CsvRecordingLayoutUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testGetCsvLineParts() {
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.longClick;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
import androidx.test.espresso.ViewAssertion;
|
||||
import androidx.test.espresso.ViewInteraction;
|
||||
import androidx.test.espresso.matcher.ViewMatchers;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EspressoCustomLayoutTest {
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<TrackListActivity> mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mGrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
@Test
|
||||
public void customLayoutTest() {
|
||||
// TrackListActivity: start recording
|
||||
ViewInteraction trackControllerRecordButton = onView(ViewMatchers.withId(R.id.track_list_fab_action));
|
||||
trackControllerRecordButton.perform(click());
|
||||
|
||||
// Get custom layout preferences and check all data fields are showed.
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
|
||||
|
||||
// stop recording
|
||||
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
|
||||
trackControllerStopButton.perform(longClick());
|
||||
}
|
||||
|
||||
private static class RecyclerViewItemCountAssertion implements ViewAssertion {
|
||||
private final int expectedCount;
|
||||
|
||||
public RecyclerViewItemCountAssertion(int expectedCount) {
|
||||
this.expectedCount = expectedCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(View view, NoMatchingViewException noViewFoundException) {
|
||||
if (noViewFoundException != null) {
|
||||
throw noViewFoundException;
|
||||
}
|
||||
|
||||
RecyclerView recyclerView = (RecyclerView) view;
|
||||
RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
|
||||
assertThat(adapter.getItemCount(), is(expectedCount));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+39
-43
@@ -17,7 +17,7 @@ import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LayoutTest extends TestCase {
|
||||
public class RecordingLayoutTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
@@ -28,21 +28,17 @@ public class LayoutTest extends TestCase {
|
||||
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();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "running");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getName(), "running");
|
||||
assertEquals(recordingLayout.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());
|
||||
@@ -64,13 +60,13 @@ public class LayoutTest extends TestCase {
|
||||
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();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(recordingLayout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,13 +75,13 @@ public class LayoutTest extends TestCase {
|
||||
String csv1 = "Layout Name;2;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(recordingLayout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,13 +90,13 @@ public class LayoutTest extends TestCase {
|
||||
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();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
assertEquals(recordingLayout.getName(), "Layout Name");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,27 +105,27 @@ public class LayoutTest extends TestCase {
|
||||
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();
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = recordingLayout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 1);
|
||||
assertEquals(recordingLayout.getName(), "Layout Name");
|
||||
assertEquals(recordingLayout.getColumnsPerRow(), 2);
|
||||
assertEquals(recordingLayout.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);
|
||||
RecordingLayout recordingLayout = new RecordingLayout("Test Layout", 2);
|
||||
recordingLayout.addField(new DataField("key1", false, false, false));
|
||||
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||
recordingLayout.addField(new DataField("key3", true, false, false));
|
||||
recordingLayout.addField(new DataField("key4", true, true, false));
|
||||
recordingLayout.addField(new DataField("key5", true, true, true));
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.toCsv();
|
||||
String csv = recordingLayout.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;");
|
||||
@@ -138,15 +134,15 @@ public class LayoutTest extends TestCase {
|
||||
@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);
|
||||
RecordingLayout recordingLayout = new RecordingLayout("Test Layout");
|
||||
recordingLayout.addField(new DataField("key1", false, false, false));
|
||||
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||
recordingLayout.addField(new DataField("key3", true, false, false));
|
||||
recordingLayout.addField(new DataField("key4", true, true, false));
|
||||
recordingLayout.addField(new DataField("key5", true, true, true));
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.toCsv();
|
||||
String csv = recordingLayout.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;");
|
||||
@@ -12,7 +12,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class StatisticsUtilsTest extends TestCase {
|
||||
@@ -44,16 +44,16 @@ public class StatisticsUtilsTest extends TestCase {
|
||||
@Test
|
||||
public void testFilterVisible() {
|
||||
// given
|
||||
Layout layout = new Layout("profile");
|
||||
layout.addField(new DataField("key1", "title1", true, true, false));
|
||||
layout.addField(new DataField("key2", "title2", false, true, false));
|
||||
layout.addField(new DataField("key3", "title3", true, true, false));
|
||||
layout.addField(new DataField("key4", "title4", false, true, false));
|
||||
layout.addField(new DataField("key5", "title5", true, true, false));
|
||||
RecordingLayout recordingLayout = new RecordingLayout("profile");
|
||||
recordingLayout.addField(new DataField("key1", true, true, false));
|
||||
recordingLayout.addField(new DataField("key2", false, true, false));
|
||||
recordingLayout.addField(new DataField("key3", true, true, false));
|
||||
recordingLayout.addField(new DataField("key4", false, true, false));
|
||||
recordingLayout.addField(new DataField("key5", true, true, false));
|
||||
|
||||
// when
|
||||
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
|
||||
Layout resultFalse = StatisticsUtils.filterVisible(layout, false);
|
||||
RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||
RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||
|
||||
// then
|
||||
assertEquals(resultTrue.getFields().size(), 3);
|
||||
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StatisticDataBuilderTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Mock
|
||||
private Track trackMock;
|
||||
|
||||
@Mock
|
||||
private TrackStatistics trackStatisticsMock;
|
||||
|
||||
@Mock
|
||||
private RecordingData recordingDataMock;
|
||||
|
||||
@Mock
|
||||
private SensorStatistics sensorStatisticsMock;
|
||||
|
||||
@Test
|
||||
public void testFromRecordingData() {
|
||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
|
||||
// given
|
||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 3);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromRecordingData_allFields() {
|
||||
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
|
||||
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
|
||||
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
|
||||
|
||||
// given
|
||||
Layout layout = new Layout(context.getString(R.string.default_activity_default));
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false);
|
||||
layout.addField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 18);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_total_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_max_speed))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_fastest_pace))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_altitude))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_gain))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_loss))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_coordinates))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyHeartRate() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
|
||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 2);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyCadence() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
|
||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(false);
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 2);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics_onlyPower() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromSensorStatistics() {
|
||||
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
|
||||
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
|
||||
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
|
||||
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
|
||||
when(sensorStatisticsMock.hasPower()).thenReturn(true);
|
||||
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
|
||||
|
||||
// when
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
|
||||
|
||||
// then
|
||||
assertEquals(statisticDataList.size(), 5);
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
|
||||
assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextClock;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.CustomLayoutFieldType;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
|
||||
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<StatisticData> statisticDataList;
|
||||
private final Context context;
|
||||
|
||||
public StatisticsAdapter(Context context) {
|
||||
this.context = context;
|
||||
this.statisticDataList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view;
|
||||
if (viewType == CustomLayoutFieldType.CLOCK.value()) {
|
||||
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_clock_item, parent, false);
|
||||
return new StatisticsAdapter.ViewClockHolder(view);
|
||||
} else {
|
||||
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_generic_item, parent, false);
|
||||
return new StatisticsAdapter.ViewHolder(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
StatisticData statisticData = statisticDataList.get(position);
|
||||
if (getItemViewType(position) == CustomLayoutFieldType.CLOCK.value()) {
|
||||
StatisticsAdapter.ViewClockHolder viewHolder = (StatisticsAdapter.ViewClockHolder) holder;
|
||||
viewHolder.setData(statisticData);
|
||||
} else {
|
||||
StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder;
|
||||
viewHolder.setData(statisticData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (statisticDataList == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return statisticDataList.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (statisticDataList.get(position).getField().getType(context) == CustomLayoutFieldType.CLOCK) {
|
||||
return CustomLayoutFieldType.CLOCK.value();
|
||||
} else {
|
||||
return CustomLayoutFieldType.GENERIC.value();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return statisticDataList.get(position).getField().isWide();
|
||||
}
|
||||
|
||||
public List<StatisticData> swapData(List<StatisticData> data) {
|
||||
if (statisticDataList == data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
statisticDataList = data;
|
||||
|
||||
if (data != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static class WithRecordedLayout extends StatisticsAdapter {
|
||||
public WithRecordedLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_recorded_item, parent, false);
|
||||
return new StatisticsAdapter.ViewRecordedHolder(view);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
final TextView descMain;
|
||||
final TextView descSecondary;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
descMain = itemView.findViewById(R.id.stats_description_main);
|
||||
descSecondary = itemView.findViewById(R.id.stats_description_secondary);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
||||
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
this.unit.setText(statisticData.getUnit());
|
||||
this.descMain.setText(statisticData.getField().getTitle());
|
||||
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
||||
if (statisticData.hasDescription()) {
|
||||
this.descSecondary.setVisibility(View.VISIBLE);
|
||||
this.descSecondary.setText(statisticData.getDescription());
|
||||
} else {
|
||||
this.descSecondary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewClockHolder extends RecyclerView.ViewHolder {
|
||||
final TextClock value;
|
||||
final TextView descMain;
|
||||
|
||||
public ViewClockHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
value = itemView.findViewById(R.id.stats_clock);
|
||||
descMain = itemView.findViewById(R.id.stats_description_main);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewRecordedHolder extends ViewHolder {
|
||||
|
||||
public ViewRecordedHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public void setData(StatisticData statisticData) {
|
||||
if (statisticData == null) {
|
||||
return;
|
||||
}
|
||||
this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
|
||||
this.unit.setText(statisticData.getUnit());
|
||||
this.descMain.setText(statisticData.getField().getTitle());
|
||||
if (statisticData.hasDescription()) {
|
||||
this.descSecondary.setVisibility(View.VISIBLE);
|
||||
this.descSecondary.setText(statisticData.getDescription());
|
||||
} else {
|
||||
this.descSecondary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.dennisguse.opentracks.data.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class HeartRateZones {
|
||||
|
||||
private final HeartRate max;
|
||||
|
||||
public HeartRateZones(@NonNull HeartRate max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public int getColorForZone(HeartRate current) {
|
||||
if (current != null) {
|
||||
if (current.getBPM() >= max.getBPM() * 0.9) {
|
||||
return R.color.heart_rate_zone_textcolor_5;
|
||||
}
|
||||
if (current.getBPM() >= max.getBPM() * 0.8) {
|
||||
return R.color.heart_rate_zone_textcolor_4;
|
||||
}
|
||||
if (current.getBPM() >= max.getBPM() * 0.7) {
|
||||
return R.color.heart_rate_zone_textcolor_3;
|
||||
}
|
||||
if (current.getBPM() >= max.getBPM() * 0.6) {
|
||||
return R.color.heart_rate_zone_textcolor_2;
|
||||
}
|
||||
}
|
||||
|
||||
return R.color.heart_rate_zone_textcolor_1;
|
||||
}
|
||||
}
|
||||
@@ -385,10 +385,6 @@ public class TrackPoint {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasSensorData() {
|
||||
return hasHeartRate() || hasCadence() || hasPower();
|
||||
}
|
||||
|
||||
public boolean hasHeartRate() {
|
||||
return heartRate != null;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,8 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackRecordedActivity;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
@@ -47,8 +44,6 @@ import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticDataBuilder;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a recorded {@link Track}.
|
||||
@@ -72,7 +67,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
}
|
||||
|
||||
private SensorStatistics sensorStatistics;
|
||||
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
|
||||
|
||||
private Track.Id trackId;
|
||||
@Nullable // Lazily loaded.
|
||||
@@ -113,8 +107,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
trackId = getArguments().getParcelable(TRACK_ID_KEY);
|
||||
contentProviderUtils = new ContentProviderUtils(getContext());
|
||||
|
||||
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,7 +115,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
|
||||
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
|
||||
sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
// sensorsRecyclerView.setAdapter(sensorsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
@@ -261,10 +253,6 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
if (sensorStatistics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
|
||||
if (sensorDataList.size() > 0) {
|
||||
sensorsAdapter.swapData(sensorDataList);
|
||||
}
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.fragments;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -9,16 +10,14 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.gridlayout.widget.GridLayout;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
@@ -26,9 +25,10 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticData;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.viewmodels.Mapping;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
|
||||
|
||||
/**
|
||||
* A fragment to display track statistics to the user for a currently recording {@link Track}.
|
||||
@@ -48,32 +48,22 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
private RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
|
||||
|
||||
private Layout layout;
|
||||
private final List<StatisticViewHolder<?>> viewHolders = new LinkedList<>();
|
||||
|
||||
private RecordingLayout recordingLayout;
|
||||
|
||||
private StatisticsRecordingBinding viewBinding;
|
||||
private StatisticsAdapter statisticsAdapter;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private StatisticsDataModel viewModel;
|
||||
private LiveData<List<StatisticData>> statisticsLiveData;
|
||||
|
||||
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
|
||||
boolean updateUInecessary = false;
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
|
||||
updateUInecessary = true;
|
||||
unitSystem = PreferencesUtils.getUnitSystem();
|
||||
updateDataOnUI();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
|
||||
updateUInecessary = true;
|
||||
layout = PreferencesUtils.getCustomLayout();
|
||||
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
getActivity().runOnUiThread(this::updateUI);
|
||||
onLayoutChanged(PreferencesUtils.getCustomLayout());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,30 +74,11 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
|
||||
statisticsAdapter = new StatisticsAdapter(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
|
||||
|
||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||
layout = PreferencesUtils.getCustomLayout();
|
||||
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
if (statisticsAdapter.isItemWide(position)) {
|
||||
return layout.getColumnsPerRow();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
recyclerView.setLayoutManager(gridLayoutManager);
|
||||
recyclerView.setAdapter(statisticsAdapter);
|
||||
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@@ -117,10 +88,6 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||
|
||||
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
|
||||
statisticsLiveData = viewModel.getStatsData();
|
||||
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
|
||||
|
||||
trackRecordingServiceConnection.startConnection(getContext());
|
||||
}
|
||||
|
||||
@@ -139,6 +106,7 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
viewHolders.clear();
|
||||
viewBinding = null;
|
||||
}
|
||||
|
||||
@@ -146,17 +114,56 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
trackRecordingServiceConnection = null;
|
||||
viewModel = null;
|
||||
|
||||
if (statisticsLiveData != null) {
|
||||
statisticsLiveData.removeObservers(getActivity());
|
||||
}
|
||||
statisticsLiveData = null;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
if (isResumed()) {
|
||||
viewModel.update(recordingData, layout, unitSystem);
|
||||
private void onLayoutChanged(@NonNull RecordingLayout newRecordingLayout) {
|
||||
if (newRecordingLayout.equals(recordingLayout)) {
|
||||
return;
|
||||
}
|
||||
recordingLayout = newRecordingLayout;
|
||||
|
||||
viewBinding.statsLayout.setColumnCount(recordingLayout.getColumnsPerRow());
|
||||
|
||||
viewBinding.statsLayout.removeAllViews(); //Let's start from scratch
|
||||
viewHolders.clear();
|
||||
|
||||
Map<String, Callable<StatisticViewHolder<?>>> m = Mapping.create(getContext());
|
||||
|
||||
int rowIndex = 0;
|
||||
int columnIndex = 0;
|
||||
for (int i = 0; i < recordingLayout.getFields().size(); i++) {
|
||||
DataField dataField = recordingLayout.getFields().get(i);
|
||||
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
|
||||
param.setGravity(Gravity.FILL_HORIZONTAL);
|
||||
param.width = 0;
|
||||
|
||||
if (dataField.isWide()) {
|
||||
rowIndex++;
|
||||
param.columnSpec = GridLayout.spec(0, recordingLayout.getColumnsPerRow(), 1);
|
||||
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
|
||||
columnIndex = 0;
|
||||
rowIndex++;
|
||||
} else {
|
||||
if (columnIndex >= recordingLayout.getColumnsPerRow()) {
|
||||
columnIndex = 0;
|
||||
rowIndex++;
|
||||
}
|
||||
param.columnSpec = GridLayout.spec(columnIndex, 1, 1);
|
||||
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
|
||||
|
||||
columnIndex++;
|
||||
}
|
||||
|
||||
try {
|
||||
StatisticViewHolder<?> viewHolder = m.get(dataField.getKey()).call();
|
||||
viewHolder.initialize(getContext(), getLayoutInflater());
|
||||
viewHolder.configureUI(dataField);
|
||||
viewHolders.add(viewHolder);
|
||||
|
||||
viewBinding.statsLayout.addView(viewHolder.getView(), param);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +176,12 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
|
||||
}
|
||||
|
||||
updateUI();
|
||||
updateDataOnUI();
|
||||
}
|
||||
|
||||
private void updateDataOnUI() {
|
||||
if (isResumed()) {
|
||||
viewHolders.forEach(i -> i.onChanged(unitSystem, recordingData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ public class BluetoothRemoteSensorManagerHeartRate extends BluetoothConnectionMa
|
||||
|
||||
@Override
|
||||
protected SensorDataHeartRate parsePayload(@NonNull ServiceMeasurementUUID serviceMeasurementUUID, String sensorName, String address, BluetoothGattCharacteristic characteristic) {
|
||||
Integer heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
HeartRate heartRate = BluetoothUtils.parseHeartRate(characteristic);
|
||||
|
||||
return heartRate != null ? new SensorDataHeartRate(address, sensorName, HeartRate.of(heartRate)) : null;
|
||||
return heartRate != null ? new SensorDataHeartRate(address, sensorName, heartRate) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.Power;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataCyclingCadence;
|
||||
@@ -101,7 +102,7 @@ public class BluetoothUtils {
|
||||
return BluetoothUtils.getAdapter(context) != null;
|
||||
}
|
||||
|
||||
public static Integer parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||
public static HeartRate parseHeartRate(BluetoothGattCharacteristic characteristic) {
|
||||
//DOCUMENTATION https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.heart_rate_measurement.xml
|
||||
byte[] raw = characteristic.getValue();
|
||||
if (raw.length == 0) {
|
||||
@@ -110,10 +111,10 @@ public class BluetoothUtils {
|
||||
|
||||
boolean formatUINT16 = ((raw[0] & 0x1) == 1);
|
||||
if (formatUINT16 && raw.length >= 3) {
|
||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
|
||||
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1));
|
||||
}
|
||||
if (!formatUINT16 && raw.length >= 2) {
|
||||
return characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
|
||||
return HeartRate.of(characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -42,11 +42,14 @@ import java.util.stream.Stream;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.HeartRate;
|
||||
import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
|
||||
import de.dennisguse.opentracks.util.IntentDashboardUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
|
||||
@@ -278,6 +281,12 @@ public class PreferencesUtils {
|
||||
return getBoolean(R.string.settings_sensor_bluetooth_service_filter_enabled_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static HeartRateZones getHeartRateZones() {
|
||||
final int DEFAULT = Integer.parseInt(resources.getString(R.string.settings_sensor_heart_rate_max_default));
|
||||
int value = getInt(R.string.settings_sensor_heart_rate_max_key, DEFAULT);
|
||||
return new HeartRateZones(HeartRate.of(value));
|
||||
}
|
||||
|
||||
public static boolean shouldShowStatsOnLockscreen() {
|
||||
final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = resources.getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default);
|
||||
return getBoolean(R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT);
|
||||
@@ -753,35 +762,35 @@ public class PreferencesUtils {
|
||||
/**
|
||||
* @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 RecordingLayout getCustomLayout() {
|
||||
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
|
||||
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
|
||||
String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null);
|
||||
if (layoutSelected == null) {
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
for (String line : csvLines) {
|
||||
Layout layout = Layout.fromCsv(line, resources);
|
||||
if (layout.sameName(layoutSelected)) {
|
||||
return layout;
|
||||
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(line, resources);
|
||||
if (recordingLayout.sameName(layoutSelected)) {
|
||||
return recordingLayout;
|
||||
}
|
||||
}
|
||||
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
|
||||
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
|
||||
public static void updateCustomLayouts(@NonNull List<RecordingLayout> recordingLayouts) {
|
||||
setString(R.string.stats_custom_layouts_key, RecordingLayoutIO.toCSV(recordingLayouts));
|
||||
}
|
||||
|
||||
public static void updateCustomLayout(@NonNull Layout layout) {
|
||||
List<Layout> preferenceLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
Optional<Layout> layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst();
|
||||
public static void updateCustomLayout(@NonNull RecordingLayout recordingLayout) {
|
||||
List<RecordingLayout> preferenceRecordingLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
Optional<RecordingLayout> layoutToBeUpdated = preferenceRecordingLayouts.stream().filter(l -> l.sameName(recordingLayout)).findFirst();
|
||||
if (layoutToBeUpdated.isPresent()) {
|
||||
layoutToBeUpdated.get().replaceAllFields(layout.getFields());
|
||||
layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow());
|
||||
PreferencesUtils.updateCustomLayouts(preferenceLayouts);
|
||||
layoutToBeUpdated.get().replaceAllFields(recordingLayout.getFields());
|
||||
layoutToBeUpdated.get().setColumnsPerRow(recordingLayout.getColumnsPerRow());
|
||||
PreferencesUtils.updateCustomLayouts(preferenceRecordingLayouts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,19 +804,19 @@ public class PreferencesUtils {
|
||||
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
|
||||
}
|
||||
|
||||
public static List<Layout> getAllCustomLayouts() {
|
||||
List<Layout> layouts = new ArrayList<>();
|
||||
public static List<RecordingLayout> getAllCustomLayouts() {
|
||||
List<RecordingLayout> recordingLayouts = 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));
|
||||
recordingLayouts.add(RecordingLayoutIO.fromCsv(line, resources));
|
||||
}
|
||||
|
||||
return layouts;
|
||||
return recordingLayouts;
|
||||
}
|
||||
|
||||
public static List<String> getAllCustomLayoutNames() {
|
||||
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
|
||||
return getAllCustomLayouts().stream().map(RecordingLayout::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void resetCustomLayoutPreferences() {
|
||||
|
||||
+25
-25
@@ -17,7 +17,7 @@ import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter;
|
||||
import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
@@ -30,8 +30,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
||||
private String profile;
|
||||
private Layout layoutFieldsVisible;
|
||||
private Layout layoutFieldsHidden;
|
||||
private RecordingLayout recordingLayoutFieldsVisible;
|
||||
private RecordingLayout recordingLayoutFieldsHidden;
|
||||
private int numColumns;
|
||||
|
||||
@Override
|
||||
@@ -39,12 +39,12 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Recycler view with visible stats.
|
||||
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = layout.getName();
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
|
||||
RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = recordingLayout.getName();
|
||||
recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible);
|
||||
|
||||
numColumns = layout.getColumnsPerRow();
|
||||
numColumns = recordingLayout.getColumnsPerRow();
|
||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@@ -65,7 +65,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
int fromPosition = viewHolder.getAdapterPosition();
|
||||
int toPosition = target.getAdapterPosition();
|
||||
layoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
||||
recordingLayoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false);
|
||||
|
||||
// Recycler view with not visible stats.
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
|
||||
recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden);
|
||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||
@@ -103,18 +103,18 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
||||
Layout layout = new Layout(profile, numColumns);
|
||||
layout.addFields(layoutFieldsVisible.getFields());
|
||||
layout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.updateCustomLayout(layout);
|
||||
if (!recordingLayoutFieldsVisible.getFields().isEmpty() || !recordingLayoutFieldsHidden.getFields().isEmpty()) {
|
||||
RecordingLayout recordingLayout = new RecordingLayout(profile, numColumns);
|
||||
recordingLayout.addFields(recordingLayoutFieldsVisible.getFields());
|
||||
recordingLayout.addFields(recordingLayoutFieldsHidden.getFields());
|
||||
PreferencesUtils.updateCustomLayout(recordingLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
layoutFieldsVisible = null;
|
||||
recordingLayoutFieldsVisible = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,24 +132,24 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement
|
||||
if (which == 0) {
|
||||
field.togglePrimary();
|
||||
} else {
|
||||
layoutFieldsVisible.removeField(field);
|
||||
recordingLayoutFieldsVisible.removeField(field);
|
||||
field.toggleVisibility();
|
||||
layoutFieldsHidden.addField(field);
|
||||
recordingLayoutFieldsHidden.addField(field);
|
||||
}
|
||||
|
||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
||||
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
layoutFieldsHidden.removeField(field);
|
||||
recordingLayoutFieldsHidden.removeField(field);
|
||||
field.toggleVisibility();
|
||||
layoutFieldsVisible.addField(field);
|
||||
recordingLayoutFieldsVisible.addField(field);
|
||||
viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||
|
||||
adapterFieldsVisible.swapValues(layoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(layoutFieldsHidden);
|
||||
adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible);
|
||||
adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -23,7 +23,7 @@ import com.google.android.material.textfield.TextInputLayout;
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutListAdapter;
|
||||
import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback;
|
||||
|
||||
@@ -96,7 +96,7 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final Layout item = adapter.getLayouts().get(position);
|
||||
final RecordingLayout item = adapter.getLayouts().get(position);
|
||||
|
||||
adapter.removeLayout(position);
|
||||
|
||||
@@ -150,9 +150,9 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) {
|
||||
public void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout) {
|
||||
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
|
||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout);
|
||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, recordingLayout);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import java.util.Objects;
|
||||
|
||||
public class DataField implements Parcelable {
|
||||
public static final String YES_VALUE = "1";
|
||||
public static final String NOT_VALUE = "0";
|
||||
|
||||
private final String key;
|
||||
private final String title;
|
||||
private boolean isVisible;
|
||||
private boolean isPrimary;
|
||||
private final boolean isWide;
|
||||
|
||||
public DataField(String key, String title, boolean isVisible, boolean isPrimary, boolean isWide) {
|
||||
public DataField(String key, boolean isVisible, boolean isPrimary, boolean isWide) {
|
||||
this.key = key;
|
||||
this.title = title;
|
||||
this.isVisible = isVisible;
|
||||
this.isPrimary = isPrimary;
|
||||
this.isWide = isWide;
|
||||
@@ -27,7 +21,6 @@ public class DataField implements Parcelable {
|
||||
|
||||
protected DataField(Parcel in) {
|
||||
key = in.readString();
|
||||
title = in.readString();
|
||||
isVisible = in.readByte() != 0;
|
||||
isPrimary = in.readByte() != 0;
|
||||
isWide = in.readByte() != 0;
|
||||
@@ -49,10 +42,6 @@ public class DataField implements Parcelable {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible;
|
||||
}
|
||||
@@ -73,65 +62,6 @@ public class DataField implements Parcelable {
|
||||
return isWide;
|
||||
}
|
||||
|
||||
public CustomLayoutFieldType getType(Context context) {
|
||||
if (key.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
|
||||
return CustomLayoutFieldType.CLOCK;
|
||||
} else {
|
||||
return CustomLayoutFieldType.GENERIC;
|
||||
}
|
||||
}
|
||||
|
||||
public String toCsv() {
|
||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
||||
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
|
||||
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) {
|
||||
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||
return resources.getString(R.string.stats_total_time);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||
return resources.getString(R.string.stats_moving_time);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
|
||||
return resources.getString(R.string.stats_distance);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
|
||||
return resources.getString(R.string.stats_speed);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
|
||||
return resources.getString(R.string.stats_pace);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||
return resources.getString(R.string.stats_average_moving_speed);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||
return resources.getString(R.string.stats_average_speed);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||
return resources.getString(R.string.stats_max_speed);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||
return resources.getString(R.string.stats_average_moving_pace);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||
return resources.getString(R.string.stats_average_pace);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||
return resources.getString(R.string.stats_fastest_pace);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||
return resources.getString(R.string.stats_altitude);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
|
||||
return resources.getString(R.string.stats_gain);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
|
||||
return resources.getString(R.string.stats_loss);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||
return resources.getString(R.string.stats_coordinates);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||
return resources.getString(R.string.stats_sensors_heart_rate);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||
return resources.getString(R.string.stats_sensors_cadence);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
|
||||
return resources.getString(R.string.stats_sensors_power);
|
||||
} else if (key.equals(resources.getString(R.string.stats_custom_layout_clock_key))) {
|
||||
return resources.getString(R.string.stats_clock);
|
||||
} else {
|
||||
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -140,9 +70,21 @@ public class DataField implements Parcelable {
|
||||
@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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DataField dataField = (DataField) o;
|
||||
return isVisible == dataField.isVisible && isPrimary == dataField.isPrimary && isWide == dataField.isWide && Objects.equals(key, dataField.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, isVisible, isPrimary, isWide);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-41
@@ -1,78 +1,50 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class Layout implements Parcelable {
|
||||
private static final String TAG = Layout.class.getSimpleName();
|
||||
public class RecordingLayout implements Parcelable {
|
||||
|
||||
// User-generated layout's name.
|
||||
private final String name;
|
||||
private int columnsPerRow;
|
||||
private final List<DataField> dataFields = new ArrayList<>();
|
||||
|
||||
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
||||
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
|
||||
if (csvParts == null) {
|
||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||
return new Layout(PreferencesUtils.getDefaultLayoutName());
|
||||
}
|
||||
|
||||
Layout layout = new Layout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));
|
||||
for (int i = 2; i < csvParts.size(); i++) {
|
||||
String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i));
|
||||
if (fieldParts == null) {
|
||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||
return layout;
|
||||
}
|
||||
layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
public Layout(String name) {
|
||||
public RecordingLayout(String name) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
|
||||
}
|
||||
|
||||
public Layout(String name, int columnsPerRow) {
|
||||
public RecordingLayout(String name, int columnsPerRow) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
protected Layout(Parcel in) {
|
||||
protected RecordingLayout(Parcel in) {
|
||||
name = in.readString();
|
||||
columnsPerRow = in.readInt();
|
||||
in.readList(dataFields, DataField.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<Layout> CREATOR = new Creator<>() {
|
||||
public static final Creator<RecordingLayout> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public Layout createFromParcel(Parcel in) {
|
||||
return new Layout(in);
|
||||
public RecordingLayout createFromParcel(Parcel in) {
|
||||
return new RecordingLayout(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout[] newArray(int size) {
|
||||
return new Layout[size];
|
||||
public RecordingLayout[] newArray(int size) {
|
||||
return new RecordingLayout[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
||||
}
|
||||
|
||||
public void addField(DataField dataField) {
|
||||
dataFields.add(dataField);
|
||||
}
|
||||
@@ -111,8 +83,8 @@ public class Layout implements Parcelable {
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
public boolean sameName(Layout layout) {
|
||||
return this.name.equalsIgnoreCase(layout.getName());
|
||||
public boolean sameName(RecordingLayout recordingLayout) {
|
||||
return this.name.equalsIgnoreCase(recordingLayout.getName());
|
||||
}
|
||||
|
||||
public boolean sameName(String name) {
|
||||
@@ -126,7 +98,7 @@ public class Layout implements Parcelable {
|
||||
}
|
||||
|
||||
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||
+ fields.stream().map(RecordingLayoutIO::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
||||
}
|
||||
|
||||
@@ -141,4 +113,17 @@ public class Layout implements Parcelable {
|
||||
parcel.writeInt(columnsPerRow);
|
||||
parcel.writeList(dataFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RecordingLayout recordingLayout = (RecordingLayout) o;
|
||||
return columnsPerRow == recordingLayout.columnsPerRow && Objects.equals(name, recordingLayout.name) && Objects.equals(dataFields, recordingLayout.dataFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, columnsPerRow, dataFields);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class RecordingLayoutIO {
|
||||
|
||||
private static final String TAG = RecordingLayout.class.getSimpleName();
|
||||
|
||||
private static final String YES_VALUE = "1";
|
||||
private static final String NOT_VALUE = "0";
|
||||
|
||||
public static RecordingLayout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
||||
List<String> csvParts = CsvLayoutUtils.getCsvLineParts(csvLine);
|
||||
if (csvParts == null) {
|
||||
Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine);
|
||||
return new RecordingLayout(PreferencesUtils.getDefaultLayoutName());
|
||||
}
|
||||
|
||||
RecordingLayout recordingLayout = new RecordingLayout(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 recordingLayout;
|
||||
}
|
||||
recordingLayout.addField(fromCSV(fieldParts, resources));
|
||||
}
|
||||
return recordingLayout;
|
||||
}
|
||||
|
||||
public static String toCSV(List<RecordingLayout> recordingLayouts) {
|
||||
return recordingLayouts.stream().map(RecordingLayout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR));
|
||||
}
|
||||
|
||||
private static DataField fromCSV(String[] fieldParts, @NonNull Resources resources) {
|
||||
return new DataField(
|
||||
fieldParts[0],
|
||||
fieldParts[1].equals(YES_VALUE),
|
||||
fieldParts[2].equals(YES_VALUE),
|
||||
fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
}
|
||||
|
||||
static String toCsv(DataField datafield) {
|
||||
String visible = datafield.isVisible() ? YES_VALUE : NOT_VALUE;
|
||||
String primary = datafield.isPrimary() ? YES_VALUE : NOT_VALUE;
|
||||
String wide = datafield.isWide() ? YES_VALUE : NOT_VALUE;
|
||||
return datafield.getKey() + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide;
|
||||
}
|
||||
}
|
||||
+85
-40
@@ -1,106 +1,151 @@
|
||||
package de.dennisguse.opentracks.ui.customRecordingLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
import de.dennisguse.opentracks.data.models.Altitude;
|
||||
import de.dennisguse.opentracks.data.models.Distance;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.CustomStatsItemBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.Mapping;
|
||||
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
|
||||
|
||||
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private Layout layout;
|
||||
private static final String TAG = SettingsCustomLayoutEditAdapter.class.getSimpleName();
|
||||
|
||||
private static final RecordingData demoData;
|
||||
|
||||
static {
|
||||
TrackStatistics trackStatistics = new TrackStatistics();
|
||||
trackStatistics.setStartTime(Instant.ofEpochMilli(0));
|
||||
trackStatistics.setMovingTime(Duration.ofMinutes(0));
|
||||
trackStatistics.setTotalTime(Duration.ofMinutes(0));
|
||||
|
||||
trackStatistics.setTotalDistance(Distance.of(0));
|
||||
|
||||
trackStatistics.setTotalAltitudeGain(0f);
|
||||
trackStatistics.setTotalAltitudeLoss(0f);
|
||||
Track track = new Track(ZoneOffset.UTC);
|
||||
track.setTrackStatistics(trackStatistics);
|
||||
|
||||
TrackPoint lastTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0));
|
||||
lastTrackPoint.setLatitude(0);
|
||||
lastTrackPoint.setLongitude(0);
|
||||
lastTrackPoint.setAltitude(Altitude.EGM2008.of(0));
|
||||
lastTrackPoint.setSpeed(Speed.of(0));
|
||||
|
||||
demoData = new RecordingData(track, lastTrackPoint, null);
|
||||
}
|
||||
|
||||
private RecordingLayout recordingLayout;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||
private final Map<String, Callable<StatisticViewHolder<?>>> mapping;
|
||||
|
||||
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
|
||||
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, RecordingLayout recordingLayout) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
this.layout = layout;
|
||||
this.recordingLayout = recordingLayout;
|
||||
|
||||
mapping = Mapping.create(context);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
|
||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
|
||||
CustomStatsItemBinding binding = CustomStatsItemBinding.inflate(LayoutInflater.from(context), parent, false);
|
||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
|
||||
DataField field = layout.getFields().get(position);
|
||||
DataField field = recordingLayout.getFields().get(position);
|
||||
viewHolder.itemView.setTag(field.getKey());
|
||||
viewHolder.title.setText(field.getTitle());
|
||||
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
|
||||
try {
|
||||
StatisticViewHolder<?> m = mapping.get(field.getKey()).call();
|
||||
m.initialize(context, LayoutInflater.from(context));
|
||||
m.configureUI(field);
|
||||
m.onChanged(UnitSystem.METRIC, demoData);
|
||||
|
||||
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader) : R.style.TextAppearance_OpenTracks_HiddenHeader);
|
||||
viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue) : R.style.TextAppearance_OpenTracks_HiddenValue);
|
||||
viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
||||
viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
||||
viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
||||
viewHolder.viewBinding.statsLayout.removeAllViews(); //TODO this is not really performant
|
||||
viewHolder.viewBinding.statsLayout.addView(m.getView());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Couldn't to instantiate UI for DataField with key " + field.getKey() + " " + e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
viewHolder.viewBinding.statsIconShowStatus.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE);
|
||||
viewHolder.viewBinding.statsIconShowStatus.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24));
|
||||
viewHolder.viewBinding.statsIvDragIndicator.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layout == null) {
|
||||
if (recordingLayout == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return layout.getFields().size();
|
||||
return recordingLayout.getFields().size();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isItemWide(int position) {
|
||||
return layout.getFields().get(position).isWide();
|
||||
return recordingLayout.getFields().get(position).isWide();
|
||||
}
|
||||
|
||||
public DataField getItem(int position) {
|
||||
return layout.getFields().get(position);
|
||||
return recordingLayout.getFields().get(position);
|
||||
}
|
||||
|
||||
public void swapValues(Layout data) {
|
||||
this.layout = data;
|
||||
if (this.layout != null) {
|
||||
public void swapValues(RecordingLayout data) {
|
||||
this.recordingLayout = data;
|
||||
if (this.recordingLayout != null) {
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Layout move(int fromPosition, int toPosition) {
|
||||
layout.moveField(fromPosition, toPosition);
|
||||
public RecordingLayout move(int fromPosition, int toPosition) {
|
||||
recordingLayout.moveField(fromPosition, toPosition);
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
return layout;
|
||||
return recordingLayout;
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView title;
|
||||
final TextView value;
|
||||
final TextView unit;
|
||||
final ImageView statusIcon;
|
||||
final ImageView moveIcon;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.stats_description_main);
|
||||
value = itemView.findViewById(R.id.stats_value);
|
||||
unit = itemView.findViewById(R.id.stats_unit);
|
||||
statusIcon = itemView.findViewById(R.id.stats_icon_show_status);
|
||||
moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator);
|
||||
itemView.setOnClickListener(this);
|
||||
CustomStatsItemBinding viewBinding;
|
||||
|
||||
public ViewHolder(@NonNull CustomStatsItemBinding viewBinding) {
|
||||
super(viewBinding.getRoot());
|
||||
this.viewBinding = viewBinding;
|
||||
|
||||
viewBinding.getRoot().setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String statTitle = (String) view.getTag();
|
||||
Optional<DataField> optionalField = layout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
|
||||
Optional<DataField> optionalField = recordingLayout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst();
|
||||
optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -17,14 +17,14 @@ import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<Layout> layoutList;
|
||||
private List<RecordingLayout> recordingLayoutList;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutProfileClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -37,37 +37,37 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
|
||||
@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());
|
||||
RecordingLayout recordingLayout = recordingLayoutList.get(position);
|
||||
viewHolder.itemView.setTag(recordingLayout.getName());
|
||||
viewHolder.title.setText(recordingLayout.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layoutList == null) {
|
||||
if (recordingLayoutList == null) {
|
||||
return 0;
|
||||
}
|
||||
return layoutList.size();
|
||||
return recordingLayoutList.size();
|
||||
}
|
||||
|
||||
public List<Layout> getLayouts() {
|
||||
return layoutList;
|
||||
public List<RecordingLayout> getLayouts() {
|
||||
return recordingLayoutList;
|
||||
}
|
||||
|
||||
public void reloadLayouts() {
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
recordingLayoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeLayout(int position) {
|
||||
layoutList.remove(position);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
recordingLayoutList.remove(position);
|
||||
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void restoreItem(Layout layout, int position) {
|
||||
layoutList.add(position, layout);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
public void restoreItem(RecordingLayout recordingLayout, int position) {
|
||||
recordingLayoutList.add(position, recordingLayout);
|
||||
PreferencesUtils.updateCustomLayouts(recordingLayoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<Recycl
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String profile = (String) view.getTag();
|
||||
Optional<Layout> optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst();
|
||||
Optional<RecordingLayout> optionalLayout = recordingLayoutList.stream().filter(layout -> layout.sameName(new RecordingLayout(profile))).findFirst();
|
||||
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SettingsCustomLayoutProfileClickListener {
|
||||
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
|
||||
void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
|
||||
|
||||
public class StatisticsUtils {
|
||||
|
||||
@Deprecated
|
||||
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_fastest_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_clock_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_time);
|
||||
@@ -24,9 +25,10 @@ public class StatisticsUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Layout filterVisible(Layout layout, boolean visible) {
|
||||
Layout result = new Layout(layout.getName());
|
||||
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
@Deprecated //Add to Layout?
|
||||
public static RecordingLayout filterVisible(RecordingLayout recordingLayout, boolean visible) {
|
||||
RecordingLayout result = new RecordingLayout(recordingLayout.getName());
|
||||
result.addFields(recordingLayout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ public class TrackIconUtils {
|
||||
|
||||
};
|
||||
|
||||
//TODO Use Map.of()
|
||||
private static final LinkedHashMap<String, Pair<Integer, Integer>> MAP = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.databinding.StatsClockItemBinding;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
|
||||
public class ClockViewHolder extends StatisticViewHolder<StatsClockItemBinding> {
|
||||
|
||||
@Override
|
||||
protected StatsClockItemBinding createViewBinding(LayoutInflater inflater) {
|
||||
return StatsClockItemBinding.inflate(inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureUI(DataField dataField) {
|
||||
//TODO Unify with GenericStatisticsViewHolder?
|
||||
getBinding().statsClock.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.HeartRateZones;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<StatsGenericItemBinding> {
|
||||
|
||||
@Override
|
||||
protected StatsGenericItemBinding createViewBinding(LayoutInflater inflater) {
|
||||
return StatsGenericItemBinding.inflate(inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureUI(DataField dataField) {
|
||||
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
|
||||
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
|
||||
}
|
||||
|
||||
public static class Distance extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Pair<String, String> valueAndUnit = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(getContext()).getDistanceParts(data.getTrackStatistics().getTotalDistance());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_distance));
|
||||
}
|
||||
}
|
||||
|
||||
public static class TotalTime extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
Pair<String, String> valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(data.getTrackStatistics().getTotalTime()), null);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_total_time));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MovingTime extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
String value = StringUtils.formatElapsedTime(data.getTrackStatistics().getMovingTime());
|
||||
|
||||
getBinding().statsValue.setText(value);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_moving_time));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpeedOrPace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
// TODO Pace wont work for now
|
||||
boolean reportSpeed = true;
|
||||
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(reportSpeed).build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
final TrackPoint latestTrackPoint = data.getLatestTrackPoint();
|
||||
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
|
||||
getBinding().statsDescriptionMain.setText(sensorDataSet.getSpeed().second);
|
||||
} else {
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.description_speed_source_gps));
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageMovingSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MaxSpeed extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(true)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_max_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AverageMovingPace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_pace));
|
||||
}
|
||||
}
|
||||
|
||||
public static class AveragePace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_pace));
|
||||
}
|
||||
}
|
||||
|
||||
public static class FastestPace extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.setReportSpeedOrPace(false)
|
||||
.build(getContext());
|
||||
|
||||
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_fastest_pace));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Altitude extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
|
||||
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
|
||||
String altitudeReference = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? getContext().getString(latestTrackPoint.getAltitude().getLabelId()) : null;
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), altitude, unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_altitude);
|
||||
getBinding().statsDescriptionSecondary.setText(altitudeReference);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Gain extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeGain(), unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_gain);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Loss extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
|
||||
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeLoss(), unitSystem);
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_loss);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Coordinates extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
|
||||
String value;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
value = StringUtils.formatCoordinate(getContext(), latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude());
|
||||
} else {
|
||||
value = getContext().getString(R.string.value_unknown);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(value);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorHeartRate extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
//TODO Loads preference every time
|
||||
HeartRateZones zones = PreferencesUtils.getHeartRateZones();
|
||||
int textColor;
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
|
||||
sensorName = sensorDataSet.getHeartRate().second;
|
||||
textColor = zones.getColorForZone(sensorDataSet.getHeartRate().first);
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
|
||||
textColor = zones.getColorForZone(null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
|
||||
getBinding().statsValue.setTextColor(ContextCompat.getColor(getContext(), textColor));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorCadence extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
|
||||
sensorName = sensorDataSet.getCadence().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SensorPower extends GenericStatisticsViewHolder {
|
||||
|
||||
@Override
|
||||
public void onChanged(UnitSystem unitSystem, RecordingData data) {
|
||||
SensorDataSet sensorDataSet = data.getSensorDataSet();
|
||||
String sensorName = getContext().getString(R.string.value_unknown);
|
||||
|
||||
Pair<String, String> valueAndUnit;
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
|
||||
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
|
||||
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
|
||||
}
|
||||
|
||||
getBinding().statsValue.setText(valueAndUnit.first);
|
||||
getBinding().statsUnit.setText(valueAndUnit.second);
|
||||
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
|
||||
|
||||
getBinding().statsDescriptionSecondary.setVisibility(View.VISIBLE);
|
||||
getBinding().statsDescriptionSecondary.setText(sensorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class Mapping {
|
||||
|
||||
public static Map<String, Callable<StatisticViewHolder<?>>> create(Context context) {
|
||||
HashMap<String, Callable<StatisticViewHolder<?>>> m = new HashMap<>();
|
||||
m.put(context.getString(R.string.stats_custom_layout_total_time_key), GenericStatisticsViewHolder.TotalTime::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_moving_time_key), GenericStatisticsViewHolder.MovingTime::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_distance_key), GenericStatisticsViewHolder.Distance::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_speed_key), GenericStatisticsViewHolder.SpeedOrPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.SpeedOrPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_moving_speed_key), GenericStatisticsViewHolder.AverageMovingSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_speed_key), GenericStatisticsViewHolder.AverageSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_max_speed_key), GenericStatisticsViewHolder.MaxSpeed::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_moving_pace_key), GenericStatisticsViewHolder.AverageMovingPace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_average_pace_key), GenericStatisticsViewHolder.AveragePace::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_fastest_pace_key), GenericStatisticsViewHolder.FastestPace::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_altitude_key), GenericStatisticsViewHolder.Altitude::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_gain_key), GenericStatisticsViewHolder.Gain::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_loss_key), GenericStatisticsViewHolder.Loss::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new);
|
||||
|
||||
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), GenericStatisticsViewHolder.SensorHeartRate::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_cadence_key), GenericStatisticsViewHolder.SensorCadence::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_power_key), GenericStatisticsViewHolder.SensorPower::new);
|
||||
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
|
||||
public class StatisticData {
|
||||
private final DataField dataField;
|
||||
private final String value;
|
||||
private final String unit;
|
||||
private final String description;
|
||||
|
||||
public StatisticData(@NonNull DataField dataField, @NonNull Pair<String, String> valueAndUnit, String description) {
|
||||
this.dataField = dataField;
|
||||
this.value = valueAndUnit.first;
|
||||
this.unit = valueAndUnit.second;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public DataField getField() {
|
||||
return dataField;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean hasDescription() {
|
||||
return description != null;
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.models.Cadence;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
import de.dennisguse.opentracks.data.models.Speed;
|
||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||
import de.dennisguse.opentracks.data.models.TrackPoint;
|
||||
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.stats.SensorStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
|
||||
public class StatisticDataBuilder {
|
||||
|
||||
public static List<StatisticData> fromRecordingData(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull Layout layout, UnitSystem unitSystem) {
|
||||
List<StatisticData> statisticDataList = layout.getFields().stream()
|
||||
.filter(DataField::isVisible)
|
||||
.map(field -> build(context, recordingData, field.getKey(), field.isPrimary(), unitSystem))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
statisticDataList.addAll(getSensorStatsDataIfNeeded(context, recordingData, statisticDataList, unitSystem));
|
||||
return statisticDataList;
|
||||
}
|
||||
|
||||
private static StatisticData build(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, UnitSystem unitSystem) {
|
||||
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
|
||||
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
final TrackStatistics trackStatistics = recordingData.getTrackStatistics();
|
||||
|
||||
String title = null;
|
||||
String description = null;
|
||||
Pair<String, String> valueAndUnit = null;
|
||||
boolean isWide = false;
|
||||
|
||||
final String sensorUnknown = context.getString(R.string.value_unknown);
|
||||
|
||||
SpeedFormatter speedFormatterPace = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(false).build(context);
|
||||
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context);
|
||||
|
||||
|
||||
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()), null);
|
||||
title = context.getString(R.string.stats_total_time);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()), null);
|
||||
title = context.getString(R.string.stats_moving_time);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
|
||||
valueAndUnit = DistanceFormatter.Builder()
|
||||
.setUnit(unitSystem)
|
||||
.build(context).getDistanceParts(trackStatistics.getTotalDistance());
|
||||
title = context.getString(R.string.stats_distance);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
|
||||
boolean reportSpeed = fieldKey.equals("speed");
|
||||
title = reportSpeed ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
|
||||
|
||||
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
|
||||
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
|
||||
description = sensorDataSet.getSpeed().second;
|
||||
} else {
|
||||
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
|
||||
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
|
||||
description = context.getString(R.string.description_speed_source_gps);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
|
||||
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageMovingSpeed());
|
||||
title = context.getString(R.string.stats_average_moving_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
|
||||
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageSpeed());
|
||||
title = context.getString(R.string.stats_average_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
|
||||
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getMaxSpeed());
|
||||
title = context.getString(R.string.stats_max_speed);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
|
||||
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageMovingSpeed());
|
||||
title = context.getString(R.string.stats_average_moving_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
|
||||
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageSpeed());
|
||||
title = context.getString(R.string.stats_average_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
|
||||
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getMaxSpeed());
|
||||
title = context.getString(R.string.stats_fastest_pace);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
|
||||
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
|
||||
title = context.getString(R.string.stats_altitude);
|
||||
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, unitSystem);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeGain(), unitSystem);
|
||||
title = context.getString(R.string.stats_gain);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
|
||||
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeLoss(), unitSystem);
|
||||
title = context.getString(R.string.stats_loss);
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
|
||||
title = context.getString(R.string.stats_coordinates);
|
||||
isWide = true;
|
||||
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
|
||||
valueAndUnit = new Pair<>(StringUtils.formatCoordinate(context, latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude()), null);
|
||||
} else {
|
||||
valueAndUnit = new Pair<>(context.getString(R.string.value_unknown), null);
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
|
||||
title = context.getString(R.string.stats_sensors_heart_rate);
|
||||
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().first);
|
||||
description = sensorDataSet.getHeartRate().second;
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getHeartRateParts(context, null);
|
||||
description = sensorUnknown;
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
|
||||
title = context.getString(R.string.stats_sensors_cadence);
|
||||
|
||||
Cadence cadence = null;
|
||||
if (sensorDataSet != null) {
|
||||
Pair<Cadence, String> cadenceData = sensorDataSet.getCadence();
|
||||
if (cadenceData != null) {
|
||||
cadence = cadenceData.first;
|
||||
description = cadenceData.second;
|
||||
}
|
||||
}
|
||||
|
||||
valueAndUnit = StringUtils.getCadenceParts(context, cadence);
|
||||
if (description == null) {
|
||||
description = sensorUnknown;
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
|
||||
title = context.getString(R.string.stats_sensors_power);
|
||||
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
|
||||
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
|
||||
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
|
||||
} else {
|
||||
valueAndUnit = StringUtils.getPowerParts(context, null);
|
||||
description = sensorUnknown;
|
||||
}
|
||||
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
|
||||
title = context.getString(R.string.stats_clock);
|
||||
valueAndUnit = new Pair<>(null, null);
|
||||
}
|
||||
|
||||
if (valueAndUnit == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StatisticData(new DataField(fieldKey, title, true, isPrimary, isWide), valueAndUnit, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a list of StatisticData with sensors configured but not in statisticDataList.
|
||||
*/
|
||||
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, RecordingData recordingData, List<StatisticData> statisticDataList, UnitSystem unitSystem) {
|
||||
List<StatisticData> sensorDataList = new ArrayList<>();
|
||||
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
|
||||
if (sensorDataSet == null) {
|
||||
return sensorDataList;
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null) {
|
||||
sensorDataList.add(build(context, recordingData, "heart_rate", true, unitSystem));
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCadence() != null) {
|
||||
sensorDataList.add(build(context, recordingData, "cadence", true, unitSystem));
|
||||
}
|
||||
if (statisticDataList.stream().noneMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
|
||||
sensorDataList.add(build(context, recordingData, "power", true, unitSystem));
|
||||
}
|
||||
|
||||
return sensorDataList;
|
||||
}
|
||||
|
||||
public static List<StatisticData> fromSensorStatistics(@NonNull Context context, @NonNull SensorStatistics sensorStatistics) {
|
||||
List<StatisticData> sensorDataList = new ArrayList<>();
|
||||
|
||||
if (sensorStatistics.hasHeartRate()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_max), true, true, false),
|
||||
StringUtils.getHeartRateParts(context, sensorStatistics.getMaxHeartRate()),
|
||||
null
|
||||
)
|
||||
);
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_average_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_avg), true, true, false),
|
||||
StringUtils.getHeartRateParts(context, sensorStatistics.getAvgHeartRate()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
if (sensorStatistics.hasCadence()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.sensor_state_cadence_max), true, true, false),
|
||||
StringUtils.getCadenceParts(context, sensorStatistics.getMaxCadence()),
|
||||
null
|
||||
)
|
||||
);
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_average_cadence_key), context.getString(R.string.sensor_state_cadence_avg), true, true, false),
|
||||
StringUtils.getCadenceParts(context, sensorStatistics.getAvgCadence()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
if (sensorStatistics.hasPower()) {
|
||||
sensorDataList.add(
|
||||
new StatisticData(
|
||||
new DataField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.sensor_state_power_avg), true, true, false),
|
||||
StringUtils.getPowerParts(context, sensorStatistics.getAvgPower()),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return sensorDataList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.viewbinding.ViewBinding;
|
||||
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
|
||||
|
||||
public abstract class StatisticViewHolder<T extends ViewBinding> {
|
||||
|
||||
private Context context;
|
||||
|
||||
private T binding;
|
||||
|
||||
public void initialize(Context context, LayoutInflater inflater) {
|
||||
this.context = context;
|
||||
this.binding = createViewBinding(inflater);
|
||||
}
|
||||
|
||||
protected abstract T createViewBinding(LayoutInflater inflater);
|
||||
|
||||
public abstract void configureUI(DataField dataField);
|
||||
|
||||
public abstract void onChanged(UnitSystem unitSystem, RecordingData data);
|
||||
|
||||
public View getView() {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
T getBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package de.dennisguse.opentracks.viewmodels;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.services.RecordingData;
|
||||
import de.dennisguse.opentracks.settings.UnitSystem;
|
||||
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
|
||||
|
||||
public class StatisticsDataModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<List<StatisticData>> statsData;
|
||||
|
||||
public StatisticsDataModel(@NonNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<StatisticData>> getStatsData() {
|
||||
if (statsData == null) {
|
||||
statsData = new MutableLiveData<>();
|
||||
}
|
||||
return statsData;
|
||||
}
|
||||
|
||||
public void update(RecordingData recordingData, Layout layout, UnitSystem unitSystem) {
|
||||
new Thread(() -> {
|
||||
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, unitSystem);
|
||||
statsData.postValue(statisticDataList);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -75,6 +75,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -22,11 +22,10 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<include
|
||||
<FrameLayout
|
||||
android:id="@+id/stats_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
layout="@layout/stats_generic_item"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toEndOf="@id/stats_iv_drag_indicator"
|
||||
app:layout_constraintEnd_toStartOf="@id/stats_icon_show_status"
|
||||
|
||||
@@ -307,6 +307,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -143,6 +143,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -251,6 +251,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -28,6 +28,8 @@ limitations under the License.
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -138,6 +138,8 @@ limitations under the License.
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -38,6 +38,8 @@ limitations under the License.
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,16 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/stats_recycler_view"
|
||||
<androidx.gridlayout.widget.GridLayout
|
||||
android:id="@+id/stats_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintEnd_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
</ScrollView>
|
||||
@@ -46,6 +46,8 @@ limitations under the License.
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -36,6 +36,8 @@ limitations under the License.
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include android:id="@+id/bottom_app_bar_layout" layout="@layout/bottomappbar" />
|
||||
<include
|
||||
android:id="@+id/bottom_app_bar_layout"
|
||||
layout="@layout/bottomappbar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
+116
-116
@@ -47,25 +47,25 @@ limitations under the License.
|
||||
<string name="activity_type_motor_boating">motorbåtkjøring</string>
|
||||
<string name="activity_type_mountain_biking">terrengsykling</string>
|
||||
<string name="activity_type_off_trail_hiking">fotturer utenfor sti</string>
|
||||
<string name="activity_type_other">andre</string>
|
||||
<string name="activity_type_other">annet</string>
|
||||
<string name="activity_type_paddling">padling</string>
|
||||
<string name="activity_type_para_gliding">paragliding</string>
|
||||
<string name="activity_type_rc_airplane">Radiostyrt fly</string>
|
||||
<string name="activity_type_rc_boat">Radiostyrt båt</string>
|
||||
<string name="activity_type_rc_helicopter">Radiostyrt helikopter</string>
|
||||
<string name="activity_type_riding">ridning</string>
|
||||
<string name="activity_type_road_biking">sykling (vei)</string>
|
||||
<string name="activity_type_road_biking">landeveissykling</string>
|
||||
<string name="activity_type_roller_skiing">rulleski</string>
|
||||
<string name="activity_type_rowing">roing</string>
|
||||
<string name="activity_type_running">jogging</string>
|
||||
<string name="activity_type_sailing">seiling</string>
|
||||
<string name="activity_type_seaplane">sjøfly</string>
|
||||
<string name="activity_type_skate_boarding">skateboarding</string>
|
||||
<string name="activity_type_skate_boarding">rullebrett</string>
|
||||
<string name="activity_type_skating">skøyter</string>
|
||||
<string name="activity_type_skiing">skisport</string>
|
||||
<string name="activity_type_sky_jumping">fallskjermhopping</string>
|
||||
<string name="activity_type_sled">sledekjøring</string>
|
||||
<string name="activity_type_snow_boarding">snowboardkjøring</string>
|
||||
<string name="activity_type_snow_boarding">snøbrett</string>
|
||||
<string name="activity_type_snow_shoeing">truger</string>
|
||||
<string name="activity_type_speed_walking">hurtig gange</string>
|
||||
<string name="activity_type_street_running">gateløp</string>
|
||||
@@ -84,9 +84,9 @@ limitations under the License.
|
||||
<!-- Description -->
|
||||
<string name="description_activity_type">Aktivitetstype: %1$s</string>
|
||||
<string name="description_average_moving_pace_in_minute">Gjennomsnittlig forflytningstempo: %1$s min/km (%2$s min/mi)</string>
|
||||
<string name="description_average_moving_speed">Gjennomsnittlig forflytningshastighet: %1$.2f km/t (%2$.1f mi/h)</string>
|
||||
<string name="description_average_pace_in_minute">Gjennomsnittlig tempo: %1$s min/km (%2$s min/mi)</string>
|
||||
<string name="description_average_speed">Gjennomsnittlig hastighet: %1$.2f km/t (%2$.1f mi/h)</string>
|
||||
<string name="description_average_moving_speed">Gjen. forflytningshastighet: %1$.2f km/t (%2$.1f mi/h)</string>
|
||||
<string name="description_average_pace_in_minute">Gjen. tempo: %1$s min/km (%2$s min/mi)</string>
|
||||
<string name="description_average_speed">Gjen. hastighet: %1$.2f km/t (%2$.1f mi/h)</string>
|
||||
<string name="description_altitude_gain">Total stigning: %1$d m (%2$d ft)</string>
|
||||
<string name="description_altitude_imperial">Høyde (ft)</string>
|
||||
<string name="description_altitude_metric">Høyde (m)</string>
|
||||
@@ -108,11 +108,11 @@ limitations under the License.
|
||||
<string name="description_total_distance">Total distanse: %1$.2f km (%2$.1f mi)</string>
|
||||
<string name="description_total_time">Total tid: %1$s</string>
|
||||
<!-- Export -->
|
||||
<string name="export_progress_message">Eksporterer til %1$s…</string>
|
||||
<string name="export_progress_message">Eksporterer til %1$s …</string>
|
||||
<!-- External Storage -->
|
||||
<!-- Generic -->
|
||||
<string name="generic_add">Legg til</string>
|
||||
<string name="generic_delete_selected_confirm_title">Vil du slette de valgte elementene?</string>
|
||||
<string name="generic_delete_selected_confirm_title">Slett valgte elementer\?</string>
|
||||
<string name="generic_description">Beskrivelse</string>
|
||||
<string name="generic_description_line">Beskrivelse: %1$s</string>
|
||||
<string name="generic_name">Navn</string>
|
||||
@@ -133,14 +133,14 @@ limitations under the License.
|
||||
<string name="image_stop">Stopp</string>
|
||||
<string name="image_track">Spor</string>
|
||||
<!-- Import -->
|
||||
<string name="import_progress_message">Importerer fra %1$s…</string>
|
||||
<string name="import_progress_message">Importerer fra %1$s …</string>
|
||||
<!-- Marker -->
|
||||
<string name="marker_add_error">Kan ikke sette inn markør. Har ikke GPS-signal. Prøv på nytt.</string>
|
||||
<string name="marker_add_success">En markør ble satt inn</string>
|
||||
<string name="marker_add_canceled">Avbrutt – ingen markør lagt til</string>
|
||||
<string name="marker_delete_one_confirm_message">Markøren blir slettet permanent fra enheten</string>
|
||||
<string name="marker_delete_one_confirm_title">Vil du slette markøren?</string>
|
||||
<string name="marker_delete_multiple_confirm_message">De valgte markørene blir slettet permanent fra enheten</string>
|
||||
<string name="marker_add_canceled">Avbrutt. Ingen markør lagt til.</string>
|
||||
<string name="marker_delete_one_confirm_message">Markøren blir slettet fra enheten for godt</string>
|
||||
<string name="marker_delete_one_confirm_title">Slett markøren\?</string>
|
||||
<string name="marker_delete_multiple_confirm_message">De valgte markørene blir slettet fra enheten for godt</string>
|
||||
<string name="marker_name_format">Markør %1$d</string>
|
||||
<string name="marker_title">%1$d av %2$d</string>
|
||||
<!-- Marker Detail -->
|
||||
@@ -175,12 +175,12 @@ limitations under the License.
|
||||
<!-- Settings Advanced -->
|
||||
<string name="settings_advanced">Avansert</string>
|
||||
<!-- Settings Chart -->
|
||||
<string name="settings_chart_by_distance">Etter distanse</string>
|
||||
<string name="settings_chart_by_distance">Etter avstand</string>
|
||||
<string name="settings_chart_by_time">Etter tid</string>
|
||||
<string name="settings_recording_default_activity_title">Standard aktivitetstype</string>
|
||||
<string name="settings_recording_default_activity_title">Forvalgt aktivitetstype</string>
|
||||
<string name="settings_recording_location_frequency_summary">%1$s mellom registrerte posisjoner</string>
|
||||
<string name="settings_recording_max_recording_distance_summary">Det blir opprettet et nytt segment hvis avstanden mellom to posisjoner er større enn %1$s</string>
|
||||
<string name="settings_recording_max_recording_distance_title">Maks avstand</string>
|
||||
<string name="settings_recording_max_recording_distance_title">Maks. avstand</string>
|
||||
<string name="settings_recording_min_recording_distance_title">Avstandsintervall for opptak</string>
|
||||
<string name="settings_recording_min_recording_interval_title">Tidsintervall for opptak</string>
|
||||
<string name="settings_recording_min_required_accuracy_summary">Posisjonen ignoreres hvis nøyaktigheten er under %1$s</string>
|
||||
@@ -188,17 +188,17 @@ limitations under the License.
|
||||
<string name="settings_recording_track_name_date_iso_8601_option">Dato (ISO 8601)</string>
|
||||
<string name="settings_recording_track_name_date_local_option">Dato (lokal)</string>
|
||||
<string name="settings_recording_track_name_number_option">Nummer</string>
|
||||
<string name="settings_recording_track_name_title">Standard-spornavn</string>
|
||||
<string name="settings_recording_track_name_title">Forvalgt spornavn</string>
|
||||
<!-- Settings Reset -->
|
||||
<string name="settings_reset_confirm_message">Alle innstillinger blir tilbakestilt til standardverdiene. Dette vil ikke slette noen spor på enheten.</string>
|
||||
<string name="settings_reset_confirm_title">Vil du tilbakestille nå?</string>
|
||||
<string name="settings_reset_done">Alle innstillinger er tilbakestilt til standardverdiene</string>
|
||||
<string name="settings_reset_confirm_message">Alle innstillinger blir tilbakestilt til forvalg. Dette sletter ikke spor på enheten.</string>
|
||||
<string name="settings_reset_confirm_title">Tilbakestill nå\?</string>
|
||||
<string name="settings_reset_done">Alle innstillinger tilbakestilt til forvalg</string>
|
||||
<!-- Settings Sensor -->
|
||||
<string name="settings_sensor_bluetooth">Bluetooth</string>
|
||||
<string name="settings_sensor_bluetooth">Blåtann</string>
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_rate_title">Foretrukket måling</string>
|
||||
<string name="settings_stats_units_imperial">Britisk (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metrisk (km, m)</string>
|
||||
<string name="settings_stats_units_imperial">Britiske (mi, ft)</string>
|
||||
<string name="settings_stats_units_metric">Metriske (km, m)</string>
|
||||
<string name="settings_stats_units_title">Foretrukne enheter</string>
|
||||
<!-- Share Track -->
|
||||
<string name="share_track_share_file_body">Jeg tror du kan være interessert i dette sporet.
|
||||
@@ -206,10 +206,10 @@ limitations under the License.
|
||||
\n%1$s</string>
|
||||
<string name="share_track_subject">Jeg vil dele et spor med deg</string>
|
||||
<!-- Stats -->
|
||||
<string name="stats_average_moving_pace">Gj. forflytningstempo</string>
|
||||
<string name="stats_average_moving_speed">Gj. forflytningshastighet</string>
|
||||
<string name="stats_average_pace">Gj. tempo</string>
|
||||
<string name="stats_average_speed">Gj. hastighet</string>
|
||||
<string name="stats_average_moving_pace">Gjen. forflytningstempo</string>
|
||||
<string name="stats_average_moving_speed">Gjen. forflytningshastighet</string>
|
||||
<string name="stats_average_pace">Gjen. tempo</string>
|
||||
<string name="stats_average_speed">Gjen. hastighet</string>
|
||||
<string name="stats_altitude">Høyde</string>
|
||||
<string name="stats_fastest_pace">Raskeste tempo</string>
|
||||
<string name="stats_gain">Total stigning</string>
|
||||
@@ -225,9 +225,9 @@ limitations under the License.
|
||||
<string name="track_name_format">Spor %1$d</string>
|
||||
<string name="track_markers">Markører for %1$s</string>
|
||||
<!-- Track Delete -->
|
||||
<string name="track_delete_one_confirm_message">Sporet og markørene slettes permanent fra enheten.</string>
|
||||
<string name="track_delete_one_confirm_title">Vil du slette sporet?</string>
|
||||
<string name="track_delete_multiple_confirm_message">De valgte sporene og markørene slettes permanent fra enheten.</string>
|
||||
<string name="track_delete_one_confirm_message">Sporet og markørene slettes fra enheten for godt.</string>
|
||||
<string name="track_delete_one_confirm_title">Slett sporet\?</string>
|
||||
<string name="track_delete_multiple_confirm_message">Valgte spor og markører slettes fra enheten for godt.</string>
|
||||
<string name="track_delete_progress_message">Sletter …</string>
|
||||
<!-- Track Detail -->
|
||||
<string name="track_detail_stats_tab">Statistikk</string>
|
||||
@@ -349,25 +349,25 @@ limitations under the License.
|
||||
<string name="gps_disabled_msg">GPS slått av</string>
|
||||
<string name="import_unable_to_import_file">Kan ikke importere filen: %1$s</string>
|
||||
<string name="import_error_list_dialog_title">Filer ikke importert</string>
|
||||
<string name="import_file_imported">Fil %1$s importert</string>
|
||||
<string name="marker_add_photo_canceled">Avbrutt, ingen fotomarkør lagt til</string>
|
||||
<string name="import_file_imported">«%1$s»-fil importert</string>
|
||||
<string name="marker_add_photo_canceled">Avbrutt. Ingen fotomarkør lagt til.</string>
|
||||
<string name="menu_resume_track">Gjenoppta spor</string>
|
||||
<string name="settings_default_trackfileformat">Filformat for eksport/deling</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">Vis statistikk på låseskjermen</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">Hold skjermen på</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">Hold skjermen på mens du tar opp.</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">Behold skjermen påslått</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">Behold skjermen påslått under opptak</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">Fullskjerm</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Mens du tar opp, vis i fullskjerm.</string>
|
||||
<string name="bluetooth_disabled">Slå på Bluetooth.</string>
|
||||
<string name="settings_night_mode_title">Brukerflatetema</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Alltid vis opptak i fullskjerm.</string>
|
||||
<string name="bluetooth_disabled">Slå på Blåtann</string>
|
||||
<string name="settings_night_mode_title">Brukerflatedrakt</string>
|
||||
<string name="settings_night_mode_option_system">System</string>
|
||||
<string name="settings_night_mode_option_day">Dag</string>
|
||||
<string name="settings_night_mode_option_night">Natt</string>
|
||||
<string name="track_recording_notification_accuracy">Posisjonsnøyaktighet: %1$s</string>
|
||||
<string name="track_detail_intervals_tab">Intervaller</string>
|
||||
<string name="aggregated_stats_empty_message">Ta opp ditt første spor for å se samlet statistikk</string>
|
||||
<string name="stats_split_interval">Intervall</string>
|
||||
<string name="interval_list_empty_message">Det er ingen intervaller ennå</string>
|
||||
<string name="stats_split_interval">Intervallinndeling</string>
|
||||
<string name="interval_list_empty_message">Ingen intervaller enda</string>
|
||||
<string name="hold_to_stop">Hold nede for å stoppe</string>
|
||||
<string name="instant_export_enabled_summary">Eksporter spor etter at opptak er ferdig</string>
|
||||
<string name="instant_export_enabled_title">Auto-eksport etter opptak</string>
|
||||
@@ -377,69 +377,69 @@ limitations under the License.
|
||||
<string name="open_track_as_GPX">Åpne som GPX</string>
|
||||
<string name="open_track_as_trackfileformat">Åpne som %1$s</string>
|
||||
<string name="export_with_photos">med bilder</string>
|
||||
<string name="export_track_already_exists_msg">Sporet %1$s finnes allerede i målmappen.</string>
|
||||
<string name="export_track_already_exists_msg">«%1$s»-sporet finnes allerede i målmappen.</string>
|
||||
<string name="import_unsupported_format">Filformatet støttes ikke</string>
|
||||
<string name="menu_insert_gallery_img">Sett inn galleribilde</string>
|
||||
<string name="permission_gps_failed">OpenTracks krever tillatelse til å bruke GPS.</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Vis statistikk under opptak uten å låse opp enheten.</string>
|
||||
<string name="share_image_subject">Jeg vil gjerne dele et bilde med deg</string>
|
||||
<string name="import_thread_interrupted">Tråden avbrutt</string>
|
||||
<string name="sensor_could_not_scan">Kunne ikke skanne etter Bluetooth-enheter (feil %1$i).</string>
|
||||
<string name="help_data_google_play_services_title">Krever/bruker OpenTracks Google Play Service\?</string>
|
||||
<string name="import_thread_interrupted">Tråd avbrutt</string>
|
||||
<string name="sensor_could_not_scan">Kunne ikke se etter Blåtannsenheter (feil %1$i).</string>
|
||||
<string name="help_data_google_play_services_title">Krever/bruker OpenTracks Google Play-tjenesten\?</string>
|
||||
<string name="help_feature_title">OpenTracks har følgende funksjoner:</string>
|
||||
<string name="help_feature_content">* opprette GPS-spor
|
||||
\n * bruke en pulssensor (kun Bluetooth LE)
|
||||
\n * talemeldinger (etter tid eller distanse)
|
||||
\n * opprette markører (inkl. bilder)
|
||||
\n * fungerer helt uten internettforbindelse
|
||||
\n * viser spor på et kart (se nedenfor)</string>
|
||||
<string name="help_feature_content">* Opprett GPS-spor
|
||||
\n * Bruk av pulssensor (kun Blåtann-LE)
|
||||
\n * Talemeldinger (etter tid eller distanse)
|
||||
\n * Opprett markører (inkl. bilder)
|
||||
\n * Fungerer uten Internett-forbindelse
|
||||
\n * Vis spor på et kart (se nedenfor)</string>
|
||||
<string name="help_track_map_title">Hvordan vise et spor på et kart\?</string>
|
||||
<string name="about_description"><i>OpenTracks</i> er en sportssporingsapp som fullt ut respekterer ditt personvern.</string>
|
||||
<string name="about_description"><i>OpenTracks</i> er et sportssporingsprogram som fullt ut respekterer ditt personvern.</string>
|
||||
<string name="about_url">Prosjektnettside: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Versjon: %1$s</string>
|
||||
<string name="about_version_code">Versjonskode: %1$d</string>
|
||||
<string name="about_history">OpenTracks er en forgrening av <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (avviklet i 2016).</string>
|
||||
<string name="about_libraries">Biblioteker</string>
|
||||
<string name="about_libraries">Bibliotek</string>
|
||||
<string name="help_support_title">Hvordan kan jeg støtte OpenTracks\?</string>
|
||||
<string name="activity_type_escooter">elsparkesykkel</string>
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<string name="gps_fixed_and_ready">GPS bestemt og klar</string>
|
||||
<string name="import_parser_error">Analysefeil: %1$s</string>
|
||||
<string name="import_error_info">%1$s: %2$s</string>
|
||||
<string name="help_storage_pictures_title">Hvordan lagrer OpenTracks bilder som er tatt som markører\?</string>
|
||||
<string name="help_storage_pictures_content">Disse lagres i appens interne lagring (dvs. bare tilgjengelig i OpenTracks).</string>
|
||||
<string name="help_storage_pictures_title">Hvordan lagrer OpenTracks bilder tatt som markører\?</string>
|
||||
<string name="help_storage_pictures_content">Disse lagres i programmets internlager (dvs. kun tilgjengelig i OpenTracks).</string>
|
||||
<string name="generic_click_twice_cancel">Trykk tilbake-knappen igjen for å avbryte</string>
|
||||
<string name="help_track_map_content"> Bruk det lille kartikonet.
|
||||
\n a) (live-oppdateringer) Installer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. OSMDashboard sørger for nettbaserte eller nettfrakoblede kart via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n Egentlig er OSMDashboard en egen app som bare har tilgang (midlertidig) på dataene til spor som skal vises.
|
||||
\n b) Via et vanlig kartprogram (f.eks. <a href="http://osmand.net/">OsmAnd</a> og <a href="https://maps.me/">MAPS.ME</a>). Disse appene må støtte KMZ-filformatet (bare posisjoner og tidsstempler). Faktisk trenger en app å støtte <i>ACTION_VIEW</i> med MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
\n a) (sanntidsoppdateringer) Installer <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Dette gir nettbaserte eller nettfrakoblede kart via <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\n Egentlig er OSMDashboard et egen program som bare har tilgang (midlertidig) på dataene til spor som skal vises.
|
||||
\n b) Via et vanlig kartprogram (f.eks. <a href="http://osmand.net/">OsmAnd</a> og <a href="https://maps.me/">MAPS.ME</a>). Disse programmene må støtte KMZ-filformatet (bare posisjoner og tidsstempler). Faktisk trenger et program å støtte <i>ACTION_VIEW</i> med MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="help_track_export_title">Hvordan eksportere/dele spor som er tatt opp\?</string>
|
||||
<string name="help_track_export_content"> Spor kan eksporteres fra Innstillinger som KMZ, KML eller GPX. KMZ/KML alle data; GPX bare posisjoner. Spor som deles via <i>deleikonet</i> blir delt som KMZ. </string>
|
||||
<string name="help_recording_title">Hvordan OpenTracks tar opp data\?</string>
|
||||
<string name="help_recording_content">OpenTracks bruker enhetens GPS-sensor til å ta opp posisjonen kontinuerlig. Hvis GPS-en rapporterer unøyaktige data (f.eks. posisjon, hastighet, høyde), kan ikke OpenTracks gjøre noe med det.</string>
|
||||
<string name="help_data_storage_title">Hvordan lagrer OpenTracks sensordata\?</string>
|
||||
<string name="help_data_storage_content"> Sensordata lagres per posisjon som er tatt opp. Når det gjelder en pulssensor betyr dette at bare <i>en</i> pulsmåling lagres per posisjon. Hvis du står stille eller er innendørs, blir ingen sensordata tatt opp (men de vil bli vist). </string>
|
||||
<string name="help_data_storage_content"> Sensordata lagres per posisjon som er tatt opp. Når det gjelder pulssensorer betyr dette at kun <i>én</i> pulsmåling lagres per posisjon. Hvis du står stille eller er innendørs, blir ingen sensordata tatt opp (men de vil bli vist). </string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks tar ikke opp posisjoner\?</string>
|
||||
<string name="help_gps_troubleshooting_content"> Hva du kan gjøre:
|
||||
\n a) sjekke om GPS er slått på,
|
||||
\n a) sjekk om GPS er slått på,
|
||||
\n b) forbedre GPS-mottak (f.eks. gå utendørs),
|
||||
\n c) sjekke med appen <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, og
|
||||
\n c) sjekk med programmet <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a>, og
|
||||
\n d) slå av strømsparing.
|
||||
\n
|
||||
\n Hvis alt dette ikke hjelper, kan du rapportere en feil. </string>
|
||||
<string name="help_support_content"> Du kan:
|
||||
\n a) spre informasjon om OpenTracks,
|
||||
\n a) spre info om OpenTracks,
|
||||
\n b) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">foreslå funksjoner</a>,
|
||||
\n c) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">rapportere feil</a>,
|
||||
\n d) hjelpe med programvareutvikling,
|
||||
\n e) kjøpe i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\n d) hjelpe til med programvareutvikling,
|
||||
\n e) kjøpe programmet i <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\n f) donere, og
|
||||
\n g) gjøre hva du tror hjelper OpenTracks. </string>
|
||||
\n g) gjøre noe du tror hjelper OpenTracks. </string>
|
||||
<string name="description_altitude_loss">Total nedstigning: %1$d m (%2$d ft)</string>
|
||||
<string name="stats_loss">Total nedstigning</string>
|
||||
<string name="import_prevent_reimport">Du har stilt inn å forhindre at spor importeres om igjen</string>
|
||||
<string name="import_prevent_reimport">Du har stilt inn forhindring av at spor importeres om igjen</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Forhindre reimport av spor</string>
|
||||
<string name="export_error_post_workout">Eksport etter opptak feilet, sjekk eksportmappen.</string>
|
||||
<string name="export_error_post_workout">Eksport etter opptak feilet. Sjekk eksportmappen.</string>
|
||||
<string name="export_progress_summary_new_msg">Nye spor eksportert</string>
|
||||
<string name="import_progress_summary_ok_msg">Filer importert</string>
|
||||
<string name="import_no_kml_file_found">Ingen doc.kml-fil funnet i KMZ</string>
|
||||
@@ -449,11 +449,11 @@ limitations under the License.
|
||||
<string name="generic_open_track">Åpne spor</string>
|
||||
<string name="import_progress_summary_exists_msg">Finnes allerede</string>
|
||||
<string name="import_progress_summary_errors_msg">Feil</string>
|
||||
<string name="sensor_state_cadence_avg">Gj. kadens</string>
|
||||
<string name="sensor_state_cadence_max">Maks kadens</string>
|
||||
<string name="sensor_state_heart_rate_avg">Gj. puls</string>
|
||||
<string name="sensor_state_heart_rate_max">Maks puls</string>
|
||||
<string name="sensor_state_power_avg">Gj. kraft</string>
|
||||
<string name="sensor_state_cadence_avg">Gjen. kadens</string>
|
||||
<string name="sensor_state_cadence_max">Maks. kadens</string>
|
||||
<string name="sensor_state_heart_rate_avg">Gjen. puls</string>
|
||||
<string name="sensor_state_heart_rate_max">Maks. puls</string>
|
||||
<string name="sensor_state_power_avg">Gjen. kraft</string>
|
||||
<string name="settings_sensor_wheel_circumference">Hjulomkrets (mm)</string>
|
||||
<string name="track_distance_notification">Distanse: %1$s</string>
|
||||
<string name="track_speed_notification">Hastighet: %1$s</string>
|
||||
@@ -461,20 +461,20 @@ limitations under the License.
|
||||
<string name="about_third_party_data">Tredjepartsdata</string>
|
||||
<string name="delete_service">Slettingstjeneste</string>
|
||||
<string name="menu_voice_rate">Talehastighet</string>
|
||||
<string name="track_delete_not_recording">Dette sporet kan ikke slettes da det tas opp nå.</string>
|
||||
<string name="track_delete_not_recording">Dette sporet kan ikke slettes siden det tas opp nå.</string>
|
||||
<string name="settings_sensor_cycling_cadence">Kadens</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Distanse og hastighet</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Distanse, hastighet og kadens</string>
|
||||
<string name="settings_recording_customize_layout_title">Tilpass opptakslayouten</string>
|
||||
<string name="settings_layout_reset_confirm_message">Alle layoutinnstillinger blir tilbakestilt til standardverdiene. Dette vil ikke slette spor på enheten.</string>
|
||||
<string name="stats_coordinates">Breddegrad/Lengdegrad</string>
|
||||
<string name="settings_recording_customize_layout_title">Endre opptakstilpasningen</string>
|
||||
<string name="settings_layout_reset_confirm_message">Alle tilpasningsinnstillinger blir tilbakestilt til forvalgsverdier. Dette sletter ikke spor på enheten.</string>
|
||||
<string name="stats_coordinates">Breddegrad/lengdegrad</string>
|
||||
<string name="stats_sensors_heart_rate">Puls</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="generic_choose_an_option">Velg et alternativ</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Datafelter per rekke</string>
|
||||
<string name="settings_layout_reset">Tilbakestill tilpasset layout</string>
|
||||
<string name="settings_layout_reset_confirm_title">Tilbakestille layoutene dine\?</string>
|
||||
<string name="settings_layout_reset_done">Alle layoutinnstillinger er tilbakestilt til standardverdiene</string>
|
||||
<string name="settings_layout_reset">Tilbakestill egendefinert tilpasning</string>
|
||||
<string name="settings_layout_reset_confirm_title">Tilbakestille tilpasningene dine\?</string>
|
||||
<string name="settings_layout_reset_done">Alle tilpasninger er tilbakestilt til forvalg</string>
|
||||
<string name="settings_all_reset">Tilbakestill</string>
|
||||
<string name="stats_sensors_cadence">Kadens</string>
|
||||
<string name="stats_sensors_power">Kraft</string>
|
||||
@@ -485,9 +485,9 @@ limitations under the License.
|
||||
<string name="field_remove_from_layout">Fjern</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Tidsintervall</string>
|
||||
<string name="settings_announcements_distance_frequency">Distanseintervall</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks krever tillatelse til å bruke Bluetooth.</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Du må manuelt gi OpenTracks tilgang til enheter i nærheten i systeminnstillinger.</string>
|
||||
<string name="settings_generic_sensor">Generell</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks krever tillatelse til å bruke Blåtann.</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Du må manuelt gi OpenTracks tilgang til «enheter i nærheten» i systeminnstillingene.</string>
|
||||
<string name="settings_generic_sensor">Generisk</string>
|
||||
<string name="settings_cycling_sensor">Sykling</string>
|
||||
<string name="settings_running_sensor">Løping</string>
|
||||
<string name="settings_import_section">Import</string>
|
||||
@@ -499,46 +499,46 @@ limitations under the License.
|
||||
<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_defaults_summary">Foretrukne enheter, forvalgsaktivitet, forvalgt spornavn</string>
|
||||
<string name="settings_sensors_summary">Sykkelsensorer, hjulomkrets, løpesensorer</string>
|
||||
<string name="settings_open_tracks_summary">Alt om OpenTracks-prosjektet</string>
|
||||
<string name="settings_defaults_title">Forvalgsenheter og aktivitet</string>
|
||||
<string name="settings_ui_title">Brukerflate</string>
|
||||
<string name="settings_ui_summary">Utseende, Opptakslayout, Tema</string>
|
||||
<string name="settings_ui_summary">Utseende, opptakstilpasning, drakt</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_gps_summary">Tidsintervall, distanseintervall, maks, distanse, nøyaktighet</string>
|
||||
<string name="settings_sensors_title">Blåtannssensorer</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_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="settings_activity_defaults_category">Aktivitetens forvalg</string>
|
||||
<string name="settings_reset_summary">Tilbakestill innstillinger til forvalg</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>
|
||||
<string name="menu_select_layout">Velg tilpasning</string>
|
||||
<string name="menu_add_custom_layout">Ny tilpasning</string>
|
||||
<string name="custom_layout_list_title">Tilpasninger</string>
|
||||
<string name="custom_layout_list_edit_name">Skriv inn navn på tilpasning å legge til</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Tilpasningens navn finnes allerede</string>
|
||||
<string name="custom_layout_list_layout_removed">Tilpasning fjernet</string>
|
||||
<string name="custom_layout_select_layout">Velg tilpasning</string>
|
||||
<string name="stats_custom_layout_default_layout">Forvalgt tilpasning</string>
|
||||
<string name="generic_filter">Filter</string>
|
||||
<string name="menu_filter">Filter</string>
|
||||
<string name="generic_from">Fra</string>
|
||||
<string name="generic_to">Til</string>
|
||||
<string name="aggregated_stats_filter_no_results">Det er ikke aktiviteter for det angitte filteret</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_enabled">Bare vis Bluetooth-enheter som melder om nødvendige tjenester</string>
|
||||
<string name="aggregated_stats_filter_no_results">Ingen aktiviteter for det angitte filteret</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_enabled">Kun vis Blåtannsenheter som melder om nødvendige tjenester</string>
|
||||
<string name="generic_today">I dag</string>
|
||||
<string name="generic_yesterday">I går</string>
|
||||
<string name="export_dir_not_writable">" (ikke skrivbar!)"</string>
|
||||
<string name="export_cannot_write_to_dir">Kan ikke skrive til eksportmappe.</string>
|
||||
<string name="no_compatible_file_manager_installed">Ingen kompatibel filbehandler installert</string>
|
||||
<string name="settings_public_api_enabled_title">Offentlig API</string>
|
||||
<string name="settings_public_api_enabled_summary_on">Andre installerte apper kan starte eller stoppe opptak.</string>
|
||||
<string name="settings_public_api_enabled_summary_off">Bare OpenTracks kan starte og stoppe opptak.</string>
|
||||
<string name="settings_public_api_disabled_toast">OpenTracks offentlige API er slått av: det kan slås på i innstillingene.</string>
|
||||
<string name="settings_public_api_enabled_summary_on">Andre installerte programmer kan starte eller stoppe opptak.</string>
|
||||
<string name="settings_public_api_enabled_summary_off">Kun OpenTracks kan starte og stoppe opptak.</string>
|
||||
<string name="settings_public_api_disabled_toast">OpenTracks offentlige API er slått av: Det kan slås på i innstillingene.</string>
|
||||
<string name="activity_type_swimming">svømming</string>
|
||||
<string name="activity_type_swimming_open">svømming i åpent vann</string>
|
||||
<string name="activity_type_workout">styrketrening</string>
|
||||
@@ -554,13 +554,13 @@ limitations under the License.
|
||||
<string name="lap_time">Rundetid</string>
|
||||
<string name="pace">Tempo</string>
|
||||
<string name="lap_speed">Rundehastighet</string>
|
||||
<string name="speed">Gjennomsnittlig forflytningshastighet</string>
|
||||
<string name="speed">Gjen. forflytningshastighet</string>
|
||||
<string name="total_distance">Total distanse</string>
|
||||
<plurals name="voiceDistanceMiles">
|
||||
<item quantity="one">1 engelsk mil</item>
|
||||
<item quantity="other">%1$.1f engelske mil</item>
|
||||
</plurals>
|
||||
<string name="average_heart_rate">Gjennomsnittlig puls</string>
|
||||
<string name="average_heart_rate">Gjen. puls</string>
|
||||
<plurals name="voiceDistanceKilometers">
|
||||
<item quantity="one">1 kilometer</item>
|
||||
<item quantity="other">%1$.1f kilometer</item>
|
||||
@@ -570,18 +570,18 @@ limitations under the License.
|
||||
<string name="error_crash_title">%1$s krasjet</string>
|
||||
<string name="share_crash_report">Del krasjrapport</string>
|
||||
<string name="report_bug">Rapporter feil</string>
|
||||
<string name="select_show_on_map_behavior">Velg virkemåte for \"Vis på kart\"</string>
|
||||
<string name="select_show_on_map_behavior">Velg virkemåte for «Vis på kart»</string>
|
||||
<string name="show_on_map_format_ask">Spør</string>
|
||||
<string name="show_on_dashboard">OpenTracks Dashboard API</string>
|
||||
<string name="show_on_dashboard">OpenTracks-API for oversikten</string>
|
||||
<string name="always">Alltid</string>
|
||||
<string name="settings_announcements_average_heart_rate">Gjennomsnittlig puls</string>
|
||||
<string name="settings_announcements_average_heart_rate">Gjen. puls</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Runde-puls</string>
|
||||
<string name="settings_announcements_average_speed_pace">Gjennomsnittlig hastighet/tempo</string>
|
||||
<string name="settings_announcements_average_speed_pace">Gjen. hastighet/tempo</string>
|
||||
<string name="image_discard">Forkast</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Runde-hastighet/tempo</string>
|
||||
<string name="description_pace_nautical">Tempo (min/NM)</string>
|
||||
<string name="description_speed_nautical">Hastighet (knop)</string>
|
||||
<string name="settings_stats_units_nautical">Nautisk (NM, ft)</string>
|
||||
<string name="settings_stats_units_nautical">Nautiske (NM, ft)</string>
|
||||
<string name="track_discarding">Forkaster spor</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<string name="unit_knots">kn</string>
|
||||
@@ -598,9 +598,9 @@ limitations under the License.
|
||||
<item quantity="one">1 nautisk mil</item>
|
||||
<item quantity="other">%1$.1f nautiske mil</item>
|
||||
</plurals>
|
||||
<string name="show_on_dashboard_not_installed">Ingen app som støtter OpenTracks Dashboard API er installert.</string>
|
||||
<string name="show_on_dashboard_not_installed">Ingen app som støtter OpenTracks-API for oversikten er installert.</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d spor</string>
|
||||
<string name="settings_public_api_package_title">Pakkenavn for Offentlig API</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks sørger ikke selv for et kart. Installer OSMDashboard for å vise opptakene dine på et kart.</string>
|
||||
<string name="settings_public_api_package_title">Pakkenavn for offentlig API</string>
|
||||
<string name="introduction_osm_dashboard">OpenTracks sørger ikke selv for kart. Installer OSMDashboard for å vise opptakene dine på et kart.</string>
|
||||
<string name="help_data_google_play_services_content">Nei</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -12,8 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
--><resources>
|
||||
<!-- Activity Type -->
|
||||
<string name="activity_type_airplane">avião</string>
|
||||
<string name="activity_type_atv">quadriciclos</string>
|
||||
@@ -263,22 +263,27 @@ limitations under the License.
|
||||
<!-- Voice -->
|
||||
<plurals name="voiceHours">
|
||||
<item quantity="one">%1$d hora</item>
|
||||
<item quantity="many">%1$d horas</item>
|
||||
<item quantity="other">%1$d horas</item>
|
||||
</plurals>
|
||||
<plurals name="voiceMinutes">
|
||||
<item quantity="one">%1$d minuto</item>
|
||||
<item quantity="many">%1$d minutos</item>
|
||||
<item quantity="other">%1$d minutos</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSeconds">
|
||||
<item quantity="one">%1$d segundo</item>
|
||||
<item quantity="many">%1$d segundos</item>
|
||||
<item quantity="other">%1$d segundos</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSpeedKilometersPerHour">
|
||||
<item quantity="one">%1$.1f quilômetro por hora</item>
|
||||
<item quantity="one">%1$.1f quilómetro por hora</item>
|
||||
<item quantity="many">%1$.1f quilómetros por hora</item>
|
||||
<item quantity="other">%1$.1f quilómetros por hora</item>
|
||||
</plurals>
|
||||
<plurals name="voiceSpeedMilesPerHour">
|
||||
<item quantity="one">%1$.1f milha por hora</item>
|
||||
<item quantity="many">%1$.1f milhas por hora</item>
|
||||
<item quantity="other">%1$.1f milhas por hora</item>
|
||||
</plurals>
|
||||
<!-- Waypoint Type -->
|
||||
@@ -359,4 +364,249 @@ limitations under the License.
|
||||
\n e) comprar em <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\n f) doar, e
|
||||
\n g) fazer o que achar que ajuda a OpenTracks. </string>
|
||||
<string name="about_preference_title">Sobre o OpenTracks</string>
|
||||
<string name="about_description"><i>OpenTracks</i> é uma aplicação de rastreamento de desportes que respeita totalmente a sua privacidade.</string>
|
||||
<string name="about_url">Página do projeto: <a href="%1$s">%1$s</a></string>
|
||||
<string name="about_version_name">Nome da versão: %1$s</string>
|
||||
<string name="about_version_code">Código da versão: %1$d</string>
|
||||
<string name="about_third_party_data">Dados de terceiros</string>
|
||||
<string name="about_libraries">Bibliotecas</string>
|
||||
<string name="help_storage_pictures_title">Como o OpenTracks armazena fotos tiradas como marcadores\?</string>
|
||||
<string name="help_storage_pictures_content">Eles são armazenados no armazenamento interno da app (ou seja, acessíveis apenas no OpenTracks).</string>
|
||||
<string name="help_data_google_play_services_title">O OpenTracks requer/usa o Google Play Services\?</string>
|
||||
<string name="help_recording_title">Como o OpenTracks regista dados\?</string>
|
||||
<string name="help_data_storage_title">Como o OpenTracks armazena dados do sensor\?</string>
|
||||
<string name="help_data_google_play_services_content">Não</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks não está gravando locais\?</string>
|
||||
<string name="help_support_title">Como posso oferecer suporte a OpenTracks\?</string>
|
||||
<string name="open_track_as_KML">Abrir como KML</string>
|
||||
<string name="open_track_as_GPX">Abrir como GPX</string>
|
||||
<string name="open_track_as_trackfileformat">Abrir como %1$s</string>
|
||||
<string name="activity_type_escooter">escolta</string>
|
||||
<string name="activity_type_swimming">natação</string>
|
||||
<string name="activity_type_swimming_open">nadando em águas abertas</string>
|
||||
<string name="activity_type_workout">treino</string>
|
||||
<string name="description_altitude_loss">Perda de elevação: %1$d m (%2$d ft)</string>
|
||||
<string name="description_pace_nautical">Ritmo (min/NM)</string>
|
||||
<string name="description_default_speed_or_pace">Por tipo de atividade</string>
|
||||
<string name="description_speed_nautical">Velocidade (nós)</string>
|
||||
<string name="description_speed_source_gps">GPS</string>
|
||||
<string name="export_with_photos">com fotos</string>
|
||||
<string name="export_without_photos">sem fotos</string>
|
||||
<string name="export_error_post_workout">A exportação pós-treino falhou, verifique o diretório de exportação.</string>
|
||||
<string name="export_cannot_write_to_dir">Não é possível escrever ao diretório de exportação.</string>
|
||||
<string name="export_dir_not_writable">" (não se escreve!)"</string>
|
||||
<string name="export_track_already_exists_msg">A trilha %1$s já existe no diretório de destino.</string>
|
||||
<string name="export_progress_summary_errors_msg">Erros</string>
|
||||
<string name="generic_open_track">Trilho aberto</string>
|
||||
<string name="generic_overwrite">Sobrescrever</string>
|
||||
<string name="generic_skip">Pular</string>
|
||||
<string name="generic_separator_done_total">/</string>
|
||||
<string name="generic_show_errors">Mostrar erros</string>
|
||||
<string name="generic_completed">Concluído</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">Concluído com %1$d erro</item>
|
||||
<item quantity="other">Concluído com %1$d erros</item>
|
||||
</plurals>
|
||||
<string name="generic_undo">Desfazer</string>
|
||||
<string name="generic_filter">Filtro</string>
|
||||
<string name="generic_from">De</string>
|
||||
<string name="generic_to">Para</string>
|
||||
<string name="generic_today">Hoje</string>
|
||||
<string name="generic_dismiss">Dispensar</string>
|
||||
<string name="gps_disabled_msg">GPS desativado</string>
|
||||
<string name="gps_fixed_and_ready">GPS corrigido e pronto</string>
|
||||
<string name="gps_recording_status">%1$s: %2$s</string>
|
||||
<string name="gps_recording_without_signal">O OpenTracks está gravando sem dados de GPS.</string>
|
||||
<string name="image_discard">Descartar</string>
|
||||
<string name="import_thread_interrupted">Tópico interrompido</string>
|
||||
<string name="import_unsupported_format">Formato de ficheiro não suportado</string>
|
||||
<string name="import_parser_error">Erro do analisador: %1$s</string>
|
||||
<string name="import_unable_to_import_file">Não foi possível importar o ficheiro: %1$s</string>
|
||||
<string name="import_no_kml_file_found">Nenhum ficheiro doc.kml encontrado no KMZ</string>
|
||||
<string name="import_file_imported">Ficheiro %1$s importado</string>
|
||||
<string name="import_prevent_reimport">Definiu evitar reimportar a trilha</string>
|
||||
<string name="import_progress_summary_ok_msg">Ficheiros importados</string>
|
||||
<string name="import_progress_summary_exists_msg">Já existe</string>
|
||||
<string name="import_progress_summary_errors_msg">Erros</string>
|
||||
<string name="marker_add_photo_canceled">Cancelado, nenhum marcador de foto adicionado</string>
|
||||
<string name="menu_insert_gallery_img">Inserir imagem da galeria</string>
|
||||
<string name="menu_voice_rate">Velocidade da voz</string>
|
||||
<string name="menu_resume_track">Retomar trilha</string>
|
||||
<string name="menu_add_custom_layout">Novo Layout</string>
|
||||
<string name="menu_filter">Filtro</string>
|
||||
<string name="delete_service">Serviço de exclusão</string>
|
||||
<string name="permission_gps_failed">OpenTracks requer permissão para usar GPS.</string>
|
||||
<string name="sensor_state_cadence_avg">Avg Cadência</string>
|
||||
<string name="sensor_state_cadence_max">Ritmo cardíaco médio</string>
|
||||
<string name="sensor_state_heart_rate_avg">Ritmo cardíaco Avg</string>
|
||||
<string name="sensor_state_heart_rate_max">Freqüência cardíaca máxima</string>
|
||||
<string name="sensor_state_power_avg">Energia Avg</string>
|
||||
<string name="sensor_could_not_scan">Não foi possível procurar aparelhos Bluetooth (erro %1$i).</string>
|
||||
<string name="bluetooth_disabled">Ative o Bluetooth.</string>
|
||||
<string name="settings_open_tracks_title">Trilhas abertas</string>
|
||||
<string name="settings_open_tracks_summary">Tudo sobre o Projeto OpenTracks</string>
|
||||
<string name="settings_defaults_title">Unidades e Atividade Default</string>
|
||||
<string name="settings_gps_summary">Intervalo de tempo, Intervalo de distância, Distância máxima, Precisão</string>
|
||||
<string name="settings_sensors_title">Sensores Bluetooth</string>
|
||||
<string name="settings_sensors_summary">Voice announcements</string>
|
||||
<string name="settings_ui_summary">Aparência, Layout de gravação, Tema</string>
|
||||
<string name="settings_gps_title">GPS</string>
|
||||
<string name="settings_announcements_title">Anúncios de voz</string>
|
||||
<string name="settings_announcements_summary">Tempo, distância, velocidade da voz</string>
|
||||
<string name="settings_import_export_title">Importar/Exportar</string>
|
||||
<string name="settings_import_export_summary">Importar, Exportar, Auto Export</string>
|
||||
<string name="settings_units_category">Unidades</string>
|
||||
<string name="settings_recording_idle_speed_title">Limite de velocidade de marcha lenta</string>
|
||||
<string name="settings_night_mode_title">Tema UI</string>
|
||||
<string name="settings_layout_reset_confirm_message">Todas as configurações de layout serão revertidas para os valores padrão. Isso não excluirá nenhuma trilha do aparelho.</string>
|
||||
<string name="settings_all_reset">Redefinir</string>
|
||||
<string name="settings_sensor_running_speed_and_cadence">Distância, velocidade e cadência</string>
|
||||
<string name="settings_sensor_wheel_circumference">Circunferência da Roda (mm)</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_enabled">Mostrar apenas aparelhos Bluetooth que anunciam os serviços necessários</string>
|
||||
<string name="settings_stats_units_nautical">Milha náutica (NM, ft)</string>
|
||||
<string name="settings_api_title">API pública</string>
|
||||
<string name="settings_public_api_package_title">Nome do pacote para API pública</string>
|
||||
<string name="settings_public_api_enabled_summary_off">Apenas o OpenTracks pode iniciar e parar gravações.</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_off">Os dados gravados não serão compartilhados automaticamente com outras aplicações.</string>
|
||||
<string name="settings_import_section">Importar</string>
|
||||
<string name="settings_export_section">Exportar</string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Impedir a reimportação de trilhas</string>
|
||||
<string name="stats_loss">Perda</string>
|
||||
<string name="stats_coordinates">Latitude/Longitude</string>
|
||||
<string name="stats_split_interval">Intervalo de divisão</string>
|
||||
<string name="stats_sensors_heart_rate">Frequência cardíaca</string>
|
||||
<string name="stats_sensors_cadence">Cadência</string>
|
||||
<string name="stats_sensors_power">Poder</string>
|
||||
<string name="stats_clock">Relógio</string>
|
||||
<string name="track_recording_notification_accuracy">Precisão da localização: %1$s</string>
|
||||
<string name="track_distance_notification">Distância: %1$s</string>
|
||||
<string name="track_delete_number_of_tracks">%1$d trilhas</string>
|
||||
<string name="track_delete_not_recording">Esta trilha não pode ser excluída porque está gravada no momento.</string>
|
||||
<string name="track_discarding">Descartando trilha</string>
|
||||
<string name="track_detail_intervals_tab">intervalos</string>
|
||||
<string name="aggregated_stats_empty_message">Registe a sua primeira trilha para ver as estatísticas agregadas</string>
|
||||
<string name="interval_list_empty_message">Não há nenhum intervalo ainda</string>
|
||||
<string name="value_float_kilometer_hour_recommended">%1$.1f km/h (Recomendado)</string>
|
||||
<string name="value_float_kilometer_hour">%1$.1f km/h</string>
|
||||
<string name="value_integer_knots">%1$d nós</string>
|
||||
<string name="value_float_mile_hour">%1$.1f mi/h</string>
|
||||
<string name="value_float_knots">%1$.1f nós</string>
|
||||
<string name="value_float_mile_hour_recommended">%1$.1f mi/h (Recomendado)</string>
|
||||
<string name="voice_per_kilometer">por quilômetro</string>
|
||||
<plurals name="voiceSpeedMKnots">
|
||||
<item quantity="one">%1$.1f nó</item>
|
||||
<item quantity="other">%1$.1f nós</item>
|
||||
</plurals>
|
||||
<string name="voice_per_mile">por milha</string>
|
||||
<string name="voice_per_nautical_mile">por milha náutica</string>
|
||||
<plurals name="voiceDistanceMiles">
|
||||
<item quantity="one">%1$.1f milha</item>
|
||||
<item quantity="other">%1$.1f milhas</item>
|
||||
</plurals>
|
||||
<plurals name="voiceDistanceNauticalMiles">
|
||||
<item quantity="one">%1$.1f milha náutica</item>
|
||||
<item quantity="other">%1$.1f milhas náuticas</item>
|
||||
</plurals>
|
||||
<string name="total_distance">Distância total</string>
|
||||
<string name="average_heart_rate">Frequência cardíaca média</string>
|
||||
<plurals name="voiceDistanceKilometers">
|
||||
<item quantity="one">%1$.1f quilómetro</item>
|
||||
<item quantity="many">%1$.1f quilómetros</item>
|
||||
<item quantity="other">%1$.1f quilómetros</item>
|
||||
</plurals>
|
||||
<string name="current_heart_rate">Frequência cardíaca atual</string>
|
||||
<string name="share_image_subject">Gostaria de compartilhar uma imagem consigo</string>
|
||||
<string name="share_image_body">Acho que pode estar interessado nesta imagem.</string>
|
||||
<string name="hold_to_stop">Segure para parar</string>
|
||||
<string name="report_bug">Relatório de problema</string>
|
||||
<string name="select_show_on_map_behavior">Selecionar comportamento de \"mostrar no mapa\"</string>
|
||||
<string name="show_on_map_format_ask">Perguntas</string>
|
||||
<string name="show_on_dashboard">Painel API do OpenTracks</string>
|
||||
<string name="settings_reset_summary">Redefinir configurações para valores padrão</string>
|
||||
<string name="settings_default_trackfileformat">Exportar/compartilhar formato de ficheiro</string>
|
||||
<string name="settings_export_filename_title">Formato do nome do ficheiro</string>
|
||||
<string name="settings_recording_customize_layout_title">Personalize o seu layout de gravação</string>
|
||||
<string name="about_history">OpenTracks é um fork do <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (descontinuado em 2016).</string>
|
||||
<string name="help_feature_title">OpenTracks tem os seguintes recursos:</string>
|
||||
<string name="help_track_export_title">Como posso exportar/compartilhar trilhas gravadas\?</string>
|
||||
<string name="help_track_export_content"> As trilhas podem ser exportadas das configurações como KMZ, KML ou GPX. KMZ/KML todos os dados; Localizações apenas GPX. As trilhas compartilhadas por meio do <i>ícone de compartilhamento</i> são compartilhadas como KMZ. </string>
|
||||
<string name="help_recording_content">OpenTracks usa o sensor GPS do aparelho para registar a localização continuamente. Se o aparelho GPS relatar dados imprecisos (por exemplo, localização, velocidade, elevação), o OpenTracks não pode fazer nada a respeito.</string>
|
||||
<string name="help_data_storage_content"> Os dados do sensor são armazenados por local registado. Em termos de sensor de frequência cardíaca, isso significa que apenas <i>uma</i> medição de frequência cardíaca é armazenada por local. Se estiver parado ou dentro de casa, nenhum dado do sensor será registrdo (mas será mostrado). </string>
|
||||
<string name="export_all_do_it_for_all">faça isso para todos os conflitos</string>
|
||||
<string name="export_track_errors">Trilhas não exportadas</string>
|
||||
<string name="export_progress_summary_new_msg">Novas trilhas exportadas</string>
|
||||
<string name="export_progress_summary_overwrite_msg">Trilhas sobrescritas</string>
|
||||
<string name="export_progress_summary_skip_msg">Trilhas puladas</string>
|
||||
<string name="generic_click_twice_cancel">Clique novamente para cancelar</string>
|
||||
<string name="generic_choose_an_option">Escolha uma opção</string>
|
||||
<string name="generic_yesterday">Ontem</string>
|
||||
<string name="import_error_info">%1$s: %2$s</string>
|
||||
<string name="import_error_list_dialog_title">Ficheiros não importados</string>
|
||||
<string name="menu_select_layout">Selecione o layout</string>
|
||||
<string name="permission_bluetooth_failed">OpenTracks requer permissão para usar Bluetooth.</string>
|
||||
<string name="permission_bluetooth_failed_rejected">Precisa conceder acesso ao OpenTracks manualmente para aparelhos próximos nas configurações do sistema.</string>
|
||||
<string name="settings_recording_customize_layout_select_columns">Campos de dados por linha</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">Mostrar estatísticas no ecrã de bloqueio</string>
|
||||
<string name="settings_defaults_summary">Unidades Preferidas, Atividade Padrão, Nome Padrão da Pista</string>
|
||||
<string name="settings_ui_title">Interface do utilizador</string>
|
||||
<string name="settings_activity_defaults_category">Defaults da atividade</string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_summary">Durante a gravação, mostre estatísticas sem desbloquear o aparelho.</string>
|
||||
<string name="settings_public_api_dashboard_enabled_title">Transferência automática de dados</string>
|
||||
<string name="settings_public_api_dashboard_enabled_summary_on">Uma aplicação que iniciou uma gravação também pode acessar os dados gravados.</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_title">Mantenha o ecrã ligada</string>
|
||||
<string name="settings_recording_keep_screen_on_while_recording_summary">Durante a gravação, mantenha o ecrã ligado.</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_title">Ecrã inteiro</string>
|
||||
<string name="settings_recording_fullscreen_on_while_recording_summary">Durante a gravação, apresente em ecrã inteiro.</string>
|
||||
<string name="settings_night_mode_option_system">Sistema</string>
|
||||
<string name="settings_night_mode_option_day">Dia</string>
|
||||
<string name="settings_night_mode_option_night">Noite</string>
|
||||
<string name="settings_announcements_totaltime_frequency">Intervalo de tempo</string>
|
||||
<string name="track_speed_notification">Velocidade: %1$s</string>
|
||||
<string name="settings_announcements_distance_frequency">Intervalo de distância</string>
|
||||
<string name="settings_announcements_average_heart_rate">Frequência cardíaca média</string>
|
||||
<string name="settings_announcements_lap_heart_rate">Frequência cardíaca da volta</string>
|
||||
<string name="settings_announcements_average_speed_pace">Velocidade/ritmo médio</string>
|
||||
<string name="settings_announcements_lap_speed_pace">Velocidade/ritmo de volta</string>
|
||||
<string name="custom_layout_list_title">Estrutura</string>
|
||||
<string name="custom_layout_list_edit_name">Digite o nome do layout a ser adicionado</string>
|
||||
<string name="custom_layout_list_edit_already_exists">O nome do layout já existe</string>
|
||||
<string name="custom_layout_list_layout_removed">Layout foi removido</string>
|
||||
<string name="custom_layout_select_layout">Selecionar layout</string>
|
||||
<string name="stats_custom_layout_default_layout">Layout padrão</string>
|
||||
<string name="settings_layout_reset">Redefinir layout personalizado</string>
|
||||
<string name="settings_layout_reset_confirm_title">Redefinir os seus layouts\?</string>
|
||||
<string name="settings_layout_reset_done">Todas as configurações de layout foram revertidas para os valores padrão</string>
|
||||
<string name="settings_generic_sensor">Genérico</string>
|
||||
<string name="settings_cycling_sensor">Ciclismo</string>
|
||||
<string name="settings_running_sensor">Correndo</string>
|
||||
<string name="settings_sensor_cycling_distance_speed">Distância e velocidade</string>
|
||||
<string name="settings_sensor_cycling_cadence">Cadência</string>
|
||||
<string name="settings_public_api_enabled_title">API pública</string>
|
||||
<string name="settings_public_api_enabled_summary_on">Outras aplicações instaladas podem iniciar ou interromper gravações.</string>
|
||||
<string name="settings_public_api_disabled_toast">A API pública do OpenTracks está desativada: pode ser ativada nas configurações.</string>
|
||||
<string name="track_pace_notification">Parcela: %1$s</string>
|
||||
<string name="aggregated_stats_filter_no_results">Não há atividades para o filtro especificado</string>
|
||||
<string name="unit_nautical_mile">NM</string>
|
||||
<string name="unit_knots">kn</string>
|
||||
<string name="unit_minute_per_nautical_mile">min/NM</string>
|
||||
<string name="location_coordinate">%1$s°</string>
|
||||
<string name="location_latitude_longitude">%1$s, %2$s</string>
|
||||
<string name="value_float_knots_recommended">%1$.1f nós (recomendado)</string>
|
||||
<string name="lap_time">Tempo da volta</string>
|
||||
<string name="pace">Ritmo</string>
|
||||
<string name="lap_speed">Velocidade da volta</string>
|
||||
<string name="speed">Velocidade</string>
|
||||
<string name="instant_export_enabled_summary">Exportar trilha para armazenamento depois que a gravação for concluída</string>
|
||||
<string name="instant_export_enabled_title">Exportação instantânea pós-treino</string>
|
||||
<string name="settings_default_export_uri_title">Diretório de exportação de trilhas</string>
|
||||
<string name="field_set_primary">Primário</string>
|
||||
<string name="field_set_secondary">Secundário</string>
|
||||
<string name="field_remove_from_layout">Remover</string>
|
||||
<string name="no_compatible_file_manager_installed">Nenhum gestor de ficheiros compatível instalado</string>
|
||||
<string name="error_crash_title">%1$s falharam</string>
|
||||
<string name="share_crash_report">Compartilhar relatório de falha</string>
|
||||
<string name="show_on_dashboard_not_installed">Nenhuma aplicação compatível com o painel API do OpenTracks instalado.</string>
|
||||
<string name="always">Sempre</string>
|
||||
<string name="introduction_osm_dashboard">O próprio OpenTracks não fornece um mapa. Instale o OSMDashboard para visualizar as suas gravações num mapa.</string>
|
||||
</resources>
|
||||
@@ -518,7 +518,6 @@ limitations under the License.
|
||||
<string name="settings_sensors_summary">Cyklistické snímače, obvod kolies, snímače behu</string>
|
||||
<string name="settings_open_tracks_summary">Viac o projekte OpenTracks</string>
|
||||
<string name="settings_gps_summary">Interval času, interval vzdialenosti, Max. vzdialenosť, Presnosť</string>
|
||||
<string name="help_support_content"/>
|
||||
<string name="share_image_body">Myslím, že by vás mohol zaujímať tento obrázok.</string>
|
||||
<string name="export_progress_summary_skip_msg">Vynechané skladby</string>
|
||||
<string name="gps_disabled_msg">GPS vypnuté</string>
|
||||
|
||||
@@ -442,7 +442,6 @@ limitations under the License.
|
||||
<string name="settings_gps_summary">Časovni interval, Interval razdalje, Največja razdalja, Točnost</string>
|
||||
<string name="settings_sensors_summary">Kolesarski senzorji, Obod kolesa, Tekaški senzorji</string>
|
||||
<string name="settings_sensors_title">Bluetooth senzorji</string>
|
||||
<string name="settings_open_tracks_title"></string>
|
||||
<string name="settings_recording_show_on_lockscreen_while_recording_title">Prikaži statistiko na zaklenjenem zaslonu</string>
|
||||
<string name="about_preference_title">O OpenTracks</string>
|
||||
<string name="export_without_photos">brez fotografij</string>
|
||||
|
||||
@@ -589,7 +589,6 @@ limitations under the License.
|
||||
<item quantity="one">1 deniz mili</item>
|
||||
<item quantity="other">%1$.1f deniz mili</item>
|
||||
</plurals>
|
||||
<string name="image_discard"/>
|
||||
<string name="settings_public_api_package_title">Genel API için paket adı</string>
|
||||
<string name="show_on_dashboard_not_installed">OpenTracks Gösterge Paneli API\'sini destekleyen hiçbir uygulama yüklü değil.</string>
|
||||
</resources>
|
||||
@@ -356,9 +356,9 @@ limitations under the License.
|
||||
<string name="instant_export_enabled_summary">Експорт треку в сховище після завершення запису</string>
|
||||
<string name="instant_export_enabled_title">Миттєвий експорт після тренування</string>
|
||||
<string name="help_gps_troubleshooting_content"> Що ви можете зробити:
|
||||
\na) перевірте, чи ввімкнено GPS,
|
||||
\na) перевірте, чи увімкнено GPS,
|
||||
\nb) покращити прийом GPS (наприклад, вийти на вулицю),
|
||||
\nc) перевірити за допомогою додатку <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> та
|
||||
\nc) перевірити за допомогою застосунку <a href="https://f-droid.org/en/packages/com.android.gpstest.osmdroid/">GPSTest</a> та
|
||||
\nd) вимкнути енергозбереження.
|
||||
\n
|
||||
\nЯкщо це не допомогло - будь ласка, повідомте про помилку. </string>
|
||||
@@ -369,13 +369,13 @@ limitations under the License.
|
||||
<string name="about_libraries">Бібліотеки</string>
|
||||
<string name="help_feature_title">OpenTracks має такі особливості:</string>
|
||||
<string name="help_storage_pictures_title">Як OpenTracks зберігає зображення зроблені за допомогою маркувань\?</string>
|
||||
<string name="help_storage_pictures_content">Вони зберігаються у внутрішній пам\'яті додатку (тобто, доступні тільки для OpenTracks).</string>
|
||||
<string name="help_storage_pictures_content">Вони зберігаються у внутрішній пам\'яті застосунку (тобто, доступні тільки для OpenTracks).</string>
|
||||
<string name="help_data_google_play_services_title">Чи потребує OpenTracks використання Google Play Service\?</string>
|
||||
<string name="help_track_map_title">Як показати трек на мапі\?</string>
|
||||
<string name="help_data_storage_title">Як OpenTracks зберігає дані датчиків\?</string>
|
||||
<string name="help_recording_title">Як OpenTracks записує дані\?</string>
|
||||
<string name="help_track_export_title">Як я можу експортувати або обмінятися записаним треком\?</string>
|
||||
<string name="settings_night_mode_title">Тема додатка</string>
|
||||
<string name="settings_night_mode_title">Тема UI</string>
|
||||
<string name="help_gps_troubleshooting_title">OpenTracks не записує моє місцеположення\?</string>
|
||||
<string name="help_support_title">Як я можу підтримати OpenTracks\?</string>
|
||||
<string name="open_track_as_KML">Відкрити як KML</string>
|
||||
@@ -412,13 +412,13 @@ limitations under the License.
|
||||
<string name="import_no_kml_file_found">Файл doc.kml не знайдено в KMZ</string>
|
||||
<string name="import_error_list_dialog_title">Файли не імпортовано</string>
|
||||
<string name="import_error_info">%1$s:%2$s</string>
|
||||
<string name="marker_add_photo_canceled">Скасовано, фото-макркування не додано</string>
|
||||
<string name="marker_add_photo_canceled">Скасовано, фото-маркування не додано</string>
|
||||
<string name="menu_insert_gallery_img">Додати зображення з галереї</string>
|
||||
<string name="menu_resume_track">Відновити трек</string>
|
||||
<string name="permission_gps_failed">OpenTracks вимагає дозволу на використання GPS.</string>
|
||||
<string name="settings_night_mode_option_system">Системна</string>
|
||||
<string name="about_description"><i>Open Tracks</i> це спортивний GPS трекер який поважає вашу приватність.</string>
|
||||
<string name="about_history">Open Tracks це відгалуження додатку <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (закритого в 2016р).</string>
|
||||
<string name="about_history">Open Tracks це відгалуження застосунку <a href="https://code.google.com/archive/p/mytracks/"><i>Google\'s MyTracks</i></a> (закритого в 2016р).</string>
|
||||
<string name="help_feature_content">* запис GPS-шляху
|
||||
\n* використання датчика ритму серця (тільки Bluetooth LE)
|
||||
\n* голосовий путівник (за часом або дистацією)
|
||||
@@ -432,9 +432,9 @@ limitations under the License.
|
||||
\nа) поширювати інформацію про OpenTracks,
|
||||
\nb) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">пропонувати функції</a>,
|
||||
\nc) <a href="https://github.com/OpenTracksApp/OpenTracks/issues">повідомляти про помилки</a>,
|
||||
\nd) допомогnb в розробці програмного забезпечення,
|
||||
\nd) допомогти в розробці програмного забезпечення,
|
||||
\ne) купувати в <a href="https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore">Google Play Store</a>,
|
||||
\nf) зробити фінаносовий внесок, та
|
||||
\nf) зробити фінансовий внесок, та
|
||||
\ng) робити все, що, на вашу думку, допомагає OpenTracks. </string>
|
||||
<string name="description_default_speed_or_pace">За активністю</string>
|
||||
<string name="export_error_post_workout">Не вдалося виконати експорт після тренування, перевірте каталог експорту.</string>
|
||||
@@ -451,9 +451,9 @@ limitations under the License.
|
||||
<string name="share_image_subject">Я хотів би поділитися з вами зображенням</string>
|
||||
<string name="settings_default_export_uri_title">Каталог експорту треків</string>
|
||||
<string name="help_track_map_content"> Натисніть іконку мапи.
|
||||
\na) (оновлення в реальному часі) Встановіть додаток <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Він дозволить подивитися мапу онлайн й оффлайн завдяки <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\nНасправді, OSMDashboard є окремим додатком який може отримати тимчасовий доступ до даних треку які будуть відобрежені на мапі.
|
||||
\nb) Через цвичайний картографічний додаток (на приклад, <a href="http://osmand.net/">OsmAnd</a> та <a href="https://maps.me/">MAPS.ME</a>). Ці додатки повинні підтримувати формати файлів треку KMZ (тільки місцеположення та мітки часу). Фактично, додаток повинен підтримувати функцію <i>ACTION_VIEW</i> із MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
\na) (оновлення в реальному часі) Встановіть застосунок <a href="https://github.com/OpenTracksApp/OSMDashboard">OSMDashboard</a>. Він дозволить подивитися карту онлайн й оффлайн завдяки <a href="https://www.openstreetmap.org">OpenStreetMap</a>.
|
||||
\nНасправді, OSMDashboard є окремим застосунком, який може отримати тимчасовий доступ до даних треку які будуть відображені на карті.
|
||||
\nb) Через звичайний картографічний застосунок (на приклад, <a href="http://osmand.net/">OsmAnd</a> та <a href="https://maps.me/">MAPS.ME</a>). Ці застосунки повинні підтримувати формати файлів треку KMZ (тільки місцеположення та мітки часу). Фактично, застосунок повинен підтримувати функцію <i>ACTION_VIEW</i> із MIME <i>application/vnd.google-earth.kmz</i>. </string>
|
||||
<string name="settings_prevent_reimport_tracks_title">Заборонити повторний імпорт треків</string>
|
||||
<plurals name="generic_completed_with_errors">
|
||||
<item quantity="one">Завершено з %1$d однією помилкою</item>
|
||||
|
||||
@@ -57,4 +57,9 @@ limitations under the License.
|
||||
|
||||
<color name="splashscreen_background">#272727</color>
|
||||
|
||||
<color name="heart_rate_zone_textcolor_5">#FF0000</color>
|
||||
<color name="heart_rate_zone_textcolor_4">#FFBD04</color>
|
||||
<color name="heart_rate_zone_textcolor_3">#6ACC2B</color>
|
||||
<color name="heart_rate_zone_textcolor_2">#64D1FD</color>
|
||||
<color name="heart_rate_zone_textcolor_1">@color/opentracks_on_primary_color</color>
|
||||
</resources>
|
||||
@@ -44,6 +44,9 @@
|
||||
true
|
||||
</bool>
|
||||
|
||||
<string name="settings_sensor_heart_rate_max_key" translatable="false">maxHeartRate</string>
|
||||
<string name="settings_sensor_heart_rate_max_default" translatable="false">220</string>
|
||||
|
||||
<string name="show_on_map_format_key" translatable="false">showOnMapFormatKey</string>
|
||||
|
||||
<string name="total_rows_deleted_key" translatable="false">totalRowsDeletedKey</string>
|
||||
|
||||
@@ -434,6 +434,8 @@ limitations under the License.
|
||||
<string name="settings_sensor_wheel_circumference">Wheel Circumference (mm)</string>
|
||||
<string name="settings_sensor_bluetooth_service_filter_enabled">Only show Bluetooth devices that announce required services</string>
|
||||
|
||||
<string name="settings_sensor_heart_rate_max">Zones: maximum Heart rate (bpm)</string>
|
||||
|
||||
<!-- Settings Stats -->
|
||||
<string name="settings_stats_rate_title">Preferred rate</string>
|
||||
<string name="settings_stats_units_imperial">Imperial (mi, ft)</string>
|
||||
@@ -443,6 +445,7 @@ limitations under the License.
|
||||
|
||||
<!-- Settings public api -->
|
||||
<string name="settings_api_title">Public API</string>
|
||||
<string name="settings_api_summary">Remote control and statistics API for other apps</string>
|
||||
<string name="settings_public_api_package_title">Package name for Public API</string>
|
||||
<string name="settings_public_api_enabled_title">Public API</string>
|
||||
<string name="settings_public_api_enabled_summary_on">Other installed applications can start or stop recordings.</string>
|
||||
@@ -479,7 +482,7 @@ limitations under the License.
|
||||
<string name="stats_distance">Distance</string>
|
||||
<string name="stats_total_time">Total time</string>
|
||||
<string name="stats_split_interval">Split interval</string>
|
||||
<string name="stats_sensors_heart_rate">Heart Rate</string>
|
||||
<string name="stats_sensors_heart_rate">Heart rate</string>
|
||||
<string name="stats_sensors_cadence">Cadence</string>
|
||||
<string name="stats_sensors_power">Power</string>
|
||||
<string name="stats_clock">Clock</string>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<Preference
|
||||
android:icon="@drawable/ic_baseline_api_24"
|
||||
android:key="@string/settings_api_key"
|
||||
android:summary="Remote control and statistics API for other apps"
|
||||
android:summary="@string/settings_api_summary"
|
||||
android:title="@string/settings_api_title"/>
|
||||
|
||||
<Preference
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
android:defaultValue="@string/sensor_type_value_none"
|
||||
android:key="@string/settings_sensor_bluetooth_heart_rate_key"
|
||||
android:title="@string/sensor_state_heart_rate" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="@string/settings_sensor_heart_rate_max_default"
|
||||
android:inputType="number"
|
||||
android:key="@string/settings_sensor_heart_rate_max_key"
|
||||
android:title="@string/settings_sensor_heart_rate_max"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_cycling_sensor">
|
||||
|
||||
Reference in New Issue
Block a user