diff --git a/build.gradle b/build.gradle index bdf100893..9520ea88e 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/fastlane/metadata/android/en-US/changelogs/3195.txt b/fastlane/metadata/android/en-US/changelogs/3195.txt index ee953c821..072932143 100644 --- a/fastlane/metadata/android/en-US/changelogs/3195.txt +++ b/fastlane/metadata/android/en-US/changelogs/3195.txt @@ -7,5 +7,5 @@ Changes: Bugfix: - Speech announcements use MAY_DUCK (again) - ChartView shows markers (again) -- Fixed landscape layout of TrackListActivity +- Fixed landscape recordingLayout of TrackListActivity - Request legacy file API behavior (necessary for Android 10) diff --git a/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesOpenHelperTest.java b/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesOpenHelperTest.java index 8d3c9bee0..fa0b706e1 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesOpenHelperTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesOpenHelperTest.java @@ -17,7 +17,8 @@ import org.junit.runner.RunWith; import java.util.List; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO; @RunWith(AndroidJUnit4.class) public class PreferencesOpenHelperTest { @@ -38,10 +39,10 @@ public class PreferencesOpenHelperTest { PreferencesOpenHelper.newInstance(1).check(); // then there should be one layout with old custom layout that has the new CSV value. - List layouts = PreferencesUtils.getAllCustomLayouts(); - assertNotNull(layouts); - assertEquals(layouts.size(), 1); - assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv()); + List recordingLayouts = PreferencesUtils.getAllCustomLayouts(); + assertNotNull(recordingLayouts); + assertEquals(recordingLayouts.size(), 1); + assertEquals(recordingLayouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv()); } @Test @@ -63,10 +64,10 @@ public class PreferencesOpenHelperTest { + PreferencesUtils.getLayoutColumnsByDefault() + ";distance,1,1,0;speed,1,1,0;"; // then there should be one layout with old custom layout that has the new CSV value. - List layouts = PreferencesUtils.getAllCustomLayouts(); - assertNotNull(layouts); - assertEquals(layouts.size(), 1); - assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv); + List recordingLayouts = PreferencesUtils.getAllCustomLayouts(); + assertNotNull(recordingLayouts); + assertEquals(recordingLayouts.size(), 1); + assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv); } @Test @@ -87,10 +88,10 @@ public class PreferencesOpenHelperTest { String updatedOldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;"; // then there should be one layout with old custom layout that has the new CSV value. - List layouts = PreferencesUtils.getAllCustomLayouts(); - assertNotNull(layouts); - assertEquals(layouts.size(), 1); - assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv); + List recordingLayouts = PreferencesUtils.getAllCustomLayouts(); + assertNotNull(recordingLayouts); + assertEquals(recordingLayouts.size(), 1); + assertEquals(recordingLayouts.get(0).toCsv(), updatedOldCustomLayoutCsv); } @Test @@ -109,10 +110,10 @@ public class PreferencesOpenHelperTest { PreferencesOpenHelper.newInstance(1).check(); // then custom layout should be equals to default layout. - Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources); - List customLayout = PreferencesUtils.getAllCustomLayouts(); + RecordingLayout defaultRecordingLayout = RecordingLayoutIO.fromCsv(PreferencesUtils.buildDefaultLayout(), resources); + List customRecordingLayout = PreferencesUtils.getAllCustomLayouts(); - assertEquals(customLayout.size(), 1); - assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv()); + assertEquals(customRecordingLayout.size(), 1); + assertEquals(defaultRecordingLayout.toCsv(), customRecordingLayout.get(0).toCsv()); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java index 09f30066e..adb806d00 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/settings/PreferencesUtilsTest.java @@ -22,7 +22,8 @@ import java.util.List; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.ui.customRecordingLayout.DataField; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO; @RunWith(AndroidJUnit4.class) public class PreferencesUtilsTest { @@ -85,13 +86,13 @@ public class PreferencesUtilsTest { editor.commit(); // when - List layouts = PreferencesUtils.getAllCustomLayouts(); + List recordingLayouts = PreferencesUtils.getAllCustomLayouts(); // then - assertEquals(layouts.size(), 1); - assertTrue(layouts.get(0).getFields().size() > 0); - assertEquals(layouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout)); - assertTrue(layouts.get(0).getFields().stream().anyMatch(DataField::isVisible)); + assertEquals(recordingLayouts.size(), 1); + assertTrue(recordingLayouts.get(0).getFields().size() > 0); + assertEquals(recordingLayouts.get(0).getName(), context.getString(R.string.stats_custom_layout_default_layout)); + assertTrue(recordingLayouts.get(0).getFields().stream().anyMatch(DataField::isVisible)); } @Test @@ -103,12 +104,12 @@ public class PreferencesUtilsTest { editor.commit(); // when - Layout layout = PreferencesUtils.getCustomLayout(); + RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout(); // then - assertTrue(layout.getFields().size() > 0); - assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout)); - assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible)); + assertTrue(recordingLayout.getFields().size() > 0); + assertEquals(recordingLayout.getName(), context.getString(R.string.stats_custom_layout_default_layout)); + assertTrue(recordingLayout.getFields().stream().anyMatch(DataField::isVisible)); } @Test @@ -126,28 +127,28 @@ public class PreferencesUtilsTest { editor.apply(); // when - Layout layout = PreferencesUtils.getCustomLayout(); + RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout(); // then - assertEquals(layout.getFields().size(), 4); - assertEquals(layout.getName(), "run"); - assertEquals(layout.getColumnsPerRow(), 2); + assertEquals(recordingLayout.getFields().size(), 4); + assertEquals(recordingLayout.getName(), "run"); + assertEquals(recordingLayout.getColumnsPerRow(), 2); - assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key)); - assertTrue(layout.getFields().get(0).isVisible()); - assertTrue(layout.getFields().get(0).isPrimary()); + assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key)); + assertTrue(recordingLayout.getFields().get(0).isVisible()); + assertTrue(recordingLayout.getFields().get(0).isPrimary()); - assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key)); - assertTrue(layout.getFields().get(1).isVisible()); - assertFalse(layout.getFields().get(1).isPrimary()); + assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key)); + assertTrue(recordingLayout.getFields().get(1).isVisible()); + assertFalse(recordingLayout.getFields().get(1).isPrimary()); - assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key)); - assertFalse(layout.getFields().get(2).isVisible()); - assertTrue(layout.getFields().get(2).isPrimary()); + assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_average_moving_speed_key)); + assertFalse(recordingLayout.getFields().get(2).isVisible()); + assertTrue(recordingLayout.getFields().get(2).isPrimary()); - assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key)); - assertFalse(layout.getFields().get(3).isVisible()); - assertFalse(layout.getFields().get(3).isPrimary()); + assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key)); + assertFalse(recordingLayout.getFields().get(3).isVisible()); + assertFalse(recordingLayout.getFields().get(3).isPrimary()); } @Test @@ -165,44 +166,44 @@ public class PreferencesUtilsTest { editor.apply(); // when - Layout layout = PreferencesUtils.getCustomLayout(); + RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout(); // then - assertEquals(layout.getFields().size(), 4); - assertEquals(layout.getName(), "walking"); - assertEquals(layout.getColumnsPerRow(), 2); + assertEquals(recordingLayout.getFields().size(), 4); + assertEquals(recordingLayout.getName(), "walking"); + assertEquals(recordingLayout.getColumnsPerRow(), 2); - assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key)); - assertTrue(layout.getFields().get(0).isVisible()); - assertTrue(layout.getFields().get(0).isPrimary()); + assertEquals(recordingLayout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key)); + assertTrue(recordingLayout.getFields().get(0).isVisible()); + assertTrue(recordingLayout.getFields().get(0).isPrimary()); - assertEquals(layout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key)); - assertTrue(layout.getFields().get(1).isVisible()); - assertFalse(layout.getFields().get(1).isPrimary()); + assertEquals(recordingLayout.getFields().get(1).getKey(), context.getString(R.string.stats_custom_layout_distance_key)); + assertTrue(recordingLayout.getFields().get(1).isVisible()); + assertFalse(recordingLayout.getFields().get(1).isPrimary()); - assertEquals(layout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key)); - assertFalse(layout.getFields().get(2).isVisible()); - assertTrue(layout.getFields().get(2).isPrimary()); - assertTrue(layout.getFields().get(2).isWide()); + assertEquals(recordingLayout.getFields().get(2).getKey(), context.getString(R.string.stats_custom_layout_coordinates_key)); + assertFalse(recordingLayout.getFields().get(2).isVisible()); + assertTrue(recordingLayout.getFields().get(2).isPrimary()); + assertTrue(recordingLayout.getFields().get(2).isWide()); - assertEquals(layout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key)); - assertFalse(layout.getFields().get(3).isVisible()); - assertFalse(layout.getFields().get(3).isPrimary()); + assertEquals(recordingLayout.getFields().get(3).getKey(), context.getString(R.string.stats_custom_layout_speed_key)); + assertFalse(recordingLayout.getFields().get(3).isVisible()); + assertFalse(recordingLayout.getFields().get(3).isPrimary()); } @Test public void testSetCustomLayout() { // 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)); + RecordingLayout recordingLayoutSrc = new RecordingLayout("road cycling"); + recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_moving_time_key), true, true, false)); + recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_distance_key), true, false, false)); + recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), false, true, false)); + recordingLayoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), false, false, false)); // when SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv()); + editor.putString(context.getString(R.string.stats_custom_layouts_key), recordingLayoutSrc.toCsv()); editor.commit(); // then @@ -215,13 +216,13 @@ public class PreferencesUtilsTest { + context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_speed_key) + ",0,0,0;"); - Layout layoutDst = PreferencesUtils.getCustomLayout(); - assertEquals(layoutSrc.getName(), layoutDst.getName()); - assertEquals(layoutSrc.getFields().size(), layoutDst.getFields().size()); - for (int i = 0; i < layoutSrc.getFields().size(); i++) { - assertEquals(layoutSrc.getFields().get(i).getKey(), layoutDst.getFields().get(i).getKey()); - assertEquals(layoutSrc.getFields().get(i).isVisible(), layoutDst.getFields().get(i).isVisible()); - assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary()); + RecordingLayout recordingLayoutDst = PreferencesUtils.getCustomLayout(); + assertEquals(recordingLayoutSrc.getName(), recordingLayoutDst.getName()); + assertEquals(recordingLayoutSrc.getFields().size(), recordingLayoutDst.getFields().size()); + for (int i = 0; i < recordingLayoutSrc.getFields().size(); i++) { + assertEquals(recordingLayoutSrc.getFields().get(i).getKey(), recordingLayoutDst.getFields().get(i).getKey()); + assertEquals(recordingLayoutSrc.getFields().get(i).isVisible(), recordingLayoutDst.getFields().get(i).isVisible()); + assertEquals(recordingLayoutSrc.getFields().get(i).isPrimary(), recordingLayoutDst.getFields().get(i).isPrimary()); } } @@ -247,7 +248,7 @@ public class PreferencesUtilsTest { editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile); editor.apply(); - List layoutsBefore = PreferencesUtils.getAllCustomLayouts(); + List layoutsBefore = PreferencesUtils.getAllCustomLayouts(); // when cyling profile is updated String cyclingProfileUpdated = "cycling;2;" @@ -256,14 +257,14 @@ public class PreferencesUtilsTest { + context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;" + context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;"; - List layoutsToBeUpdated = new ArrayList<>(); - layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources)); - layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources)); + List layoutsToBeUpdated = new ArrayList<>(); + layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources)); + layoutsToBeUpdated.add(RecordingLayoutIO.fromCsv(runningProfile, resources)); PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated); // then only updated profile is modified in the custom layouts - List layoutsAfter = PreferencesUtils.getAllCustomLayouts(); + List layoutsAfter = PreferencesUtils.getAllCustomLayouts(); assertEquals(layoutsBefore.size(), 2); assertEquals(layoutsAfter.size(), 2); @@ -294,7 +295,7 @@ public class PreferencesUtilsTest { editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile); editor.apply(); - List layoutsBefore = PreferencesUtils.getAllCustomLayouts(); + List layoutsBefore = PreferencesUtils.getAllCustomLayouts(); // when cyling profile is updated String cyclingProfileUpdated = "cycling;2;" @@ -302,11 +303,11 @@ public class PreferencesUtilsTest { + context.getString(R.string.stats_custom_layout_distance_key) + ",0,0;" + context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,0;" + context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;"; - Layout layoutToBeUpdated = Layout.fromCsv(cyclingProfileUpdated, resources); - PreferencesUtils.updateCustomLayout(layoutToBeUpdated); + RecordingLayout recordingLayoutToBeUpdated = RecordingLayoutIO.fromCsv(cyclingProfileUpdated, resources); + PreferencesUtils.updateCustomLayout(recordingLayoutToBeUpdated); // then only updated profile is modified in the custom layouts - List layoutsAfter = PreferencesUtils.getAllCustomLayouts(); + List layoutsAfter = PreferencesUtils.getAllCustomLayouts(); assertEquals(layoutsBefore.size(), 2); assertEquals(layoutsAfter.size(), 2); @@ -337,9 +338,9 @@ public class PreferencesUtilsTest { editor.apply(); // when it gets the custom layout - Layout layout = PreferencesUtils.getCustomLayout(); + RecordingLayout recordingLayout = PreferencesUtils.getCustomLayout(); // then the first one was returned - assertEquals(layout.getName(), "cycling"); + assertEquals(recordingLayout.getName(), "cycling"); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/CsvLayoutUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/CsvRecordingLayoutUtilsTest.java similarity index 98% rename from src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/CsvLayoutUtilsTest.java rename to src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/CsvRecordingLayoutUtilsTest.java index 1ee67fbdc..e4b691046 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/CsvLayoutUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/CsvRecordingLayoutUtilsTest.java @@ -10,7 +10,7 @@ import org.junit.runner.RunWith; import java.util.List; @RunWith(AndroidJUnit4.class) -public class CsvLayoutUtilsTest extends TestCase { +public class CsvRecordingLayoutUtilsTest extends TestCase { @Test public void testGetCsvLineParts() { diff --git a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomRecordingLayoutTest.java similarity index 90% rename from src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java rename to src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomRecordingLayoutTest.java index 45b32c4a4..1a0317334 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomLayoutTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/EspressoCustomRecordingLayoutTest.java @@ -30,7 +30,7 @@ import de.dennisguse.opentracks.settings.PreferencesUtils; @LargeTest @RunWith(AndroidJUnit4.class) -public class EspressoCustomLayoutTest { +public class EspressoCustomRecordingLayoutTest { @Rule public ActivityScenarioRule mActivityTestRule = new ActivityScenarioRule<>(TrackListActivity.class); @@ -45,9 +45,9 @@ public class EspressoCustomLayoutTest { trackControllerRecordButton.perform(click()); // Get custom layout preferences and check all data fields are showed. - Layout layout = PreferencesUtils.getCustomLayout(); + RecordingLayout recordingLayout = 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) recordingLayout.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/RecordingLayoutTest.java similarity index 58% rename from src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutTest.java rename to src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutTest.java index fcb0d5d63..3ab75fef0 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/LayoutTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutTest.java @@ -17,7 +17,7 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.settings.PreferencesUtils; @RunWith(AndroidJUnit4.class) -public class LayoutTest extends TestCase { +public class RecordingLayoutTest extends TestCase { private final Context context = ApplicationProvider.getApplicationContext(); private final Resources resources = ApplicationProvider.getApplicationContext().getResources(); @@ -28,21 +28,17 @@ public class LayoutTest extends TestCase { String csv = "running;2;" + context.getString(R.string.stats_custom_layout_speed_key) + ",1,1,0;" + context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;" + context.getString(R.string.stats_custom_layout_altitude_key) + ",0,1,0;" + context.getString(R.string.stats_custom_layout_gain_key) + ",0,0,0;"; // when create a layout from CSV line - Layout layout = Layout.fromCsv(csv, resources); - List dataFieldList = layout.getFields(); + RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv, resources); + List dataFieldList = recordingLayout.getFields(); // then layout and data fields are built correctly - assertEquals(layout.getName(), "running"); - assertEquals(layout.getColumnsPerRow(), 2); + assertEquals(recordingLayout.getName(), "running"); + assertEquals(recordingLayout.getColumnsPerRow(), 2); assertEquals(dataFieldList.size(), 4); assertEquals(dataFieldList.get(0).getKey(), context.getString(R.string.stats_custom_layout_speed_key)); 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()); @@ -64,13 +60,13 @@ public class LayoutTest extends TestCase { String csv1 = "Layout Name;speed,1,1;distance,0,0;"; // when create a layout from CSV line - Layout layout = Layout.fromCsv(csv1, resources); - List dataFieldList = layout.getFields(); + RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources); + List dataFieldList = recordingLayout.getFields(); // then layout and data fields are built correctly - assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName()); - assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault()); - assertEquals(layout.getFields().size(), 0); + assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName()); + assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault()); + assertEquals(recordingLayout.getFields().size(), 0); } @Test @@ -79,13 +75,13 @@ public class LayoutTest extends TestCase { String csv1 = "Layout Name;2;"; // when create a layout from CSV line - Layout layout = Layout.fromCsv(csv1, resources); - List dataFieldList = layout.getFields(); + RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources); + List dataFieldList = recordingLayout.getFields(); // then layout and data fields are built correctly - assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName()); - assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault()); - assertEquals(layout.getFields().size(), 0); + assertEquals(recordingLayout.getName(), PreferencesUtils.getDefaultLayoutName()); + assertEquals(recordingLayout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault()); + assertEquals(recordingLayout.getFields().size(), 0); } @Test @@ -94,13 +90,13 @@ public class LayoutTest extends TestCase { String csv1 = "Layout Name;2;speed,distance,total time;"; // when create a layout from CSV line - Layout layout = Layout.fromCsv(csv1, resources); - List dataFieldList = layout.getFields(); + RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources); + List dataFieldList = recordingLayout.getFields(); // then layout and data fields are built correctly - assertEquals(layout.getName(), "Layout Name"); - assertEquals(layout.getColumnsPerRow(), 2); - assertEquals(layout.getFields().size(), 0); + assertEquals(recordingLayout.getName(), "Layout Name"); + assertEquals(recordingLayout.getColumnsPerRow(), 2); + assertEquals(recordingLayout.getFields().size(), 0); } @Test @@ -109,27 +105,27 @@ public class LayoutTest extends TestCase { String csv1 = "Layout Name;2;speed,1,0;distance;"; // when create a layout from CSV line - Layout layout = Layout.fromCsv(csv1, resources); - List dataFieldList = layout.getFields(); + RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(csv1, resources); + List dataFieldList = recordingLayout.getFields(); // then layout and data fields are built correctly - assertEquals(layout.getName(), "Layout Name"); - assertEquals(layout.getColumnsPerRow(), 2); - assertEquals(layout.getFields().size(), 1); + assertEquals(recordingLayout.getName(), "Layout Name"); + assertEquals(recordingLayout.getColumnsPerRow(), 2); + assertEquals(recordingLayout.getFields().size(), 1); } @Test public void testToCsv() { // given a layout's object - Layout layout = new Layout("Test Layout", 2); - layout.addField("key1", "Title 1", false, false, false); - layout.addField("key2", "Title 2", false, true, false); - layout.addField("key3", "Title 3", true, false, false); - layout.addField("key4", "Title 4", true, true, false); - layout.addField("key5", "Title 5", true, true, true); + RecordingLayout recordingLayout = new RecordingLayout("Test Layout", 2); + recordingLayout.addField(new DataField("key1", false, false, false)); + recordingLayout.addField(new DataField("key2", false, true, false)); + recordingLayout.addField(new DataField("key3", true, false, false)); + recordingLayout.addField(new DataField("key4", true, true, false)); + recordingLayout.addField(new DataField("key5", true, true, true)); // when converts it to CSV - String csv = layout.toCsv(); + String csv = recordingLayout.toCsv(); // then csv is well built assertEquals(csv, "Test Layout;2;key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;"); @@ -138,15 +134,15 @@ public class LayoutTest extends TestCase { @Test public void testToCsv_columnsByDefault() { // given a layout's object - Layout layout = new Layout("Test Layout"); - layout.addField("key1", "Title 1", false, false, false); - layout.addField("key2", "Title 2", false, true, false); - layout.addField("key3", "Title 3", true, false, false); - layout.addField("key4", "Title 4", true, true, false); - layout.addField("key5", "Title 5", true, true, true); + RecordingLayout recordingLayout = new RecordingLayout("Test Layout"); + recordingLayout.addField(new DataField("key1", false, false, false)); + recordingLayout.addField(new DataField("key2", false, true, false)); + recordingLayout.addField(new DataField("key3", true, false, false)); + recordingLayout.addField(new DataField("key4", true, true, false)); + recordingLayout.addField(new DataField("key5", true, true, true)); // when converts it to CSV - String csv = layout.toCsv(); + String csv = recordingLayout.toCsv(); // then csv is well built assertEquals(csv, "Test Layout;" + PreferencesUtils.getLayoutColumnsByDefault() + ";key1,0,0,0;key2,0,1,0;key3,1,0,0;key4,1,1,0;key5,1,1,1;"); diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java index d9a26e53c..b1ad77d4b 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/StatisticsUtilsTest.java @@ -12,7 +12,7 @@ import org.junit.runner.RunWith; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.ui.customRecordingLayout.DataField; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; @RunWith(AndroidJUnit4.class) public class StatisticsUtilsTest extends TestCase { @@ -44,16 +44,16 @@ public class StatisticsUtilsTest extends TestCase { @Test 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)); + RecordingLayout recordingLayout = new RecordingLayout("profile"); + recordingLayout.addField(new DataField("key1", true, true, false)); + recordingLayout.addField(new DataField("key2", false, true, false)); + recordingLayout.addField(new DataField("key3", true, true, false)); + recordingLayout.addField(new DataField("key4", false, true, false)); + recordingLayout.addField(new DataField("key5", true, true, false)); // when - Layout resultTrue = StatisticsUtils.filterVisible(layout, true); - Layout resultFalse = StatisticsUtils.filterVisible(layout, false); + RecordingLayout resultTrue = StatisticsUtils.filterVisible(recordingLayout, true); + RecordingLayout resultFalse = StatisticsUtils.filterVisible(recordingLayout, false); // then assertEquals(resultTrue.getFields().size(), 3); 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 8f504ffe7..000000000 --- a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/StatisticDataBuilderTest.java +++ /dev/null @@ -1,199 +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.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(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, true, false); - layout.addField(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.getField().getTitle().equals(context.getString(R.string.stats_total_time)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().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(context.getString(R.string.stats_custom_layout_total_time_key), context.getString(R.string.stats_total_time), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_moving_time_key), context.getString(R.string.stats_moving_time), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_distance_key), context.getString(R.string.stats_distance), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_average_moving_speed_key), context.getString(R.string.stats_average_moving_speed), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_average_speed_key), context.getString(R.string.stats_average_speed), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_max_speed_key), context.getString(R.string.stats_max_speed), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_pace_key), context.getString(R.string.stats_pace), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_average_moving_pace_key), context.getString(R.string.stats_average_moving_pace), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_average_pace_key), context.getString(R.string.stats_average_pace), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_fastest_pace_key), context.getString(R.string.stats_fastest_pace), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_altitude_key), context.getString(R.string.stats_altitude), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_gain_key), context.getString(R.string.stats_gain), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_loss_key), context.getString(R.string.stats_loss), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_coordinates_key), context.getString(R.string.stats_coordinates), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_heart_rate_key), context.getString(R.string.stats_sensors_heart_rate), true, true, false); - layout.addField(context.getString(R.string.stats_custom_layout_cadence_key), context.getString(R.string.stats_sensors_cadence), true, true, false); - layout.addField(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.getField().getTitle().equals(context.getString(R.string.stats_total_time)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_moving_time)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_distance)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_speed)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_speed)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_speed)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_max_speed)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_pace)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_moving_pace)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_average_pace)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_fastest_pace)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_altitude)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_gain)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_loss)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_coordinates)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_heart_rate)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.stats_sensors_cadence)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().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.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().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.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().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.getField().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.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_max)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_heart_rate_avg)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_max)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().getTitle().equals(context.getString(R.string.sensor_state_cadence_avg)))); - assertTrue(statisticDataList.stream().anyMatch(i -> i.getField().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 fcef8fe5c..000000000 --- a/src/main/java/de/dennisguse/opentracks/adapters/StatisticsAdapter.java +++ /dev/null @@ -1,176 +0,0 @@ -package de.dennisguse.opentracks.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextClock; -import android.widget.TextView; - -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.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) { - View view; - if (viewType == CustomLayoutFieldType.CLOCK.value()) { - view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_clock_item, parent, false); - return new StatisticsAdapter.ViewClockHolder(view); - } else { - view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_generic_item, parent, false); - return new StatisticsAdapter.ViewHolder(view); - } - } - - @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 { - StatisticsAdapter.ViewHolder viewHolder = (StatisticsAdapter.ViewHolder) holder; - viewHolder.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).getField().getType(context) == CustomLayoutFieldType.CLOCK) { - return CustomLayoutFieldType.CLOCK.value(); - } else { - return CustomLayoutFieldType.GENERIC.value(); - } - } - - public boolean isItemWide(int position) { - return statisticDataList.get(position).getField().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) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.stats_recorded_item, parent, false); - return new StatisticsAdapter.ViewRecordedHolder(view); - } - } - - private class ViewHolder extends RecyclerView.ViewHolder { - final TextView value; - final TextView unit; - final TextView descMain; - final TextView descSecondary; - - public ViewHolder(@NonNull View itemView) { - super(itemView); - value = itemView.findViewById(R.id.stats_value); - unit = itemView.findViewById(R.id.stats_unit); - descMain = itemView.findViewById(R.id.stats_description_main); - descSecondary = itemView.findViewById(R.id.stats_description_secondary); - } - - public void setData(StatisticData statisticData) { - if (statisticData == null) { - return; - } - this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown)); - this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue); - this.unit.setText(statisticData.getUnit()); - this.descMain.setText(statisticData.getField().getTitle()); - this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader); - if (statisticData.hasDescription()) { - this.descSecondary.setVisibility(View.VISIBLE); - this.descSecondary.setText(statisticData.getDescription()); - } else { - this.descSecondary.setVisibility(View.GONE); - } - } - } - - private class ViewClockHolder extends RecyclerView.ViewHolder { - final TextClock value; - final TextView descMain; - - public ViewClockHolder(@NonNull View itemView) { - super(itemView); - value = itemView.findViewById(R.id.stats_clock); - descMain = itemView.findViewById(R.id.stats_description_main); - } - - public void setData(StatisticData statisticData) { - if (statisticData == null) { - return; - } - this.value.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue); - this.descMain.setTextAppearance(context, statisticData.getField().isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader); - } - } - - private class ViewRecordedHolder extends ViewHolder { - - public ViewRecordedHolder(@NonNull View itemView) { - super(itemView); - } - - public void setData(StatisticData statisticData) { - if (statisticData == null) { - return; - } - this.value.setText(statisticData.hasValue() ? statisticData.getValue() : context.getString(R.string.value_unknown)); - this.unit.setText(statisticData.getUnit()); - this.descMain.setText(statisticData.getField().getTitle()); - if (statisticData.hasDescription()) { - this.descSecondary.setVisibility(View.VISIBLE); - this.descSecondary.setText(statisticData.getDescription()); - } else { - this.descSecondary.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..e6dbffd45 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.Layout; -import de.dennisguse.opentracks.viewmodels.StatisticData; -import de.dennisguse.opentracks.viewmodels.StatisticsDataModel; +import de.dennisguse.opentracks.ui.customRecordingLayout.DataField; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; +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 Layout layout; + private final List> viewHolders = new LinkedList<>(); + + private RecordingLayout recordingLayout; 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 RecordingLayout newRecordingLayout) { + if (newRecordingLayout.equals(recordingLayout)) { + return; + } + recordingLayout = newRecordingLayout; + + viewBinding.statsLayout.setColumnCount(recordingLayout.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 < recordingLayout.getFields().size(); i++) { + DataField dataField = recordingLayout.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, recordingLayout.getColumnsPerRow(), 1); + param.rowSpec = GridLayout.spec(rowIndex, 1, 1); + columnIndex = 0; + rowIndex++; + } else { + if (columnIndex >= recordingLayout.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 c8c5fd14c..2b8a6e426 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -46,7 +46,8 @@ import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFilenameGenerator; import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO; import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.TrackIconUtils; @@ -753,35 +754,35 @@ public class PreferencesUtils { /** * @return custom layout selected or the first one if any has been selected or the one selected is not exists anymore. */ - public static Layout getCustomLayout() { + public static RecordingLayout getCustomLayout() { String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout()); String[] csvLines = csvCustomLayouts.split(CsvLayoutUtils.LINE_SEPARATOR); String layoutSelected = getString(R.string.stats_custom_layout_selected_layout_key, null); if (layoutSelected == null) { - return Layout.fromCsv(csvLines[0], resources); + return RecordingLayoutIO.fromCsv(csvLines[0], resources); } for (String line : csvLines) { - Layout layout = Layout.fromCsv(line, resources); - if (layout.sameName(layoutSelected)) { - return layout; + RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(line, resources); + if (recordingLayout.sameName(layoutSelected)) { + return recordingLayout; } } - return Layout.fromCsv(csvLines[0], resources); + return RecordingLayoutIO.fromCsv(csvLines[0], resources); } - 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))); + public static void updateCustomLayouts(@NonNull List recordingLayouts) { + setString(R.string.stats_custom_layouts_key, RecordingLayoutIO.toCSV(recordingLayouts)); } - public static void updateCustomLayout(@NonNull Layout layout) { - List preferenceLayouts = PreferencesUtils.getAllCustomLayouts(); - Optional layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst(); + public static void updateCustomLayout(@NonNull RecordingLayout recordingLayout) { + List preferenceRecordingLayouts = PreferencesUtils.getAllCustomLayouts(); + Optional layoutToBeUpdated = preferenceRecordingLayouts.stream().filter(l -> l.sameName(recordingLayout)).findFirst(); if (layoutToBeUpdated.isPresent()) { - layoutToBeUpdated.get().replaceAllFields(layout.getFields()); - layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow()); - PreferencesUtils.updateCustomLayouts(preferenceLayouts); + layoutToBeUpdated.get().replaceAllFields(recordingLayout.getFields()); + layoutToBeUpdated.get().setColumnsPerRow(recordingLayout.getColumnsPerRow()); + PreferencesUtils.updateCustomLayouts(preferenceRecordingLayouts); } } @@ -795,19 +796,19 @@ public class PreferencesUtils { setString(R.string.stats_custom_layout_selected_layout_key, layoutName); } - public static List getAllCustomLayouts() { - List layouts = new ArrayList<>(); + public static List getAllCustomLayouts() { + List recordingLayouts = new ArrayList<>(); String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout()); String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR); for (String line : csvLines) { - layouts.add(Layout.fromCsv(line, resources)); + recordingLayouts.add(RecordingLayoutIO.fromCsv(line, resources)); } - return layouts; + return recordingLayouts; } public static List getAllCustomLayoutNames() { - return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList()); + return getAllCustomLayouts().stream().map(RecordingLayout::getName).collect(Collectors.toList()); } public static void resetCustomLayoutPreferences() { diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java index ffd400181..b16d07485 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutEditActivity.java @@ -17,7 +17,7 @@ import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding; import de.dennisguse.opentracks.ui.customRecordingLayout.DataField; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutEditAdapter; import de.dennisguse.opentracks.ui.util.ArrayAdapterFilterDisabled; import de.dennisguse.opentracks.util.StatisticsUtils; @@ -30,8 +30,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement private SettingsCustomLayoutEditAdapter adapterFieldsVisible; private SettingsCustomLayoutEditAdapter adapterFieldsHidden; private String profile; - private Layout layoutFieldsVisible; - private Layout layoutFieldsHidden; + private RecordingLayout recordingLayoutFieldsVisible; + private RecordingLayout recordingLayoutFieldsHidden; private int numColumns; @Override @@ -39,12 +39,12 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement super.onCreate(savedInstanceState); // Recycler view with visible stats. - Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT); - profile = layout.getName(); - layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true); - adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible); + RecordingLayout recordingLayout = getIntent().getParcelableExtra(EXTRA_LAYOUT); + profile = recordingLayout.getName(); + recordingLayoutFieldsVisible = StatisticsUtils.filterVisible(recordingLayout, true); + adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsVisible); - numColumns = layout.getColumnsPerRow(); + numColumns = recordingLayout.getColumnsPerRow(); RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible; gridLayoutManager = new GridLayoutManager(this, numColumns); gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @@ -65,7 +65,7 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { int fromPosition = viewHolder.getAdapterPosition(); int toPosition = target.getAdapterPosition(); - layoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition); + recordingLayoutFieldsVisible = adapterFieldsVisible.move(fromPosition, toPosition); return true; } @@ -90,8 +90,8 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement viewBinding.rowsOptions.setText(rowsOptionAdapter.getItem(numColumns - 1).toString(), false); // Recycler view with not visible stats. - layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false); - adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden); + recordingLayoutFieldsHidden = StatisticsUtils.filterVisible(recordingLayout, false); + adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, recordingLayoutFieldsHidden); RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible; recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this)); recyclerViewNotVisible.setAdapter(adapterFieldsHidden); @@ -103,18 +103,18 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement @Override protected void onPause() { super.onPause(); - if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) { - Layout layout = new Layout(profile, numColumns); - layout.addFields(layoutFieldsVisible.getFields()); - layout.addFields(layoutFieldsHidden.getFields()); - PreferencesUtils.updateCustomLayout(layout); + if (!recordingLayoutFieldsVisible.getFields().isEmpty() || !recordingLayoutFieldsHidden.getFields().isEmpty()) { + RecordingLayout recordingLayout = new RecordingLayout(profile, numColumns); + recordingLayout.addFields(recordingLayoutFieldsVisible.getFields()); + recordingLayout.addFields(recordingLayoutFieldsHidden.getFields()); + PreferencesUtils.updateCustomLayout(recordingLayout); } } @Override protected void onDestroy() { super.onDestroy(); - layoutFieldsVisible = null; + recordingLayoutFieldsVisible = null; } @Override @@ -132,24 +132,24 @@ public class SettingsCustomLayoutEditActivity extends AbstractActivity implement if (which == 0) { field.togglePrimary(); } else { - layoutFieldsVisible.removeField(field); + recordingLayoutFieldsVisible.removeField(field); field.toggleVisibility(); - layoutFieldsHidden.addField(field); + recordingLayoutFieldsHidden.addField(field); } - adapterFieldsVisible.swapValues(layoutFieldsVisible); - adapterFieldsHidden.swapValues(layoutFieldsHidden); + adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible); + adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden); }) .create() .show(); } else { - layoutFieldsHidden.removeField(field); + recordingLayoutFieldsHidden.removeField(field); field.toggleVisibility(); - layoutFieldsVisible.addField(field); + recordingLayoutFieldsVisible.addField(field); viewBinding.scrollView.fullScroll(ScrollView.FOCUS_UP); - adapterFieldsVisible.swapValues(layoutFieldsVisible); - adapterFieldsHidden.swapValues(layoutFieldsHidden); + adapterFieldsVisible.swapValues(recordingLayoutFieldsVisible); + adapterFieldsHidden.swapValues(recordingLayoutFieldsHidden); } } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java index 9acf0ef11..83131201e 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsCustomLayoutListActivity.java @@ -23,7 +23,7 @@ import com.google.android.material.textfield.TextInputLayout; import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; import de.dennisguse.opentracks.ui.customRecordingLayout.SettingsCustomLayoutListAdapter; import de.dennisguse.opentracks.ui.util.RecyclerViewSwipeDeleteCallback; @@ -96,7 +96,7 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { final int position = viewHolder.getAdapterPosition(); - final Layout item = adapter.getLayouts().get(position); + final RecordingLayout item = adapter.getLayouts().get(position); adapter.removeLayout(position); @@ -150,9 +150,9 @@ public class SettingsCustomLayoutListActivity extends AbstractActivity implement } @Override - public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) { + public void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout) { Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class); - intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout); + intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, recordingLayout); startActivity(intent); } 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 512721a38..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,25 +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 { - public static final String YES_VALUE = "1"; - public static final String NOT_VALUE = "0"; 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; @@ -27,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; @@ -49,10 +42,6 @@ public class DataField implements Parcelable { return key; } - public String getTitle() { - return title; - } - public boolean isVisible() { return isVisible; } @@ -73,65 +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 String toCsv() { - String visible = this.isVisible ? YES_VALUE : NOT_VALUE; - String primary = this.isPrimary ? YES_VALUE : NOT_VALUE; - String wide = this.isWide ? YES_VALUE : NOT_VALUE; - return key + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide; - } - - 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; @@ -140,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/RecordingLayout.java similarity index 54% rename from src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/Layout.java rename to src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayout.java index ecd3ddeb9..9bda6c556 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/Layout.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayout.java @@ -1,78 +1,50 @@ package de.dennisguse.opentracks.ui.customRecordingLayout; -import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; - -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.settings.PreferencesUtils; -public class Layout implements Parcelable { - private static final String TAG = Layout.class.getSimpleName(); +public class RecordingLayout implements Parcelable { // User-generated layout's name. private final String name; private int columnsPerRow; private final List dataFields = new ArrayList<>(); - public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) { - List csvParts = CsvLayoutUtils.getCsvLineParts(csvLine); - if (csvParts == null) { - Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine); - return new Layout(PreferencesUtils.getDefaultLayoutName()); - } - - Layout layout = new Layout(csvParts.get(0), Integer.parseInt(csvParts.get(1))); - for (int i = 2; i < csvParts.size(); i++) { - String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i)); - if (fieldParts == null) { - Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine); - return layout; - } - layout.addField(fieldParts[0], DataField.getTitleByKey(resources, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key))); - } - return layout; - } - - public Layout(String name) { + public RecordingLayout(String name) { this.name = name; this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault(); } - public Layout(String name, int columnsPerRow) { + public RecordingLayout(String name, int columnsPerRow) { this.name = name; this.columnsPerRow = columnsPerRow; } - protected Layout(Parcel in) { + protected RecordingLayout(Parcel in) { name = in.readString(); columnsPerRow = in.readInt(); in.readList(dataFields, DataField.class.getClassLoader()); } - public static final Creator CREATOR = new Creator<>() { + public static final Creator CREATOR = new Creator<>() { @Override - public Layout createFromParcel(Parcel in) { - return new Layout(in); + public RecordingLayout createFromParcel(Parcel in) { + return new RecordingLayout(in); } @Override - public Layout[] newArray(int size) { - return new Layout[size]; + public RecordingLayout[] newArray(int size) { + return new RecordingLayout[size]; } }; - public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) { - dataFields.add(new DataField(key, title, visible, primary, isWide)); - } - public void addField(DataField dataField) { dataFields.add(dataField); } @@ -111,8 +83,8 @@ public class Layout implements Parcelable { this.columnsPerRow = columnsPerRow; } - public boolean sameName(Layout layout) { - return this.name.equalsIgnoreCase(layout.getName()); + public boolean sameName(RecordingLayout recordingLayout) { + return this.name.equalsIgnoreCase(recordingLayout.getName()); } public boolean sameName(String name) { @@ -126,7 +98,7 @@ public class Layout implements Parcelable { } return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR - + fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR)) + + fields.stream().map(RecordingLayoutIO::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR)) + CsvLayoutUtils.ITEM_SEPARATOR; } @@ -141,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; + RecordingLayout recordingLayout = (RecordingLayout) o; + return columnsPerRow == recordingLayout.columnsPerRow && Objects.equals(name, recordingLayout.name) && Objects.equals(dataFields, recordingLayout.dataFields); + } + + @Override + public int hashCode() { + return Objects.hash(name, columnsPerRow, dataFields); + } } diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java new file mode 100644 index 000000000..688b46355 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/RecordingLayoutIO.java @@ -0,0 +1,58 @@ +package de.dennisguse.opentracks.ui.customRecordingLayout; + +import android.content.res.Resources; +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.util.List; +import java.util.stream.Collectors; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.settings.PreferencesUtils; + +public class RecordingLayoutIO { + + private static final String TAG = RecordingLayout.class.getSimpleName(); + + private static final String YES_VALUE = "1"; + private static final String NOT_VALUE = "0"; + + public static RecordingLayout fromCsv(@NonNull String csvLine, @NonNull Resources resources) { + List csvParts = CsvLayoutUtils.getCsvLineParts(csvLine); + if (csvParts == null) { + Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine); + return new RecordingLayout(PreferencesUtils.getDefaultLayoutName()); + } + + RecordingLayout recordingLayout = new RecordingLayout(csvParts.get(0), Integer.parseInt(csvParts.get(1))); + for (int i = 2; i < csvParts.size(); i++) { + String[] fieldParts = CsvLayoutUtils.getCsvFieldParts(csvParts.get(i)); + if (fieldParts == null) { + Log.e(TAG, "Invalid CSV layout. It shouldn't happen: " + csvLine); + return recordingLayout; + } + recordingLayout.addField(fromCSV(fieldParts, resources)); + } + return recordingLayout; + } + + public static String toCSV(List recordingLayouts) { + return recordingLayouts.stream().map(RecordingLayout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)); + } + + private static DataField fromCSV(String[] fieldParts, @NonNull Resources resources) { + return new DataField( + fieldParts[0], + fieldParts[1].equals(YES_VALUE), + fieldParts[2].equals(YES_VALUE), + fieldParts[0].equals(resources.getString(R.string.stats_custom_layout_coordinates_key))); + } + + static String toCsv(DataField datafield) { + String visible = datafield.isVisible() ? YES_VALUE : NOT_VALUE; + String primary = datafield.isPrimary() ? YES_VALUE : NOT_VALUE; + String wide = datafield.isWide() ? YES_VALUE : NOT_VALUE; + return datafield.getKey() + CsvLayoutUtils.PROPERTY_SEPARATOR + visible + CsvLayoutUtils.PROPERTY_SEPARATOR + primary + CsvLayoutUtils.PROPERTY_SEPARATOR + wide; + } +} 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..203889da0 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutEditAdapter.java @@ -1,106 +1,151 @@ package de.dennisguse.opentracks.ui.customRecordingLayout; import android.content.Context; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.appcompat.content.res.AppCompatResources; import androidx.recyclerview.widget.RecyclerView; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; +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.data.models.Altitude; +import de.dennisguse.opentracks.data.models.Distance; +import de.dennisguse.opentracks.data.models.Speed; +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.data.models.TrackPoint; +import de.dennisguse.opentracks.databinding.CustomStatsItemBinding; +import de.dennisguse.opentracks.services.RecordingData; +import de.dennisguse.opentracks.settings.UnitSystem; +import de.dennisguse.opentracks.stats.TrackStatistics; +import de.dennisguse.opentracks.viewmodels.Mapping; +import de.dennisguse.opentracks.viewmodels.StatisticViewHolder; public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter { - private Layout layout; + private static final String TAG = SettingsCustomLayoutEditAdapter.class.getSimpleName(); + + private static final RecordingData demoData; + + static { + TrackStatistics trackStatistics = new TrackStatistics(); + trackStatistics.setStartTime(Instant.ofEpochMilli(0)); + trackStatistics.setMovingTime(Duration.ofMinutes(0)); + trackStatistics.setTotalTime(Duration.ofMinutes(0)); + + trackStatistics.setTotalDistance(Distance.of(0)); + + trackStatistics.setTotalAltitudeGain(0f); + trackStatistics.setTotalAltitudeLoss(0f); + Track track = new Track(ZoneOffset.UTC); + track.setTrackStatistics(trackStatistics); + + TrackPoint lastTrackPoint = new TrackPoint(TrackPoint.Type.TRACKPOINT, Instant.ofEpochMilli(0)); + lastTrackPoint.setLatitude(0); + lastTrackPoint.setLongitude(0); + lastTrackPoint.setAltitude(Altitude.EGM2008.of(0)); + lastTrackPoint.setSpeed(Speed.of(0)); + + demoData = new RecordingData(track, lastTrackPoint, null); + } + + private RecordingLayout recordingLayout; private final Context context; private final SettingsCustomLayoutItemClickListener itemClickListener; + private final Map>> mapping; - public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) { + + public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, RecordingLayout recordingLayout) { this.context = context; this.itemClickListener = itemClickListener; - this.layout = layout; + this.recordingLayout = recordingLayout; + + 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); - return new SettingsCustomLayoutEditAdapter.ViewHolder(view); + CustomStatsItemBinding binding = CustomStatsItemBinding.inflate(LayoutInflater.from(context), parent, false); + return new SettingsCustomLayoutEditAdapter.ViewHolder(binding); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder; - DataField field = layout.getFields().get(position); + DataField field = recordingLayout.getFields().get(position); viewHolder.itemView.setTag(field.getKey()); - viewHolder.title.setText(field.getTitle()); - viewHolder.value.setText(StatisticsUtils.emptyValue(context, field.getKey())); + try { + StatisticViewHolder m = mapping.get(field.getKey()).call(); + m.initialize(context, LayoutInflater.from(context)); + m.configureUI(field); + m.onChanged(UnitSystem.METRIC, demoData); - viewHolder.title.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryHeader : R.style.TextAppearance_OpenTracks_SecondaryHeader) : R.style.TextAppearance_OpenTracks_HiddenHeader); - viewHolder.value.setTextAppearance(context, field.isVisible() ? (field.isPrimary() ? R.style.TextAppearance_OpenTracks_PrimaryValue : R.style.TextAppearance_OpenTracks_SecondaryValue) : R.style.TextAppearance_OpenTracks_HiddenValue); - viewHolder.statusIcon.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE); - viewHolder.statusIcon.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24)); - viewHolder.moveIcon.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE); + viewHolder.viewBinding.statsLayout.removeAllViews(); //TODO this is not really performant + viewHolder.viewBinding.statsLayout.addView(m.getView()); + } catch (Exception e) { + Log.e(TAG, "Couldn't to instantiate UI for DataField with key " + field.getKey() + " " + e.getMessage()); + throw new RuntimeException(e); + } + viewHolder.viewBinding.statsIconShowStatus.setVisibility(field.isVisible() ? View.GONE : View.VISIBLE); + viewHolder.viewBinding.statsIconShowStatus.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_baseline_visibility_off_24)); + viewHolder.viewBinding.statsIvDragIndicator.setVisibility(field.isVisible() ? View.VISIBLE : View.GONE); } @Override public int getItemCount() { - if (layout == null) { + if (recordingLayout == null) { return 0; } else { - return layout.getFields().size(); + return recordingLayout.getFields().size(); } } public boolean isItemWide(int position) { - return layout.getFields().get(position).isWide(); + return recordingLayout.getFields().get(position).isWide(); } public DataField getItem(int position) { - return layout.getFields().get(position); + return recordingLayout.getFields().get(position); } - public void swapValues(Layout data) { - this.layout = data; - if (this.layout != null) { + public void swapValues(RecordingLayout data) { + this.recordingLayout = data; + if (this.recordingLayout != null) { this.notifyDataSetChanged(); } } - public Layout move(int fromPosition, int toPosition) { - layout.moveField(fromPosition, toPosition); + public RecordingLayout move(int fromPosition, int toPosition) { + recordingLayout.moveField(fromPosition, toPosition); notifyItemMoved(fromPosition, toPosition); - return layout; + return recordingLayout; } private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - final TextView title; - final TextView value; - final TextView unit; - final ImageView statusIcon; - final ImageView moveIcon; - public ViewHolder(@NonNull View itemView) { - super(itemView); - title = itemView.findViewById(R.id.stats_description_main); - value = itemView.findViewById(R.id.stats_value); - unit = itemView.findViewById(R.id.stats_unit); - statusIcon = itemView.findViewById(R.id.stats_icon_show_status); - moveIcon = itemView.findViewById(R.id.stats_iv_drag_indicator); - itemView.setOnClickListener(this); + CustomStatsItemBinding viewBinding; + + public ViewHolder(@NonNull CustomStatsItemBinding viewBinding) { + super(viewBinding.getRoot()); + this.viewBinding = viewBinding; + + viewBinding.getRoot().setOnClickListener(this); } @Override public void onClick(View view) { String statTitle = (String) view.getTag(); - Optional optionalField = layout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst(); + Optional optionalField = recordingLayout.getFields().stream().filter(f -> f.getKey().equals(statTitle)).findFirst(); optionalField.ifPresent(itemClickListener::onSettingsCustomLayoutItemClicked); } } diff --git a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutListAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutListAdapter.java index c3ea86901..8fbcf628e 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutListAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/ui/customRecordingLayout/SettingsCustomLayoutListAdapter.java @@ -17,14 +17,14 @@ import de.dennisguse.opentracks.settings.PreferencesUtils; public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter { - private List layoutList; + private List recordingLayoutList; private final Context context; private final SettingsCustomLayoutProfileClickListener itemClickListener; public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) { this.context = context; this.itemClickListener = itemClickListener; - layoutList = PreferencesUtils.getAllCustomLayouts(); + recordingLayoutList = PreferencesUtils.getAllCustomLayouts(); } @NonNull @@ -37,37 +37,37 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter getLayouts() { - return layoutList; + public List getLayouts() { + return recordingLayoutList; } public void reloadLayouts() { - layoutList = PreferencesUtils.getAllCustomLayouts(); + recordingLayoutList = PreferencesUtils.getAllCustomLayouts(); notifyDataSetChanged(); } public void removeLayout(int position) { - layoutList.remove(position); - PreferencesUtils.updateCustomLayouts(layoutList); + recordingLayoutList.remove(position); + PreferencesUtils.updateCustomLayouts(recordingLayoutList); notifyDataSetChanged(); } - public void restoreItem(Layout layout, int position) { - layoutList.add(position, layout); - PreferencesUtils.updateCustomLayouts(layoutList); + public void restoreItem(RecordingLayout recordingLayout, int position) { + recordingLayoutList.add(position, recordingLayout); + PreferencesUtils.updateCustomLayouts(recordingLayoutList); notifyDataSetChanged(); } @@ -83,12 +83,12 @@ public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst(); + Optional optionalLayout = recordingLayoutList.stream().filter(layout -> layout.sameName(new RecordingLayout(profile))).findFirst(); optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked); } } public interface SettingsCustomLayoutProfileClickListener { - void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout); + void onSettingsCustomLayoutProfileClicked(@NonNull RecordingLayout recordingLayout); } } diff --git a/src/main/java/de/dennisguse/opentracks/util/StatisticsUtils.java b/src/main/java/de/dennisguse/opentracks/util/StatisticsUtils.java index 5f841db6e..3d3910c24 100644 --- a/src/main/java/de/dennisguse/opentracks/util/StatisticsUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/StatisticsUtils.java @@ -8,10 +8,11 @@ import java.util.stream.Collectors; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.settings.PreferencesUtils; -import de.dennisguse.opentracks.ui.customRecordingLayout.Layout; +import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout; public class StatisticsUtils { + @Deprecated public static String emptyValue(@NonNull Context context, @NonNull String statTitle) { if (PreferencesUtils.isKey(R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_fastest_pace_key, statTitle) || PreferencesUtils.isKey(R.string.stats_custom_layout_clock_key, statTitle)) { return context.getString(R.string.stats_empty_value_time); @@ -24,9 +25,10 @@ public class StatisticsUtils { } } - public static Layout filterVisible(Layout layout, boolean visible) { - Layout result = new Layout(layout.getName()); - result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList())); + @Deprecated //Add to Layout? + public static RecordingLayout filterVisible(RecordingLayout recordingLayout, boolean visible) { + RecordingLayout result = new RecordingLayout(recordingLayout.getName()); + result.addFields(recordingLayout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList())); return result; } } diff --git a/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java b/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java index f5f663725..1f30b3b87 100644 --- a/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/TrackIconUtils.java @@ -94,6 +94,7 @@ public class TrackIconUtils { }; + //TODO Use Map.of() private static final LinkedHashMap> 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..053b4abaa --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/ClockViewHolder.java @@ -0,0 +1,27 @@ +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 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..61e0354b9 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java @@ -0,0 +1,322 @@ +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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 8d929df1c..000000000 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticData.java +++ /dev/null @@ -1,45 +0,0 @@ -package de.dennisguse.opentracks.viewmodels; - -import android.util.Pair; - -import androidx.annotation.NonNull; - -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 DataField getField() { - return dataField; - } - - 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 1963b3d14..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.getField().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.getField().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.getField().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..726be1527 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/StatisticViewHolder.java @@ -0,0 +1,41 @@ +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); + + 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/about.xml b/src/main/res/layout/about.xml index 6378ce9fb..2e6a97ed9 100644 --- a/src/main/res/layout/about.xml +++ b/src/main/res/layout/about.xml @@ -120,6 +120,8 @@ - + diff --git a/src/main/res/layout/activity_settings_custom_layout.xml b/src/main/res/layout/activity_settings_custom_layout.xml index 87a2ef216..3a4b8491d 100644 --- a/src/main/res/layout/activity_settings_custom_layout.xml +++ b/src/main/res/layout/activity_settings_custom_layout.xml @@ -56,6 +56,8 @@ - + \ No newline at end of file diff --git a/src/main/res/layout/activity_settings_custom_layout_list.xml b/src/main/res/layout/activity_settings_custom_layout_list.xml index 5a9615f93..6d0154f6d 100644 --- a/src/main/res/layout/activity_settings_custom_layout_list.xml +++ b/src/main/res/layout/activity_settings_custom_layout_list.xml @@ -75,6 +75,8 @@ - + \ No newline at end of file diff --git a/src/main/res/layout/activity_show_error.xml b/src/main/res/layout/activity_show_error.xml index 15594187d..880c1b8d2 100644 --- a/src/main/res/layout/activity_show_error.xml +++ b/src/main/res/layout/activity_show_error.xml @@ -21,6 +21,8 @@ - + diff --git a/src/main/res/layout/aggregated_stats.xml b/src/main/res/layout/aggregated_stats.xml index a4aa700a4..006f05dde 100644 --- a/src/main/res/layout/aggregated_stats.xml +++ b/src/main/res/layout/aggregated_stats.xml @@ -26,6 +26,8 @@ - + \ No newline at end of file diff --git a/src/main/res/layout/custom_stats_item.xml b/src/main/res/layout/custom_stats_item.xml index feaee18c9..3c56f007a 100644 --- a/src/main/res/layout/custom_stats_item.xml +++ b/src/main/res/layout/custom_stats_item.xml @@ -22,11 +22,10 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> - - + \ No newline at end of file diff --git a/src/main/res/layout/help.xml b/src/main/res/layout/help.xml index 8a58acf4a..48940216b 100644 --- a/src/main/res/layout/help.xml +++ b/src/main/res/layout/help.xml @@ -143,6 +143,8 @@ - + \ No newline at end of file diff --git a/src/main/res/layout/import_activity.xml b/src/main/res/layout/import_activity.xml index 1a3f0f6c9..13ea6e93e 100644 --- a/src/main/res/layout/import_activity.xml +++ b/src/main/res/layout/import_activity.xml @@ -251,6 +251,8 @@ - + \ No newline at end of file diff --git a/src/main/res/layout/marker_detail_activity.xml b/src/main/res/layout/marker_detail_activity.xml index cee3613d4..77a4d9fc4 100644 --- a/src/main/res/layout/marker_detail_activity.xml +++ b/src/main/res/layout/marker_detail_activity.xml @@ -28,6 +28,8 @@ limitations under the License. android:layout_height="match_parent" /> - + \ No newline at end of file diff --git a/src/main/res/layout/marker_edit.xml b/src/main/res/layout/marker_edit.xml index 21dec491e..83cd9beca 100644 --- a/src/main/res/layout/marker_edit.xml +++ b/src/main/res/layout/marker_edit.xml @@ -138,6 +138,8 @@ limitations under the License. - + \ No newline at end of file diff --git a/src/main/res/layout/marker_list.xml b/src/main/res/layout/marker_list.xml index 5c465cb21..3bd12501e 100644 --- a/src/main/res/layout/marker_list.xml +++ b/src/main/res/layout/marker_list.xml @@ -38,6 +38,8 @@ limitations under the License. - + \ No newline at end of file diff --git a/src/main/res/layout/settings.xml b/src/main/res/layout/settings.xml index 2ec505182..5802a1398 100644 --- a/src/main/res/layout/settings.xml +++ b/src/main/res/layout/settings.xml @@ -17,6 +17,8 @@ - + \ No newline at end of file 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 diff --git a/src/main/res/layout/track_edit.xml b/src/main/res/layout/track_edit.xml index 45c1fc7f6..579577e7e 100644 --- a/src/main/res/layout/track_edit.xml +++ b/src/main/res/layout/track_edit.xml @@ -46,6 +46,8 @@ limitations under the License. - + \ No newline at end of file diff --git a/src/main/res/layout/track_recorded.xml b/src/main/res/layout/track_recorded.xml index 7b655408a..1801ba712 100644 --- a/src/main/res/layout/track_recorded.xml +++ b/src/main/res/layout/track_recorded.xml @@ -36,6 +36,8 @@ limitations under the License. - + \ No newline at end of file