forked from upstream-mirrors/OpenTracks
Aggregate statistics are now computed in the database.
This commit is contained in:
+90
-158
@@ -1,12 +1,12 @@
|
|||||||
package de.dennisguse.opentracks.ui.aggregatedStatistics;
|
package de.dennisguse.opentracks.ui.aggregatedStatistics;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
@@ -16,6 +16,9 @@ import java.time.Instant;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.AggregatedStatistic;
|
||||||
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
|
import de.dennisguse.opentracks.data.TrackSelection;
|
||||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||||
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
|
import de.dennisguse.opentracks.data.models.AltitudeExtremities;
|
||||||
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
import de.dennisguse.opentracks.data.models.AltitudeGainLoss;
|
||||||
@@ -29,7 +32,7 @@ public class AggregatedStatisticsTest {
|
|||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
private static Track createTrack(Context context, Distance totalDistance, Duration totalTime, String activityTypeLocalized) {
|
private static void createTrack(Context context, Distance totalDistance, Duration totalTime, String activityTypeLocalized) {
|
||||||
Track track = new Track(new Statistics(
|
Track track = new Track(new Statistics(
|
||||||
Instant.ofEpochMilli(1000),
|
Instant.ofEpochMilli(1000),
|
||||||
Instant.ofEpochMilli(1000).plus(totalTime),
|
Instant.ofEpochMilli(1000).plus(totalTime),
|
||||||
@@ -44,199 +47,128 @@ public class AggregatedStatisticsTest {
|
|||||||
));
|
));
|
||||||
track.setActivityType(ActivityType.findByLocalizedString(context, activityTypeLocalized));
|
track.setActivityType(ActivityType.findByLocalizedString(context, activityTypeLocalized));
|
||||||
track.setActivityTypeLocalized(activityTypeLocalized);
|
track.setActivityTypeLocalized(activityTypeLocalized);
|
||||||
return track;
|
|
||||||
|
new ContentProviderUtils(context).insertTrack(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
new ContentProviderUtils(context).deleteAllTracks(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAggregate() {
|
public void testAggregate_empty() {
|
||||||
|
// when
|
||||||
|
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(List.of(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_oneBikingTrack() {
|
||||||
// given
|
// given
|
||||||
// 10km in 40 minutes.
|
Distance totalDistance = Distance.ofKilometer(10);
|
||||||
Distance totalDistance = Distance.of(10000);
|
Duration totalTime = Duration.ofMinutes(40);
|
||||||
Duration totalTime = Duration.ofMillis(2400000);
|
|
||||||
String biking = context.getString(R.string.activity_type_biking);
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
Track track = createTrack(context, totalDistance, totalTime, biking);
|
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1, aggregatedStatistics.getCount());
|
assertEquals(List.of(
|
||||||
assertNotNull(aggregatedStatistics.get(biking));
|
new AggregatedStatistic("biking", 1, Duration.ofMinutes(40), Distance.ofKilometer(10), Speed.of(50))
|
||||||
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
), result);
|
||||||
|
|
||||||
assertEquals(totalDistance, aggregatedStatistics.get(biking).totalDistance);
|
|
||||||
assertEquals(totalTime, aggregatedStatistics.get(biking).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAggregate_mountainBiking() {
|
|
||||||
// given
|
|
||||||
// 10km in 40 minutes.
|
|
||||||
Distance totalDistance = Distance.of(10000);
|
|
||||||
Duration totalTime = Duration.ofMillis(2400000);
|
|
||||||
String mountainBiking = context.getString(R.string.activity_type_mountain_biking);
|
|
||||||
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
|
|
||||||
|
|
||||||
// when
|
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertNotNull(aggregatedStatistics.get(mountainBiking));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAggregate_trailRunning() {
|
|
||||||
// given
|
|
||||||
// 10km in 40 minutes.
|
|
||||||
Distance totalDistance = Distance.of(10000);
|
|
||||||
Duration totalTime = Duration.ofMillis(2400000);
|
|
||||||
String trailRunning = context.getString(R.string.activity_type_trail_running);
|
|
||||||
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
|
|
||||||
|
|
||||||
// when
|
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(List.of(track));
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertNotNull(aggregatedStatistics.get(trailRunning));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAggregate_twoBikingTracks() {
|
public void testAggregate_twoBikingTracks() {
|
||||||
// given
|
// given
|
||||||
// 10km in 40 minutes.
|
Distance totalDistance = Distance.ofKilometer(10);
|
||||||
Distance totalDistance = Distance.of(10000);
|
Duration totalTime = Duration.ofMinutes(40);
|
||||||
Duration totalTime = Duration.ofMillis(2400000);
|
|
||||||
String biking = context.getString(R.string.activity_type_biking);
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
List<Track> tracks = List.of(createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, biking));
|
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1, aggregatedStatistics.getCount());
|
assertEquals(List.of(
|
||||||
assertNotNull(aggregatedStatistics.get(biking));
|
new AggregatedStatistic("biking", 2, Duration.ofMinutes(80), Distance.ofKilometer(20), Speed.of(50))
|
||||||
assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
|
), result);
|
||||||
|
|
||||||
assertEquals(totalDistance.multipliedBy(2), aggregatedStatistics.get(biking).totalDistance);
|
|
||||||
assertEquals(totalTime.multipliedBy(2), aggregatedStatistics.get(biking).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAggregate_threeDifferentTracks() {
|
|
||||||
// given
|
|
||||||
// 10km in 40 minutes.
|
|
||||||
Distance totalDistance = Distance.of(10000);
|
|
||||||
|
|
||||||
String biking = context.getString(R.string.activity_type_biking);
|
|
||||||
String running = context.getString(R.string.activity_type_running);
|
|
||||||
String walking = context.getString(R.string.activity_type_walking);
|
|
||||||
Duration totalTime = Duration.ofMillis(2400000);
|
|
||||||
List<Track> tracks = List.of(
|
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
|
||||||
createTrack(context, totalDistance, totalTime, running),
|
|
||||||
createTrack(context, totalDistance, totalTime, walking)
|
|
||||||
);
|
|
||||||
|
|
||||||
// when
|
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertEquals(3, aggregatedStatistics.getCount());
|
|
||||||
assertNotNull(aggregatedStatistics.get(biking));
|
|
||||||
assertNotNull(aggregatedStatistics.get(running));
|
|
||||||
assertNotNull(aggregatedStatistics.get(walking));
|
|
||||||
assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
|
|
||||||
assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
|
|
||||||
assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
|
|
||||||
|
|
||||||
{
|
|
||||||
assertEquals(totalDistance, aggregatedStatistics.get(biking).totalDistance);
|
|
||||||
assertEquals(totalTime, aggregatedStatistics.get(biking).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
assertEquals(totalDistance, aggregatedStatistics.get(running).totalDistance);
|
|
||||||
assertEquals(totalTime, aggregatedStatistics.get(running).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
assertEquals(totalDistance, aggregatedStatistics.get(walking).totalDistance);
|
|
||||||
assertEquals(totalTime, aggregatedStatistics.get(walking).totalMovingTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAggregate_severalTracksWithSeveralActivities() {
|
public void testAggregate_severalTracksWithSeveralActivities() {
|
||||||
// given
|
// given
|
||||||
// 10km in 40 minutes.
|
Distance totalDistance = Distance.ofKilometer(10);
|
||||||
Distance totalDistance = Distance.of(10000);
|
Duration totalTime = Duration.ofMinutes(40);
|
||||||
Duration totalTime = Duration.ofMillis(2400000);
|
|
||||||
String biking = context.getString(R.string.activity_type_biking);
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
String running = context.getString(R.string.activity_type_running);
|
String running = context.getString(R.string.activity_type_running);
|
||||||
String walking = context.getString(R.string.activity_type_walking);
|
String walking = context.getString(R.string.activity_type_walking);
|
||||||
String driving = context.getString(R.string.activity_type_driving);
|
String driving = context.getString(R.string.activity_type_driving);
|
||||||
List<Track> tracks = List.of(
|
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
|
||||||
createTrack(context, totalDistance, totalTime, running),
|
|
||||||
createTrack(context, totalDistance, totalTime, walking),
|
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
|
||||||
createTrack(context, totalDistance, totalTime, running),
|
|
||||||
createTrack(context, totalDistance, totalTime, walking),
|
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
|
||||||
createTrack(context, totalDistance, totalTime, biking),
|
|
||||||
createTrack(context, totalDistance, totalTime, driving)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, running);
|
||||||
|
createTrack(context, totalDistance, totalTime, walking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, running);
|
||||||
|
createTrack(context, totalDistance, totalTime, walking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, driving);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(new TrackSelection());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
// 4 sports.
|
assertEquals(List.of(
|
||||||
assertEquals(4, aggregatedStatistics.getCount());
|
new AggregatedStatistic("biking", 5, Duration.ofMinutes(200), Distance.ofKilometer(50), Speed.of(50)),
|
||||||
|
new AggregatedStatistic("walking", 2, Duration.ofMinutes(80), Distance.ofKilometer(20), Speed.of(50)),
|
||||||
|
new AggregatedStatistic("running", 2, Duration.ofMinutes(80), Distance.ofKilometer(20), Speed.of(50)),
|
||||||
|
new AggregatedStatistic("driving", 1, Duration.ofMinutes(40), Distance.ofKilometer(10), Speed.of(50))
|
||||||
|
), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAggregate_severalTracksWithSeveralActivities_TrackSelection() {
|
||||||
|
// given
|
||||||
|
Distance totalDistance = Distance.ofKilometer(10);
|
||||||
|
Duration totalTime = Duration.ofMinutes(40);
|
||||||
|
|
||||||
// There is a map for every sport.
|
String biking = context.getString(R.string.activity_type_biking);
|
||||||
assertNotNull(aggregatedStatistics.get(biking));
|
String running = context.getString(R.string.activity_type_running);
|
||||||
assertNotNull(aggregatedStatistics.get(running));
|
String walking = context.getString(R.string.activity_type_walking);
|
||||||
assertNotNull(aggregatedStatistics.get(walking));
|
String driving = context.getString(R.string.activity_type_driving);
|
||||||
assertNotNull(aggregatedStatistics.get(driving));
|
|
||||||
|
|
||||||
// Number of tracks by sport.
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
|
createTrack(context, totalDistance, totalTime, running);
|
||||||
assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
|
createTrack(context, totalDistance, totalTime, walking);
|
||||||
assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
|
createTrack(context, totalDistance, totalTime, running);
|
||||||
|
createTrack(context, totalDistance, totalTime, walking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, biking);
|
||||||
|
createTrack(context, totalDistance, totalTime, driving);
|
||||||
|
|
||||||
// Biking.
|
TrackSelection trackSelection = new TrackSelection();
|
||||||
{
|
trackSelection.addActivityType("biking");
|
||||||
assertEquals(totalDistance.multipliedBy(5), aggregatedStatistics.get(biking).totalDistance);
|
trackSelection.addActivityType("driving");
|
||||||
assertEquals(totalTime.multipliedBy(5), aggregatedStatistics.get(biking).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Running.
|
// when
|
||||||
{
|
List<AggregatedStatistic> result = new ContentProviderUtils(context).getAggregatedStatisticsForTracks(trackSelection);
|
||||||
assertEquals(totalDistance.multipliedBy(2), aggregatedStatistics.get(running).totalDistance);
|
|
||||||
assertEquals(totalTime.multipliedBy(2), aggregatedStatistics.get(running).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walking.
|
// then
|
||||||
{
|
assertEquals(List.of(
|
||||||
assertEquals(totalDistance.multipliedBy(2), aggregatedStatistics.get(walking).totalDistance);
|
new AggregatedStatistic("biking", 5, Duration.ofMinutes(200), Distance.ofKilometer(50), Speed.of(50)),
|
||||||
assertEquals(totalTime.multipliedBy(2), aggregatedStatistics.get(walking).totalMovingTime);
|
new AggregatedStatistic("driving", 1, Duration.ofMinutes(40), Distance.ofKilometer(10), Speed.of(50))
|
||||||
}
|
), result);
|
||||||
|
|
||||||
// Driving.
|
|
||||||
{
|
|
||||||
assertEquals(totalDistance, aggregatedStatistics.get(driving).totalDistance);
|
|
||||||
assertEquals(totalTime, aggregatedStatistics.get(driving).totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check order
|
|
||||||
|
|
||||||
{
|
|
||||||
assertEquals(biking, aggregatedStatistics.getItem(0).getActivityTypeLocalized());
|
|
||||||
assertEquals(driving, aggregatedStatistics.getItem(3).getActivityTypeLocalized());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package de.dennisguse.opentracks.data;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.models.Distance;
|
||||||
|
import de.dennisguse.opentracks.data.models.Speed;
|
||||||
|
|
||||||
|
public record AggregatedStatistic(
|
||||||
|
@NonNull
|
||||||
|
String activityTypeLocalized,
|
||||||
|
int countTracks,
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
Duration totalMovingTime,
|
||||||
|
@NonNull
|
||||||
|
Distance totalDistance,
|
||||||
|
@NonNull
|
||||||
|
Speed maxSpeed
|
||||||
|
) {
|
||||||
|
|
||||||
|
public Speed getAverageMovingSpeed() {
|
||||||
|
return Speed.of(totalDistance, totalMovingTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,7 @@ import de.dennisguse.opentracks.util.FileUtils;
|
|||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
|
//TODO Check if do {} while can be replaced by while (cursor.moveToNext)
|
||||||
public class ContentProviderUtils {
|
public class ContentProviderUtils {
|
||||||
|
|
||||||
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
private static final String TAG = ContentProviderUtils.class.getSimpleName();
|
||||||
@@ -80,6 +81,7 @@ public class ContentProviderUtils {
|
|||||||
|
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
|
||||||
|
//TODO Check if this can be used in more than one place. If not remove.
|
||||||
public interface ContentProviderSelectionInterface {
|
public interface ContentProviderSelectionInterface {
|
||||||
SelectionData buildSelection();
|
SelectionData buildSelection();
|
||||||
}
|
}
|
||||||
@@ -200,21 +202,7 @@ public class ContentProviderUtils {
|
|||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Track> getTracks(ContentProviderSelectionInterface selection) {
|
//TODO Implement someting like TrackPointIterator: just as a cursor
|
||||||
SelectionData selectionData = selection.buildSelection();
|
|
||||||
ArrayList<Track> tracks = new ArrayList<>();
|
|
||||||
try (Cursor cursor = getTrackCursor(selectionData.selection(), selectionData.selectionArgs(), TracksColumns._ID)) {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
tracks.ensureCapacity(cursor.getCount());
|
|
||||||
do {
|
|
||||||
tracks.add(createTrack(cursor));
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Cursor searchTracks(String searchQuery) {
|
public Cursor searchTracks(String searchQuery) {
|
||||||
// Needed, because MARKER_COUNT is a virtual column and has to be explicitly requested.
|
// Needed, because MARKER_COUNT is a virtual column and has to be explicitly requested.
|
||||||
// Used only be TrackListAdapter
|
// Used only be TrackListAdapter
|
||||||
@@ -311,6 +299,8 @@ public class ContentProviderUtils {
|
|||||||
values.put(TracksColumns.ACTIVITY_TYPE, track.getActivityType() != null ? track.getActivityType().getId() : null);
|
values.put(TracksColumns.ACTIVITY_TYPE, track.getActivityType() != null ? track.getActivityType().getId() : null);
|
||||||
values.put(TracksColumns.ACTIVITY_TYPE_LOCALIZED, track.getActivityTypeLocalized());
|
values.put(TracksColumns.ACTIVITY_TYPE_LOCALIZED, track.getActivityTypeLocalized());
|
||||||
values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
|
values.put(TracksColumns.STARTTIME_OFFSET, track.getZoneOffset().getTotalSeconds());
|
||||||
|
|
||||||
|
//TODO Duplicates createContentValues(Statistics trackStatistics)
|
||||||
if (trackStatistics.startTime() != null) {
|
if (trackStatistics.startTime() != null) {
|
||||||
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
|
values.put(TracksColumns.STARTTIME, trackStatistics.startTime().toEpochMilli());
|
||||||
}
|
}
|
||||||
@@ -897,4 +887,22 @@ public class ContentProviderUtils {
|
|||||||
}
|
}
|
||||||
return sensorStatistics;
|
return sensorStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<AggregatedStatistic> getAggregatedStatisticsForTracks(ContentProviderSelectionInterface selection) {
|
||||||
|
SelectionData data = selection.buildSelection();
|
||||||
|
try (Cursor cursor = contentResolver.query(TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS, null, data.selection(), data.selectionArgs(), null)) {
|
||||||
|
ArrayList<AggregatedStatistic> aggregatedStatistics = new ArrayList<>(cursor.getCount());
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
aggregatedStatistics.add(new AggregatedStatistic(
|
||||||
|
cursor.getString(cursor.getColumnIndexOrThrow(TracksColumns.ACTIVITY_TYPE_LOCALIZED)),
|
||||||
|
cursor.getInt(cursor.getColumnIndexOrThrow(TracksColumns.TRACK_COUNT)),
|
||||||
|
Duration.ofMillis(cursor.getLong(cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME))),
|
||||||
|
Distance.of(cursor.getFloat(cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE))),
|
||||||
|
Speed.of(cursor.getFloat(cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED)))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return aggregatedStatistics;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import android.text.TextUtils;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
* Finally, it ignores manual pause (SEGMENT_START_MANUAL).
|
* Finally, it ignores manual pause (SEGMENT_START_MANUAL).
|
||||||
*/
|
*/
|
||||||
private final String SENSOR_STATS_QUERY =
|
private final String SENSOR_STATS_QUERY =
|
||||||
"WITH time_select as " +
|
"WITH time_select AS " +
|
||||||
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
"(SELECT t1." + TrackPointsColumns.TIME + " * (t1." + TrackPointsColumns.TYPE + " NOT IN (" + TrackPoint.Type.SEGMENT_START_MANUAL.type_db + ")) time_value " +
|
||||||
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
"FROM " + TrackPointsColumns.TABLE_NAME + " t1 " +
|
||||||
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
"WHERE t1." + TrackPointsColumns._ID + " > t." + TrackPointsColumns._ID + " AND t1." + TrackPointsColumns.TRACKID + " = ? ORDER BY _id LIMIT 1) " +
|
||||||
@@ -101,6 +100,7 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
|
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath(), UrlType.TRACKS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_SENSOR_STATS.getPath() + "/#", UrlType.TRACKS_SENSOR_STATS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_SENSOR_STATS.getPath() + "/#", UrlType.TRACKS_SENSOR_STATS.ordinal());
|
||||||
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI_AGGREGATED_STATISTICS.getPath(), UrlType.TRACKS_AGGREGATED_STATISTICS.ordinal());
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.CONTENT_URI.getPath() + "/*", UrlType.TRACKS_BY_ID.ordinal());
|
||||||
|
|
||||||
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, MarkerColumns.CONTENT_URI.getPath(), UrlType.MARKERS.ordinal());
|
||||||
@@ -223,6 +223,8 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {
|
public Cursor query(@NonNull Uri url, String[] projection, String selection, String[] selectionArgs, String sort) {
|
||||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
String sortOrder = null;
|
String sortOrder = null;
|
||||||
|
String groupBy = null;
|
||||||
|
//TODO queryBuilder.appendWhere() should use selectionArgs?
|
||||||
switch (getUrlType(url)) {
|
switch (getUrlType(url)) {
|
||||||
case TRACKPOINTS -> {
|
case TRACKPOINTS -> {
|
||||||
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
queryBuilder.setTables(TrackPointsColumns.TABLE_NAME);
|
||||||
@@ -249,9 +251,23 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
queryBuilder.appendWhere(TracksColumns._ID + " IN (" + TextUtils.join(SQL_LIST_DELIMITER, ContentProviderUtils.parseTrackIdsFromUri(url)) + ")");
|
||||||
}
|
}
|
||||||
case TRACKS_SENSOR_STATS -> {
|
case TRACKS_SENSOR_STATS -> {
|
||||||
|
//TODO Replace with SQLQueryBuilder?
|
||||||
long trackId = ContentUris.parseId(url);
|
long trackId = ContentUris.parseId(url);
|
||||||
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
return db.rawQuery(SENSOR_STATS_QUERY, new String[]{String.valueOf(trackId), String.valueOf(trackId)});
|
||||||
}
|
}
|
||||||
|
case TRACKS_AGGREGATED_STATISTICS -> {
|
||||||
|
queryBuilder.setTables(TracksColumns.TABLE_NAME);
|
||||||
|
projection = new String[]{
|
||||||
|
"COUNT(*) AS " + TracksColumns.TRACK_COUNT,
|
||||||
|
TracksColumns.ACTIVITY_TYPE_LOCALIZED,
|
||||||
|
"SUM(" + TracksColumns.MOVINGTIME + ") AS " + TracksColumns.MOVINGTIME,
|
||||||
|
"SUM(" + TracksColumns.TOTALDISTANCE + ") AS " + TracksColumns.TOTALDISTANCE,
|
||||||
|
"MAX(" + TracksColumns.MAXSPEED + ") AS " + TracksColumns.MAXSPEED
|
||||||
|
};
|
||||||
|
|
||||||
|
sortOrder = TracksColumns.TRACK_COUNT + " DESC";
|
||||||
|
groupBy = TracksColumns.ACTIVITY_TYPE_LOCALIZED;
|
||||||
|
}
|
||||||
case MARKERS -> {
|
case MARKERS -> {
|
||||||
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
queryBuilder.setTables(MarkerColumns.TABLE_NAME);
|
||||||
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
sortOrder = sort != null ? sort : MarkerColumns.DEFAULT_SORT_ORDER;
|
||||||
@@ -266,7 +282,7 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
default -> throw new IllegalArgumentException("Unknown url " + url);
|
default -> throw new IllegalArgumentException("Unknown url " + url);
|
||||||
}
|
}
|
||||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder);
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), url);
|
cursor.setNotificationUri(getContext().getContentResolver(), url);
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@@ -379,14 +395,14 @@ public class CustomContentProvider extends ContentProvider {
|
|||||||
throw new SQLException("Failed to insert a marker " + url);
|
throw new SQLException("Failed to insert a marker " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
private enum UrlType {
|
||||||
enum UrlType {
|
|
||||||
TRACKPOINTS,
|
TRACKPOINTS,
|
||||||
TRACKPOINTS_BY_ID,
|
TRACKPOINTS_BY_ID,
|
||||||
TRACKPOINTS_BY_TRACKID,
|
TRACKPOINTS_BY_TRACKID,
|
||||||
TRACKS,
|
TRACKS,
|
||||||
TRACKS_BY_ID,
|
TRACKS_BY_ID,
|
||||||
TRACKS_SENSOR_STATS,
|
TRACKS_SENSOR_STATS,
|
||||||
|
TRACKS_AGGREGATED_STATISTICS,
|
||||||
MARKERS,
|
MARKERS,
|
||||||
MARKERS_BY_ID,
|
MARKERS_BY_ID,
|
||||||
MARKERS_BY_TRACKID
|
MARKERS_BY_TRACKID
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ public record Distance(double distance_m) {
|
|||||||
return Distance.of((double) distance_m);
|
return Distance.of((double) distance_m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated //TODO Remove
|
||||||
public static Distance of(String distance_m) {
|
public static Distance of(String distance_m) {
|
||||||
return of(Float.parseFloat(distance_m));
|
return of(Float.parseFloat(distance_m));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public record Speed(double speed_mps) {
|
|||||||
return new Speed(speed_mps);
|
return new Speed(speed_mps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated //TODO Remove
|
||||||
public static Speed of(String speed_mps) {
|
public static Speed of(String speed_mps) {
|
||||||
return of(Float.parseFloat(speed_mps));
|
return of(Float.parseFloat(speed_mps));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ public record Statistics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Speed getAverageSpeed() {
|
public Speed getAverageSpeed() {
|
||||||
if (totalTime.isZero()) {
|
|
||||||
return Speed.ZERO;
|
|
||||||
}
|
|
||||||
return Speed.of(totalDistance, totalTime);
|
return Speed.of(totalDistance, totalTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
String TABLE_NAME = "tracks";
|
String TABLE_NAME = "tracks";
|
||||||
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
|
||||||
Uri CONTENT_URI_SENSOR_STATS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/sensorstats");
|
Uri CONTENT_URI_SENSOR_STATS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/sensorstats");
|
||||||
|
Uri CONTENT_URI_AGGREGATED_STATISTICS = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/aggregated/");
|
||||||
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
|
||||||
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
|
||||||
String DEFAULT_SORT_ORDER = _ID;
|
String DEFAULT_SORT_ORDER = _ID;
|
||||||
@@ -46,7 +47,9 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
String STARTTIME_OFFSET = "time_offset"; // in plus/minus in seconds
|
String STARTTIME_OFFSET = "time_offset"; // in plus/minus in seconds
|
||||||
String STARTTIME = "time_start"; // track start time
|
String STARTTIME = "time_start"; // track start time
|
||||||
String STOPTIME = "time_stop"; // track stop time
|
String STOPTIME = "time_stop"; // track stop time
|
||||||
|
|
||||||
String MARKER_COUNT = "markerCount"; // the numbers of markers (virtual column)
|
String MARKER_COUNT = "markerCount"; // the numbers of markers (virtual column)
|
||||||
|
String TRACK_COUNT = "trackCount"; // the numbers of markers (virtual column)
|
||||||
|
|
||||||
String TOTALDISTANCE = "distance"; // total distance
|
String TOTALDISTANCE = "distance"; // total distance
|
||||||
String TOTALTIME = "duration_total";
|
String TOTALTIME = "duration_total";
|
||||||
|
|||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
package de.dennisguse.opentracks.ui.aggregatedStatistics;
|
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.data.models.Distance;
|
|
||||||
import de.dennisguse.opentracks.data.models.Speed;
|
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
|
||||||
|
|
||||||
public class AggregatedStatistics {
|
|
||||||
|
|
||||||
private final List<AggregatedStatistic> dataList;
|
|
||||||
|
|
||||||
public AggregatedStatistics(@NonNull List<Track> tracks) {
|
|
||||||
Map<String, List<Track>> groupedByActivityType = tracks.stream().collect(Collectors.groupingBy(Track::getActivityTypeLocalized));
|
|
||||||
|
|
||||||
List<Pair<String, List<Track>>> groupedByActivityTypeList = new ArrayList<>();
|
|
||||||
groupedByActivityType.forEach((activityTypeLocalized, trackList) -> groupedByActivityTypeList.add(new Pair<>(activityTypeLocalized, trackList)));
|
|
||||||
|
|
||||||
dataList = groupedByActivityTypeList.parallelStream()
|
|
||||||
.map(i -> new AggregatedStatistic(i.first, i.second))
|
|
||||||
.sorted((o1, o2) -> {
|
|
||||||
if (o1.getCountTracks() == o2.getCountTracks()) {
|
|
||||||
return o1.getActivityTypeLocalized().compareTo(o2.getActivityTypeLocalized());
|
|
||||||
}
|
|
||||||
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
|
||||||
return dataList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public AggregatedStatistic get(String activityTypeLocalized) {
|
|
||||||
return dataList.stream().filter(i -> i.getActivityTypeLocalized().equals(activityTypeLocalized)).findFirst().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AggregatedStatistic getItem(int position) {
|
|
||||||
return dataList.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AggregatedStatistic {
|
|
||||||
final String activityTypeLocalized;
|
|
||||||
final int countTracks;
|
|
||||||
|
|
||||||
final Duration totalMovingTime;
|
|
||||||
|
|
||||||
final Distance totalDistance;
|
|
||||||
|
|
||||||
final Speed avgMovingSpeed;
|
|
||||||
|
|
||||||
final Speed maxSpeed;
|
|
||||||
|
|
||||||
public AggregatedStatistic(String activityTypeLocalized, List<Track> tracks) {
|
|
||||||
this.activityTypeLocalized = activityTypeLocalized;
|
|
||||||
this.countTracks = tracks.size();
|
|
||||||
|
|
||||||
totalMovingTime = tracks.stream().map(t -> t.getStatistics().movingTime()).reduce(Duration.ZERO, Duration::plus);
|
|
||||||
totalDistance = tracks.stream().map(t -> t.getStatistics().totalDistance()).reduce(Distance.ZERO, Distance::plus);
|
|
||||||
maxSpeed = tracks.stream().map(t -> t.getStatistics().maxSpeed()).max((o1, o2) -> (int) (o1.speed_mps() - o2.speed_mps())).orElse(Speed.ZERO);
|
|
||||||
avgMovingSpeed = Speed.of(totalDistance, totalMovingTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getActivityTypeLocalized() {
|
|
||||||
return activityTypeLocalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCountTracks() {
|
|
||||||
return countTracks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-6
@@ -55,12 +55,7 @@ public class AggregatedStatisticsActivity extends AbstractActivity implements Fi
|
|||||||
|
|
||||||
viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
|
||||||
viewModel.getAggregatedStats(selection).observe(this, aggregatedStatistics -> {
|
viewModel.getAggregatedStats(selection).observe(this, aggregatedStatistics -> {
|
||||||
if ((aggregatedStatistics == null || aggregatedStatistics.getCount() == 0) && !selection.isEmpty()) {
|
adapter.swapData(aggregatedStatistics);
|
||||||
viewBinding.aggregatedStatsEmptyView.setText(getString(R.string.aggregated_stats_filter_no_results));
|
|
||||||
}
|
|
||||||
if (aggregatedStatistics != null) {
|
|
||||||
adapter.swapData(aggregatedStatistics);
|
|
||||||
}
|
|
||||||
checkListEmpty();
|
checkListEmpty();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,6 +66,9 @@ public class AggregatedStatisticsActivity extends AbstractActivity implements Fi
|
|||||||
if (adapter.getItemCount() == 0) {
|
if (adapter.getItemCount() == 0) {
|
||||||
viewBinding.aggregatedStatsList.setVisibility(View.GONE);
|
viewBinding.aggregatedStatsList.setVisibility(View.GONE);
|
||||||
viewBinding.aggregatedStatsEmptyView.setVisibility(View.VISIBLE);
|
viewBinding.aggregatedStatsEmptyView.setVisibility(View.VISIBLE);
|
||||||
|
if (!selection.isEmpty()) {
|
||||||
|
viewBinding.aggregatedStatsEmptyView.setText(getString(R.string.aggregated_stats_filter_no_results));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
viewBinding.aggregatedStatsList.setVisibility(View.VISIBLE);
|
viewBinding.aggregatedStatsList.setVisibility(View.VISIBLE);
|
||||||
viewBinding.aggregatedStatsEmptyView.setVisibility(View.GONE);
|
viewBinding.aggregatedStatsEmptyView.setVisibility(View.GONE);
|
||||||
|
|||||||
+23
-28
@@ -8,10 +8,10 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.data.AggregatedStatistic;
|
||||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||||
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
import de.dennisguse.opentracks.data.models.SpeedFormatter;
|
||||||
@@ -22,10 +22,10 @@ import de.dennisguse.opentracks.util.StringUtils;
|
|||||||
|
|
||||||
public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
private AggregatedStatistics aggregatedStatistics;
|
private List<AggregatedStatistic> aggregatedStatistics;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
|
public AggregatedStatisticsAdapter(Context context, @NonNull List<AggregatedStatistic> aggregatedStatistics) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.aggregatedStatistics = aggregatedStatistics;
|
this.aggregatedStatistics = aggregatedStatistics;
|
||||||
}
|
}
|
||||||
@@ -40,9 +40,9 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
|||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
ViewHolder viewHolder = (ViewHolder) holder;
|
ViewHolder viewHolder = (ViewHolder) holder;
|
||||||
|
|
||||||
AggregatedStatistics.AggregatedStatistic aggregatedStatistic = aggregatedStatistics.getItem(position);
|
AggregatedStatistic aggregatedStatistic = aggregatedStatistics.get(position);
|
||||||
|
|
||||||
String type = aggregatedStatistic.getActivityTypeLocalized();
|
String type = aggregatedStatistic.activityTypeLocalized();
|
||||||
if (ActivityType.findByLocalizedString(context, type).isShowSpeedPreferred()) {
|
if (ActivityType.findByLocalizedString(context, type).isShowSpeedPreferred()) {
|
||||||
viewHolder.setSpeed(aggregatedStatistic);
|
viewHolder.setSpeed(aggregatedStatistic);
|
||||||
} else {
|
} else {
|
||||||
@@ -52,23 +52,18 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
if (aggregatedStatistics == null) {
|
return aggregatedStatistics.size();
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return aggregatedStatistics.getCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapData(AggregatedStatistics aggregatedStatistics) {
|
public void swapData(List<AggregatedStatistic> aggregatedStatistics) {
|
||||||
this.aggregatedStatistics = aggregatedStatistics;
|
this.aggregatedStatistics = aggregatedStatistics;
|
||||||
this.notifyDataSetChanged();
|
this.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getCategories() {
|
public List<String> getCategories() {
|
||||||
List<String> categories = new ArrayList<>();
|
return aggregatedStatistics.stream()
|
||||||
for (int i = 0; i < aggregatedStatistics.getCount(); i++) {
|
.map(AggregatedStatistic::activityTypeLocalized)
|
||||||
categories.add(aggregatedStatistics.getItem(i).getActivityTypeLocalized());
|
.toList();
|
||||||
}
|
|
||||||
return categories;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
@@ -81,38 +76,38 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
|||||||
this.viewBinding = viewBinding;
|
this.viewBinding = viewBinding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
public void setSpeed(AggregatedStatistic aggregatedStatistic) {
|
||||||
setCommonValues(aggregatedStatistic);
|
setCommonValues(aggregatedStatistic);
|
||||||
|
|
||||||
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
|
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.avgMovingSpeed);
|
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getAverageMovingSpeed());
|
||||||
viewBinding.aggregatedStatsAvgRate.setText(parts.first);
|
viewBinding.aggregatedStatsAvgRate.setText(parts.first);
|
||||||
viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
|
viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
|
||||||
viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.maxSpeed);
|
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.maxSpeed());
|
||||||
viewBinding.aggregatedStatsMaxRate.setText(parts.first);
|
viewBinding.aggregatedStatsMaxRate.setText(parts.first);
|
||||||
viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
|
viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
|
||||||
viewBinding.aggregatedStatsMaxRateLabel.setText(context.getString(R.string.stats_max_speed));
|
viewBinding.aggregatedStatsMaxRateLabel.setText(context.getString(R.string.stats_max_speed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPace(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
public void setPace(AggregatedStatistic aggregatedStatistic) {
|
||||||
setCommonValues(aggregatedStatistic);
|
setCommonValues(aggregatedStatistic);
|
||||||
|
|
||||||
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
|
SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.avgMovingSpeed);
|
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getAverageMovingSpeed());
|
||||||
viewBinding.aggregatedStatsAvgRate.setText(parts.first);
|
viewBinding.aggregatedStatsAvgRate.setText(parts.first);
|
||||||
viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
|
viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
|
||||||
viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_pace));
|
viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_pace));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.maxSpeed);
|
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.maxSpeed());
|
||||||
viewBinding.aggregatedStatsMaxRate.setText(parts.first);
|
viewBinding.aggregatedStatsMaxRate.setText(parts.first);
|
||||||
viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
|
viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
|
||||||
viewBinding.aggregatedStatsMaxRateLabel.setText(R.string.stats_fastest_pace);
|
viewBinding.aggregatedStatsMaxRateLabel.setText(R.string.stats_fastest_pace);
|
||||||
@@ -120,27 +115,27 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO Check preference handling.
|
//TODO Check preference handling.
|
||||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
private void setCommonValues(AggregatedStatistic aggregatedStatistic) {
|
||||||
String activityType = aggregatedStatistic.getActivityTypeLocalized();
|
String activityType = aggregatedStatistic.activityTypeLocalized();
|
||||||
|
|
||||||
reportSpeed = PreferencesUtils.isReportSpeed(activityType);
|
reportSpeed = PreferencesUtils.isReportSpeed(activityType);
|
||||||
unitSystem = PreferencesUtils.getUnitSystem();
|
unitSystem = PreferencesUtils.getUnitSystem();
|
||||||
|
|
||||||
viewBinding.activityIcon.setImageResource(getIcon(aggregatedStatistic));
|
viewBinding.activityIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||||
viewBinding.aggregatedStatsTypeLabel.setText(activityType);
|
viewBinding.aggregatedStatsTypeLabel.setText(activityType);
|
||||||
viewBinding.aggregatedStatsNumTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.getCountTracks())));
|
viewBinding.aggregatedStatsNumTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.countTracks())));
|
||||||
|
|
||||||
Pair<String, String> parts = DistanceFormatter.Builder()
|
Pair<String, String> parts = DistanceFormatter.Builder()
|
||||||
.setUnit(unitSystem)
|
.setUnit(unitSystem)
|
||||||
.build(context).getDistanceParts(aggregatedStatistic.totalDistance);
|
.build(context).getDistanceParts(aggregatedStatistic.totalDistance());
|
||||||
viewBinding.aggregatedStatsDistance.setText(parts.first);
|
viewBinding.aggregatedStatsDistance.setText(parts.first);
|
||||||
viewBinding.aggregatedStatsDistanceUnit.setText(parts.second);
|
viewBinding.aggregatedStatsDistanceUnit.setText(parts.second);
|
||||||
|
|
||||||
viewBinding.aggregatedStatsTime.setText(StringUtils.formatElapsedTime(aggregatedStatistic.totalMovingTime));
|
viewBinding.aggregatedStatsTime.setText(StringUtils.formatElapsedTime(aggregatedStatistic.totalMovingTime()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
private int getIcon(AggregatedStatistic aggregatedStatistic) {
|
||||||
String localizedActivityType = aggregatedStatistic.getActivityTypeLocalized();
|
String localizedActivityType = aggregatedStatistic.activityTypeLocalized();
|
||||||
return ActivityType.findByLocalizedString(context, localizedActivityType)
|
return ActivityType.findByLocalizedString(context, localizedActivityType)
|
||||||
.getIconDrawableId();
|
.getIconDrawableId();
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -10,19 +10,19 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.data.AggregatedStatistic;
|
||||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.data.TrackSelection;
|
import de.dennisguse.opentracks.data.TrackSelection;
|
||||||
import de.dennisguse.opentracks.data.models.Track;
|
|
||||||
|
|
||||||
public class AggregatedStatisticsModel extends AndroidViewModel {
|
public class AggregatedStatisticsModel extends AndroidViewModel {
|
||||||
|
|
||||||
private MutableLiveData<AggregatedStatistics> aggregatedStats;
|
private MutableLiveData<List<AggregatedStatistic>> aggregatedStats;
|
||||||
|
|
||||||
public AggregatedStatisticsModel(@NonNull Application application) {
|
public AggregatedStatisticsModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<AggregatedStatistics> getAggregatedStats(@Nullable TrackSelection selection) {
|
public LiveData<List<AggregatedStatistic>> getAggregatedStats(@Nullable TrackSelection selection) {
|
||||||
if (aggregatedStats == null) {
|
if (aggregatedStats == null) {
|
||||||
aggregatedStats = new MutableLiveData<>();
|
aggregatedStats = new MutableLiveData<>();
|
||||||
loadAggregatedStats(selection);
|
loadAggregatedStats(selection);
|
||||||
@@ -41,11 +41,7 @@ public class AggregatedStatisticsModel extends AndroidViewModel {
|
|||||||
private void loadAggregatedStats(TrackSelection selection) {
|
private void loadAggregatedStats(TrackSelection selection) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
||||||
List<Track> tracks = selection != null ? contentProviderUtils.getTracks(selection) : contentProviderUtils.getTracks();
|
aggregatedStats.postValue(contentProviderUtils.getAggregatedStatisticsForTracks(selection));
|
||||||
|
|
||||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
|
||||||
|
|
||||||
aggregatedStats.postValue(aggregatedStatistics);
|
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user