diff --git a/build.gradle b/build.gradle index 80ed87f66..482fe6c5f 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java index 43880aeb4..d015bb730 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java @@ -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(); diff --git a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java index 45b32c4a4..2161df263 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java @@ -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)); diff --git a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutTest.java b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutTest.java index 69a8abe9b..5c77b4645 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutTest.java @@ -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(); diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java index d9a26e53c..f5be3ec5f 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java @@ -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); diff --git a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilderTest.java b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilderTest.java deleted file mode 100644 index 7a7becddd..000000000 --- a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilderTest.java +++ /dev/null @@ -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 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 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 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 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 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 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)))); - } -} diff --git a/src/main/java/de/dennisguse/opentracks/adapters/StatisticsAdapter.java b/src/main/java/de/dennisguse/opentracks/adapters/StatisticsAdapter.java deleted file mode 100644 index 96ac96d27..000000000 --- a/src/main/java/de/dennisguse/opentracks/adapters/StatisticsAdapter.java +++ /dev/null @@ -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 { - - private List 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 swapData(List 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); - } - } - } -} diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java index 1258a128e..350cf7732 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java @@ -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 sensorDataList = StatisticDataBuilder.fromSensorStatistics(getContext(), sensorStatistics); - if (sensorDataList.size() > 0) { - sensorsAdapter.swapData(sensorDataList); - } + //TODO } } diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java index 76dfb8bd7..bd57ada2f 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java @@ -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> viewHolders = new LinkedList<>(); + private Layout layout; private StatisticsRecordingBinding viewBinding; - private StatisticsAdapter statisticsAdapter; - private GridLayoutManager gridLayoutManager; - private StatisticsDataModel viewModel; - private LiveData> 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>> 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)); + } } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 31276a23b..0b1a57a0c 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -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 layouts) { setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR))); } diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/DataField.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/DataField.java index 7c78a3b35..0850de611 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/DataField.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/DataField.java @@ -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); + } } diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/Layout.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/Layout.java index e2d633e93..77879021a 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/Layout.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/Layout.java @@ -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); + } } diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutIO.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutIO.java index 7eb06a654..0f50e5eb5 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutIO.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutIO.java @@ -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))); diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java index 848294e79..aca86c938 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java @@ -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 { private Layout layout; private final Context context; private final SettingsCustomLayoutItemClickListener itemClickListener; + private final Map>> 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> MAP = new LinkedHashMap<>(); static { diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/ClockViewHolder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/ClockViewHolder.java new file mode 100644 index 000000000..a01876e43 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/ClockViewHolder.java @@ -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 { + + @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) { + } +} diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java new file mode 100644 index 000000000..1495054a3 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java @@ -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 { + + @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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); + } + } +} diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java b/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java new file mode 100644 index 000000000..ddd07682a --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/Mapping.java @@ -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>> create(Context context) { + HashMap>> 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; + } +} diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticData.java b/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticData.java deleted file mode 100644 index 23e375f47..000000000 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticData.java +++ /dev/null @@ -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 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; - } -} diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilder.java deleted file mode 100644 index ed66195a4..000000000 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilder.java +++ /dev/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 fromRecordingData(@NonNull Context context, @NonNull RecordingData recordingData, @NonNull Layout layout, UnitSystem unitSystem) { - List 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 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 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 getSensorStatsDataIfNeeded(Context context, RecordingData recordingData, List statisticDataList, UnitSystem unitSystem) { - List 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 fromSensorStatistics(@NonNull Context context, @NonNull SensorStatistics sensorStatistics) { - List 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; - } -} diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticViewHolder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticViewHolder.java new file mode 100644 index 000000000..9d0d69ada --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticViewHolder.java @@ -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 { + + 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; + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticsDataModel.java b/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticsDataModel.java deleted file mode 100644 index 58efab998..000000000 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticsDataModel.java +++ /dev/null @@ -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> statsData; - - public StatisticsDataModel(@NonNull Application application) { - super(application); - } - - public MutableLiveData> getStatsData() { - if (statsData == null) { - statsData = new MutableLiveData<>(); - } - return statsData; - } - - public void update(RecordingData recordingData, Layout layout, UnitSystem unitSystem) { - new Thread(() -> { - List statisticDataList = StatisticDataBuilder.fromRecordingData(getApplication(), recordingData, layout, unitSystem); - statsData.postValue(statisticDataList); - }).start(); - } -} diff --git a/src/main/res/layout/statistics_recording.xml b/src/main/res/layout/statistics_recording.xml index 7d1a01374..fa6214c08 100644 --- a/src/main/res/layout/statistics_recording.xml +++ b/src/main/res/layout/statistics_recording.xml @@ -1,16 +1,11 @@ - - - - \ No newline at end of file + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" /> + \ No newline at end of file