Introduced Statistics as a value object.

This commit is contained in:
Dennis Guse
2025-10-26 20:23:39 +01:00
parent fba08fe8dd
commit 920ad5129a
8 changed files with 105 additions and 29 deletions
@@ -30,6 +30,7 @@ import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater; import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
import de.dennisguse.opentracks.stats.Statistics;
/** /**
* A track. * A track.
@@ -51,6 +52,7 @@ public class Track {
private ZoneOffset zoneOffset; private ZoneOffset zoneOffset;
@Deprecated // TODO Replace with Statistic
private SegmentStatisticUpdater trackStatistics = new SegmentStatisticUpdater(); private SegmentStatisticUpdater trackStatistics = new SegmentStatisticUpdater();
@VisibleForTesting @VisibleForTesting
@@ -137,15 +139,26 @@ public class Track {
.getStopTime().atOffset(zoneOffset); .getStopTime().atOffset(zoneOffset);
} }
public Statistics getStatistics() {
return trackStatistics.getStatistics();
}
public Statistics setStatistics() {
throw new RuntimeException("TODO");
}
@Deprecated
@NonNull @NonNull
public SegmentStatisticUpdater getTrackStatistics() { public SegmentStatisticUpdater getTrackStatistics() {
return trackStatistics; return trackStatistics;
} }
@Deprecated
public void setTrackStatistics(@NonNull SegmentStatisticUpdater trackStatistics) { public void setTrackStatistics(@NonNull SegmentStatisticUpdater trackStatistics) {
this.trackStatistics = trackStatistics; this.trackStatistics = trackStatistics;
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return "Track{" + return "Track{" +
@@ -156,6 +169,7 @@ public class Track {
'}'; '}';
} }
@Deprecated //TODO Remove: should not be used.
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@@ -164,6 +178,7 @@ public class Track {
return id.equals(track.id); return id.equals(track.id);
} }
@Deprecated //TODO Remove: should not be used.
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id); return Objects.hash(id);
@@ -6,11 +6,13 @@ 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.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater; import de.dennisguse.opentracks.stats.SegmentStatisticUpdater;
import de.dennisguse.opentracks.stats.Statistics;
/** /**
* {@link Track} and {@link TrackPoint} must be immutable (i.e., their content does not change). * {@link Track} and {@link TrackPoint} must be immutable (i.e., their content does not change).
*/ */
public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorDataSet sensorDataSet, SegmentStatisticUpdater currentSegment) { public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorDataSet sensorDataSet,
Statistics currentSegment) {
public String getTrackCategory() { public String getTrackCategory() {
if (track == null) { if (track == null) {
return ""; return "";
@@ -26,14 +28,4 @@ public record RecordingData(Track track, TrackPoint latestTrackPoint, SensorData
return track.getTrackStatistics(); return track.getTrackStatistics();
} }
@NonNull
public SegmentStatisticUpdater getStatisticsCurrentSegment() {
if (currentSegment == null) {
return new SegmentStatisticUpdater();
}
return currentSegment;
}
} }
@@ -131,9 +131,8 @@ public class TrackRecordingManager implements SharedPreferences.OnSharedPreferen
} }
track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics()); track.setTrackStatistics(tmpTrackStatisticsUpdater.getTrackStatistics());
SegmentStatisticUpdater currentSegment = tmpTrackStatisticsUpdater.getCurrentSegment();
return new RecordingData(track, current.first, current.second, currentSegment); return new RecordingData(track, current.first, current.second, tmpTrackStatisticsUpdater.getCurrentSegment());
} }
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@@ -24,6 +24,7 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Distance; 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.Power; import de.dennisguse.opentracks.data.models.Power;
@@ -198,6 +199,24 @@ public class SegmentStatisticUpdater {
setStartTime(startTime); setStartTime(startTime);
} }
public Statistics getStatistics() {
return new Statistics(
startTime,
stopTime,
totalTime,
movingTime,
totalDistance,
isIdle,
getMaxSpeed(),
altitudeExtremities.hasData() ? altitudeExtremities.getMin() : null,
altitudeExtremities.hasData() ? altitudeExtremities.getMax() : null,
totalAltitudeGain_m != null && totalAltitudeLoss_m != null ? new AltitudeGainLoss(totalAltitudeGain_m, totalAltitudeLoss_m) : null,
avgHeartRate,
avgPower
);
}
public Instant getStartTime() { public Instant getStartTime() {
return startTime; return startTime;
} }
@@ -0,0 +1,43 @@
package de.dennisguse.opentracks.stats;
import androidx.annotation.Nullable;
import java.time.Duration;
import java.time.Instant;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
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;
public record Statistics(
Instant startTime,
Instant stopTime,
//TODO Rename to totalDuration
Duration totalTime,
//TODO Rename to movingDuration
Duration movingTime, // Based on when we believe the user is traveling
Distance totalDistance,
boolean isIdle, //TODO Check if this is persisted; if not: remove
Speed maxSpeed,
//TODO Create record for min/max altitude
@Nullable
Double altitudeMin_m,
@Nullable
Double altitudeMax_m,
@Nullable
AltitudeGainLoss altitudeGainLoss,
@Nullable
HeartRate avgHeartRate,
@Nullable
Power avgPower
) {
public Speed getAverageMovingSpeed() {
return Speed.of(totalDistance, movingTime);
}
}
@@ -78,9 +78,9 @@ public class TrackStatisticsUpdater {
stats.merge(currentSegment); stats.merge(currentSegment);
return stats; return stats;
} }
public SegmentStatisticUpdater getCurrentSegment() {
// Take a snapshot - we don't want anyone messing with our currentSegment public Statistics getCurrentSegment() {
return new SegmentStatisticUpdater(currentSegment); return currentSegment.getStatistics();
} }
public void addTrackPoints(List<TrackPoint> trackPoints) { public void addTrackPoints(List<TrackPoint> trackPoints) {
@@ -19,6 +19,7 @@ import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Altitude; import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Position; import de.dennisguse.opentracks.data.models.Position;
import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Speed;
@@ -27,7 +28,7 @@ import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.CustomStatsItemBinding; import de.dennisguse.opentracks.databinding.CustomStatsItemBinding;
import de.dennisguse.opentracks.services.RecordingData; import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem; import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SegmentStatisticUpdater; import de.dennisguse.opentracks.stats.Statistics;
import de.dennisguse.opentracks.viewmodels.Mapping; import de.dennisguse.opentracks.viewmodels.Mapping;
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder; import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
@@ -38,17 +39,24 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
private static final RecordingData demoData; private static final RecordingData demoData;
static { static {
SegmentStatisticUpdater trackStatistics = new SegmentStatisticUpdater(); Statistics statistics = new Statistics(
trackStatistics.setStartTime(Instant.ofEpochMilli(0)); Instant.ofEpochMilli(0),
trackStatistics.setMovingTime(Duration.ofMinutes(0)); Instant.ofEpochMilli(0),
trackStatistics.setTotalTime(Duration.ofMinutes(0)); Duration.ofMinutes(0),
Duration.ofMinutes(0),
trackStatistics.setTotalDistance(Distance.of(0)); Distance.of(0),
false,
Speed.of(0),
null,
null,
trackStatistics.setTotalAltitudeGain(0f); new AltitudeGainLoss(0f, 0f),
trackStatistics.setTotalAltitudeLoss(0f);
Track track = new Track(ZoneOffset.UTC); null,
track.setTrackStatistics(trackStatistics); null
);
Track track = new Track(ZoneOffset.UTC); //TODO Pass statistics as well
Position position = new Position( Position position = new Position(
Instant.ofEpochMilli(0), Instant.ofEpochMilli(0),
@@ -61,7 +69,7 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
Speed.zero() Speed.zero()
); );
demoData = new RecordingData(track, new TrackPoint(TrackPoint.Type.TRACKPOINT, position), null, trackStatistics) ; demoData = new RecordingData(track, new TrackPoint(TrackPoint.Type.TRACKPOINT, position), null, statistics);
} }
private RecordingLayout recordingLayout; private RecordingLayout recordingLayout;
@@ -208,7 +208,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
.setReportSpeedOrPace(false) .setReportSpeedOrPace(false)
.build(getContext()); .build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getStatisticsCurrentSegment().getAverageMovingSpeed()); Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.currentSegment().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first); getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second); getBinding().statsUnit.setText(valueAndUnit.second);
@@ -220,7 +220,7 @@ public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<St
@Override @Override
public void onChanged(UnitSystem unitSystem, RecordingData data) { public void onChanged(UnitSystem unitSystem, RecordingData data) {
String value = StringUtils.formatElapsedTime(data.getStatisticsCurrentSegment().getTotalTime()); String value = StringUtils.formatElapsedTime(data.currentSegment().totalTime());
getBinding().statsValue.setText(value); getBinding().statsValue.setText(value);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_elapsed_segment_time)); getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_elapsed_segment_time));