forked from upstream-mirrors/OpenTracks
Aggregate statistics are now computed in the database.
This commit is contained in:
-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.getAggregatedStats(selection).observe(this, aggregatedStatistics -> {
|
||||
if ((aggregatedStatistics == null || aggregatedStatistics.getCount() == 0) && !selection.isEmpty()) {
|
||||
viewBinding.aggregatedStatsEmptyView.setText(getString(R.string.aggregated_stats_filter_no_results));
|
||||
}
|
||||
if (aggregatedStatistics != null) {
|
||||
adapter.swapData(aggregatedStatistics);
|
||||
}
|
||||
adapter.swapData(aggregatedStatistics);
|
||||
checkListEmpty();
|
||||
});
|
||||
|
||||
@@ -71,6 +66,9 @@ public class AggregatedStatisticsActivity extends AbstractActivity implements Fi
|
||||
if (adapter.getItemCount() == 0) {
|
||||
viewBinding.aggregatedStatsList.setVisibility(View.GONE);
|
||||
viewBinding.aggregatedStatsEmptyView.setVisibility(View.VISIBLE);
|
||||
if (!selection.isEmpty()) {
|
||||
viewBinding.aggregatedStatsEmptyView.setText(getString(R.string.aggregated_stats_filter_no_results));
|
||||
}
|
||||
} else {
|
||||
viewBinding.aggregatedStatsList.setVisibility(View.VISIBLE);
|
||||
viewBinding.aggregatedStatsEmptyView.setVisibility(View.GONE);
|
||||
|
||||
+23
-28
@@ -8,10 +8,10 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.data.AggregatedStatistic;
|
||||
import de.dennisguse.opentracks.data.models.ActivityType;
|
||||
import de.dennisguse.opentracks.data.models.DistanceFormatter;
|
||||
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> {
|
||||
|
||||
private AggregatedStatistics aggregatedStatistics;
|
||||
private List<AggregatedStatistic> aggregatedStatistics;
|
||||
private final Context context;
|
||||
|
||||
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
|
||||
public AggregatedStatisticsAdapter(Context context, @NonNull List<AggregatedStatistic> aggregatedStatistics) {
|
||||
this.context = context;
|
||||
this.aggregatedStatistics = aggregatedStatistics;
|
||||
}
|
||||
@@ -40,9 +40,9 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
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()) {
|
||||
viewHolder.setSpeed(aggregatedStatistic);
|
||||
} else {
|
||||
@@ -52,23 +52,18 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (aggregatedStatistics == null) {
|
||||
return 0;
|
||||
}
|
||||
return aggregatedStatistics.getCount();
|
||||
return aggregatedStatistics.size();
|
||||
}
|
||||
|
||||
public void swapData(AggregatedStatistics aggregatedStatistics) {
|
||||
public void swapData(List<AggregatedStatistic> aggregatedStatistics) {
|
||||
this.aggregatedStatistics = aggregatedStatistics;
|
||||
this.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<String> getCategories() {
|
||||
List<String> categories = new ArrayList<>();
|
||||
for (int i = 0; i < aggregatedStatistics.getCount(); i++) {
|
||||
categories.add(aggregatedStatistics.getItem(i).getActivityTypeLocalized());
|
||||
}
|
||||
return categories;
|
||||
return aggregatedStatistics.stream()
|
||||
.map(AggregatedStatistic::activityTypeLocalized)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
@@ -81,38 +76,38 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
this.viewBinding = viewBinding;
|
||||
}
|
||||
|
||||
public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
public void setSpeed(AggregatedStatistic aggregatedStatistic) {
|
||||
setCommonValues(aggregatedStatistic);
|
||||
|
||||
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.aggregatedStatsAvgRateUnit.setText(parts.second);
|
||||
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.aggregatedStatsMaxRateUnit.setText(parts.second);
|
||||
viewBinding.aggregatedStatsMaxRateLabel.setText(context.getString(R.string.stats_max_speed));
|
||||
}
|
||||
}
|
||||
|
||||
public void setPace(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
public void setPace(AggregatedStatistic aggregatedStatistic) {
|
||||
setCommonValues(aggregatedStatistic);
|
||||
|
||||
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.aggregatedStatsAvgRateUnit.setText(parts.second);
|
||||
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.aggregatedStatsMaxRateUnit.setText(parts.second);
|
||||
viewBinding.aggregatedStatsMaxRateLabel.setText(R.string.stats_fastest_pace);
|
||||
@@ -120,27 +115,27 @@ public class AggregatedStatisticsAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
}
|
||||
|
||||
//TODO Check preference handling.
|
||||
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
|
||||
String activityType = aggregatedStatistic.getActivityTypeLocalized();
|
||||
private void setCommonValues(AggregatedStatistic aggregatedStatistic) {
|
||||
String activityType = aggregatedStatistic.activityTypeLocalized();
|
||||
|
||||
reportSpeed = PreferencesUtils.isReportSpeed(activityType);
|
||||
unitSystem = PreferencesUtils.getUnitSystem();
|
||||
|
||||
viewBinding.activityIcon.setImageResource(getIcon(aggregatedStatistic));
|
||||
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()
|
||||
.setUnit(unitSystem)
|
||||
.build(context).getDistanceParts(aggregatedStatistic.totalDistance);
|
||||
.build(context).getDistanceParts(aggregatedStatistic.totalDistance());
|
||||
viewBinding.aggregatedStatsDistance.setText(parts.first);
|
||||
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) {
|
||||
String localizedActivityType = aggregatedStatistic.getActivityTypeLocalized();
|
||||
private int getIcon(AggregatedStatistic aggregatedStatistic) {
|
||||
String localizedActivityType = aggregatedStatistic.activityTypeLocalized();
|
||||
return ActivityType.findByLocalizedString(context, localizedActivityType)
|
||||
.getIconDrawableId();
|
||||
}
|
||||
|
||||
+4
-8
@@ -10,19 +10,19 @@ import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.data.AggregatedStatistic;
|
||||
import de.dennisguse.opentracks.data.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.data.TrackSelection;
|
||||
import de.dennisguse.opentracks.data.models.Track;
|
||||
|
||||
public class AggregatedStatisticsModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<AggregatedStatistics> aggregatedStats;
|
||||
private MutableLiveData<List<AggregatedStatistic>> aggregatedStats;
|
||||
|
||||
public AggregatedStatisticsModel(@NonNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
public LiveData<AggregatedStatistics> getAggregatedStats(@Nullable TrackSelection selection) {
|
||||
public LiveData<List<AggregatedStatistic>> getAggregatedStats(@Nullable TrackSelection selection) {
|
||||
if (aggregatedStats == null) {
|
||||
aggregatedStats = new MutableLiveData<>();
|
||||
loadAggregatedStats(selection);
|
||||
@@ -41,11 +41,7 @@ public class AggregatedStatisticsModel extends AndroidViewModel {
|
||||
private void loadAggregatedStats(TrackSelection selection) {
|
||||
new Thread(() -> {
|
||||
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
|
||||
List<Track> tracks = selection != null ? contentProviderUtils.getTracks(selection) : contentProviderUtils.getTracks();
|
||||
|
||||
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
|
||||
|
||||
aggregatedStats.postValue(aggregatedStatistics);
|
||||
aggregatedStats.postValue(contentProviderUtils.getAggregatedStatisticsForTracks(selection));
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user