Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/93 create base UI page for day statistics page #114

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7cac0f1
Daily switch added in Aggregated stats page
RamosEsteban Mar 22, 2024
702059d
fix: app was crashing due to missing layout_width attribute in aggreg…
Apanian25 Mar 23, 2024
fc9f048
Daily view switch now properly toggle the boolean isDailyView
RamosEsteban Mar 23, 2024
f671376
feat: get the toggle to function properly with new adapter
Apanian25 Mar 23, 2024
cd97d3d
feat: add day to display list
Apanian25 Mar 24, 2024
1bae6ae
Implement daily lift summary - waiting for Group7.
Radiohead610 Mar 24, 2024
d5616da
feat: change grouping to group by day instead of category when on dai…
Apanian25 Mar 24, 2024
e0a3564
Implement number of runs in daily summary
xkeyo Mar 24, 2024
1dc7f24
minor fixes
xkeyo Mar 24, 2024
8afc249
fix merge conflict
NawarTurk Mar 24, 2024
1c43192
fix: make the date display on the list items show the proper date
Apanian25 Mar 25, 2024
77a641b
Merge branch 'feature-56-show-lift-summary' into feature/93-create-ba…
NawarTurk Mar 25, 2024
2ac06d9
Add activity type label to day summary page.
Radiohead610 Mar 25, 2024
720121b
Add run elevation views to daily stats layout
NawarTurk Mar 25, 2024
0df3dd1
Merge branch 'main' into feature/93-create-base-ui-page-for-day-stati…
RamosEsteban Mar 25, 2024
77265c1
Set text for run elevation unit and label in view binding
NawarTurk Mar 25, 2024
48535c2
Add max vertical metric to aggregated daily stats with UI and functio…
NawarTurk Mar 25, 2024
1e5fdf5
Fix activity label, now day stats are grouped by both day and activit…
Radiohead610 Mar 25, 2024
f8b57eb
Merge branch 'main' of https://github.com/NawarTurk/OpenTracks-Winter…
Radiohead610 Mar 25, 2024
49c2f06
Merge branch 'main' into feature/93-create-base-ui-page-for-day-stati…
Apanian25 Mar 25, 2024
9e764ec
Fix max vertical value and unit.
Radiohead610 Mar 25, 2024
9a20345
Changed size of layout
xkeyo Mar 25, 2024
05646a9
add daily total distance
jtirona Mar 25, 2024
231a7ab
Implement Average speeed for daily summmary
xkeyo Mar 25, 2024
f91fd3c
Minor changes to avg speed
xkeyo Mar 25, 2024
1095c11
add max run speed
Mar 26, 2024
971ca03
Merge branch 'main' into feature/93-create-base-ui-page-for-day-stati…
Apanian25 Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,3 @@ public boolean isUserRidingSkiLift() {
return false;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.dennisguse.opentracks.data.models;

public class SkiRun {
//TODO: Add class attributes
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;

import android.content.Context;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.ActivityType;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.databinding.AggregatedDailyStatsListItemBinding;
import de.dennisguse.opentracks.databinding.AggregatedStatsListItemBinding;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.util.StringUtils;

public class AggregatedDayStatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private AggregatedStatistics aggregatedStatistics;
private SimpleDateFormat formatter = new SimpleDateFormat("MM dd yyy");

private final Context context;

public AggregatedDayStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
this.context = context;
this.aggregatedStatistics = aggregatedStatistics;
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(AggregatedDailyStatsListItemBinding.inflate(LayoutInflater.from(parent.getContext())));
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ViewHolder viewHolder = (ViewHolder) holder;

AggregatedStatistics.AggregatedStatistic aggregatedStatistic = aggregatedStatistics.getItem(position);

String type = aggregatedStatistic.getActivityTypeLocalized();
if (ActivityType.findByLocalizedString(context, type).isShowSpeedPreferred()) {
viewHolder.setSpeed(aggregatedStatistic);
} else {
viewHolder.setPace(aggregatedStatistic);
}
}

@Override
public int getItemCount() {
if (aggregatedStatistics == null) {
return 0;
}
return aggregatedStatistics.getCount();
}

public void swapData(AggregatedStatistics aggregatedStatistics) {
this.aggregatedStatistics = aggregatedStatistics;
this.notifyDataSetChanged();
}

public List<String> getDays() {
List<String> days = new ArrayList<>();
for (int i = 0; i < aggregatedStatistics.getCount(); i++) {
Date day = Date.from(aggregatedStatistics.getItem(i).getTrackStatistics().getStopTime());
days.add(formatter.format(day));
}
return days;
}

private class ViewHolder extends RecyclerView.ViewHolder {
private final AggregatedDailyStatsListItemBinding viewBinding;
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
private boolean reportSpeed;

public ViewHolder(AggregatedDailyStatsListItemBinding viewBinding) {
super(viewBinding.getRoot());
this.viewBinding = viewBinding;
}

public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
setCommonValues(aggregatedStatistic);

SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
{
//TODO Fill in the infomation here
Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed());
// viewBinding.aggregatedStatsAvgRate.setText(parts.first);
// viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
// viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_speed));
// Average Run Speed
viewBinding.dailyRunAvgSpeed.setText(parts.first);
viewBinding.dailyRunAvgSpeedUnit.setText(parts.second);
viewBinding.dailyRunAvgSpeedLabel.setText(context.getString(R.string.daily_run_avg_speed_label));
}

{
//TODO Fill in the information here
// Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed());
// 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) {
setCommonValues(aggregatedStatistic);

SpeedFormatter formatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
{
//TODO Fill in the information here
// Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed());
// viewBinding.aggregatedStatsAvgRate.setText(parts.first);
// viewBinding.aggregatedStatsAvgRateUnit.setText(parts.second);
// viewBinding.aggregatedStatsAvgRateLabel.setText(context.getString(R.string.stats_average_moving_pace));
}

{
//TODO Fill in the information here
// Pair<String, String> parts = formatter.getSpeedParts(aggregatedStatistic.getTrackStatistics().getMaxSpeed());
// viewBinding.aggregatedStatsMaxRate.setText(parts.first);
// viewBinding.aggregatedStatsMaxRateUnit.setText(parts.second);
// viewBinding.aggregatedStatsMaxRateLabel.setText(R.string.stats_fastest_pace);
}
}

//TODO Check preference handling.
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String activityType = aggregatedStatistic.getActivityTypeLocalized();
String day = aggregatedStatistic.getDay();

reportSpeed = PreferencesUtils.isReportSpeed(activityType);
unitSystem = PreferencesUtils.getUnitSystem();

viewBinding.activityIcon.setImageResource(getIcon(aggregatedStatistic));
viewBinding.aggregatedStatsDayLabel.setText(day);
viewBinding.aggregatedStatsNumDayTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.getCountTracks())));

