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`
* **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:
* Set track data: `TRACK_NAME`, `TRACK_CATEGORY`, and `TRACK_DESCRIPTION`
* Send recorded data to another application via _Dashboard API_: `STATS_TARGET_PACKAGE`
and `STATS_TARGET_CLASS`
* 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)).
NOTE: if `TRACK_ICON` is not present, `TRACK_CATEGORY` will be used to determine the icon (localized).
* 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 settings.
The Public API is disabled by default to protect the user's privacy, but it can easily be enabled in the settings.
__IMPORTANT__: triggering `StartRecording` does not check if Android permissions (location or
Bluetooth) were granted. If they are not granted`
__IMPORTANT__: triggering `StartRecording` does not check if Android permissions (location or Bluetooth) were granted. If they are not granted, nothing will be recorded.
## Dashboard API (incl. map)
+7 -4
View File
@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
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 {
applicationId "de.dennisguse.opentracks"
versionCode 5004
versionName "v4.0.7"
versionCode 5017
versionName "v4.0.8"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -81,6 +81,8 @@ android {
}
}
namespace 'de.dennisguse.opentracks'
buildTypes {
debug {
applicationIdSuffix ".debug"
@@ -118,7 +120,7 @@ android {
}
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.lifecycle:lifecycle-viewmodel:2.5.1'
@@ -127,6 +129,7 @@ dependencies {
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.6.1'
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'
@@ -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
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
zipStorePath=wrapper/dists
@@ -7,6 +7,7 @@ import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.Track;
@@ -105,7 +106,7 @@ public class TrackSelectionTest extends TestCase {
long oneDay = 24 * 60 * 60 * 1000;
TrackSelection filterWrong1 = new TrackSelection().addDateRange(instant, null);
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
SelectionData selectionWrong1 = filterWrong1.buildSelection();
@@ -463,7 +463,7 @@ public class ExportImportTest {
contentProviderUtils.deleteTrack(context, trackId);
// 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))
.lines()
.collect(Collectors.joining("\n"));
@@ -69,7 +69,7 @@ public class GPXTrackImporterTest {
public void gpx_with_pause_resume() throws IOException {
// given
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
// 1. import
@@ -123,7 +123,7 @@ public class GPXTrackImporterTest {
public void gpx_without_speed() throws IOException {
// given
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
// 1. import
@@ -171,7 +171,7 @@ public class GPXTrackImporterTest {
public void gpx_speed_no_namespace() throws IOException {
// given
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
// 1. import
@@ -221,8 +221,8 @@ public class GPXTrackImporterTest {
public void importExportTest_timezone() throws IOException {
// given
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 inputStreamExpected = 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.test.R.raw.gpx_timezone);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -60,7 +60,7 @@ public class KMLTrackImporterTest {
public void kml22_order_location_and_when() throws IOException {
// given
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
// 1. import
@@ -109,7 +109,7 @@ public class KMLTrackImporterTest {
public void kml22_time_decreases() throws IOException {
// given
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
// 1. import
@@ -149,7 +149,7 @@ public class KMLTrackImporterTest {
public void kml22_with_statistics_marker() throws IOException {
// given
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
// 1. import
@@ -223,7 +223,7 @@ public class KMLTrackImporterTest {
public void kml_without_locations() throws IOException {
// given
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
importTrackId = importer.importFile(inputStream).get(0);
@@ -234,7 +234,7 @@ public class KMLTrackImporterTest {
public void kml_when_locations_different() throws IOException {
// given
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
importTrackId = importer.importFile(inputStream).get(0);
@@ -99,7 +99,7 @@ public class DescriptionGeneratorTest {
+ "Min elevation: -500 m (-1640 ft)<br>"
+ "Elevation gain: 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));
}
@@ -135,7 +135,7 @@ public class DescriptionGeneratorTest {
+ "Fastest pace: 0:10 min/km (0:16 min/mi)<br>"
+ "Elevation gain: 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));
}
@@ -21,7 +21,6 @@ import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Track;
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.TrackStatisticsUpdater;
@@ -180,7 +179,7 @@ public class IntervalStatisticsTest {
// then
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));
if (totalGain != null) {
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"
xmlns:tools="http://schemas.android.com/tools"
package="de.dennisguse.opentracks"
android:installLocation="auto">
<supports-screens
@@ -38,6 +38,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.core.splashscreen.SplashScreen;
import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
@@ -158,8 +159,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co
@Override
protected void onCreate(Bundle savedInstanceState) {
// Reset theme after splash
setTheme(R.style.ThemeCustom);
SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
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.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -41,7 +40,7 @@ public class ChartPoint {
if (smoothedSpeed != null) {
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()) {
heartRate = (double) trackPoint.getHeartRate().getBPM();
@@ -2,22 +2,35 @@ package de.dennisguse.opentracks.data.models;
import androidx.annotation.NonNull;
import java.time.Duration;
import java.util.Objects;
public class Cadence {
public static Cadence of(float value) {
return new Cadence(value);
public static Cadence of(float value, Duration duration) {
if (duration.isZero()) {
return zero();
}
return new Cadence(value / (duration.toMillis() / (float) Duration.ofMinutes(1).toMillis()));
}
private final float value;
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) {
this.value = value;
this.value_rpm = value;
}
public float getRPM() {
return value;
return value_rpm;
}
@Override
@@ -25,19 +38,19 @@ public class Cadence {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cadence cadence = (Cadence) o;
return Float.compare(cadence.value, value) == 0;
return Float.compare(cadence.value_rpm, value_rpm) == 0;
}
@Override
public int hashCode() {
return Objects.hash(value);
return Objects.hash(value_rpm);
}
@NonNull
@Override
public String toString() {
return "Cadence{" +
"value=" + value + " rpm" +
"value=" + value_rpm + " rpm" +
'}';
}
}
@@ -34,15 +34,15 @@ public class Distance {
}
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) {
return of(distance_mile * UnitConversions.NAUTICAL_MILE_TO_M);
return of(distance_mile * NAUTICAL_MILE_TO_M);
}
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) {
@@ -117,19 +117,19 @@ public class Distance {
}
public double toKM() {
return distance_m * UnitConversions.M_TO_KM;
return distance_m * M_TO_KM;
}
public double toFT() {
return distance_m * UnitConversions.M_TO_FT;
return distance_m * M_TO_FT;
}
public double toMI() {
return toKM() * UnitConversions.KM_TO_MI;
return distance_m * M_TO_MI;
}
public double toNauticalMiles() {
return toKM() * UnitConversions.KM_TO_NAUTICAL_MILE;
return distance_m * M_TO_NAUTICAL_MILE;
}
public double toKM_Miles(UnitSystem unitSystem) {
@@ -177,4 +177,17 @@ public class Distance {
"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 double threshold;
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.decimalCount = decimalCount;
this.threshold = threshold;
this.unitSystem = unitSystem;
assert unitSystem != null;
}
@@ -56,19 +59,19 @@ public class DistanceFormatter {
switch (unitSystem) {
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));
} else {
return new Pair<>(StringUtils.formatDecimal(distance.toM(), decimalCount), resources.getString(R.string.unit_meter));
}
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));
} else {
return new Pair<>(StringUtils.formatDecimal(distance.toFT(), decimalCount), resources.getString(R.string.unit_feet));
}
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));
} else {
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 double threshold;
public Builder() {
decimalCount = 2;
threshold = 0.5;
}
public Builder setDecimalCount(int decimalCount) {
@@ -102,8 +108,13 @@ public class DistanceFormatter {
return this;
}
public Builder setThreshold(double threshold) {
this.threshold = threshold;
return this;
}
public DistanceFormatter build(Resources resource) {
return new DistanceFormatter(resource, decimalCount, unitSystem);
return new DistanceFormatter(resource, decimalCount, threshold, unitSystem);
}
public DistanceFormatter build(Context context) {
@@ -15,7 +15,7 @@ public class Speed {
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) {
@@ -85,16 +85,23 @@ public class Speed {
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() {
return speed_mps * UnitConversions.MPS_TO_KMH;
return toH().toKM();
}
public double toMPH() {
return toKMH() * UnitConversions.KM_TO_MI;
return toH().toMI();
}
public double toKnots() {
return toKMH() * UnitConversions.KM_TO_NAUTICAL_MILE;
return toH().toNauticalMiles();
}
public Duration toPace(UnitSystem unitSystem) {
@@ -102,20 +109,7 @@ public class Speed {
return Duration.ofSeconds(0);
}
double distance = speed_mps;
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");
}
double distance = Distance.of(speed_mps).toKM_Miles(unitSystem);
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) {
viewBinding.statsNameValue.setText(track.getName());
viewBinding.statsDescriptionValue.setText(track.getDescription());
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTime(track.getStartTime()));
viewBinding.statsStartDatetimeValue.setText(StringUtils.formatDateTimeWithOffsetIfDifferent(track.getStartTime()));
}
private void updateUI() {
@@ -6,6 +6,7 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.services.TrackRecordingService;
@@ -30,7 +31,10 @@ public abstract class AbstractAPIActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
splashScreen.setKeepOnScreenCondition(() -> true );
if (PreferencesUtils.isPublicAPIenabled()) {
Log.i(TAG, "Received and trying to execute requested action.");
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_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_STATS_TARGET_PACKAGE = "STATS_TARGET_PACKAGE";
@@ -40,8 +41,11 @@ public class StartRecording extends AbstractAPIActivity {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Track track = contentProviderUtils.getTrack(trackId);
TrackUtils.updateTrack(this, track, bundle.getString(EXTRA_TRACK_NAME, null),
bundle.getString(EXTRA_TRACK_CATEGORY, null), bundle.getString(EXTRA_TRACK_DESCRIPTION, null),
TrackUtils.updateTrack(this, track,
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);
}
@@ -11,7 +11,6 @@ import java.time.Duration;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.sensors.UintUtils;
/**
@@ -68,8 +67,9 @@ public final class SensorDataCycling {
public void compute(CyclingCadence previous) {
if (hasData() && previous != null && previous.hasData()) {
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
if (timeDiff_ms <= 0) {
float timeDiff_ms = UintUtils.diff(crankRevolutionsTime, previous.crankRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * 1000;
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
if (timeDiff.isZero() || timeDiff.isNegative()) {
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
value = null;
return;
@@ -82,8 +82,7 @@ public final class SensorDataCycling {
}
long crankDiff = UintUtils.diff(crankRevolutionsCount, previous.crankRevolutionsCount, UintUtils.UINT32_MAX);
float cadence_ms = crankDiff / timeDiff_ms;
value = Cadence.of((float) (cadence_ms / UnitConversions.MS_TO_S / UnitConversions.S_TO_MIN));
value = Cadence.of(crankDiff, timeDiff);
}
}
@@ -147,7 +146,7 @@ public final class SensorDataCycling {
public void compute(DistanceSpeed previous, Distance wheelCircumference) {
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);
if (timeDiff.isZero() || timeDiff.isNegative()) {
Log.e(TAG, "Timestamps difference is invalid: cannot compute speed.");
@@ -323,7 +323,7 @@ public class PreferencesUtils {
UnitSystem unitSystem = getUnitSystem();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1)
.setDecimalCount(0)
.setUnit(unitSystem)
.build(resources);
for (int i = 0; i < values.length; i++) {
@@ -417,7 +417,8 @@ public class PreferencesUtils {
DistanceFormatter formatter = DistanceFormatter.Builder()
.setUnit(unitSystem)
.setDecimalCount(1)
.setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.build(resources);
for (int i = 0; i < entryValues.length; i++) {
int value = Integer.parseInt(entryValues[i]);
@@ -461,7 +462,8 @@ public class PreferencesUtils {
UnitSystem unitSystem = getUnitSystem();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1)
.setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.setUnit(unitSystem)
.build(resources);
for (int i = 0; i < entryValues.length; i++) {
@@ -534,7 +536,8 @@ public class PreferencesUtils {
UnitSystem unitSystem = getUnitSystem();
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1)
.setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.setUnit(unitSystem)
.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.SpeedFormatter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.UnitConversions;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
@@ -154,7 +153,7 @@ public class DescriptionGenerator {
}
// 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);
return builder.toString();
@@ -230,7 +229,7 @@ public class DescriptionGenerator {
@VisibleForTesting
void writeAltitude(double altitude_m, StringBuilder builder, int resId, String lineBreak) {
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(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.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.data.models.UnitConversions;
/**
* 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.
Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
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());
return speedDifference.lessThan(maxSpeedDifference);
@@ -108,7 +108,11 @@ public class ListItemUtils {
String timeValue = null;
if (!isRecording) {
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, timeTextView, timeValue, hasPhoto);
@@ -61,13 +61,6 @@ public class 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).
*/
@@ -75,6 +68,13 @@ public class StringUtils {
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) {
return localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
}
@@ -257,7 +257,8 @@ public class StringUtils {
//TODO altitude_m should be double or a value object
public static Pair<String, String> getAltitudeParts(Context context, Float altitude_m, UnitSystem unitSystem) {
DistanceFormatter formatter = DistanceFormatter.Builder()
.setDecimalCount(1)
.setDecimalCount(0)
.setThreshold(Double.MAX_VALUE)
.setUnit(unitSystem)
.build(context);
@@ -26,12 +26,17 @@ import de.dennisguse.opentracks.data.models.Track;
*
* @author Jimmy Shih
*/
@Deprecated //TODO Refactor: all this should happen somewhere else (ContentProviderUtils?)
public class TrackUtils {
private TrackUtils() {
}
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;
if (name != null) {
track.setName(name);
@@ -39,9 +44,13 @@ public class TrackUtils {
}
if (category != null) {
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
update = true;
}
if (iconValue != null) {
track.setIcon(iconValue);
} else if (category != null){
track.setIcon(TrackIconUtils.getIconValue(context, category));
}
if (description != null) {
track.setDescription(description);
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>
</style>
<style name="SplashTheme" parent="ThemeCustom">
<item name="android:statusBarColor">@color/splashscreen_background</item>
<item name="android:navigationBarColor">@color/splashscreen_background</item>
<item name="android:windowBackground">@drawable/splashscreen</item>
<style name="SplashTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_logo_color_splashscreen</item>
<item name="postSplashScreenTheme">@style/ThemeCustom</item>
</style>
<style name="Widget.OpenTracks.FloatingActionButton" parent="Widget.MaterialComponents.FloatingActionButton">