StatisticsRecordingFragment: render Views manually (instead of RecyclerView).

Part of #1118.
This commit is contained in:
Dennis Guse
2022-11-10 21:38:51 +01:00
parent d5864726f6
commit d70e7b8788
23 changed files with 667 additions and 875 deletions
+1
View File
@@ -125,6 +125,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.core:core-splashscreen:1.0.0'
@@ -196,10 +196,10 @@ public class PreferencesUtilsTest {
// given
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
Layout layoutSrc = new Layout("road cycling");
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, false, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), false, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), true, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), true, false, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), false, true, false));
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), false, false, false));
// when
SharedPreferences.Editor editor = sharedPreferences.edit();
@@ -47,7 +47,7 @@ public class EspressoCustomLayoutTest {
// Get custom layout preferences and check all data fields are showed.
Layout layout = PreferencesUtils.getCustomLayout();
onView(withId(R.id.stats_recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
onView(withId(R.id.recycler_view)).check(new RecyclerViewItemCountAssertion((int) layout.getFields().stream().filter(DataField::isVisible).count()));
// stop recording
ViewInteraction trackControllerStopButton = onView(withId(R.id.track_recording_fab_action));
@@ -39,10 +39,6 @@ public class LayoutTest extends TestCase {
assertEquals(dataFieldList.get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key));
assertEquals(dataFieldList.get(2).getKey(), context.getString(R.string.stats_custom_layout_altitude_key));
assertEquals(dataFieldList.get(3).getKey(), context.getString(R.string.stats_custom_layout_gain_key));
assertEquals(dataFieldList.get(0).getTitle(), context.getString(R.string.stats_speed));
assertEquals(dataFieldList.get(1).getTitle(), context.getString(R.string.stats_distance));
assertEquals(dataFieldList.get(2).getTitle(), context.getString(R.string.stats_altitude));
assertEquals(dataFieldList.get(3).getTitle(), context.getString(R.string.stats_gain));
assertTrue(dataFieldList.get(0).isVisible());
assertTrue(dataFieldList.get(0).isPrimary());
assertFalse(dataFieldList.get(0).isWide());
@@ -122,11 +118,11 @@ public class LayoutTest extends TestCase {
public void testToCsv() {
// given a layout's object
Layout layout = new Layout("Test Layout", 2);
layout.addField(new DataField("key1", "Title 1", false, false, false));
layout.addField(new DataField("key2", "Title 2", false, true, false));
layout.addField(new DataField("key3", "Title 3", true, false, false));
layout.addField(new DataField("key4", "Title 4", true, true, false));
layout.addField(new DataField("key5", "Title 5", true, true, true));
layout.addField(new DataField("key1", false, false, false));
layout.addField(new DataField("key2", false, true, false));
layout.addField(new DataField("key3", true, false, false));
layout.addField(new DataField("key4", true, true, false));
layout.addField(new DataField("key5", true, true, true));
// when converts it to CSV
String csv = layout.toCsv();
@@ -139,11 +135,11 @@ public class LayoutTest extends TestCase {
public void testToCsv_columnsByDefault() {
// given a layout's object
Layout layout = new Layout("Test Layout");
layout.addField(new DataField("key1", "Title 1", false, false, false));
layout.addField(new DataField("key2", "Title 2", false, true, false));
layout.addField(new DataField("key3", "Title 3", true, false, false));
layout.addField(new DataField("key4", "Title 4", true, true, false));
layout.addField(new DataField("key5", "Title 5", true, true, true));
layout.addField(new DataField("key1", false, false, false));
layout.addField(new DataField("key2", false, true, false));
layout.addField(new DataField("key3", true, false, false));
layout.addField(new DataField("key4", true, true, false));
layout.addField(new DataField("key5", true, true, true));
// when converts it to CSV
String csv = layout.toCsv();
@@ -45,11 +45,11 @@ public class StatisticsUtilsTest extends TestCase {
public void testFilterVisible() {
// given
Layout layout = new Layout("profile");
layout.addField(new DataField("key1", "title1", true, true, false));
layout.addField(new DataField("key2", "title2", false, true, false));
layout.addField(new DataField("key3", "title3", true, true, false));
layout.addField(new DataField("key4", "title4", false, true, false));
layout.addField(new DataField("key5", "title5", true, true, false));
layout.addField(new DataField("key1", true, true, false));
layout.addField(new DataField("key2", false, true, false));
layout.addField(new DataField("key3", true, true, false));
layout.addField(new DataField("key4", false, true, false));
layout.addField(new DataField("key5", true, true, false));
// when
Layout resultTrue = StatisticsUtils.filterVisible(layout, true);
@@ -1,200 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
@RunWith(MockitoJUnitRunner.class)
public class StatisticDataBuilderTest extends TestCase {
private final Context context = ApplicationProvider.getApplicationContext();
@Mock
private Track trackMock;
@Mock
private TrackStatistics trackStatisticsMock;
@Mock
private RecordingData recordingDataMock;
@Mock
private SensorStatistics sensorStatisticsMock;
@Test
public void testFromRecordingData() {
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
// given
Layout layout = new Layout(context.getString(R.string.default_activity_default));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), false, true, false));
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
// then
assertEquals(statisticDataList.size(), 3);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_total_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_moving_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_distance))));
}
@Test
public void testFromRecordingData_allFields() {
when(trackMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
when(trackStatisticsMock.getTotalTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getMovingTime()).thenReturn(Duration.ofMillis(0));
when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0));
when(recordingDataMock.getTrackStatistics()).thenReturn(trackStatisticsMock);
// given
Layout layout = new Layout(context.getString(R.string.default_activity_default));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false));
layout.addField(new DataField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.stats_sensors_power), true, true, false));
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(context, recordingDataMock, layout, UnitSystem.METRIC);
// then
assertEquals(statisticDataList.size(), 18);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_total_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_moving_time))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_distance))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_average_moving_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_average_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_max_speed))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_average_moving_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_average_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_fastest_pace))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_altitude))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_gain))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_loss))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_coordinates))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_sensors_cadence))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.stats_sensors_power))));
}
@Test
public void testFromSensorStatistics_onlyHeartRate() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
when(sensorStatisticsMock.hasPower()).thenReturn(false);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
// then
assertEquals(statisticDataList.size(), 2);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
}
@Test
public void testFromSensorStatistics_onlyCadence() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
when(sensorStatisticsMock.hasPower()).thenReturn(false);
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
// then
assertEquals(statisticDataList.size(), 2);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
}
@Test
public void testFromSensorStatistics_onlyPower() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(false);
when(sensorStatisticsMock.hasCadence()).thenReturn(false);
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
// then
assertEquals(statisticDataList.size(), 1);
}
@Test
public void testFromSensorStatistics() {
when(sensorStatisticsMock.hasHeartRate()).thenReturn(true);
when(sensorStatisticsMock.getMaxHeartRate()).thenReturn(HeartRate.of(200f));
when(sensorStatisticsMock.getAvgHeartRate()).thenReturn(HeartRate.of(150f));
when(sensorStatisticsMock.hasCadence()).thenReturn(true);
when(sensorStatisticsMock.getAvgCadence()).thenReturn(Cadence.of(90f));
when(sensorStatisticsMock.getMaxCadence()).thenReturn(Cadence.of(110f));
when(sensorStatisticsMock.hasPower()).thenReturn(true);
when(sensorStatisticsMock.getAvgPower()).thenReturn(Power.of(300f));
// when
List<StatisticData> statisticDataList = StatisticDataBuilder.fromSensorStatistics(context, sensorStatisticsMock);
// then
assertEquals(statisticDataList.size(), 5);
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_cadence_max))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_cadence_avg))));
assertTrue(statisticDataList.stream().anyMatch(i -> i.getTitle().equals(context.getString(R.string.sensor_state_power_avg))));
}
}
@@ -1,173 +0,0 @@
package de.dennisguse.opentracks.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
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.databinding.StatsClockItemBinding;
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
import de.dennisguse.opentracks.databinding.StatsRecordedItemBinding;
import de.dennisguse.opentracks.ui.customRecordingLayout.CustomLayoutFieldType;
import de.dennisguse.opentracks.viewmodels.StatisticData;
public class StatisticsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<StatisticData> statisticDataList;
private final Context context;
public StatisticsAdapter(Context context) {
this.context = context;
this.statisticDataList = new ArrayList<>();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == CustomLayoutFieldType.CLOCK.value()) {
return new ViewClockHolder(StatsClockItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
} else {
return new GenericViewHolder(StatsGenericItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
StatisticData statisticData = statisticDataList.get(position);
if (getItemViewType(position) == CustomLayoutFieldType.CLOCK.value()) {
StatisticsAdapter.ViewClockHolder viewHolder = (StatisticsAdapter.ViewClockHolder) holder;
viewHolder.setData(statisticData);
} else {
GenericViewHolder genericViewHolder = (GenericViewHolder) holder;
genericViewHolder.setData(statisticData);
}
}
@Override
public int getItemCount() {
if (statisticDataList == null) {
return 0;
} else {
return statisticDataList.size();
}
}
@Override
public int getItemViewType(int position) {
if (statisticDataList.get(position).getType(context) == CustomLayoutFieldType.CLOCK) {
return CustomLayoutFieldType.CLOCK.value();
} else {
return CustomLayoutFieldType.GENERIC.value();
}
}
public boolean isItemWide(int position) {
return statisticDataList.get(position).isWide();
}
public List<StatisticData> swapData(List<StatisticData> data) {
if (statisticDataList == data) {
return null;
}
statisticDataList = data;
if (data != null) {
this.notifyDataSetChanged();
}
return data;
}
public static class WithRecordedLayout extends StatisticsAdapter {
public WithRecordedLayout(Context context) {
super(context);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewRecordedHolder(StatsRecordedItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
}
}
private class GenericViewHolder extends RecyclerView.ViewHolder {
private final StatsGenericItemBinding itemBinding;
public GenericViewHolder(@NonNull StatsGenericItemBinding itemBinding) {
super(itemBinding.getRoot());
this.itemBinding = itemBinding;
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
itemBinding.statsValue.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
itemBinding.statsValue.setTextAppearance(context, statisticData.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
itemBinding.statsUnit.setText(statisticData.getUnit());
itemBinding.statsDescriptionMain.setText(statisticData.getTitle());
itemBinding.statsDescriptionMain.setTextAppearance(context, statisticData.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
if (statisticData.hasDescription()) {
itemBinding.statsDescriptionSecondary.setVisibility(View.VISIBLE);
itemBinding.statsDescriptionSecondary.setText(statisticData.getDescription());
} else {
itemBinding.statsDescriptionSecondary.setVisibility(View.GONE);
}
}
}
private class ViewClockHolder extends RecyclerView.ViewHolder {
private final StatsClockItemBinding itemView;
public ViewClockHolder(@NonNull StatsClockItemBinding itemView) {
super(itemView.getRoot());
this.itemView = itemView;
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
itemView.statsClock.setTextAppearance(context, statisticData.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
itemView.statsDescriptionMain.setTextAppearance(context, statisticData.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
}
private class ViewRecordedHolder extends RecyclerView.ViewHolder {
private final StatsRecordedItemBinding itemBinding;
public ViewRecordedHolder(@NonNull StatsRecordedItemBinding itemBinding) {
super(itemBinding.getRoot());
this.itemBinding = itemBinding;
}
public void setData(StatisticData statisticData) {
if (statisticData == null) {
return;
}
itemBinding.statsValue.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown));
itemBinding.statsUnit.setText(statisticData.getUnit());
itemBinding.statsDescriptionMain.setText(statisticData.getTitle());
if (statisticData.hasDescription()) {
itemBinding.statsDescriptionSecondary.setVisibility(View.VISIBLE);
itemBinding.statsDescriptionSecondary.setText(statisticData.getDescription());
} else {
itemBinding.statsDescriptionSecondary.setVisibility(View.GONE);
}
}
}
}
@@ -31,11 +31,8 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackRecordedActivity;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.data.ContentProviderUtils;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
@@ -47,8 +44,6 @@ import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticDataBuilder;
/**
* A fragment to display track statistics to the user for a recorded {@link Track}.
@@ -72,7 +67,6 @@ public class StatisticsRecordedFragment extends Fragment {
}
private SensorStatistics sensorStatistics;
private StatisticsAdapter.WithRecordedLayout sensorsAdapter;
private Track.Id trackId;
@Nullable // Lazily loaded.
@@ -113,8 +107,6 @@ public class StatisticsRecordedFragment extends Fragment {
trackId = getArguments().getParcelable(TRACK_ID_KEY);
contentProviderUtils = new ContentProviderUtils(getContext());
sensorsAdapter = new StatisticsAdapter.WithRecordedLayout(getContext());
}
@Override
@@ -123,7 +115,7 @@ public class StatisticsRecordedFragment extends Fragment {
RecyclerView sensorsRecyclerView = viewBinding.statsSensorsRecyclerView;
sensorsRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2));
sensorsRecyclerView.setAdapter(sensorsAdapter);
// sensorsRecyclerView.setAdapter(sensorsAdapter);
return viewBinding.getRoot();
}
@@ -261,10 +253,6 @@ public class StatisticsRecordedFragment extends Fragment {
if (sensorStatistics == null) {
return;
}
List<StatisticData> sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics);
if (sensorDataList.size() > 0) {
sensorsAdapter.swapData(sensorDataList);
}
//TODO
}
}
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.fragments;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -9,16 +10,14 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.gridlayout.widget.GridLayout;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.adapters.StatisticsAdapter;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.databinding.StatisticsRecordingBinding;
import de.dennisguse.opentracks.services.RecordingData;
@@ -26,9 +25,10 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.viewmodels.StatisticData;
import de.dennisguse.opentracks.viewmodels.StatisticsDataModel;
import de.dennisguse.opentracks.viewmodels.Mapping;
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
/**
* A fragment to display track statistics to the user for a currently recording {@link Track}.
@@ -48,32 +48,22 @@ public class StatisticsRecordingFragment extends Fragment {
private RecordingData recordingData = TrackRecordingService.NOT_RECORDING;
private final List<StatisticViewHolder<?>> viewHolders = new LinkedList<>();
private Layout layout;
private StatisticsRecordingBinding viewBinding;
private StatisticsAdapter statisticsAdapter;
private GridLayoutManager gridLayoutManager;
private StatisticsDataModel viewModel;
private LiveData<List<StatisticData>> statisticsLiveData;
private UnitSystem unitSystem = UnitSystem.defaultUnitSystem();
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> {
boolean updateUInecessary = false;
if (PreferencesUtils.isKey(R.string.stats_units_key, key)) {
updateUInecessary = true;
unitSystem = PreferencesUtils.getUnitSystem();
updateDataOnUI();
}
if (PreferencesUtils.isKey(R.string.stats_custom_layouts_key, key) || PreferencesUtils.isKey(R.string.stats_custom_layout_selected_layout_key, key)) {
updateUInecessary = true;
layout = PreferencesUtils.getCustomLayout();
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
}
if (key != null && updateUInecessary && isResumed()) {
getActivity().runOnUiThread(this::updateUI);
onLayoutChanged(PreferencesUtils.getCustomLayout());
}
};
@@ -84,30 +74,11 @@ public class StatisticsRecordingFragment extends Fragment {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
statisticsAdapter = new StatisticsAdapter(getContext());
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
viewBinding = StatisticsRecordingBinding.inflate(inflater, container, false);
RecyclerView recyclerView = viewBinding.statsRecyclerView;
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
layout = PreferencesUtils.getCustomLayout();
gridLayoutManager = new GridLayoutManager(getContext(), layout.getColumnsPerRow());
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (statisticsAdapter.isItemWide(position)) {
return layout.getColumnsPerRow();
}
return 1;
}
});
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setAdapter(statisticsAdapter);
return viewBinding.getRoot();
}
@@ -117,10 +88,6 @@ public class StatisticsRecordingFragment extends Fragment {
PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
viewModel = new ViewModelProvider(getActivity()).get(StatisticsDataModel.class);
statisticsLiveData = viewModel.getStatsData();
statisticsLiveData.observe(getActivity(), statsDataList -> statisticsAdapter.swapData(statsDataList));
trackRecordingServiceConnection.startConnection(getContext());
}
@@ -133,6 +100,7 @@ public class StatisticsRecordingFragment extends Fragment {
@Override
public void onStop() {
super.onStop();
viewHolders.clear();
trackRecordingServiceConnection.unbind(getContext());
}
@@ -146,17 +114,56 @@ public class StatisticsRecordingFragment extends Fragment {
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection = null;
viewModel = null;
if (statisticsLiveData != null) {
statisticsLiveData.removeObservers(getActivity());
}
statisticsLiveData = null;
}
private void updateUI() {
if (isResumed()) {
viewModel.update(recordingData, layout, unitSystem);
private void onLayoutChanged(@NonNull Layout newLayout) {
if (newLayout.equals(layout)) {
return;
}
layout = newLayout;
viewBinding.statsLayout.setColumnCount(layout.getColumnsPerRow());
viewBinding.statsLayout.removeAllViews(); //Let's start from scratch
viewHolders.clear();
Map<String, Callable<StatisticViewHolder<?>>> m = Mapping.create(getContext());
int rowIndex = 0;
int columnIndex = 0;
for (int i = 0; i < layout.getFields().size(); i++) {
DataField dataField = layout.getFields().get(i);
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
param.setGravity(Gravity.FILL_HORIZONTAL);
param.width = 0;
if (dataField.isWide()) {
rowIndex++;
param.columnSpec = GridLayout.spec(0, layout.getColumnsPerRow(), 1);
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
columnIndex = 0;
rowIndex++;
} else {
if (columnIndex >= layout.getColumnsPerRow()) {
columnIndex = 0;
rowIndex++;
}
param.columnSpec = GridLayout.spec(columnIndex, 1, 1);
param.rowSpec = GridLayout.spec(rowIndex, 1, 1);
columnIndex++;
}
try {
StatisticViewHolder<?> viewHolder = m.get(dataField.getKey()).call();
viewHolder.initialize(getContext(), getLayoutInflater());
viewHolder.configureUI(dataField);
viewHolders.add(viewHolder);
viewBinding.statsLayout.addView(viewHolder.getView(), param);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -169,6 +176,12 @@ public class StatisticsRecordingFragment extends Fragment {
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, getString(R.string.stats_rate_key));
}
updateUI();
updateDataOnUI();
}
private void updateDataOnUI() {
if (isResumed()) {
viewHolders.forEach(i -> i.onChanged(unitSystem, recordingData));
}
}
}
@@ -754,6 +754,7 @@ public class PreferencesUtils {
/**
* @return custom layout selected or the first one if any has been selected or the one selected is not exists anymore.
*/
@Deprecated //TODO Move parsing to LayoutIO
public static Layout getCustomLayout() {
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR);
@@ -772,6 +773,7 @@ public class PreferencesUtils {
return LayoutIO.fromCsv(csvLines[0], resources);
}
//TODO Move string generation to LayoutIO
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
}
@@ -1,23 +1,19 @@
package de.dennisguse.opentracks.ui.customRecordingLayout;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import de.dennisguse.opentracks.R;
import java.util.Objects;
public class DataField implements Parcelable {
private final String key;
private final String title;
private boolean isVisible;
private boolean isPrimary;
private final boolean isWide;
public DataField(String key, String title, boolean isVisible, boolean isPrimary, boolean isWide) {
public DataField(String key, boolean isVisible, boolean isPrimary, boolean isWide) {
this.key = key;
this.title = title;
this.isVisible = isVisible;
this.isPrimary = isPrimary;
this.isWide = isWide;
@@ -25,7 +21,6 @@ public class DataField implements Parcelable {
protected DataField(Parcel in) {
key = in.readString();
title = in.readString();
isVisible = in.readByte() != 0;
isPrimary = in.readByte() != 0;
isWide = in.readByte() != 0;
@@ -47,10 +42,6 @@ public class DataField implements Parcelable {
return key;
}
public String getTitle() {
return title;
}
public boolean isVisible() {
return isVisible;
}
@@ -71,58 +62,6 @@ public class DataField implements Parcelable {
return isWide;
}
public CustomLayoutFieldType getType(Context context) {
if (key.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
return CustomLayoutFieldType.CLOCK;
} else {
return CustomLayoutFieldType.GENERIC;
}
}
public static String getTitleByKey(Resources resources, String key) {
if (key.equals(resources.getString(R.string.stats_custom_layout_total_time_key))) {
return resources.getString(R.string.stats_total_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_moving_time_key))) {
return resources.getString(R.string.stats_moving_time);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_distance_key))) {
return resources.getString(R.string.stats_distance);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_speed_key))) {
return resources.getString(R.string.stats_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_pace_key))) {
return resources.getString(R.string.stats_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
return resources.getString(R.string.stats_average_moving_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_speed_key))) {
return resources.getString(R.string.stats_average_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_max_speed_key))) {
return resources.getString(R.string.stats_max_speed);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
return resources.getString(R.string.stats_average_moving_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_average_pace_key))) {
return resources.getString(R.string.stats_average_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_fastest_pace_key))) {
return resources.getString(R.string.stats_fastest_pace);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_altitude_key))) {
return resources.getString(R.string.stats_altitude);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_gain_key))) {
return resources.getString(R.string.stats_gain);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_loss_key))) {
return resources.getString(R.string.stats_loss);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_coordinates_key))) {
return resources.getString(R.string.stats_coordinates);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_heart_rate_key))) {
return resources.getString(R.string.stats_sensors_heart_rate);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_cadence_key))) {
return resources.getString(R.string.stats_sensors_cadence);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_power_key))) {
return resources.getString(R.string.stats_sensors_power);
} else if (key.equals(resources.getString(R.string.stats_custom_layout_clock_key))) {
return resources.getString(R.string.stats_clock);
} else {
throw new RuntimeException("It doesn't exists a field with key: " + key);
}
}
@Override
public int describeContents() {
return 0;
@@ -131,9 +70,21 @@ public class DataField implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(key);
parcel.writeString(title);
parcel.writeByte((byte) (isVisible ? 1 : 0));
parcel.writeByte((byte) (isPrimary ? 1 : 0));
parcel.writeByte((byte) (isWide ? 1 : 0));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DataField dataField = (DataField) o;
return isVisible == dataField.isVisible && isPrimary == dataField.isPrimary && isWide == dataField.isWide && Objects.equals(key, dataField.key);
}
@Override
public int hashCode() {
return Objects.hash(key, isVisible, isPrimary, isWide);
}
}
@@ -5,6 +5,7 @@ import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.settings.PreferencesUtils;
@@ -112,4 +113,17 @@ public class Layout implements Parcelable {
parcel.writeInt(columnsPerRow);
parcel.writeList(dataFields);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Layout layout = (Layout) o;
return columnsPerRow == layout.columnsPerRow && Objects.equals(name, layout.name) && Objects.equals(dataFields, layout.dataFields);
}
@Override
public int hashCode() {
return Objects.hash(name, columnsPerRow, dataFields);
}
}
@@ -46,7 +46,6 @@ public class LayoutIO {
private static DataField fromCSV(String[] fieldParts, @NonNull Resources resources) {
return new DataField(
fieldParts[0],
DataField.getTitleByKey(resources, fieldParts[0]),
fieldParts[1].equals(YES_VALUE),
fieldParts[2].equals(YES_VALUE),
fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key)));
@@ -11,27 +11,35 @@ import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.StatisticsUtils;
import de.dennisguse.opentracks.viewmodels.Mapping;
import de.dennisguse.opentracks.viewmodels.StatisticViewHolder;
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Layout layout;
private final Context context;
private final SettingsCustomLayoutItemClickListener itemClickListener;
private final Map<String, Callable<StatisticViewHolder<?>>> mapping;
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
this.context = context;
this.itemClickListener = itemClickListener;
this.layout = layout;
mapping = Mapping.create(context);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false);
View view = LayoutInflater.from(context).inflate(R.layout.custom_stats_item, parent, false); //TODO CustomStatsItemBinding or rather use mapping.get().call() directly
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
}
@@ -40,7 +48,11 @@ public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<Recycl
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
DataField field = layout.getFields().get(position);
viewHolder.itemView.setTag(field.getKey());
viewHolder.title.setText(field.getTitle());
try {
viewHolder.title.setText(mapping.get(field.getKey()).call().getTitleId());
} catch (Exception e) {
//Ignored.
}
viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey()));
viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader) : R.style.TextAppearance_OpenTracks_HiddenHeader);
@@ -94,6 +94,7 @@ public class TrackIconUtils {
};
//TODO Use Map.of()
private static final LinkedHashMap<String, Pair<Integer, Integer>> MAP = new LinkedHashMap<>();
static {
@@ -0,0 +1,32 @@
package de.dennisguse.opentracks.viewmodels;
import android.view.LayoutInflater;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.databinding.StatsClockItemBinding;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
public class ClockViewHolder extends StatisticViewHolder<StatsClockItemBinding> {
@Override
protected StatsClockItemBinding createViewBinding(LayoutInflater inflater) {
return StatsClockItemBinding.inflate(inflater);
}
@Override
public void configureUI(DataField dataField) {
//TODO Unify with GenericStatisticsViewHolder?
getBinding().statsClock.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
}
@Override
public int getTitleId() {
return R.string.stats_clock;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
}
}
@@ -0,0 +1,407 @@
package de.dennisguse.opentracks.viewmodels;
import android.util.Pair;
import android.view.LayoutInflater;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.databinding.StatsGenericItemBinding;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.util.StringUtils;
public abstract class GenericStatisticsViewHolder extends StatisticViewHolder<StatsGenericItemBinding> {
@Override
protected StatsGenericItemBinding createViewBinding(LayoutInflater inflater) {
return StatsGenericItemBinding.inflate(inflater);
}
@Override
public void configureUI(DataField dataField) {
getBinding().statsValue.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue);
getBinding().statsDescriptionMain.setTextAppearance(getContext(), dataField.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader);
}
public static class Distance extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_distance;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = DistanceFormatter.Builder()
.setUnit(unitSystem)
.build(getContext()).getDistanceParts(data.getTrackStatistics().getTotalDistance());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_distance));
}
}
public static class TotalTime extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_total_time;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(data.getTrackStatistics().getTotalTime()), null);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_total_time));
}
}
public static class MovingTime extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_moving_time;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
String value = StringUtils.formatElapsedTime(data.getTrackStatistics().getMovingTime());
getBinding().statsValue.setText(value);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_moving_time));
}
}
public static class SpeedOrPace extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_speed;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
// TODO Pace wont work for now
boolean reportSpeed = true;
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(reportSpeed).build(getContext());
Pair<String, String> valueAndUnit;
SensorDataSet sensorDataSet = data.getSensorDataSet();
final TrackPoint latestTrackPoint = data.getLatestTrackPoint();
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
getBinding().statsDescriptionMain.setText(sensorDataSet.getSpeed().second);
} else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.description_speed_source_gps));
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
}
}
public static class AverageMovingSpeed extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_average_moving_speed;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(true)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_speed));
}
}
public static class AverageSpeed extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_average_speed;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(true)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_speed));
}
}
public static class MaxSpeed extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_max_speed;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(true)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_max_speed));
}
}
public static class AverageMovingPace extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_average_moving_pace;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(false)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_moving_pace));
}
}
public static class AveragePace extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_average_pace;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(false)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getAverageMovingSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_average_pace));
}
}
public static class FastestPace extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_fastest_pace;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder()
.setUnit(unitSystem)
.setReportSpeedOrPace(false)
.build(getContext());
Pair<String, String> valueAndUnit = speedFormatterSpeed.getSpeedParts(data.getTrackStatistics().getMaxSpeed());
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(getContext().getString(R.string.stats_fastest_pace));
}
}
public static class Altitude extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_altitude;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
String altitudeReference = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? getContext().getString(latestTrackPoint.getAltitude().getLabelId()) : null;
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), altitude, unitSystem);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_altitude);
getBinding().statsDescriptionSecondary.setText(altitudeReference);
}
}
public static class Gain extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_gain;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeGain(), unitSystem);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_gain);
}
}
public static class Loss extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_loss;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
Pair<String, String> valueAndUnit = StringUtils.getAltitudeParts(getContext(), data.getTrackStatistics().getTotalAltitudeLoss(), unitSystem);
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_loss);
}
}
public static class Coordinates extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_coordinates;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
TrackPoint latestTrackPoint = data.getLatestTrackPoint();
String value;
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
value = StringUtils.formatCoordinate(getContext(), latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude());
} else {
value = getContext().getString(R.string.value_unknown);
}
getBinding().statsValue.setText(value);
getBinding().statsDescriptionMain.setText(R.string.stats_coordinates);
}
}
public static class SensorHeartRate extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_sensors_heart_rate;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), sensorDataSet.getHeartRate().first);
sensorName = sensorDataSet.getHeartRate().second;
} else {
valueAndUnit = StringUtils.getHeartRateParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_heart_rate);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
public static class SensorCadence extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_sensors_cadence;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCadence() != null) {
valueAndUnit = StringUtils.getCadenceParts(getContext(), sensorDataSet.getCadence().first);
sensorName = sensorDataSet.getCadence().second;
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_cadence);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
public static class SensorPower extends GenericStatisticsViewHolder {
@Override
public int getTitleId() {
return R.string.stats_sensors_power;
}
@Override
public void onChanged(UnitSystem unitSystem, RecordingData data) {
SensorDataSet sensorDataSet = data.getSensorDataSet();
String sensorName = getContext().getString(R.string.value_unknown);
Pair<String, String> valueAndUnit;
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null) {
valueAndUnit = StringUtils.getPowerParts(getContext(), sensorDataSet.getCyclingPower().getValue());
sensorName = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getCadenceParts(getContext(), null);
}
getBinding().statsValue.setText(valueAndUnit.first);
getBinding().statsUnit.setText(valueAndUnit.second);
getBinding().statsDescriptionMain.setText(R.string.stats_sensors_power);
getBinding().statsDescriptionSecondary.setText(sensorName);
}
}
}
@@ -0,0 +1,41 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import de.dennisguse.opentracks.R;
public class Mapping {
public static Map<String, Callable<StatisticViewHolder<?>>> create(Context context) {
HashMap<String, Callable<StatisticViewHolder<?>>> m = new HashMap<>();
m.put(context.getString(R.string.stats_custom_layout_total_time_key), GenericStatisticsViewHolder.TotalTime::new);
m.put(context.getString(R.string.stats_custom_layout_moving_time_key), GenericStatisticsViewHolder.MovingTime::new);
m.put(context.getString(R.string.stats_custom_layout_distance_key), GenericStatisticsViewHolder.Distance::new);
m.put(context.getString(R.string.stats_custom_layout_speed_key), GenericStatisticsViewHolder.SpeedOrPace::new);
m.put(context.getString(R.string.stats_custom_layout_pace_key), GenericStatisticsViewHolder.SpeedOrPace::new);
m.put(context.getString(R.string.stats_custom_layout_average_moving_speed_key), GenericStatisticsViewHolder.AverageMovingSpeed::new);
m.put(context.getString(R.string.stats_custom_layout_average_speed_key), GenericStatisticsViewHolder.AverageSpeed::new);
m.put(context.getString(R.string.stats_custom_layout_max_speed_key), GenericStatisticsViewHolder.MaxSpeed::new);
m.put(context.getString(R.string.stats_custom_layout_average_moving_pace_key), GenericStatisticsViewHolder.AverageMovingPace::new);
m.put(context.getString(R.string.stats_custom_layout_average_pace_key), GenericStatisticsViewHolder.AveragePace::new);
m.put(context.getString(R.string.stats_custom_layout_fastest_pace_key), GenericStatisticsViewHolder.FastestPace::new);
m.put(context.getString(R.string.stats_custom_layout_altitude_key), GenericStatisticsViewHolder.Altitude::new);
m.put(context.getString(R.string.stats_custom_layout_gain_key), GenericStatisticsViewHolder.Gain::new);
m.put(context.getString(R.string.stats_custom_layout_loss_key), GenericStatisticsViewHolder.Loss::new);
m.put(context.getString(R.string.stats_custom_layout_coordinates_key), GenericStatisticsViewHolder.Coordinates::new);
m.put(context.getString(R.string.stats_custom_layout_heart_rate_key), GenericStatisticsViewHolder.SensorHeartRate::new);
m.put(context.getString(R.string.stats_custom_layout_cadence_key), GenericStatisticsViewHolder.SensorCadence::new);
m.put(context.getString(R.string.stats_custom_layout_power_key), GenericStatisticsViewHolder.SensorPower::new);
m.put(context.getString(R.string.stats_custom_layout_clock_key), ClockViewHolder::new);
return m;
}
}
@@ -1,63 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import android.util.Pair;
import androidx.annotation.NonNull;
import de.dennisguse.opentracks.ui.customRecordingLayout.CustomLayoutFieldType;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
public class StatisticData {
private final DataField dataField;
private final String value;
private final String unit;
private final String description;
public StatisticData(@NonNull DataField dataField, @NonNull Pair<String, String> valueAndUnit, String description) {
this.dataField = dataField;
this.value = valueAndUnit.first;
this.unit = valueAndUnit.second;
this.description = description;
}
public CustomLayoutFieldType getType(Context context) {
return dataField.getType(context);
}
public String getTitle() {
return dataField.getTitle();
}
public boolean isWide() {
return dataField.isWide();
}
public boolean isPrimary() {
return dataField.isPrimary();
}
public boolean isVisible() {
return dataField.isVisible();
}
public String getValue() {
return value;
}
public boolean hasValue() {
return value != null;
}
public String getUnit() {
return unit;
}
public String getDescription() {
return description;
}
public boolean hasDescription() {
return description != null;
}
}
@@ -1,232 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import android.util.Pair;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Cadence;
import de.dennisguse.opentracks.data.models.DistanceFormatter;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.SpeedFormatter;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
import de.dennisguse.opentracks.util.StringUtils;
public class StatisticDataBuilder {
public static List<StatisticData> fromRecordingData(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull Layout layout, UnitSystem unitSystem) {
List<StatisticData> statisticDataList = layout.getFields().stream()
.filter(DataField::isVisible)
.map(field -> build(context, recordingData, field.getKey(), field.isPrimary(), unitSystem))
.filter(Objects::nonNull)
.collect(Collectors.toList());
statisticDataList.addAll(getSensorStatsDataIfNeeded(context, recordingData, statisticDataList, unitSystem));
return statisticDataList;
}
private static StatisticData build(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull String fieldKey, boolean isPrimary, UnitSystem unitSystem) {
final TrackPoint latestTrackPoint = recordingData.getLatestTrackPoint();
final SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
final TrackStatistics trackStatistics = recordingData.getTrackStatistics();
String title = null;
String description = null;
Pair<String, String> valueAndUnit = null;
boolean isWide = false;
final String sensorUnknown = context.getString(R.string.value_unknown);
SpeedFormatter speedFormatterPace = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(false).build(context);
SpeedFormatter speedFormatterSpeed = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(true).build(context);
if (fieldKey.equals(context.getString(R.string.stats_custom_layout_total_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getTotalTime()), null);
title = context.getString(R.string.stats_total_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_moving_time_key))) {
valueAndUnit = new Pair<>(StringUtils.formatElapsedTime(trackStatistics.getMovingTime()), null);
title = context.getString(R.string.stats_moving_time);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_distance_key))) {
valueAndUnit = DistanceFormatter.Builder()
.setUnit(unitSystem)
.build(context).getDistanceParts(trackStatistics.getTotalDistance());
title = context.getString(R.string.stats_distance);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_speed_key)) || fieldKey.equals(context.getString(R.string.stats_custom_layout_pace_key))) {
boolean reportSpeed = fieldKey.equals("speed");
title = reportSpeed ? context.getString(R.string.stats_speed) : context.getString(R.string.stats_pace);
SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder().setUnit(unitSystem).setReportSpeedOrPace(reportSpeed).build(context);
if (sensorDataSet != null && sensorDataSet.getSpeed() != null) {
valueAndUnit = localSpeedFormatter.getSpeedParts(sensorDataSet.getSpeed().first);
description = sensorDataSet.getSpeed().second;
} else {
Speed speed = latestTrackPoint != null && latestTrackPoint.hasSpeed() ? latestTrackPoint.getSpeed() : null;
valueAndUnit = localSpeedFormatter.getSpeedParts(speed);
description = context.getString(R.string.description_speed_source_gps);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_speed_key))) {
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageMovingSpeed());
title = context.getString(R.string.stats_average_moving_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_speed_key))) {
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getAverageSpeed());
title = context.getString(R.string.stats_average_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_max_speed_key))) {
valueAndUnit = speedFormatterSpeed.getSpeedParts(trackStatistics.getMaxSpeed());
title = context.getString(R.string.stats_max_speed);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_moving_pace_key))) {
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageMovingSpeed());
title = context.getString(R.string.stats_average_moving_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_average_pace_key))) {
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getAverageSpeed());
title = context.getString(R.string.stats_average_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_fastest_pace_key))) {
valueAndUnit = speedFormatterPace.getSpeedParts(trackStatistics.getMaxSpeed());
title = context.getString(R.string.stats_fastest_pace);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_altitude_key))) {
Float altitude = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? (float) latestTrackPoint.getAltitude().toM() : null;
title = context.getString(R.string.stats_altitude);
description = latestTrackPoint != null && latestTrackPoint.hasAltitude() ? context.getString(latestTrackPoint.getAltitude().getLabelId()) : null;
valueAndUnit = StringUtils.getAltitudeParts(context, altitude, unitSystem);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_gain_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeGain(), unitSystem);
title = context.getString(R.string.stats_gain);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_loss_key))) {
valueAndUnit = StringUtils.getAltitudeParts(context, trackStatistics.getTotalAltitudeLoss(), unitSystem);
title = context.getString(R.string.stats_loss);
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_coordinates_key))) {
title = context.getString(R.string.stats_coordinates);
isWide = true;
if (latestTrackPoint != null && latestTrackPoint.hasLocation()) {
valueAndUnit = new Pair<>(StringUtils.formatCoordinate(context, latestTrackPoint.getLatitude(), latestTrackPoint.getLongitude()), null);
} else {
valueAndUnit = new Pair<>(context.getString(R.string.value_unknown), null);
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_heart_rate_key))) {
title = context.getString(R.string.stats_sensors_heart_rate);
if (sensorDataSet != null && sensorDataSet.getHeartRate() != null) {
valueAndUnit = StringUtils.getHeartRateParts(context, sensorDataSet.getHeartRate().first);
description = sensorDataSet.getHeartRate().second;
} else {
valueAndUnit = StringUtils.getHeartRateParts(context, null);
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_cadence_key))) {
title = context.getString(R.string.stats_sensors_cadence);
Cadence cadence = null;
if (sensorDataSet != null) {
Pair<Cadence, String> cadenceData = sensorDataSet.getCadence();
if (cadenceData != null) {
cadence = cadenceData.first;
description = cadenceData.second;
}
}
valueAndUnit = StringUtils.getCadenceParts(context, cadence);
if (description == null) {
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_power_key))) {
title = context.getString(R.string.stats_sensors_power);
if (sensorDataSet != null && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
valueAndUnit = StringUtils.getPowerParts(context, sensorDataSet.getCyclingPower().getValue());
description = sensorDataSet.getCyclingPower().getSensorNameOrAddress();
} else {
valueAndUnit = StringUtils.getPowerParts(context, null);
description = sensorUnknown;
}
} else if (fieldKey.equals(context.getString(R.string.stats_custom_layout_clock_key))) {
title = context.getString(R.string.stats_clock);
valueAndUnit = new Pair<>(null, null);
}
if (valueAndUnit == null) {
return null;
}
return new StatisticData(new DataField(fieldKey, title, true, isPrimary, isWide), valueAndUnit, description);
}
/**
* Builds a list of StatisticData with sensors configured but not in statisticDataList.
*/
private static List<StatisticData> getSensorStatsDataIfNeeded(Context context, RecordingData recordingData, List<StatisticData> statisticDataList, UnitSystem unitSystem) {
List<StatisticData> sensorDataList = new ArrayList<>();
SensorDataSet sensorDataSet = recordingData.getSensorDataSet();
if (sensorDataSet == null) {
return sensorDataList;
}
if (statisticDataList.stream().noneMatch(i -> i.getTitle().equals(context.getString(R.string.stats_sensors_heart_rate))) && sensorDataSet.getHeartRate() != null) {
sensorDataList.add(build(context, recordingData, "heart_rate", true, unitSystem));
}
if (statisticDataList.stream().noneMatch(i -> i.getTitle().equals(context.getString(R.string.stats_sensors_cadence))) && sensorDataSet.getCadence() != null) {
sensorDataList.add(build(context, recordingData, "cadence", true, unitSystem));
}
if (statisticDataList.stream().noneMatch(i -> i.getTitle().equals(context.getString(R.string.stats_sensors_power))) && sensorDataSet.getCyclingPower() != null && sensorDataSet.getCyclingPower().hasValue()) {
sensorDataList.add(build(context, recordingData, "power", true, unitSystem));
}
return sensorDataList;
}
public static List<StatisticData> fromSensorStatistics(@NonNull Context context, @NonNull SensorStatistics sensorStatistics) {
List<StatisticData> sensorDataList = new ArrayList<>();
if (sensorStatistics.hasHeartRate()) {
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_max), true, true, false),
StringUtils.getHeartRateParts(context, sensorStatistics.getMaxHeartRate()),
null
)
);
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_average_heart_rate_key), context.getString(R.string.sensor_state_heart_rate_avg), true, true, false),
StringUtils.getHeartRateParts(context, sensorStatistics.getAvgHeartRate()),
null
)
);
}
if (sensorStatistics.hasCadence()) {
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.sensor_state_cadence_max), true, true, false),
StringUtils.getCadenceParts(context, sensorStatistics.getMaxCadence()),
null
)
);
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_average_cadence_key), context.getString(R.string.sensor_state_cadence_avg), true, true, false),
StringUtils.getCadenceParts(context, sensorStatistics.getAvgCadence()),
null
)
);
}
if (sensorStatistics.hasPower()) {
sensorDataList.add(
new StatisticData(
new DataField(context.getString(R.string.stats_custom_layout_power_key), context.getString(R.string.sensor_state_power_avg), true, true, false),
StringUtils.getPowerParts(context, sensorStatistics.getAvgPower()),
null
)
);
}
return sensorDataList;
}
}
@@ -0,0 +1,44 @@
package de.dennisguse.opentracks.viewmodels;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import androidx.viewbinding.ViewBinding;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
public abstract class StatisticViewHolder<T extends ViewBinding> {
private Context context;
private T binding;
public void initialize(Context context, LayoutInflater inflater) {
this.context = context;
this.binding = createViewBinding(inflater);
}
protected abstract T createViewBinding(LayoutInflater inflater);
public abstract void configureUI(DataField dataField);
@Deprecated
public abstract int getTitleId();
public abstract void onChanged(UnitSystem unitSystem, RecordingData data);
public View getView() {
return binding.getRoot();
}
T getBinding() {
return binding;
}
Context getContext() {
return context;
}
}
@@ -1,36 +0,0 @@
package de.dennisguse.opentracks.viewmodels;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import de.dennisguse.opentracks.services.RecordingData;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.ui.customRecordingLayout.Layout;
public class StatisticsDataModel extends AndroidViewModel {
private MutableLiveData<List<StatisticData>> statsData;
public StatisticsDataModel(@NonNull Application application) {
super(application);
}
public MutableLiveData<List<StatisticData>> getStatsData() {
if (statsData == null) {
statsData = new MutableLiveData<>();
}
return statsData;
}
public void update(RecordingData recordingData, Layout layout, UnitSystem unitSystem) {
new Thread(() -> {
List<StatisticData> statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, unitSystem);
statsData.postValue(statisticDataList);
}).start();
}
}
+6 -11
View File
@@ -1,16 +1,11 @@
<?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"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/stats_recycler_view"
<androidx.gridlayout.widget.GridLayout
android:id="@+id/stats_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</ScrollView>