Pair<String, String> parts = DistanceFormatter.Builder()
.setUnit(unitSystem)
.build(context).getDistanceParts(aggregatedStatistic.getTrackStatistics().getTotalDistance());
viewBinding.dailyTotalDistanceNumber.setText(parts.first);
viewBinding.dailyTotalDistanceUnit.setText(context.getString(R.string.daily_total_distance_unit));
viewBinding.dailyTotalDistanceLabel.setText(context.getString(R.string.daily_total_distance_label));

// viewBinding.aggregatedStatsTime.setText(StringUtils.formatElapsedTime(aggregatedStatistic.getTrackStatistics().getMovingTime()));
// Number of Lifts
viewBinding.dailyLiftNumber.setText(String.valueOf(aggregatedStatistic.getCountTracks()));
viewBinding.dailyLiftNumberUnit.setText(context.getString(R.string.daily_lift_number_unit));
viewBinding.dailyLiftNumberLabel.setText(context.getString(R.string.daily_lift_number_label));
// Lift Total Time
viewBinding.dailyLiftTotalTime.setText(String.valueOf(aggregatedStatistic.getTotalTime()));
viewBinding.dailyLiftTotalTimeLabel.setText(context.getString(R.string.daily_lift_total_time_label));
// Lift Moving Time
viewBinding.dailyLiftMovingTime.setText(String.valueOf(aggregatedStatistic.getMovingTime()));
viewBinding.dailyLiftMovingTimeLabel.setText(context.getString(R.string.daily_lift_moving_time_label));

// Number of Runs
viewBinding.dailyRunNumber.setText(String.valueOf(aggregatedStatistic.getCountTracks()));
viewBinding.dailyRunNumberUnit.setText(context.getString(R.string.daily_run_number_unit));
viewBinding.dailyRunNumberLabel.setText(context.getString(R.string.daily_run_number_label));

// Run Elevation
viewBinding.dailyRunMaxVertical.setText(String.valueOf(aggregatedStatistic.getMaxVertical()));
viewBinding.dailyRunMaxVerticalUnit.setText(context.getString(R.string.daily_run_max_vertical_unit));
viewBinding.dailyRunMaxVerticalLabel.setText(context.getString(R.string.daily_run_max_vertical_label));

