forked from upstream-mirrors/OpenTracks
Added profiles for custom layouts.
Users can add severals custom layouts and select one of them in recording fragment. The last selected layout is the one that will be use in the next recording. Fixes #938.
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LayoutTest extends TestCase {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
|
||||
@Test
|
||||
public void testFromCsv() {
|
||||
// given a csv line
|
||||
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<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "running");
|
||||
assertEquals(layout.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());
|
||||
assertTrue(dataFieldList.get(1).isVisible());
|
||||
assertFalse(dataFieldList.get(1).isPrimary());
|
||||
assertFalse(dataFieldList.get(1).isWide());
|
||||
assertFalse(dataFieldList.get(2).isVisible());
|
||||
assertTrue(dataFieldList.get(2).isPrimary());
|
||||
assertFalse(dataFieldList.get(2).isWide());
|
||||
assertFalse(dataFieldList.get(3).isVisible());
|
||||
assertFalse(dataFieldList.get(3).isPrimary());
|
||||
assertFalse(dataFieldList.get(3).isWide());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong1() {
|
||||
// given a csv line without number of columns
|
||||
String csv1 = "Layout Name;speed,1,1;distance,0,0;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong2() {
|
||||
// given a csv line without any field
|
||||
String csv1 = "Layout Name;2;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), PreferencesUtils.getDefaultLayoutName());
|
||||
assertEquals(layout.getColumnsPerRow(), PreferencesUtils.getLayoutColumnsByDefault());
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong3() {
|
||||
// given a csv line with wrong fields description
|
||||
String csv1 = "Layout Name;2;speed,distance,total time;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.getFields().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromCsv_Wrong4() {
|
||||
// given a csv line with the first field ok but not the others
|
||||
String csv1 = "Layout Name;2;speed,1,0;distance;";
|
||||
|
||||
// when create a layout from CSV line
|
||||
Layout layout = Layout.fromCsv(csv1, resources);
|
||||
List<DataField> dataFieldList = layout.getFields();
|
||||
|
||||
// then layout and data fields are built correctly
|
||||
assertEquals(layout.getName(), "Layout Name");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
assertEquals(layout.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);
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.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;");
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
// when converts it to CSV
|
||||
String csv = layout.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;");
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesOpenHelperTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
|
||||
@Test
|
||||
public void test_upgradeFrom0To1_withoutStatsCustomLayouts() {
|
||||
// given the version 0
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(context.getString(R.string.stats_custom_layouts_key));
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 0);
|
||||
editor.commit();
|
||||
|
||||
// when update to version 1
|
||||
PreferencesOpenHelper.newInstance(1).check();
|
||||
|
||||
// then there should be one layout with old custom layout that has the new CSV value.
|
||||
List<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), PreferencesUtils.getCustomLayout().toCsv());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_upgradeFrom1To2_withOldVersion() {
|
||||
// given the version 1
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
String oldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";distance,1,1,0;speed,1,1,0;";
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
|
||||
|
||||
editor.commit();
|
||||
|
||||
// when update to version 2
|
||||
PreferencesOpenHelper.newInstance(2).check();
|
||||
|
||||
String updatedOldCustomLayoutCsv = context.getString(R.string.activity_type_unknown) + ";"
|
||||
+ 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<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_upgradeFrom1To2_withNewVersion() {
|
||||
// given the version 1
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
String oldCustomLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), oldCustomLayoutCsv);
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 1);
|
||||
|
||||
editor.commit();
|
||||
|
||||
// when update to version 2
|
||||
PreferencesOpenHelper.newInstance(2).check();
|
||||
|
||||
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<Layout> layouts = PreferencesUtils.getAllCustomLayouts();
|
||||
assertNotNull(layouts);
|
||||
assertEquals(layouts.size(), 1);
|
||||
assertEquals(layouts.get(0).toCsv(), updatedOldCustomLayoutCsv);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_downgrade() {
|
||||
// given version 2
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
String customLayoutCsv = "whatever;3;distance,1,1,0;speed,1,1,0;";
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), customLayoutCsv);
|
||||
editor.putInt(context.getString(R.string.prefs_last_version_key), 2);
|
||||
|
||||
editor.commit();
|
||||
|
||||
// when downgrade to version 1
|
||||
PreferencesOpenHelper.newInstance(1).check();
|
||||
|
||||
// then custom layout should be equals to default layout.
|
||||
Layout defaultLayout = Layout.fromCsv(PreferencesUtils.buildDefaultLayout(), resources);
|
||||
List<Layout> customLayout = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(customLayout.size(), 1);
|
||||
assertEquals(defaultLayout.toCsv(), customLayout.get(0).toCsv());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CsvLayoutUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testGetCsvLineParts() {
|
||||
// given
|
||||
String csvLineOk = "Layout Name;2;distance,1,1;speed,1,1;";
|
||||
String csvLineWrong1 = "Layout Name;distance,1,1;speed,1,1;";
|
||||
String csvLineWrong2 = "Layout Name;1;";
|
||||
|
||||
// when
|
||||
List<String> partsOk = CsvLayoutUtils.getCsvLineParts(csvLineOk);
|
||||
List<String> partsWrong1 = CsvLayoutUtils.getCsvLineParts(csvLineWrong1);
|
||||
List<String> partsWrong2 = CsvLayoutUtils.getCsvLineParts(csvLineWrong2);
|
||||
|
||||
// then
|
||||
assertNotNull(partsOk);
|
||||
assertEquals(partsOk.size(), 4);
|
||||
assertEquals(partsOk.get(0), "Layout Name");
|
||||
assertEquals(partsOk.get(1), "2");
|
||||
assertEquals(partsOk.get(2), "distance,1,1");
|
||||
assertEquals(partsOk.get(3), "speed,1,1");
|
||||
assertNull(partsWrong1);
|
||||
assertNull(partsWrong2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCsvFieldParts() {
|
||||
// given
|
||||
String csvFieldOk1 = "distance,0,0";
|
||||
String csvFieldOk2 = "speed,0,1";
|
||||
String csvFieldOk3 = "time,1,0";
|
||||
String csvFieldOk4 = "moving time,1,1";
|
||||
String csvFieldOk5 = ";moving time,1,1";
|
||||
String csvLineWrong1 = "";
|
||||
String csvLineWrong2 = "distance,speed,time";
|
||||
String csvLineWrong3 = "distance,0,1o";
|
||||
String csvLineWrong4 = "distance,0z,1";
|
||||
String csvLineWrong5 = "distance";
|
||||
String csvLineWrong6 = "distance,0";
|
||||
String csvLineWrong7 = "distance,1,1;";
|
||||
|
||||
// when
|
||||
String[] ok1 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk1);
|
||||
String[] ok2 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk2);
|
||||
String[] ok3 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk3);
|
||||
String[] ok4 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk4);
|
||||
String[] ok5 = CsvLayoutUtils.getCsvFieldParts(csvFieldOk5);
|
||||
String[] wrong1 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong1);
|
||||
String[] wrong2 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong2);
|
||||
String[] wrong3 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong3);
|
||||
String[] wrong4 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong4);
|
||||
String[] wrong5 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong5);
|
||||
String[] wrong6 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong6);
|
||||
String[] wrong7 = CsvLayoutUtils.getCsvFieldParts(csvLineWrong7);
|
||||
|
||||
// then
|
||||
assertNotNull(ok1);
|
||||
assertNotNull(ok2);
|
||||
assertNotNull(ok3);
|
||||
assertNotNull(ok4);
|
||||
assertNotNull(ok5);
|
||||
assertFieldOk(ok1, "distance", "0", "0");
|
||||
assertFieldOk(ok2, "speed", "0", "1");
|
||||
assertFieldOk(ok3, "time", "1", "0");
|
||||
assertFieldOk(ok4, "moving time", "1", "1");
|
||||
assertFieldOk(ok5, ";moving time", "1", "1");
|
||||
assertNull(wrong1);
|
||||
assertNull(wrong2);
|
||||
assertNull(wrong3);
|
||||
assertNull(wrong4);
|
||||
assertNull(wrong5);
|
||||
assertNull(wrong6);
|
||||
assertNull(wrong7);
|
||||
}
|
||||
|
||||
private void assertFieldOk(String[] ok, String name, String val1, String val2) {
|
||||
assertEquals(ok[0], name);
|
||||
assertEquals(ok[1], val1);
|
||||
assertEquals(ok[2], val2);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
@@ -21,10 +22,14 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PreferencesUtilsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
private final Resources resources = ApplicationProvider.getApplicationContext().getResources();
|
||||
|
||||
@Test
|
||||
public void ExportTrackFileFormat_ok() {
|
||||
@@ -72,6 +77,24 @@ public class PreferencesUtilsTest {
|
||||
assertEquals(TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, trackFileFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAllCustomLayouts_default() {
|
||||
// given
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
|
||||
// when
|
||||
List<Layout> layouts = 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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_default() {
|
||||
// given
|
||||
@@ -85,7 +108,7 @@ public class PreferencesUtilsTest {
|
||||
|
||||
// then
|
||||
assertTrue(layout.getFields().size() > 0);
|
||||
assertEquals(layout.getProfile(), context.getString(R.string.default_activity_default));
|
||||
assertEquals(layout.getName(), context.getString(R.string.stats_custom_layout_default_layout));
|
||||
assertTrue(layout.getFields().stream().anyMatch(DataField::isVisible));
|
||||
}
|
||||
|
||||
@@ -95,8 +118,8 @@ public class PreferencesUtilsTest {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"run;"
|
||||
context.getString(R.string.stats_custom_layouts_key),
|
||||
"run;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
@@ -108,7 +131,8 @@ public class PreferencesUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "run");
|
||||
assertEquals(layout.getName(), "run");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
@@ -133,8 +157,8 @@ public class PreferencesUtilsTest {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(
|
||||
context.getString(R.string.stats_custom_layout_fields_key),
|
||||
"walking;"
|
||||
context.getString(R.string.stats_custom_layouts_key),
|
||||
"walking;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_coordinates_key) + ",0,1;"
|
||||
@@ -146,7 +170,8 @@ public class PreferencesUtilsTest {
|
||||
|
||||
// then
|
||||
assertEquals(layout.getFields().size(), 4);
|
||||
assertEquals(layout.getProfile(), "walking");
|
||||
assertEquals(layout.getName(), "walking");
|
||||
assertEquals(layout.getColumnsPerRow(), 2);
|
||||
|
||||
assertEquals(layout.getFields().get(0).getKey(), context.getString(R.string.stats_custom_layout_moving_time_key));
|
||||
assertTrue(layout.getFields().get(0).isVisible());
|
||||
@@ -177,20 +202,22 @@ public class PreferencesUtilsTest {
|
||||
layoutSrc.addField(new DataField(context.getString(R.string.stats_custom_layout_speed_key), context.getString(R.string.stats_speed), false, false, false));
|
||||
|
||||
// when
|
||||
PreferencesUtils.setCustomLayout(layoutSrc);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), layoutSrc.toCsv());
|
||||
editor.commit();
|
||||
|
||||
// then
|
||||
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layout_fields_key), null);
|
||||
String csv = sharedPreferences.getString(context.getString(R.string.stats_custom_layouts_key), null);
|
||||
assertNotNull(csv);
|
||||
assertEquals(csv,
|
||||
"road cycling;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",0,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",0,0;");
|
||||
"road cycling;" + PreferencesUtils.getLayoutColumnsByDefault() + ";"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,0,0;"
|
||||
+ 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.getProfile(), layoutDst.getProfile());
|
||||
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());
|
||||
@@ -198,4 +225,122 @@ public class PreferencesUtilsTest {
|
||||
assertEquals(layoutSrc.getFields().get(i).isPrimary(), layoutDst.getFields().get(i).isPrimary());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditCustomLayouts() {
|
||||
// update all custom layouts
|
||||
|
||||
// given a custom layout with two profiles
|
||||
String cyclingProfile = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
|
||||
|
||||
String runningProfile = "running;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
String cyclingProfileUpdated = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ 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;";
|
||||
|
||||
List<Layout> layoutsToBeUpdated = new ArrayList<>();
|
||||
layoutsToBeUpdated.add(Layout.fromCsv(cyclingProfileUpdated, resources));
|
||||
layoutsToBeUpdated.add(Layout.fromCsv(runningProfile, resources));
|
||||
|
||||
PreferencesUtils.updateCustomLayouts(layoutsToBeUpdated);
|
||||
|
||||
// then only updated profile is modified in the custom layouts
|
||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(layoutsBefore.size(), 2);
|
||||
assertEquals(layoutsAfter.size(), 2);
|
||||
|
||||
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
|
||||
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditCustomLayout() {
|
||||
// Update only one custom layout
|
||||
|
||||
// given a custom layout with two profiles
|
||||
String cyclingProfile = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
|
||||
|
||||
String runningProfile = "running;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.apply();
|
||||
|
||||
List<Layout> layoutsBefore = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
// when cyling profile is updated
|
||||
String cyclingProfileUpdated = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ 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);
|
||||
|
||||
// then only updated profile is modified in the custom layouts
|
||||
List<Layout> layoutsAfter = PreferencesUtils.getAllCustomLayouts();
|
||||
|
||||
assertEquals(layoutsBefore.size(), 2);
|
||||
assertEquals(layoutsAfter.size(), 2);
|
||||
|
||||
assertEquals(layoutsBefore.get(0).getFields().stream().filter(DataField::isVisible).count(), 4);
|
||||
assertEquals(layoutsAfter.get(0).getFields().stream().filter(DataField::isVisible).count(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCustomLayout_whenSelectedOneNotExists() {
|
||||
// given a custom layout with two profiles and not existing custom layout selected
|
||||
String cyclingProfile = "cycling;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_moving_speed_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_speed_key) + ",1,1;";
|
||||
|
||||
String runningProfile = "running;2;"
|
||||
+ context.getString(R.string.stats_custom_layout_moving_time_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_distance_key) + ",1,1;"
|
||||
+ context.getString(R.string.stats_custom_layout_average_pace_key) + ",0,0;"
|
||||
+ context.getString(R.string.stats_custom_layout_pace_key) + ",0,0;";
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(context.getString(R.string.stats_custom_layouts_key), cyclingProfile + "\n" + runningProfile);
|
||||
editor.putString(context.getString(R.string.stats_custom_layout_selected_layout_key), "Not Exists");
|
||||
editor.apply();
|
||||
|
||||
// when it gets the custom layout
|
||||
Layout layout = PreferencesUtils.getCustomLayout();
|
||||
|
||||
// then the first one was returned
|
||||
assertEquals(layout.getName(), "cycling");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +324,8 @@ limitations under the License.
|
||||
<activity android:name=".io.file.exporter.ExportActivity" />
|
||||
|
||||
<activity android:name=".settings.SettingsActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutListActivity" />
|
||||
<activity android:name=".settings.SettingsCustomLayoutEditActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".ShowOnMapProxyActivity$KML"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
@@ -18,6 +19,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
@@ -263,6 +266,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_insert_marker) {
|
||||
Intent intent = IntentUtils
|
||||
.newIntent(this, MarkerEditActivity.class)
|
||||
@@ -271,8 +279,11 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getItemId() == R.id.track_detail_menu_show_on_map) {
|
||||
IntentDashboardUtils.startDashboard(this, true, trackId);
|
||||
if (item.getItemId() == R.id.track_detail_menu_select_layout) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
List<String> layoutNames = PreferencesUtils.getAllCustomLayoutNames();
|
||||
builder.setTitle(getString(R.string.custom_layout_select_layout)).setItems(layoutNames.toArray(new String[0]), (dialog, which) -> PreferencesUtils.setDefaultLayout(layoutNames.get(which)));
|
||||
builder.create().show();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -18,13 +18,13 @@ import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
public class SettingsCustomLayoutEditAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private Layout layout;
|
||||
private final Context context;
|
||||
private final SettingsCustomLayoutItemClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
public SettingsCustomLayoutEditAdapter(Context context, SettingsCustomLayoutItemClickListener itemClickListener, Layout layout) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
this.layout = layout;
|
||||
@@ -34,12 +34,12 @@ public class SettingsCustomLayoutAdapter extends RecyclerView.Adapter<RecyclerVi
|
||||
@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 SettingsCustomLayoutAdapter.ViewHolder(view);
|
||||
return new SettingsCustomLayoutEditAdapter.ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutAdapter.ViewHolder viewHolder = (SettingsCustomLayoutAdapter.ViewHolder) holder;
|
||||
SettingsCustomLayoutEditAdapter.ViewHolder viewHolder = (SettingsCustomLayoutEditAdapter.ViewHolder) holder;
|
||||
DataField field = layout.getFields().get(position);
|
||||
viewHolder.itemView.setTag(field.getKey());
|
||||
viewHolder.title.setText(field.getTitle());
|
||||
@@ -0,0 +1,95 @@
|
||||
package de.dennisguse.opentracks.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
|
||||
public class SettingsCustomLayoutListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private List<Layout> layoutList;
|
||||
private final Context context;
|
||||
private SettingsCustomLayoutProfileClickListener itemClickListener;
|
||||
|
||||
public SettingsCustomLayoutListAdapter(Context context, SettingsCustomLayoutProfileClickListener itemClickListener) {
|
||||
this.context = context;
|
||||
this.itemClickListener = itemClickListener;
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_layout_item, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
SettingsCustomLayoutListAdapter.ViewHolder viewHolder = (SettingsCustomLayoutListAdapter.ViewHolder) holder;
|
||||
Layout layout = layoutList.get(position);
|
||||
viewHolder.itemView.setTag(layout.getName());
|
||||
viewHolder.title.setText(layout.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (layoutList == null) {
|
||||
return 0;
|
||||
}
|
||||
return layoutList.size();
|
||||
}
|
||||
|
||||
public List<Layout> getLayouts() {
|
||||
return layoutList;
|
||||
}
|
||||
|
||||
public void reloadLayouts() {
|
||||
layoutList = PreferencesUtils.getAllCustomLayouts();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeLayout(int position) {
|
||||
layoutList.remove(position);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void restoreItem(Layout layout, int position) {
|
||||
layoutList.add(position, layout);
|
||||
PreferencesUtils.updateCustomLayouts(layoutList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView title;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.custom_layout_title);
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String profile = (String) view.getTag();
|
||||
Optional<Layout> optionalLayout = layoutList.stream().filter(layout -> layout.sameName(new Layout(profile))).findFirst();
|
||||
optionalLayout.ifPresent(itemClickListener::onSettingsCustomLayoutProfileClicked);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SettingsCustomLayoutProfileClickListener {
|
||||
void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.CsvConstants;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
|
||||
public class DataField {
|
||||
public class DataField implements Parcelable {
|
||||
public static final String YES_VALUE = "1";
|
||||
public static final String NOT_VALUE = "0";
|
||||
|
||||
@@ -24,6 +26,26 @@ public class DataField {
|
||||
this.isWide = isWide;
|
||||
}
|
||||
|
||||
protected DataField(Parcel in) {
|
||||
key = in.readString();
|
||||
title = in.readString();
|
||||
isVisible = in.readByte() != 0;
|
||||
isPrimary = in.readByte() != 0;
|
||||
isWide = in.readByte() != 0;
|
||||
}
|
||||
|
||||
public static final Creator<DataField> CREATOR = new Creator<DataField>() {
|
||||
@Override
|
||||
public DataField createFromParcel(Parcel in) {
|
||||
return new DataField(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataField[] newArray(int size) {
|
||||
return new DataField[size];
|
||||
}
|
||||
};
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
@@ -63,7 +85,8 @@ public class DataField {
|
||||
public String toCsv() {
|
||||
String visible = this.isVisible ? YES_VALUE : NOT_VALUE;
|
||||
String primary = this.isPrimary ? YES_VALUE : NOT_VALUE;
|
||||
return key + CsvConstants.ITEM_SEPARATOR + visible + CsvConstants.ITEM_SEPARATOR + primary;
|
||||
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) {
|
||||
@@ -109,4 +132,18 @@ public class DataField {
|
||||
throw new RuntimeException("It doesn't exists a field with key: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,75 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
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.stream.Collectors;
|
||||
|
||||
public class Layout {
|
||||
private final String profile;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
|
||||
public class Layout implements Parcelable {
|
||||
private static final String TAG = Layout.class.getSimpleName();
|
||||
|
||||
// User-generated layout's name.
|
||||
private final String name;
|
||||
private int columnsPerRow;
|
||||
private final List<DataField> dataFields = new ArrayList<>();
|
||||
|
||||
public Layout(String profile) {
|
||||
this.profile = profile;
|
||||
public static Layout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
|
||||
List<String> 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) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
|
||||
}
|
||||
|
||||
public Layout(String name, int columnsPerRow) {
|
||||
this.name = name;
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
protected Layout(Parcel in) {
|
||||
name = in.readString();
|
||||
columnsPerRow = in.readInt();
|
||||
in.readList(dataFields, DataField.class.getClassLoader());
|
||||
}
|
||||
|
||||
public static final Creator<Layout> CREATOR = new Creator<Layout>() {
|
||||
@Override
|
||||
public Layout createFromParcel(Parcel in) {
|
||||
return new Layout(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout[] newArray(int size) {
|
||||
return new Layout[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void addField(String key, String title, boolean visible, boolean primary, boolean isWide) {
|
||||
dataFields.add(new DataField(key, title, visible, primary, isWide));
|
||||
}
|
||||
@@ -27,6 +86,11 @@ public class Layout {
|
||||
dataFields.remove(dataField);
|
||||
}
|
||||
|
||||
public void replaceAllFields(List<DataField> newFields) {
|
||||
dataFields.clear();
|
||||
addFields(newFields);
|
||||
}
|
||||
|
||||
public List<DataField> getFields() {
|
||||
return new ArrayList<>(dataFields);
|
||||
}
|
||||
@@ -36,7 +100,46 @@ public class Layout {
|
||||
dataFields.add(to, dataFieldToMove);
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return profile;
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getColumnsPerRow() {
|
||||
return columnsPerRow;
|
||||
}
|
||||
|
||||
public void setColumnsPerRow(int columnsPerRow) {
|
||||
this.columnsPerRow = columnsPerRow;
|
||||
}
|
||||
|
||||
public boolean sameName(Layout layout) {
|
||||
return this.name.equalsIgnoreCase(layout.getName());
|
||||
}
|
||||
|
||||
public boolean sameName(String name) {
|
||||
return this.name.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
public String toCsv() {
|
||||
List<DataField> fields = getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return getName() + CsvLayoutUtils.ITEM_SEPARATOR + getColumnsPerRow() + CsvLayoutUtils.ITEM_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(name);
|
||||
parcel.writeInt(columnsPerRow);
|
||||
parcel.writeList(dataFields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,10 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
preferenceMetricUnits = PreferencesUtils.isMetricUnits();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_fields_key, key)) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (PreferencesUtils.isKey(R.string.stats_custom_layout_columns_key, key)) {
|
||||
gridLayoutManager.setSpanCount(PreferencesUtils.getLayoutColumns());
|
||||
gridLayoutManager.setSpanCount(layout.getColumnsPerRow());
|
||||
}
|
||||
|
||||
if (key != null && updateUInecessary && isResumed()) {
|
||||
@@ -107,13 +104,13 @@ public class StatisticsRecordingFragment extends Fragment {
|
||||
|
||||
RecyclerView recyclerView = viewBinding.statsRecyclerView;
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getContext(), RecyclerView.VERTICAL));
|
||||
final int numColumns = PreferencesUtils.getLayoutColumns();
|
||||
gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
|
||||
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 numColumns;
|
||||
return layout.getColumnsPerRow();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,73 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
|
||||
class PreferencesOpenHelper {
|
||||
|
||||
private static final int PREFERENCES_VERSION = 1;
|
||||
private final int version;
|
||||
|
||||
private PreferencesOpenHelper() {
|
||||
private PreferencesOpenHelper(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
static PreferencesOpenHelper newInstance() {
|
||||
return new PreferencesOpenHelper();
|
||||
static PreferencesOpenHelper newInstance(int version) {
|
||||
return new PreferencesOpenHelper(version);
|
||||
}
|
||||
|
||||
void checkForUpgrade() {
|
||||
void check() {
|
||||
int lastVersion = PreferencesUtils.getInt(R.string.prefs_last_version_key, 0);
|
||||
if (PREFERENCES_VERSION > lastVersion) {
|
||||
if (version > lastVersion) {
|
||||
onUpgrade();
|
||||
} else if (version < lastVersion) {
|
||||
onDowngrade();
|
||||
}
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, PREFERENCES_VERSION);
|
||||
switch (PREFERENCES_VERSION) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
PreferencesUtils.setInt(R.string.prefs_last_version_key, version);
|
||||
for (int i = 1; i <= version; i++) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
break;
|
||||
case 2:
|
||||
upgradeFrom1to2();
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Not implemented: upgrade to " + version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeFrom0to1() {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout());
|
||||
String preferenceValue = PreferencesUtils.getString(R.string.stats_custom_layouts_key, "");
|
||||
if (preferenceValue.isEmpty()) {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeFrom1to2() {
|
||||
String csvVersion1CustomLayout = PreferencesUtils.getString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
ArrayList<String> parts = new ArrayList<>();
|
||||
Collections.addAll(parts, csvVersion1CustomLayout.split(CsvLayoutUtils.ITEM_SEPARATOR));
|
||||
|
||||
if (parts.size() < 2) {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parts.get(1).matches("\\d+")) {
|
||||
parts.add(1, String.valueOf(PreferencesUtils.getLayoutColumnsByDefault()));
|
||||
}
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, parts.stream().collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR)));
|
||||
}
|
||||
|
||||
private void onDowngrade() {
|
||||
PreferencesUtils.setString(R.string.stats_custom_layouts_key, PreferencesUtils.buildDefaultLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
@@ -34,17 +35,16 @@ import java.time.Duration;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
import de.dennisguse.opentracks.util.CsvConstants;
|
||||
import de.dennisguse.opentracks.util.CsvLayoutUtils;
|
||||
import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -57,6 +57,8 @@ public class PreferencesUtils {
|
||||
|
||||
private final static String TAG = PreferencesUtils.class.getSimpleName();
|
||||
|
||||
private static final int PREFERENCES_VERSION = 2;
|
||||
|
||||
private PreferencesUtils() {
|
||||
}
|
||||
|
||||
@@ -71,7 +73,7 @@ public class PreferencesUtils {
|
||||
PreferencesUtils.resources = resources;
|
||||
PreferencesUtils.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
PreferencesOpenHelper.newInstance().checkForUpgrade();
|
||||
PreferencesOpenHelper.newInstance(PREFERENCES_VERSION).check();
|
||||
}
|
||||
|
||||
public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener changeListener) {
|
||||
@@ -546,12 +548,8 @@ public class PreferencesUtils {
|
||||
return getDefaultExportDirectoryUri(context) != null;
|
||||
}
|
||||
|
||||
public static int getLayoutColumns() {
|
||||
return getInt(R.string.stats_custom_layout_columns_key, resources.getInteger(R.integer.stats_custom_layout_columns_default));
|
||||
}
|
||||
|
||||
public static void setLayoutColumns(int columns) {
|
||||
setInt(R.string.stats_custom_layout_columns_key, columns);
|
||||
public static int getLayoutColumnsByDefault() {
|
||||
return resources.getInteger(R.integer.stats_custom_layout_columns_default);
|
||||
}
|
||||
|
||||
private static List<TypedArray> getMultiTypedArray(String key) {
|
||||
@@ -576,46 +574,84 @@ public class PreferencesUtils {
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
static String buildDefaultLayout() {
|
||||
private static String buildDefaultFields() {
|
||||
List<TypedArray> fieldsArrays = getMultiTypedArray("stats_custom_layout_fields_default_value");
|
||||
return resources.getString(R.string.default_activity_default) + CsvConstants.LINE_SEPARATOR
|
||||
+ fieldsArrays.stream().map(i -> i.getString(0) + CsvConstants.ITEM_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
|
||||
+ CsvConstants.LINE_SEPARATOR;
|
||||
return fieldsArrays.stream().map(i -> i.getString(0) + CsvLayoutUtils.PROPERTY_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
|
||||
+ CsvLayoutUtils.ITEM_SEPARATOR;
|
||||
}
|
||||
|
||||
static String buildDefaultLayout() {
|
||||
return resources.getString(R.string.stats_custom_layout_default_layout) + CsvLayoutUtils.ITEM_SEPARATOR + getLayoutColumnsByDefault() + CsvLayoutUtils.ITEM_SEPARATOR + buildDefaultFields();
|
||||
}
|
||||
|
||||
public static String getDefaultLayoutName() {
|
||||
return resources.getString(R.string.stats_custom_layout_default_layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
String csvCustomLayout = getString(R.string.stats_custom_layout_fields_key, buildDefaultLayout());
|
||||
List<String> csvParts = Arrays.asList(csvCustomLayout.split(CsvConstants.LINE_SEPARATOR));
|
||||
Layout layout = new Layout(csvParts.get(0));
|
||||
for (int i = 1; i < csvParts.size(); i++) {
|
||||
String[] fieldParts = csvParts.get(i).split(CsvConstants.ITEM_SEPARATOR);
|
||||
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)));
|
||||
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 layout;
|
||||
for (String line : csvLines) {
|
||||
Layout layout = Layout.fromCsv(line, resources);
|
||||
if (layout.sameName(layoutSelected)) {
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
|
||||
return Layout.fromCsv(csvLines[0], resources);
|
||||
}
|
||||
|
||||
public static void setCustomLayout(Layout layout) {
|
||||
List<DataField> fields = layout.getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
public static void updateCustomLayouts(@NonNull List<Layout> layouts) {
|
||||
setString(R.string.stats_custom_layouts_key, layouts.stream().map(Layout::toCsv).collect(Collectors.joining(CsvLayoutUtils.LINE_SEPARATOR)));
|
||||
}
|
||||
|
||||
public static void updateCustomLayout(@NonNull Layout layout) {
|
||||
List<Layout> preferenceLayouts = PreferencesUtils.getAllCustomLayouts();
|
||||
Optional<Layout> layoutToBeUpdated = preferenceLayouts.stream().filter(l -> l.sameName(layout)).findFirst();
|
||||
if (layoutToBeUpdated.isPresent()) {
|
||||
layoutToBeUpdated.get().replaceAllFields(layout.getFields());
|
||||
layoutToBeUpdated.get().setColumnsPerRow(layout.getColumnsPerRow());
|
||||
PreferencesUtils.updateCustomLayouts(preferenceLayouts);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCustomLayout(@NonNull String layoutName) {
|
||||
String newLayoutCsv = layoutName + CsvLayoutUtils.ITEM_SEPARATOR + getLayoutColumnsByDefault() + CsvLayoutUtils.ITEM_SEPARATOR + buildDefaultFields();
|
||||
String customLayoutCsv = getString(R.string.stats_custom_layouts_key, buildDefaultLayout()) + CsvLayoutUtils.LINE_SEPARATOR + newLayoutCsv;
|
||||
setString(R.string.stats_custom_layouts_key, customLayoutCsv);
|
||||
}
|
||||
|
||||
public static void setDefaultLayout(String layoutName) {
|
||||
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
|
||||
}
|
||||
|
||||
public static List<Layout> getAllCustomLayouts() {
|
||||
List<Layout> layouts = 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));
|
||||
}
|
||||
|
||||
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR))
|
||||
+ CsvConstants.LINE_SEPARATOR;
|
||||
setString(R.string.stats_custom_layout_fields_key, csv);
|
||||
return layouts;
|
||||
}
|
||||
|
||||
public static List<String> getAllCustomLayoutNames() {
|
||||
return getAllCustomLayouts().stream().map(Layout::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void resetCustomLayoutPreferences() {
|
||||
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layout_fields_key))) {
|
||||
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layouts_key))) {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(resources.getString(R.string.stats_custom_layout_fields_key));
|
||||
editor.commit();
|
||||
}
|
||||
if (sharedPreferences.contains(resources.getString(R.string.stats_custom_layout_columns_key))) {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(resources.getString(R.string.stats_custom_layout_columns_key));
|
||||
editor.remove(resources.getString(R.string.stats_custom_layouts_key));
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
+20
-16
@@ -18,18 +18,20 @@ import java.util.stream.IntStream;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutAdapter;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutEditAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.content.data.DataField;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutBinding;
|
||||
import de.dennisguse.opentracks.util.StatisticsUtils;
|
||||
|
||||
public class SettingsCustomLayoutActivity extends AbstractActivity implements SettingsCustomLayoutAdapter.SettingsCustomLayoutItemClickListener {
|
||||
public class SettingsCustomLayoutEditActivity extends AbstractActivity implements SettingsCustomLayoutEditAdapter.SettingsCustomLayoutItemClickListener {
|
||||
|
||||
public static final String EXTRA_LAYOUT = "extraLayout";
|
||||
private ActivitySettingsCustomLayoutBinding viewBinding;
|
||||
private GridLayoutManager gridLayoutManager;
|
||||
private SettingsCustomLayoutAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutAdapter adapterFieldsHidden;
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsVisible;
|
||||
private SettingsCustomLayoutEditAdapter adapterFieldsHidden;
|
||||
private String profile;
|
||||
private Layout layoutFieldsVisible;
|
||||
private Layout layoutFieldsHidden;
|
||||
private int numColumns;
|
||||
@@ -38,12 +40,13 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
// Recycler view with visible stats.
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutAdapter(this, this, layoutFieldsVisible);
|
||||
Layout layout = getIntent().getParcelableExtra(EXTRA_LAYOUT);
|
||||
profile = layout.getName();
|
||||
layoutFieldsVisible = StatisticsUtils.filterVisible(layout, true);
|
||||
adapterFieldsVisible = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsVisible);
|
||||
|
||||
numColumns = PreferencesUtils.getLayoutColumns();
|
||||
numColumns = layout.getColumnsPerRow();
|
||||
RecyclerView recyclerViewVisible = viewBinding.recyclerViewVisible;
|
||||
gridLayoutManager = new GridLayoutManager(this, numColumns);
|
||||
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@@ -86,7 +89,6 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
numColumns = position + 1;
|
||||
gridLayoutManager.setSpanCount(numColumns);
|
||||
PreferencesUtils.setLayoutColumns(position + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,24 +96,26 @@ public class SettingsCustomLayoutActivity extends AbstractActivity implements Se
|
||||
|
||||
}
|
||||
});
|
||||
viewBinding.spinnerOptions.setSelection(PreferencesUtils.getLayoutColumns() - 1);
|
||||
viewBinding.spinnerOptions.setSelection(numColumns - 1);
|
||||
|
||||
// Recycler view with not visible stats.
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(PreferencesUtils.getCustomLayout(), false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutAdapter(this, this, layoutFieldsHidden);
|
||||
layoutFieldsHidden = StatisticsUtils.filterVisible(layout, false);
|
||||
adapterFieldsHidden = new SettingsCustomLayoutEditAdapter(this, this, layoutFieldsHidden);
|
||||
RecyclerView recyclerViewNotVisible = viewBinding.recyclerViewNotVisible;
|
||||
recyclerViewNotVisible.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerViewNotVisible.setAdapter(adapterFieldsHidden);
|
||||
|
||||
setTitle(profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (!layoutFieldsVisible.getFields().isEmpty() || !layoutFieldsHidden.getFields().isEmpty()) {
|
||||
Layout newLayout = new Layout(layoutFieldsVisible.getProfile());
|
||||
newLayout.addFields(layoutFieldsVisible.getFields());
|
||||
newLayout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.setCustomLayout(newLayout);
|
||||
Layout layout = new Layout(profile, numColumns);
|
||||
layout.addFields(layoutFieldsVisible.getFields());
|
||||
layout.addFields(layoutFieldsHidden.getFields());
|
||||
PreferencesUtils.updateCustomLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package de.dennisguse.opentracks.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import de.dennisguse.opentracks.AbstractActivity;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.adapters.SettingsCustomLayoutListAdapter;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
import de.dennisguse.opentracks.databinding.ActivitySettingsCustomLayoutListBinding;
|
||||
import de.dennisguse.opentracks.util.RecyclerViewSwipeDeleteCallback;
|
||||
|
||||
public class SettingsCustomLayoutListActivity extends AbstractActivity implements SettingsCustomLayoutListAdapter.SettingsCustomLayoutProfileClickListener {
|
||||
|
||||
private ActivitySettingsCustomLayoutListBinding viewBinding;
|
||||
private SettingsCustomLayoutListAdapter adapter;
|
||||
private RecyclerView recyclerView;
|
||||
private View addProfileLayout;
|
||||
private TextInputEditText addProfileEditText;
|
||||
private TextInputLayout addProfileInputLayout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(getString(R.string.custom_layout_list_title));
|
||||
|
||||
adapter = new SettingsCustomLayoutListAdapter(this, this);
|
||||
recyclerView = viewBinding.recyclerView;
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
Button cancelButton = findViewById(R.id.custom_layout_list_cancel_button);
|
||||
Button okButton = findViewById(R.id.custom_layout_list_ok_button);
|
||||
|
||||
cancelButton.setOnClickListener(view -> clearAndHideEditLayout());
|
||||
|
||||
okButton.setEnabled(false);
|
||||
okButton.setOnClickListener(view -> {
|
||||
PreferencesUtils.addCustomLayout(addProfileEditText.getText().toString());
|
||||
clearAndHideEditLayout();
|
||||
adapter.reloadLayouts();
|
||||
});
|
||||
|
||||
addProfileLayout = findViewById(R.id.custom_layout_list_add_linear_layout);
|
||||
addProfileInputLayout = findViewById(R.id.custom_layout_list_input_layout);
|
||||
addProfileEditText = findViewById(R.id.custom_layout_list_edit_name);
|
||||
addProfileEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (s == null || s.toString().isEmpty()) {
|
||||
okButton.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (adapter.getLayouts().stream().anyMatch(layout -> layout.sameName(s.toString()))) {
|
||||
okButton.setEnabled(false);
|
||||
addProfileInputLayout.setError(getString(R.string.custom_layout_list_edit_already_exists));
|
||||
} else {
|
||||
okButton.setEnabled(true);
|
||||
addProfileInputLayout.setError("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
|
||||
RecyclerViewSwipeDeleteCallback recyclerViewSwipeDeleteCallback = new RecyclerViewSwipeDeleteCallback(this) {
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
// When there's only one profile it cannot be deleted (so, "disable" movements: drag flags and swipe flags).
|
||||
return adapter.getItemCount() > 1 ? makeMovementFlags(0, ItemTouchHelper.LEFT) : makeMovementFlags(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
final Layout item = adapter.getLayouts().get(position);
|
||||
|
||||
adapter.removeLayout(position);
|
||||
|
||||
Snackbar snackbar = Snackbar.make(recyclerView, getString(R.string.custom_layout_list_layout_removed), Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(getString(R.string.generic_undo).toUpperCase(), view -> {
|
||||
adapter.restoreItem(item, position);
|
||||
recyclerView.scrollToPosition(position);
|
||||
});
|
||||
|
||||
snackbar.show();
|
||||
}
|
||||
};
|
||||
|
||||
ItemTouchHelper itemTouchhelper = new ItemTouchHelper(recyclerViewSwipeDeleteCallback);
|
||||
itemTouchhelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
adapter.reloadLayouts();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
PreferencesUtils.getCustomLayout();
|
||||
viewBinding = ActivitySettingsCustomLayoutListBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.custom_layout_edit, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.custom_layout_edit_add_profile) {
|
||||
addProfileLayout.setVisibility(View.VISIBLE);
|
||||
addProfileEditText.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(addProfileEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettingsCustomLayoutProfileClicked(@NonNull Layout layout) {
|
||||
Intent intent = new Intent(this, SettingsCustomLayoutEditActivity.class);
|
||||
intent.putExtra(SettingsCustomLayoutEditActivity.EXTRA_LAYOUT, layout);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void clearAndHideEditLayout() {
|
||||
addProfileEditText.setText("");
|
||||
addProfileInputLayout.setError("");
|
||||
addProfileLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class UserInterfaceSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key));
|
||||
customLayoutPreference.setOnPreferenceClickListener((preference) -> {
|
||||
Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class);
|
||||
Intent intent = new Intent(getContext(), SettingsCustomLayoutListActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
public final class CsvConstants {
|
||||
private CsvConstants() {
|
||||
|
||||
}
|
||||
|
||||
public static final String LINE_SEPARATOR = ";";
|
||||
public static final String ITEM_SEPARATOR = ",";
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.navigation.NavigationBarView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class CsvLayoutUtils {
|
||||
public static final String LINE_SEPARATOR = "\n";
|
||||
public static final String ITEM_SEPARATOR = ";";
|
||||
public static final String PROPERTY_SEPARATOR = ",";
|
||||
|
||||
private CsvLayoutUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param csvLine Layout description in a CSV format.
|
||||
* @return All CSV parts from the csvLine or null if it's malformed.
|
||||
*/
|
||||
@Nullable
|
||||
public static List<String> getCsvLineParts(String csvLine) {
|
||||
if (csvLine == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The line must have three items, at least: layout's name, number of columns and one field.
|
||||
List<String> csvParts = Arrays.asList(csvLine.split(ITEM_SEPARATOR));
|
||||
if (csvParts.size() < 3 || !hasValue(csvParts.get(0)) || !isInt(csvParts.get(1))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return csvParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param csvField Layout's field in a CSV format.
|
||||
* @return List of field parts or null if it's malformed.
|
||||
*/
|
||||
@Nullable
|
||||
public static String[] getCsvFieldParts(@Nullable String csvField) {
|
||||
if (csvField == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Field must have three items: key, is visible (0 or 1), is primary (0 or 1).
|
||||
String[] fieldParts = csvField.split(CsvLayoutUtils.PROPERTY_SEPARATOR);
|
||||
if (fieldParts.length < 3 || !hasValue(fieldParts[0]) || !hasZeroOneValue(fieldParts[1]) || !hasZeroOneValue(fieldParts[2])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldParts;
|
||||
}
|
||||
|
||||
private static boolean hasValue(String value) {
|
||||
return value != null && !value.isEmpty();
|
||||
}
|
||||
|
||||
private static boolean isInt(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasZeroOneValue(String value) {
|
||||
return isInt(value) && (Integer.parseInt(value) == 0 || Integer.parseInt(value) == 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
abstract public class RecyclerViewSwipeDeleteCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
private final Paint clearPaint;
|
||||
private final ColorDrawable background;
|
||||
private final int backgroundColor;
|
||||
private final Drawable deleteDrawable;
|
||||
private final int intrinsicWidth;
|
||||
private final int intrinsicHeight;
|
||||
|
||||
public RecyclerViewSwipeDeleteCallback(Context context) {
|
||||
background = new ColorDrawable();
|
||||
backgroundColor = context.getResources().getColor(R.color.colorBackgroundDelete);
|
||||
clearPaint = new Paint();
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
deleteDrawable = ContextCompat.getDrawable(context, R.drawable.ic_delete_forever_24dp);
|
||||
deleteDrawable.setTint(context.getResources().getColor(R.color.colorForegroundDelete));
|
||||
intrinsicWidth = deleteDrawable.getIntrinsicWidth();
|
||||
intrinsicHeight = deleteDrawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
|
||||
View itemView = viewHolder.itemView;
|
||||
int itemHeight = itemView.getHeight();
|
||||
|
||||
boolean isCancelled = dX == 0 && !isCurrentlyActive;
|
||||
|
||||
if (isCancelled) {
|
||||
clearCanvas(c, itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
return;
|
||||
}
|
||||
|
||||
background.setColor(backgroundColor);
|
||||
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
|
||||
background.draw(c);
|
||||
|
||||
int deleteIconTop = itemView.getTop() + (itemHeight - intrinsicHeight) / 2;
|
||||
int deleteIconMargin = (itemHeight - intrinsicHeight) / 2;
|
||||
int deleteIconLeft = itemView.getRight() - deleteIconMargin - intrinsicWidth;
|
||||
int deleteIconRight = itemView.getRight() - deleteIconMargin;
|
||||
int deleteIconBottom = deleteIconTop + intrinsicHeight;
|
||||
|
||||
deleteDrawable.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom);
|
||||
deleteDrawable.draw(c);
|
||||
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
private void clearCanvas(Canvas c, Float left, Float top, Float right, Float bottom) {
|
||||
c.drawRect(left, top, right, bottom, clearPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
return 0.7f;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class StatisticsUtils {
|
||||
}
|
||||
|
||||
public static Layout filterVisible(Layout layout, boolean visible) {
|
||||
Layout result = new Layout(layout.getProfile());
|
||||
Layout result = new Layout(layout.getName());
|
||||
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/toolbar" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/custom_layout_list_add_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="50dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/custom_layout_list_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
android:layout_margin="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/custom_layout_list_edit_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textCapWords"
|
||||
android:imeOptions="actionDone"
|
||||
android:hint="@string/custom_layout_list_edit_name" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/custom_layout_list_bottom_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/custom_layout_list_cancel_button"
|
||||
style="@style/BottomButton"
|
||||
android:text="@android:string/cancel" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/custom_layout_list_ok_button"
|
||||
style="@style/BottomButton"
|
||||
android:text="@android:string/ok" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/HorizontalLine" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/StatsCardView">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/custom_layout_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Profile/Custom Layout Title"
|
||||
style="@style/LargeLabel"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/custom_layout_edit_add_profile"
|
||||
android:icon="@drawable/ic_baseline_add_24"
|
||||
android:orderInCategory="1"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_add_custom_layout" />
|
||||
|
||||
</menu>
|
||||
@@ -15,6 +15,11 @@ limitations under the License.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/track_detail_menu_show_on_map"
|
||||
android:icon="@drawable/ic_map_24dp"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_show_on_map" />
|
||||
<item
|
||||
android:id="@+id/track_detail_insert_marker"
|
||||
android:icon="@drawable/ic_marker_add_24dp"
|
||||
@@ -22,10 +27,11 @@ limitations under the License.
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_insert_marker" />
|
||||
<item
|
||||
android:id="@+id/track_detail_menu_show_on_map"
|
||||
android:icon="@drawable/ic_map_24dp"
|
||||
android:id="@+id/track_detail_menu_select_layout"
|
||||
android:icon="@drawable/ic_ui"
|
||||
android:orderInCategory="2"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/menu_show_on_map" />
|
||||
android:title="@string/menu_select_layout" />
|
||||
<item
|
||||
android:id="@+id/track_detail_markers"
|
||||
android:icon="@drawable/ic_marker_show_24dp"
|
||||
|
||||
@@ -30,6 +30,9 @@ limitations under the License.
|
||||
<color name="colorAccent">@android:color/holo_orange_dark</color>
|
||||
<color name="colorAccentTransparent">#55FF8800</color>
|
||||
|
||||
<color name="colorBackgroundDelete">#F44336</color>
|
||||
<color name="colorForegroundDelete">#FEFEFE</color>
|
||||
|
||||
<color name="recording_text">#FFBE161B</color> <!-- red -->
|
||||
<color name="stats_separator">#99999999</color> <!-- light gray -->
|
||||
<color name="text_gradient_center">#B2000000</color> <!-- black 70% opacity -->
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<string name="settings_import" translatable="false">settingsImport</string>
|
||||
<string name="settings_export" translatable="false">settingsExport</string>
|
||||
|
||||
<!-- Stats layout customization default layout -->
|
||||
<string name="default_activity_key" translatable="false">defaultActivity</string>
|
||||
<string name="default_activity_default" translatable="false">@string/activity_type_unknown</string>
|
||||
<string-array name="activity_types">
|
||||
@@ -310,11 +311,11 @@
|
||||
<string name="stats_custom_layout_key" translatable="false">statsCustomLayoutKey</string>
|
||||
|
||||
<!-- Stats layout customization: number of columns per row -->
|
||||
<string name="stats_custom_layout_columns_key" translatable="false">statsCustomLayoutColumnsKey</string>
|
||||
<integer name="stats_custom_layout_columns_default" translatable="false">2</integer>
|
||||
|
||||
<!-- Stats layout customization: key -->
|
||||
<string name="stats_custom_layout_fields_key" translatable="false">statsCustomLayoutFieldsKey</string>
|
||||
<string name="stats_custom_layouts_key" translatable="false">statsCustomLayoutFieldsKey</string>
|
||||
<string name="stats_custom_layout_selected_layout_key" translatable="false">statsCustomSelectedLayoutKey</string>
|
||||
<!-- Stats keys -->
|
||||
<string name="stats_custom_layout_distance_key" translatable="false">distance</string>
|
||||
<string name="stats_custom_layout_total_time_key" translatable="false">total_time</string>
|
||||
|
||||
@@ -238,6 +238,7 @@ limitations under the License.
|
||||
<string name="generic_completed">Completed</string>
|
||||
<string name="generic_click_twice_cancel">Click back again to cancel</string>
|
||||
<string name="generic_choose_an_option">Choose an option</string>
|
||||
<string name="generic_undo">Undo</string>
|
||||
<!-- Gps -->
|
||||
<string name="gps_starting">Starting GPS</string>
|
||||
<string name="gps_wait_for_better_signal">Waiting for a better GPS signal</string>
|
||||
@@ -298,11 +299,13 @@ limitations under the License.
|
||||
<string name="menu_select_all">Select all</string>
|
||||
<string name="menu_settings">Settings</string>
|
||||
<string name="menu_share">Share</string>
|
||||
<string name="menu_select_layout">Select Layout</string>
|
||||
<string name="menu_show_on_map">Show on map</string>
|
||||
<string name="menu_start_gps">Start GPS</string>
|
||||
<string name="menu_stop_gps">Stop GPS</string>
|
||||
<string name="menu_voice_rate">Voice speed</string>
|
||||
<string name="menu_resume_track">Resume track</string>
|
||||
<string name="menu_add_custom_layout">New Layout</string>
|
||||
<!-- OpenTracks -->
|
||||
<string name="recording_service">Recording service</string>
|
||||
<string name="delete_service">Deletion service</string>
|
||||
@@ -395,6 +398,13 @@ limitations under the License.
|
||||
<string name="settings_announcements_distance_frequency">Distance interval</string>
|
||||
|
||||
<string name="settings_reset">Reset</string>
|
||||
<!-- Custom Layout -->
|
||||
<string name="custom_layout_list_title">Layouts</string>
|
||||
<string name="custom_layout_list_edit_name">Type layout\'s name to add</string>
|
||||
<string name="custom_layout_list_edit_already_exists">Layout\'s name already exists</string>
|
||||
<string name="custom_layout_list_layout_removed">Layout was removed</string>
|
||||
<string name="custom_layout_select_layout">Select Layout</string>
|
||||
<string name="stats_custom_layout_default_layout">Default Layout</string>
|
||||
<!-- Custom Layout Reset -->
|
||||
<string name="settings_layout_reset">Reset Custom Layout</string>
|
||||
<string name="settings_layout_reset_confirm_message">All layout settings will be reverted to the default values. This will not delete any tracks on the device.</string>
|
||||
|
||||
Reference in New Issue
Block a user