Aggregated statistics by activity type. Fixes #112.

This commit is contained in:
Román
2020-06-07 12:01:22 +02:00
parent a3ba1e1490
commit bae3373fe9
14 changed files with 790 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
@@ -0,0 +1,257 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.TrackIconUtils;
@RunWith(JUnit4.class)
public class AggregatedStatisticsTest {
private final Context context = ApplicationProvider.getApplicationContext();
/**
* Create a TrackStatistics object.
*
* @param totalDistance distance in meters.
* @param totalTime total time in milliseconds.
* @return TrackStatistics object.
*/
private static Track createTrack(Context context, long totalDistance, long totalTime, String category) {
TrackStatistics statistics = new TrackStatistics();
statistics.setStartTime_ms(1000L); // Resulting start time
statistics.setStopTime_ms(1000L + totalTime);
statistics.setTotalTime(totalTime);
statistics.setMovingTime(totalTime);
statistics.setTotalDistance(totalDistance);
statistics.setTotalElevationGain(50.0);
statistics.setMaxSpeed(50.0); // Resulting max speed
statistics.setMaxElevation(1250.0);
statistics.setMinElevation(1200.0); // Resulting min elevation
Track track = new Track();
track.setIcon(TrackIconUtils.getIconValue(context, category));
track.setCategory(category);
track.setTrackStatistics(statistics);
return track;
}
@Test
public void testAggregate() {
// given
// 10km in 40 minutes.
long totalDistance = 10000;
long totalTime = 2400000;
String biking = context.getString(R.string.activity_type_biking);
Track track = createTrack(context, totalDistance, totalTime, biking);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(track);
// then
Assert.assertEquals(1, aggregatedStatistics.getCount());
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
}
@Test
public void testAggregate_mountainBiking() {
// given
// 10km in 40 minutes.
long totalDistance = 10000;
long totalTime = 2400000;
String mountainBiking = context.getString(R.string.activity_type_mountain_biking);
Track track = createTrack(context, totalDistance, totalTime, mountainBiking);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(track);
// then
Assert.assertTrue(aggregatedStatistics.get(mountainBiking) != null);
}
@Test
public void testAggregate_trailRunning() {
// given
// 10km in 40 minutes.
long totalDistance = 10000;
long totalTime = 2400000;
String trailRunning = context.getString(R.string.activity_type_trail_running);
Track track = createTrack(context, totalDistance, totalTime, trailRunning);
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(track);
// then
Assert.assertTrue(aggregatedStatistics.get(trailRunning) != null);
}
@Test
public void testAggregate_twoBikingTracks() {
// given
// 10km in 40 minutes.
long totalDistance = 10000;
long totalTime = 2400000;
String biking = context.getString(R.string.activity_type_biking);
Track[] tracks = new Track[2];
for (int i = 0; i < 2; i++) {
tracks[i] = createTrack(context, totalDistance, totalTime, biking);
}
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
// then
Assert.assertEquals(1, aggregatedStatistics.getCount());
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
Assert.assertEquals(2, aggregatedStatistics.get(biking).getCountTracks());
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 2L, statistics2.getMovingTime());
}
@Test
public void testAggregate_threeDifferentTracks() {
// given
// 10km in 40 minutes.
long totalDistance = 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);
long totalTime = 2400000;
Track[] tracks = new Track[]{
createTrack(context, totalDistance, totalTime, biking),
createTrack(context, totalDistance, totalTime, running),
createTrack(context, totalDistance, totalTime, walking)
};
// when
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(tracks[0]); // biking activity 1.
aggregatedStatistics.aggregate(tracks[1]); // biking activity 2.
aggregatedStatistics.aggregate(tracks[2]); // biking activity 3.
// then
Assert.assertEquals(3, aggregatedStatistics.getCount());
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
Assert.assertTrue(aggregatedStatistics.get(running) != null);
Assert.assertTrue(aggregatedStatistics.get(walking) != null);
Assert.assertEquals(1, aggregatedStatistics.get(biking).getCountTracks());
Assert.assertEquals(1, aggregatedStatistics.get(running).getCountTracks());
Assert.assertEquals(1, aggregatedStatistics.get(walking).getCountTracks());
{
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
}
{
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
}
{
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
}
}
@Test
public void testAggregate_severalTracksWithSeveralActivities() {
// given
// 10km in 40 minutes.
long totalDistance = 10000;
long totalTime = 2400000;
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);
String driving = context.getString(R.string.activity_type_driving);
Track[] tracks = new Track[]{
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
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
for (Track track : tracks) {
aggregatedStatistics.aggregate(track);
}
// then
// 4 sports.
Assert.assertEquals(4, aggregatedStatistics.getCount());
// There is a map for every sport.
Assert.assertTrue(aggregatedStatistics.get(biking) != null);
Assert.assertTrue(aggregatedStatistics.get(running) != null);
Assert.assertTrue(aggregatedStatistics.get(walking) != null);
Assert.assertTrue(aggregatedStatistics.get(driving) != null);
// Number of tracks by sport.
Assert.assertEquals(5, aggregatedStatistics.get(biking).getCountTracks()); // Biking.
Assert.assertEquals(2, aggregatedStatistics.get(running).getCountTracks()); // Running.
Assert.assertEquals(2, aggregatedStatistics.get(walking).getCountTracks()); // Walking.
Assert.assertEquals(1, aggregatedStatistics.get(driving).getCountTracks()); // Driving.
// Biking.
{
TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics();
Assert.assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 5, statistics2.getMovingTime());
}
// Running.
{
TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics();
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
}
// Walking.
{
TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics();
Assert.assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime * 2, statistics2.getMovingTime());
}
// Driving.
{
TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics();
Assert.assertEquals(totalDistance, statistics2.getTotalDistance(), 0);
Assert.assertEquals(totalTime, statistics2.getMovingTime());
}
}
}
+5
View File
@@ -98,6 +98,11 @@ limitations under the License.
android:value=".SearchListActivity" />
</activity>
<activity
android:name=".AggregatedStatisticsActivity"
android:screenOrientation="userPortrait"
android:label="@string/menu_aggregated_statistics"/>
<activity
android:name=".io.file.exporter.ExportActivity"
android:theme="@style/ThemeCustomTransparent" />
@@ -0,0 +1,41 @@
package de.dennisguse.opentracks;
import android.os.Bundle;
import android.widget.ListView;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import de.dennisguse.opentracks.adapters.AggregatedStatisticsAdapter;
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
import de.dennisguse.opentracks.viewmodels.AggregatedStatisticsModel;
public class AggregatedStatisticsActivity extends AbstractActivity {
private ListView listView;
private AggregatedStatisticsAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listView = findViewById(R.id.aggregated_stats_list);
final AggregatedStatisticsModel viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
viewModel.getAggregatedStats().observe(this, new Observer<AggregatedStatistics>() {
@Override
public void onChanged(AggregatedStatistics aggregatedStatistics) {
if (aggregatedStatistics != null) {
adapter = new AggregatedStatisticsAdapter(getApplicationContext(), aggregatedStatistics);
listView.setAdapter(adapter);
}
adapter.notifyDataSetChanged();
}
});
}
@Override
protected int getLayoutResId() {
return R.layout.aggregated_stats;
}
}
@@ -396,6 +396,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
this.invalidateOptionsMenu();
}
return true;
case R.id.track_list_aggregated_stats:
intent = IntentUtils.newIntent(this, AggregatedStatisticsActivity.class);
startActivity(intent);
return true;
case R.id.track_list_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class);
startActivity(intent);
@@ -0,0 +1,181 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.AggregatedStatistics;
public class AggregatedStatisticsAdapter extends BaseAdapter {
private AggregatedStatistics aggregatedStatistics;
private Context context;
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
this.context = context;
this.aggregatedStatistics = aggregatedStatistics;
}
@Override
public int getCount() {
if (aggregatedStatistics != null) {
return aggregatedStatistics.getCount();
}
return 0;
}
@Override
public AggregatedStatistics.AggregatedStatistic getItem(int position) {
return aggregatedStatistics.getItem(position);
}
@Override
public long getItemId(int position) {
// Not important because data are not database register but cooked data.
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AggregatedStatistics.AggregatedStatistic aggregatedStats = getItem(position);
ViewHolder viewHolder;
if (isSpeedSport(position)) {
viewHolder = new ViewSpeedHolder();
} else {
viewHolder = new ViewPaceHolder();
}
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
viewHolder.setValues(convertView, getIcon(position), getName(position), aggregatedStats);
return convertView;
}
private boolean isSpeedSport(int position) {
String category = aggregatedStatistics.getSportName(position);
return TrackIconUtils.isSpeedIcon(context, category);
}
private int getIcon(int position) {
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistics.getSportName(position));
return TrackIconUtils.getIconDrawable(iconValue);
}
private String getName(int position) {
return aggregatedStatistics.getSportName(position);
}
private class ViewHolder {
protected ImageView sportIcon;
protected TextView typeLabel;
protected TextView numTracks;
protected TextView distance;
protected TextView distanceUnit;
protected TextView time;
protected boolean metricsUnits;
public ViewHolder() {
metricsUnits = PreferencesUtils.isMetricUnits(context);
}
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
distance = view.findViewById(R.id.aggregated_stats_distance);
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
time = view.findViewById(R.id.aggregated_stats_time);
sportIcon.setImageResource(iconDrawable);
typeLabel.setText(name);
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStats.getCountTracks())));
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStats.getTrackStatistics().getTotalDistance(), metricsUnits);
distance.setText(parts.first);
distanceUnit.setText(parts.second);
time.setText(StringUtils.formatElapsedTime(aggregatedStats.getTrackStatistics().getMovingTime()));
}
}
private class ViewSpeedHolder extends ViewHolder {
private TextView avgSpeed;
private TextView avgSpeedUnit;
private TextView avgSpeedLabel;
private TextView maxSpeed;
private TextView maxSpeedUnit;
private TextView maxSpeedLabel;
@Override
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
super.setValues(view, iconDrawable, name, aggregatedStats);
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, true);
avgSpeed.setText(parts.first);
avgSpeedUnit.setText(parts.second);
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
}
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, true);
maxSpeed.setText(parts.first);
maxSpeedUnit.setText(parts.second);
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
}
}
}
private class ViewPaceHolder extends ViewHolder {
private TextView avgPace;
private TextView avgPaceUnit;
private TextView avgPaceLabel;
private TextView maxPace;
private TextView maxPaceUnit;
private TextView maxPaceLabel;
@Override
public void setValues(View view, int iconDrawable, String name, AggregatedStatistics.AggregatedStatistic aggregatedStats) {
super.setValues(view, iconDrawable, name, aggregatedStats);
avgPace = view.findViewById(R.id.aggregated_stats_avg_rate);
avgPaceUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
avgPaceLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
maxPace = view.findViewById(R.id.aggregated_stats_max_rate);
maxPaceUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
maxPaceLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, false);
avgPace.setText(parts.first);
avgPaceUnit.setText(parts.second);
avgPaceLabel.setText(R.string.stats_average_moving_pace);
}
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStats.getTrackStatistics().getMaxSpeed(), metricsUnits, false);
maxPace.setText(parts.first);
maxPaceUnit.setText(parts.second);
maxPaceLabel.setText(R.string.stats_fastest_pace);
}
}
}
}
@@ -426,4 +426,8 @@ public class StringUtils {
}
return new Pair<>(value, unit);
}
public static String valueInParentheses(String text) {
return "(" + text + ")";
}
}
@@ -0,0 +1,74 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.TrackIconUtils;
public class AggregatedStatistics {
private HashMap<String, AggregatedStatistic> aggregatedStatistics = new HashMap<>();
public void aggregate(List<Track> tracks) {
for (Track track : tracks) {
aggregate(track);
}
}
public void aggregate(Track track) {
String category = track.getCategory();
if (aggregatedStatistics.containsKey(category)) {
aggregatedStatistics.get(category).add(track.getTrackStatistics());
} else {
aggregatedStatistics.put(category, new AggregatedStatistic(track.getTrackStatistics()));
}
}
public int getCount() {
return aggregatedStatistics.size();
}
public String getSportName(int position) {
return getKey(position);
}
public AggregatedStatistic get(String category) {
return aggregatedStatistics.get(category);
}
private String getKey(int position) {
return (new ArrayList<>(aggregatedStatistics.keySet())).get(position);
}
public AggregatedStatistic getItem(int position) {
return (new ArrayList<>(aggregatedStatistics.values())).get(position);
}
public static class AggregatedStatistic {
private TrackStatistics trackStatistics;
private int countTracks;
public AggregatedStatistic(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics;
this.countTracks = 1;
}
public TrackStatistics getTrackStatistics() {
return trackStatistics;
}
public int getCountTracks() {
return countTracks;
}
void add(TrackStatistics statistics) {
trackStatistics.merge(statistics);
countTracks++;
}
}
}
@@ -0,0 +1,42 @@
package de.dennisguse.opentracks.viewmodels;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
public class AggregatedStatisticsModel extends AndroidViewModel {
private MutableLiveData<AggregatedStatistics> aggregatedStats;
public AggregatedStatisticsModel(@NonNull Application application) {
super(application);
}
public LiveData<AggregatedStatistics> getAggregatedStats() {
if (aggregatedStats == null) {
aggregatedStats = new MutableLiveData<>();
loadAggregatedStats();
}
return aggregatedStats;
}
private void loadAggregatedStats() {
new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
List<Track> tracks = contentProviderUtils.getAllTracks();
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics();
aggregatedStatistics.aggregate(tracks);
aggregatedStats.postValue(aggregatedStatistics);
}).start();
}
}
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/>
</vector>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<ListView
android:id="@+id/aggregated_stats_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0" />
</LinearLayout>
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="24dp">
<!-- Guidelines -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline1"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_begin="8dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_end="8dp" />
<!-- Icon and sport name -->
<ImageView
android:id="@+id/aggregated_stats_sport_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
app:layout_constraintLeft_toLeftOf="@id/guideline1"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_activity_bike_24dp" />
<TextView
android:id="@+id/aggregated_stats_type_label"
style="@style/StatsLargeLabel"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_sport_icon"
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_sport_icon"
app:layout_constraintTop_toTopOf="@+id/aggregated_stats_sport_icon"
tools:text="@string/activity_type_cycling"
android:layout_marginLeft="16dp" />
<TextView
android:id="@+id/aggregated_stats_num_tracks"
style="@style/StatsLargeLabel"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_type_label"
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_type_label"
app:layout_constraintTop_toTopOf="@+id/aggregated_stats_type_label"
tools:text="(1)"
android:layout_marginLeft="4dp" />
<!-- Distance -->
<TextView
android:id="@+id/aggregated_stats_distance"
style="@style/StatsLargeValue"
app:layout_constraintLeft_toLeftOf="@id/guideline1"
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_sport_icon"
tools:text="100000"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/aggregated_stats_distance_unit"
style="@style/StatsUnit"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_distance"
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_distance"
tools:text="km" />
<!-- Time -->
<TextView
android:id="@+id/aggregated_stats_time"
style="@style/StatsLargeValue"
app:layout_constraintRight_toRightOf="@id/guideline3"
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_sport_icon"
tools:text="00:00:00"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp" />
<!-- Avg. rate -->
<TextView
android:id="@+id/aggregated_stats_avg_rate"
style="@style/StatsSmallValue"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/aggregated_stats_distance"
app:layout_constraintLeft_toRightOf="@id/guideline1"
app:layout_constraintRight_toLeftOf="@+id/aggregated_stats_avg_rate_unit"
android:layout_marginTop="24dp"
tools:text="30" />
<TextView
android:id="@+id/aggregated_stats_avg_rate_unit"
style="@style/StatsUnit"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_avg_rate"
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_avg_rate"
app:layout_constraintRight_toRightOf="@id/guideline2"
tools:text="km/h" />
<TextView
android:id="@+id/aggregated_stats_avg_rate_label"
style="@style/StatsTinyLabel"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_avg_rate"
app:layout_constraintLeft_toRightOf="@id/guideline1"
app:layout_constraintRight_toRightOf="@id/guideline2"
tools:text="@string/stats_average_moving_speed" />
<!-- Max. rate -->
<TextView
android:id="@+id/aggregated_stats_max_rate"
style="@style/StatsSmallValue"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/aggregated_stats_time"
app:layout_constraintLeft_toRightOf="@id/guideline2"
app:layout_constraintRight_toLeftOf="@+id/aggregated_stats_max_rate_unit"
android:layout_marginTop="24dp"
tools:text="70" />
<TextView
android:id="@+id/aggregated_stats_max_rate_unit"
style="@style/StatsUnit"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@+id/aggregated_stats_max_rate"
app:layout_constraintLeft_toRightOf="@+id/aggregated_stats_max_rate"
app:layout_constraintRight_toLeftOf="@id/guideline3"
tools:text="km/h" />
<TextView
android:id="@+id/aggregated_stats_max_rate_label"
style="@style/StatsTinyLabel"
app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@+id/aggregated_stats_max_rate"
app:layout_constraintLeft_toRightOf="@id/guideline2"
app:layout_constraintRight_toLeftOf="@id/guideline3"
tools:text="@string/stats_max_speed" />
</androidx.constraintlayout.widget.ConstraintLayout>
+5
View File
@@ -28,6 +28,11 @@ limitations under the License.
android:title="@string/menu_start_gps"
android:visible="false"
app:showAsAction="ifRoom" />
<item
android:id="@+id/track_list_aggregated_stats"
android:icon="@drawable/ic_statistics_24dp"
android:title="@string/menu_aggregated_statistics"
app:showAsAction="ifRoom" />
<item
android:id="@+id/track_list_markers"
android:icon="@drawable/ic_marker_show_24dp"
+7
View File
@@ -195,6 +195,13 @@ limitations under the License.
<item name="android:singleLine">true</item>
<item name="android:textColor">@android:color/white</item>
</style>
<!-- stats small label -->
<style name="StatsTinyLabel" parent="@style/TextSmall">
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">false</item>
<item name="android:textColor">@android:color/secondary_text_light_nodisable</item>
</style>
<!-- stats small value container -->
<style name="StatsSmallValueContainer" parent="@style/StatsLargeValueContainer">
<item name="android:layout_marginTop">-2dp</item>