Compare commits

...

13 Commits

Author SHA1 Message Date
Dennis Guse e5d1e421a4 Release: v4.0.8 2022-09-03 10:21:38 +02:00
Dennis Guse c3b30815c8 SplashScreen: use Androidx library.
Improvement for Android 12+.
Fixes #1365.
2022-08-28 17:01:10 +02:00
Dennis Guse 5a4919efd2 PublicAPI: support setting the icon.
Fixes #1282.
2022-08-28 15:17:42 +02:00
dependabot[bot] bb549fdc93 Bump desugar_jdk_libs from 1.2.0 to 1.2.2
Bumps [desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.2.0 to 1.2.2.
- [Release notes](https://github.com/google/desugar_jdk_libs/releases)
- [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/desugar_jdk_libs/commits)

---
updated-dependencies:
- dependency-name: com.android.tools:desugar_jdk_libs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-25 21:58:51 +02:00
Dennis Guse 73e59cbba2 UI: show timezone if different than local.
Fixes #1296.
2022-08-22 18:43:53 +02:00
Dennis Guse 27727b6dcf Merge pull request #1310 from OpenTracksApp/time
Refactoring things regarding Duration, Speed, and Distance
2022-08-21 23:25:44 +02:00
Dennis Guse 06d2957407 Desugar: upgrade to AGP 7.3.0-rc01
Required for desugar 1.2.0)
2022-08-21 23:25:06 +02:00
Dennis Guse 8b08af6bcf Upgrade to Gradlew 7.5.1 2022-08-21 22:02:49 +02:00
Dennis Guse 78dad95ef6 Altitude: show always in m/ft.
Fixes #1319.
2022-08-21 22:02:49 +02:00
Dennis Guse be9d56721e Preferences: nicer formattings for distances. 2022-08-21 22:02:49 +02:00
Dennis Guse e5785dd3ac Distance now does all conversions itself. 2022-08-21 22:02:49 +02:00
Dennis Guse 11d912d14d Speed: use Distance for conversions. 2022-08-21 22:02:49 +02:00
Dennis Guse 807ac3fc9f Remove UnitConversions for time/durations. 2022-08-21 22:02:49 +02:00
32 changed files with 201 additions and 191 deletions
+6 -8
View File
@@ -111,19 +111,17 @@ The API can be invoked by sending an explicit Intent to start an activity.
* **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording` * **Start a recording:** `de.dennisguse.opentracks.publicapi.StartRecording`
* **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording` * **Stop a recording:** `de.dennisguse.opentracks.publicapi.StopRecording`
For testing via adb: `adb shell am start -n "package/class"` For testing via adb: `adb shell am start -e someParameter someValue -n "package/class"`
`StartRecording` supports the following parameters: `StartRecording` supports the following parameters:
* Set track data: `TRACK_NAME`, `TRACK_CATEGORY`, and `TRACK_DESCRIPTION` * Set track data: `TRACK_NAME`, `TRACK_DESCRIPTION`, `TRACK_CATEGORY`, and `TRACK_ICON` (non-localized identifier see [/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java#L38](/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java#L38)).
* Send recorded data to another application via _Dashboard API_: `STATS_TARGET_PACKAGE` NOTE: if `TRACK_ICON` is not present, `TRACK_CATEGORY` will be used to determine the icon (localized).
and `STATS_TARGET_CLASS` * Send recorded data to another application via _Dashboard API_: `STATS_TARGET_PACKAGE` and `STATS_TARGET_CLASS`
The Public API is disabled by default to protect the user's privacy, but it can easily be enabled in The Public API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
the settings.
__IMPORTANT__: triggering `StartRecording` does not check if Android permissions (location or __IMPORTANT__: triggering `StartRecording` does not check if Android permissions (location or Bluetooth) were granted. If they are not granted, nothing will be recorded.
Bluetooth) were granted. If they are not granted`
## Dashboard API (incl. map) ## Dashboard API (incl. map)
+7 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.2' classpath 'com.android.tools.build:gradle:7.3.0-rc01'
} }
} }
@@ -59,8 +59,8 @@ android {
defaultConfig { defaultConfig {
applicationId "de.dennisguse.opentracks" applicationId "de.dennisguse.opentracks"
versionCode 5004 versionCode 5017
versionName "v4.0.7" versionName "v4.0.8"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -81,6 +81,8 @@ android {
} }
} }
namespace 'de.dennisguse.opentracks'
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
@@ -118,7 +120,7 @@ android {
} }
dependencies { dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
implementation 'androidx.appcompat:appcompat:1.5.0' implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
@@ -127,6 +129,7 @@ dependencies {
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.6.1' implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 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:core:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
@@ -0,0 +1,11 @@
v4.0.8: OpenTracks
Changes:
- Altitude always shown in m/ft
- Show track timezone if different than device timezone
- PublicAPI: support setting the track icon explicitly
Developer:
- Introduced DistanceFormatter
- Upgrade to AGP 7.3/desugar for Duration.class
- Android12+: do splashscreen via Androidx
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
@@ -7,6 +7,7 @@ import junit.framework.TestCase;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
@@ -105,7 +106,7 @@ public class TrackSelectionTest extends TestCase {
long oneDay = 24 * 60 * 60 * 1000; long oneDay = 24 * 60 * 60 * 1000;
TrackSelection filterWrong1 = new TrackSelection().addDateRange(instant, null); TrackSelection filterWrong1 = new TrackSelection().addDateRange(instant, null);
TrackSelection filterWrong2 = new TrackSelection().addDateRange(null, instant); TrackSelection filterWrong2 = new TrackSelection().addDateRange(null, instant);
TrackSelection filterOk = new TrackSelection().addDateRange(instant, instant.plusMillis(oneDay)); TrackSelection filterOk = new TrackSelection().addDateRange(instant, instant.plus(Duration.ofDays(1)));
// when // when
SelectionData selectionWrong1 = filterWrong1.buildSelection(); SelectionData selectionWrong1 = filterWrong1.buildSelection();
@@ -463,7 +463,7 @@ public class ExportImportTest {
contentProviderUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId);
// then // then
InputStream expected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.csv_export); InputStream expected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.csv_export);
String expectedText = new BufferedReader(new InputStreamReader(expected, StandardCharsets.UTF_8)) String expectedText = new BufferedReader(new InputStreamReader(expected, StandardCharsets.UTF_8))
.lines() .lines()
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
@@ -69,7 +69,7 @@ public class GPXTrackImporterTest {
public void gpx_with_pause_resume() throws IOException { public void gpx_with_pause_resume() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_gpx_pause_resume); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_gpx_pause_resume);
// when // when
// 1. import // 1. import
@@ -123,7 +123,7 @@ public class GPXTrackImporterTest {
public void gpx_without_speed() throws IOException { public void gpx_without_speed() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_without_speed); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_without_speed);
// when // when
// 1. import // 1. import
@@ -171,7 +171,7 @@ public class GPXTrackImporterTest {
public void gpx_speed_no_namespace() throws IOException { public void gpx_speed_no_namespace() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx11_with_speed_no_namespace); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx11_with_speed_no_namespace);
// when // when
// 1. import // 1. import
@@ -221,8 +221,8 @@ public class GPXTrackImporterTest {
public void importExportTest_timezone() throws IOException { public void importExportTest_timezone() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new GpxTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx_timezone); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.gpx_timezone); InputStream inputStreamExpected = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.gpx_timezone);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -60,7 +60,7 @@ public class KMLTrackImporterTest {
public void kml22_order_location_and_when() throws IOException { public void kml22_order_location_and_when() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.kml22_order_location_and_when); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_order_location_and_when);
// when // when
// 1. import // 1. import
@@ -109,7 +109,7 @@ public class KMLTrackImporterTest {
public void kml22_time_decreases() throws IOException { public void kml22_time_decreases() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.kml22_time_decreases); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_time_decreases);
// when // when
// 1. import // 1. import
@@ -149,7 +149,7 @@ public class KMLTrackImporterTest {
public void kml22_with_statistics_marker() throws IOException { public void kml22_with_statistics_marker() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.legacy_kml22_statistics_marker); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.legacy_kml22_statistics_marker);
// when // when
// 1. import // 1. import
@@ -223,7 +223,7 @@ public class KMLTrackImporterTest {
public void kml_without_locations() throws IOException { public void kml_without_locations() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.kml22_without_locations); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_without_locations);
// when // when
importTrackId = importer.importFile(inputStream).get(0); importTrackId = importer.importFile(inputStream).get(0);
@@ -234,7 +234,7 @@ public class KMLTrackImporterTest {
public void kml_when_locations_different() throws IOException { public void kml_when_locations_different() throws IOException {
// given // given
XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter)); XMLImporter importer = new XMLImporter(new KmlTrackImporter(context, trackImporter));
InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.debug.test.R.raw.kml22_when_locations_different); InputStream inputStream = InstrumentationRegistry.getInstrumentation().getContext().getResources().openRawResource(de.dennisguse.opentracks.test.R.raw.kml22_when_locations_different);
// when // when
importTrackId = importer.importFile(inputStream).get(0); importTrackId = importer.importFile(inputStream).get(0);
@@ -99,7 +99,7 @@ public class DescriptionGeneratorTest {
+ "Min elevation: -500 m (-1640 ft)<br>" + "Min elevation: -500 m (-1640 ft)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>" + "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>" + "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>"; + "Recorded: " + StringUtils.formatDateTimeWithOffset(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true)); assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
} }
@@ -135,7 +135,7 @@ public class DescriptionGeneratorTest {
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>" + "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
+ "Elevation gain: 6000 m (19685 ft)<br>" + "Elevation gain: 6000 m (19685 ft)<br>"
+ "Elevation loss: 6000 m (19685 ft)<br>" + "Elevation loss: 6000 m (19685 ft)<br>"
+ "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>"; + "Recorded: " + StringUtils.formatDateTimeWithOffset(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "<br>";
assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true)); assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true));
} }
@@ -21,7 +21,6 @@ import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
@@ -180,7 +179,7 @@ public class IntervalStatisticsTest {
// then // then
assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01); assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance.toM(), 0.01);
assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1); assertEquals(trackStatistics.getTotalTime().toSeconds(), totalTime, 0.01);
assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval)); assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval));
if (totalGain != null) { if (totalGain != null) {
assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1); assertEquals(totalGain, numberOfPoints * TestDataUtil.ALTITUDE_GAIN, 0.1);
-1
View File
@@ -15,7 +15,6 @@ limitations under the License.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="de.dennisguse.opentracks"
android:installLocation="auto"> android:installLocation="auto">
<supports-screens <supports-screens
@@ -38,6 +38,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.splashscreen.SplashScreen;
import androidx.cursoradapter.widget.ResourceCursorAdapter; import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader; import androidx.loader.content.CursorLoader;
@@ -158,8 +159,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// Reset theme after splash SplashScreen.installSplashScreen(this);
setTheme(R.style.ThemeCustom);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
@@ -6,7 +6,6 @@ import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.settings.UnitSystem; import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -41,7 +40,7 @@ public class ChartPoint {
if (smoothedSpeed != null) { if (smoothedSpeed != null) {
speed = smoothedSpeed.to(unitSystem); speed = smoothedSpeed.to(unitSystem);
pace = smoothedSpeed.toPace(unitSystem).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN; pace = smoothedSpeed.toPace(unitSystem).toSeconds() / 60d;
} }
if (trackPoint.hasHeartRate()) { if (trackPoint.hasHeartRate()) {
heartRate = (double) trackPoint.getHeartRate().getBPM(); heartRate = (double) trackPoint.getHeartRate().getBPM();
@@ -2,22 +2,35 @@ package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.time.Duration;
import java.util.Objects; import java.util.Objects;
public class Cadence { public class Cadence {
public static Cadence of(float value) { public static Cadence of(float value, Duration duration) {
return new Cadence(value); if (duration.isZero()) {
return zero();
} }
private final float value; return new Cadence(value / (duration.toMillis() / (float) Duration.ofMinutes(1).toMillis()));
}
public static Cadence of(float value_rpm) {
return new Cadence(value_rpm);
}
public static Cadence zero() {
return of(0.0f);
}
private final float value_rpm;
private Cadence(float value) { private Cadence(float value) {
this.value = value; this.value_rpm = value;
} }
public float getRPM() { public float getRPM() {
return value; return value_rpm;
} }
@Override @Override
@@ -25,19 +38,19 @@ public class Cadence {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Cadence cadence = (Cadence) o; Cadence cadence = (Cadence) o;
return Float.compare(cadence.value, value) == 0; return Float.compare(cadence.value_rpm, value_rpm) == 0;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(value); return Objects.hash(value_rpm);
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "Cadence{" + return "Cadence{" +
"value=" + value + " rpm" + "value=" + value_rpm + " rpm" +
'}'; '}';
} }
} }
@@ -34,15 +34,15 @@ public class Distance {
} }
public static Distance ofMile(double distance_mile) { public static Distance ofMile(double distance_mile) {
return of(distance_mile * UnitConversions.MI_TO_M); return of(distance_mile * MI_TO_M);
} }
public static Distance ofNauticalMile(double distance_mile) { public static Distance ofNauticalMile(double distance_mile) {
return of(distance_mile * UnitConversions.NAUTICAL_MILE_TO_M); return of(distance_mile * NAUTICAL_MILE_TO_M);
} }
public static Distance ofKilometer(double distance_km) { public static Distance ofKilometer(double distance_km) {
return of(distance_km * UnitConversions.KM_TO_M); return of(distance_km * KM_TO_M);
} }
public static Distance ofMM(double distance_mm) { public static Distance ofMM(double distance_mm) {
@@ -117,19 +117,19 @@ public class Distance {
} }
public double toKM() { public double toKM() {
return distance_m * UnitConversions.M_TO_KM; return distance_m * M_TO_KM;
} }
public double toFT() { public double toFT() {
return distance_m * UnitConversions.M_TO_FT; return distance_m * M_TO_FT;
} }
public double toMI() { public double toMI() {
return toKM() * UnitConversions.KM_TO_MI; return distance_m * M_TO_MI;
} }
public double toNauticalMiles() { public double toNauticalMiles() {
return toKM() * UnitConversions.KM_TO_NAUTICAL_MILE; return distance_m * M_TO_NAUTICAL_MILE;
} }
public double toKM_Miles(UnitSystem unitSystem) { public double toKM_Miles(UnitSystem unitSystem) {
@@ -177,4 +177,17 @@ public class Distance {
"distance_m=" + distance_m + "distance_m=" + distance_m +
'}'; '}';
} }
// multiplication factors for conversion
private static final double KM_TO_M = 1000.0;
private static final double M_TO_KM = 1 / KM_TO_M;
public static final double MI_TO_M = 1609.344;
public static final double M_TO_MI = 1 / MI_TO_M;
private static final double MI_TO_FT = 5280.0;
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
private static final double NAUTICAL_MILE_TO_M = 1852.0;
private static final double M_TO_NAUTICAL_MILE = 1 / NAUTICAL_MILE_TO_M;
} }
@@ -16,11 +16,14 @@ public class DistanceFormatter {
private final int decimalCount; private final int decimalCount;
private final double threshold;
private final UnitSystem unitSystem; private final UnitSystem unitSystem;
private DistanceFormatter(Resources resources, int decimalCount, UnitSystem unitSystem) { private DistanceFormatter(Resources resources, int decimalCount, double threshold, UnitSystem unitSystem) {
this.resources = resources; this.resources = resources;
this.decimalCount = decimalCount; this.decimalCount = decimalCount;
this.threshold = threshold;
this.unitSystem = unitSystem; this.unitSystem = unitSystem;
assert unitSystem != null; assert unitSystem != null;
} }
@@ -56,19 +59,19 @@ public class DistanceFormatter {
switch (unitSystem) { switch (unitSystem) {
case METRIC: case METRIC:
if (distance.greaterThan(Distance.of(500))) { if (distance.greaterThan(Distance.ofKilometer(threshold))) {
return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer)); return new Pair<>(StringUtils.formatDecimal(distance.toKM(), decimalCount), resources.getString(R.string.unit_kilometer));
} else { } else {
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter)); return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
} }
case IMPERIAL: case IMPERIAL:
if (distance.greaterThan(Distance.ofMile(0.5))) { if (distance.greaterThan(Distance.ofMile(threshold))) {
return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile)); return new Pair<>(StringUtils.formatDecimal(distance.toMI(), decimalCount), resources.getString(R.string.unit_mile));
} else { } else {
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet)); return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
} }
case NAUTICAL_IMPERIAL: case NAUTICAL_IMPERIAL:
if (distance.greaterThan(Distance.ofNauticalMile(0.5))) { if (distance.greaterThan(Distance.ofNauticalMile(threshold))) {
return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile)); return new Pair<>(StringUtils.formatDecimal(distance.toNauticalMiles(), decimalCount), resources.getString(R.string.unit_nautical_mile));
} else { } else {
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet)); return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
@@ -88,8 +91,11 @@ public class DistanceFormatter {
private UnitSystem unitSystem; private UnitSystem unitSystem;
private double threshold;
public Builder() { public Builder() {
decimalCount = 2; decimalCount = 2;
threshold = 0.5;
} }
public Builder setDecimalCount(int decimalCount) { public Builder setDecimalCount(int decimalCount) {
@@ -102,8 +108,13 @@ public class DistanceFormatter {
return this; return this;
} }
public Builder setThreshold(double threshold) {
this.threshold = threshold;
return this;
}
public DistanceFormatter build(Resources resource) { public DistanceFormatter build(Resources resource) {
return new DistanceFormatter(resource, decimalCount, unitSystem); return new DistanceFormatter(resource, decimalCount, threshold, unitSystem);
} }
public DistanceFormatter build(Context context) { public DistanceFormatter build(Context context) {
@@ -15,7 +15,7 @@ public class Speed {
return zero(); return zero();
} }
return new Speed(distance.toM() / (duration.toMillis() * UnitConversions.MS_TO_S)); return new Speed(distance.toM() / (duration.toMillis() / (double) Duration.ofSeconds(1).toMillis()));
} }
public static Speed of(double speed_mps) { public static Speed of(double speed_mps) {
@@ -85,16 +85,23 @@ public class Speed {
return speed_mps; return speed_mps;
} }
/**
* We interpret {@link Speed} here as a {@link Distance} over 1h.
*/
private Distance toH() {
return Distance.of(speed_mps * Duration.ofHours(1).toSeconds());
}
public double toKMH() { public double toKMH() {
return speed_mps * UnitConversions.MPS_TO_KMH; return toH().toKM();
} }
public double toMPH() { public double toMPH() {
return toKMH() * UnitConversions.KM_TO_MI; return toH().toMI();
} }
public double toKnots() { public double toKnots() {
return toKMH() * UnitConversions.KM_TO_NAUTICAL_MILE; return toH().toNauticalMiles();
} }
public Duration toPace(UnitSystem unitSystem) { public Duration toPace(UnitSystem unitSystem) {
@@ -102,20 +109,7 @@ public class Speed {
return Duration.ofSeconds(0); return Duration.ofSeconds(0);
} }
double distance = speed_mps; double distance = Distance.of(speed_mps).toKM_Miles(unitSystem);
switch (unitSystem) { //TODO Can we use to(UnitSystem)?
case METRIC:
distance *= UnitConversions.M_TO_KM;
break;
case IMPERIAL:
distance *= UnitConversions.M_TO_MI;
break;
case NAUTICAL_IMPERIAL:
distance *= UnitConversions.M_TO_NAUTICAL_MILE;
break;
default:
throw new RuntimeException("Not implemented");
}
return Duration.ofSeconds(Math.round(1 / distance)); return Duration.ofSeconds(Math.round(1 / distance));
} }
@@ -1,69 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.data.models;
/**
* Unit conversion constants.
*
* @author Sandor Dornbush
*/
public class UnitConversions {
// Time //TODO Use Duration
// multiplication factor to convert seconds to milliseconds
@Deprecated
public static final long S_TO_MS = 1000;
// multiplication factor to convert milliseconds to seconds
@Deprecated
public static final double MS_TO_S = 1d / S_TO_MS;
// multiplication factor to convert minutes to seconds
@Deprecated
public static final double MIN_TO_S = 60.0;
// multiplication factor to convert seconds to minutes
@Deprecated
public static final double S_TO_MIN = 1 / MIN_TO_S;
// multiplication factor to convert minutes to hours
@Deprecated
public static final double MIN_TO_HR = 1 / 60.0;
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// Distance //TODO Make private to Distance class!
// multiplication factor to convert miles to feet
private static final double MI_TO_FT = 5280.0;
// multiplication factor to covert kilometers to meters
public static final double KM_TO_M = 1000.0;
// multiplication factor to convert meters to kilometers
public static final double M_TO_KM = 1 / KM_TO_M;
// multiplication factor to convert meters to miles
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
// multiplication factor to convert meters to feet
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
public static final double NAUTICAL_MILE_TO_M = 1852.0;
public static final double M_TO_NAUTICAL_MILE = 1 / NAUTICAL_MILE_TO_M;
public static final double KM_TO_NAUTICAL_MILE = 1000 * M_TO_NAUTICAL_MILE;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MPS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
// multiplication factor to convert miles to km
public static final double MI_TO_KM = 1 / KM_TO_MI;
// multiplication factor to convert miles to m
public static final double MI_TO_M = MI_TO_KM * KM_TO_M;
private UnitConversions() {
}
}
@@ -182,7 +182,7 @@ public class StatisticsRecordedFragment extends Fragment {
private void loadTrackDescription(@NonNull Track track) { private void loadTrackDescription(@NonNull Track track) {
viewBinding.statsNameValue.setText(track.getName()); viewBinding.statsNameValue.setText(track.getName());
viewBinding.statsDescriptionValue.setText(track.getDescription()); viewBinding.statsDescriptionValue.setText(track.getDescription());
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(track.getStartTime())); viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTimeWithOffsetIfDifferent(track.getStartTime()));
} }
private void updateUI() { private void updateUI() {
@@ -6,6 +6,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingService;
@@ -30,7 +31,10 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
splashScreen.setKeepOnScreenCondition(() -> true );
if (PreferencesUtils.isPublicAPIenabled()) { if (PreferencesUtils.isPublicAPIenabled()) {
Log.i(TAG, "Received and trying to execute requested action."); Log.i(TAG, "Received and trying to execute requested action.");
new TrackRecordingServiceConnection(serviceConnectedCallback) new TrackRecordingServiceConnection(serviceConnectedCallback)
@@ -15,6 +15,7 @@ public class StartRecording extends AbstractAPIActivity {
public static final String EXTRA_TRACK_NAME = "TRACK_NAME"; public static final String EXTRA_TRACK_NAME = "TRACK_NAME";
public static final String EXTRA_TRACK_CATEGORY = "TRACK_CATEGORY"; public static final String EXTRA_TRACK_CATEGORY = "TRACK_CATEGORY";
public static final String EXTRA_TRACK_ICON = "TRACK_ICON";
public static final String EXTRA_TRACK_DESCRIPTION = "TRACK_DESCRIPTION"; public static final String EXTRA_TRACK_DESCRIPTION = "TRACK_DESCRIPTION";
public static final String EXTRA_STATS_TARGET_PACKAGE = "STATS_TARGET_PACKAGE"; public static final String EXTRA_STATS_TARGET_PACKAGE = "STATS_TARGET_PACKAGE";
@@ -40,8 +41,11 @@ public class StartRecording extends AbstractAPIActivity {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackUtils.updateTrack(this, track, bundle.getString(EXTRA_TRACK_NAME, null), TrackUtils.updateTrack(this, track,
bundle.getString(EXTRA_TRACK_CATEGORY, null), bundle.getString(EXTRA_TRACK_DESCRIPTION, null), bundle.getString(EXTRA_TRACK_NAME, null),
bundle.getString(EXTRA_TRACK_CATEGORY, null),
bundle.getString(EXTRA_TRACK_ICON, null),
bundle.getString(EXTRA_TRACK_DESCRIPTION, null),
contentProviderUtils); contentProviderUtils);
} }
@@ -11,7 +11,6 @@ import java.time.Duration;
import de.dennisguse.opentracks.data.models.Cadence; import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.sensors.UintUtils; import de.dennisguse.opentracks.sensors.UintUtils;
/** /**
@@ -68,8 +67,9 @@ public final class SensorDataCycling {
public void compute(CyclingCadence previous) { public void compute(CyclingCadence previous) {
if (hasData() && previous != null && previous.hasData()) { if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS; float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
if (timeDiff_ms <= 0) { Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
if (timeDiff.isZero() || timeDiff.isNegative()) {
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence."); Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
value = null; value = null;
return; return;
@@ -82,8 +82,7 @@ public final class SensorDataCycling {
} }
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX); long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
float cadence_ms = crankDiff / timeDiff_ms; value = Cadence.of(crankDiff, timeDiff);
value = Cadence.of((float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN));
} }
} }
@@ -147,7 +146,7 @@ public final class SensorDataCycling {
public void compute(DistanceSpeed previous, Distance wheelCircumference) { public void compute(DistanceSpeed previous, Distance wheelCircumference) {
if (hasData() && previous != null && previous.hasData()) { if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS; float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms); Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
if (timeDiff.isZero() || timeDiff.isNegative()) { if (timeDiff.isZero() || timeDiff.isNegative()) {
Log.e(TAG, "Timestamps difference is invalid: cannot compute speed."); Log.e(TAG, "Timestamps difference is invalid: cannot compute speed.");
@@ -323,7 +323,7 @@ public class PreferencesUtils {
UnitSystem unitSystem = getUnitSystem(); UnitSystem unitSystem = getUnitSystem();
DistanceFormatter formatter = DistanceFormatter.Builder() DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1) .setDecimalCount(0)
.setUnit(unitSystem) .setUnit(unitSystem)
.build(resources); .build(resources);
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -417,7 +417,8 @@ public class PreferencesUtils {
DistanceFormatter formatter = DistanceFormatter.Builder() DistanceFormatter formatter = DistanceFormatter.Builder()
.setUnit(unitSystem) .setUnit(unitSystem)
.setDecimalCount(1) .setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.build(resources); .build(resources);
for (int i = 0; i < entryValues.length; i++) { for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]); int value = Integer.parseInt(entryValues[i]);
@@ -461,7 +462,8 @@ public class PreferencesUtils {
UnitSystem unitSystem = getUnitSystem(); UnitSystem unitSystem = getUnitSystem();
DistanceFormatter formatter = DistanceFormatter.Builder() DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1) .setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.setUnit(unitSystem) .setUnit(unitSystem)
.build(resources); .build(resources);
for (int i = 0; i < entryValues.length; i++) { for (int i = 0; i < entryValues.length; i++) {
@@ -534,7 +536,8 @@ public class PreferencesUtils {
UnitSystem unitSystem = getUnitSystem(); UnitSystem unitSystem = getUnitSystem();
DistanceFormatter formatter = DistanceFormatter.Builder() DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1) .setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.setUnit(unitSystem) .setUnit(unitSystem)
.build(resources); .build(resources);
@@ -30,7 +30,6 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter; import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.settings.UnitSystem; import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
@@ -154,7 +153,7 @@ public class DescriptionGenerator {
} }
// Recorded time // Recorded time
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(OffsetDateTime.ofInstant(stats.getStartTime(), ZoneId.systemDefault())))); builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTimeWithOffset(OffsetDateTime.ofInstant(stats.getStartTime(), ZoneId.systemDefault()))));
builder.append(lineBreak); builder.append(lineBreak);
return builder.toString(); return builder.toString();
@@ -230,7 +229,7 @@ public class DescriptionGenerator {
@VisibleForTesting @VisibleForTesting
void writeAltitude(double altitude_m, StringBuilder builder, int resId, String lineBreak) { void writeAltitude(double altitude_m, StringBuilder builder, int resId, String lineBreak) {
long altitudeInM = Math.round(altitude_m); long altitudeInM = Math.round(altitude_m);
long altitudeInFt = Math.round(altitude_m * UnitConversions.M_TO_FT); long altitudeInFt = Math.round(Distance.of(altitude_m).toFT());
builder.append(context.getString(resId, altitudeInM, altitudeInFt)); builder.append(context.getString(resId, altitudeInM, altitudeInFt));
builder.append(lineBreak); builder.append(lineBreak);
} }
@@ -27,7 +27,6 @@ import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate; import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint; import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
/** /**
* Updater for {@link TrackStatistics}. * Updater for {@link TrackStatistics}.
@@ -196,7 +195,7 @@ public class TrackStatisticsUpdater {
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g. // See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Speed maxSpeedDifference = Speed.of(Distance.of(SPEED_MAX_ACCELERATION), Duration.ofMillis(1000)) Speed maxSpeedDifference = Speed.of(Distance.of(SPEED_MAX_ACCELERATION), Duration.ofMillis(1000))
.mul(timeDifference.toMillis() / UnitConversions.S_TO_MS); .mul(timeDifference.toSeconds());
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed()); Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
return speedDifference.lessThan(maxSpeedDifference); return speedDifference.lessThan(maxSpeedDifference);
@@ -108,7 +108,11 @@ public class ListItemUtils {
String timeValue = null; String timeValue = null;
if (!isRecording) { if (!isRecording) {
dateValue = StringUtils.formatDateTodayRelative(context, offsetDateTime); dateValue = StringUtils.formatDateTodayRelative(context, offsetDateTime);
timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern("HH:mm")); String pattern = "HH:mm";
if (!offsetDateTime.getOffset().equals(OffsetDateTime.now().getOffset())) {
pattern = "HH:mm x";
}
timeValue = offsetDateTime.format(DateTimeFormatter.ofPattern(pattern));
} }
setTextView(context, dateTextView, dateValue, hasPhoto); setTextView(context, dateTextView, dateValue, hasPhoto);
setTextView(context, timeTextView, timeValue, hasPhoto); setTextView(context, timeTextView, timeValue, hasPhoto);
@@ -61,13 +61,6 @@ public class StringUtils {
private StringUtils() { private StringUtils() {
} }
/**
* Formats the date and time of the OffsetDateTime (using default Locale format)
*/
public static String formatDateTime(OffsetDateTime odt) {
return odt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
}
/** /**
* Formats the date and time with the offset (using default Locale format). * Formats the date and time with the offset (using default Locale format).
*/ */
@@ -75,6 +68,13 @@ public class StringUtils {
return odt.toZonedDateTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)); return odt.toZonedDateTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL));
} }
public static String formatDateTimeWithOffsetIfDifferent(OffsetDateTime odt) {
if (!odt.getOffset().equals(OffsetDateTime.now().getOffset())) {
return odt.toZonedDateTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL));
}
return odt.toZonedDateTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
}
public static String formatLocalDateTime(LocalDateTime localDateTime) { public static String formatLocalDateTime(LocalDateTime localDateTime) {
return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)); return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
} }
@@ -257,7 +257,8 @@ public class StringUtils {
//TODO altitude_m should be double or a value object //TODO altitude_m should be double or a value object
public static Pair<String, String> getAltitudeParts(Context context, Float altitude_m, UnitSystem unitSystem) { public static Pair<String, String> getAltitudeParts(Context context, Float altitude_m, UnitSystem unitSystem) {
DistanceFormatter formatter = DistanceFormatter.Builder() DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1) .setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.setUnit(unitSystem) .setUnit(unitSystem)
.build(context); .build(context);
@@ -26,12 +26,17 @@ import de.dennisguse.opentracks.data.models.Track;
* *
* @author Jimmy Shih * @author Jimmy Shih
*/ */
@Deprecated //TODO Refactor: all this should happen somewhere else (ContentProviderUtils?)
public class TrackUtils { public class TrackUtils {
private TrackUtils() { private TrackUtils() {
} }
public static void updateTrack(Context context, Track track, String name, String category, String description, ContentProviderUtils contentProviderUtils) { public static void updateTrack(Context context, Track track, String name, String category, String description, ContentProviderUtils contentProviderUtils) {
updateTrack(context, track, name, category, TrackIconUtils.getIconValue(context, category), description, contentProviderUtils);
}
public static void updateTrack(Context context, Track track, String name, String category, String iconValue, String description, ContentProviderUtils contentProviderUtils) {
boolean update = false; boolean update = false;
if (name != null) { if (name != null) {
track.setName(name); track.setName(name);
@@ -39,9 +44,13 @@ public class TrackUtils {
} }
if (category != null) { if (category != null) {
track.setCategory(category); track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
update = true; update = true;
} }
if (iconValue != null) {
track.setIcon(iconValue);
} else if (category != null){
track.setIcon(TrackIconUtils.getIconValue(context, category));
}
if (description != null) { if (description != null) {
track.setDescription(description); track.setDescription(description);
update = true; update = true;
@@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group
android:name="rotationGroup"
android:pivotX="50"
android:pivotY="50"
android:scaleX="0.6"
android:scaleY="0.6"
>
<path android:pathData="M82.489,12.834C83.196,10.418 82.043,9.556 79.926,10.917L10.087,55.828c-2.117,1.361 -1.795,2.628 0.714,2.815L39.806,60.807c2.51,0.188 5.291,2.267 6.18,4.621l10.282,27.208c0.89,2.354 2.195,2.304 2.902,-0.111z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="70.0"
android:endY="30.0"
android:startX="7.0"
android:startY="83.0"
android:type="linear">
<item
android:color="#FFFBB22F"
android:offset="0" />
<item
android:color="#FFFF6D00"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
</group>
</vector>
-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:opacity="opaque">
<item android:drawable="@color/splashscreen_background" />
<item
android:width="300dp"
android:height="300dp"
android:gravity="center"
tools:targetApi="m">
<inset
android:drawable="@drawable/ic_logo_color_24dp"
android:inset="20dp" />
</item>
</layer-list>
+5 -4
View File
@@ -59,10 +59,11 @@ limitations under the License.
<item name="indicatorColor">?attr/colorSecondary</item> <item name="indicatorColor">?attr/colorSecondary</item>
</style> </style>
<style name="SplashTheme" parent="ThemeCustom"> <style name="SplashTheme" parent="Theme.SplashScreen">
<item name="android:statusBarColor">@color/splashscreen_background</item> <item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="android:navigationBarColor">@color/splashscreen_background</item>
<item name="android:windowBackground">@drawable/splashscreen</item> <item name="windowSplashScreenAnimatedIcon">@drawable/ic_logo_color_splashscreen</item>
<item name="postSplashScreenTheme">@style/ThemeCustom</item>
</style> </style>
<style name="Widget.OpenTracks.FloatingActionButton" parent="Widget.MaterialComponents.FloatingActionButton"> <style name="Widget.OpenTracks.FloatingActionButton" parent="Widget.MaterialComponents.FloatingActionButton">