// Max Speed
viewBinding.dailyTotalDistanceNumber.setText(parts.first);
viewBinding.dailyTotalDistanceUnit.setText(context.getString(R.string.daily_max_speed_unit));
viewBinding.dailyTotalDistanceLabel.setText(context.getString(R.string.daily_max_speed_label));

//Activity type
viewBinding.activityTypeLabel.setText(String.valueOf(aggregatedStatistic.getActivityTypeLocalized()));
}

private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String localizedActivityType = aggregatedStatistic.getActivityTypeLocalized();
return ActivityType.findByLocalizedString(context, localizedActivityType)
.getIconDrawableId();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;

import static java.lang.Math.round;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.time.Duration;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;

Expand All @@ -31,13 +41,55 @@ public AggregatedStatistics(@NonNull List<Track> tracks) {
});
}

public AggregatedStatistics(@NonNull List<Track> tracks, Boolean isDaily) {
for (Track track : tracks) {
if (isDaily) {
aggregateDays(track);
} else {
aggregate(track);
}
}

if(isDaily) {
dataList.addAll(dataMap.values());
dataList.sort((o1, o2) -> {
if (o1.getCountTracks() == o2.getCountTracks()) {
return o1.getDay().compareTo(o2.getDay());
}
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
});
} else {
dataList.addAll(dataMap.values());
dataList.sort((o1, o2) -> {
if (o1.getCountTracks() == o2.getCountTracks()) {
return o1.getActivityTypeLocalized().compareTo(o2.getActivityTypeLocalized());
}
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
});
}
}

@VisibleForTesting
public void aggregate(@NonNull Track track) {
String activityTypeLocalized = track.getActivityTypeLocalized();
if (dataMap.containsKey(activityTypeLocalized)) {
dataMap.get(activityTypeLocalized).add(track.getTrackStatistics());
} else {
dataMap.put(activityTypeLocalized, new AggregatedStatistic(activityTypeLocalized, track.getTrackStatistics()));
dataMap.put(activityTypeLocalized,
new AggregatedStatistic(activityTypeLocalized, track.getTrackStatistics()));
}
}

@VisibleForTesting
public void aggregateDays(@NonNull Track track) {
String activityTypeLocalized = track.getActivityTypeLocalized();
SimpleDateFormat formatter = new SimpleDateFormat("MM dd yyy");
String day = formatter.format(Date.from(track.getTrackStatistics().getStopTime()));
String combinedKey = day + " - " + activityTypeLocalized;
if (dataMap.containsKey(combinedKey)) {
dataMap.get(combinedKey).add(track.getTrackStatistics());
} else {
dataMap.put(combinedKey, new AggregatedStatistic(track.getActivityTypeLocalized(), track.getTrackStatistics(), day));
}
}

Expand All @@ -55,6 +107,8 @@ public AggregatedStatistic getItem(int position) {

public static class AggregatedStatistic {
private final String activityTypeLocalized;

private String day;
private final TrackStatistics trackStatistics;
private int countTracks = 1;

Expand All @@ -63,10 +117,20 @@ public AggregatedStatistic(String activityTypeLocalized, TrackStatistics trackSt
this.trackStatistics = trackStatistics;
}

public AggregatedStatistic(String activityTypeLocalized, TrackStatistics trackStatistics, String day) {
this.day = day;
this.activityTypeLocalized = activityTypeLocalized;
this.trackStatistics = trackStatistics;
}

public String getActivityTypeLocalized() {
return activityTypeLocalized;
}

public String getDay() {
return day;
}

public TrackStatistics getTrackStatistics() {
return trackStatistics;
}
Expand All @@ -79,5 +143,50 @@ void add(TrackStatistics statistics) {
trackStatistics.merge(statistics);
countTracks++;
}

public String getTotalTime() {
Duration duration = trackStatistics.getTotalTime();
String formattedTime = formatDuration(duration);
return formattedTime;
}

public String getMovingTime() {
Duration duration = trackStatistics.getMovingTime();
String formattedTime = formatDuration(duration);
return formattedTime;
}

public Distance getTotalDistance() {
return trackStatistics.getTotalDistance();
}

public Speed getMaxSpeed() {
return trackStatistics.getMaxSpeed();

}

public double getMaxVertical() {
double value = trackStatistics.getMaxAltitude();
double roundedValue = round(value, 2);
return roundedValue;
}

public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();

BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}

private String formatDuration(Duration duration) {
long seconds = duration.getSeconds();
long hours = seconds / 3600;
long minutes = (seconds % 3600) / 60;
long secs = seconds % 60;

// Format hours, minutes and seconds to ensure they are in 00:00:00 format
return String.format("%02d:%02d:%02d", hours, minutes, secs);
}
}
}
Loading