Refactor: RecordingLayout.

This commit is contained in:
Dennis Guse
2026-01-07 17:15:22 +01:00
parent 54844b9a9a
commit e9deebea6c
10 changed files with 113 additions and 200 deletions
@@ -44,6 +44,7 @@ import de.dennisguse.opentracks.services.TrackRecordingService;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.intervals.IntervalsFragment;
import de.dennisguse.opentracks.ui.markers.MarkerEditActivity;
import de.dennisguse.opentracks.ui.markers.MarkerListActivity;
@@ -285,8 +286,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
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)));
List<String> layoutNames = PreferencesUtils.getAllCustomLayouts().stream()
.map(RecordingLayout::getName)
.toList();
builder.setTitle(getString(R.string.custom_layout_select_layout)).setItems(layoutNames.toArray(new String[0]), (dialog, which) -> PreferencesUtils.setSelectedLayoutName(layoutNames.get(which)));
builder.create().show();
return true;
}
@@ -56,7 +56,7 @@ class PreferencesOpenHelper {
}
if (!parts.get(1).matches("\\d+")) {
parts.add(1, String.valueOf(PreferencesUtils.getLayoutColumnsByDefault()));
parts.add(1, String.valueOf(2));
}
PreferencesUtils.setString(R.string.stats_custom_layouts_key, String.join(CsvLayoutUtils.ITEM_SEPARATOR, parts));
}
@@ -16,17 +16,16 @@
package de.dennisguse.opentracks.settings;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
@@ -34,12 +33,11 @@ import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.ActivityType;
@@ -51,7 +49,7 @@ import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.TrackFilenameGenerator;
import de.dennisguse.opentracks.sensors.SensorType;
import de.dennisguse.opentracks.ui.customRecordingLayout.CsvLayoutUtils;
import de.dennisguse.opentracks.ui.customRecordingLayout.DataField;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayout;
import de.dennisguse.opentracks.ui.customRecordingLayout.RecordingLayoutIO;
@@ -808,72 +806,54 @@ public class PreferencesUtils {
return getDefaultExportDirectoryUri() != null;
}
public static int getLayoutColumnsByDefault() {
return resources.getInteger(R.integer.stats_custom_layout_columns_default);
}
public static RecordingLayout getDefaultLayout() {
//TODO We could merge this with Mapping, right?
List<DataField> defaultFields = List.of(
new DataField(resources.getString(R.string.stats_custom_layout_distance_key), true, true, false),
new DataField(resources.getString(R.string.stats_custom_layout_total_time_key), true, true, false),
new DataField(resources.getString(R.string.stats_custom_layout_speed_key), true, true, false),
new DataField(resources.getString(R.string.stats_custom_layout_moving_time_key), true, true, false),
new DataField(resources.getString(R.string.stats_custom_layout_average_speed_key), true, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_max_speed_key), true, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_average_moving_speed_key), true, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_altitude_key), true, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_gain_key), true, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_loss_key), true, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_pace_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_average_moving_pace_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_average_pace_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_average_segment_pace_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_elapsed_segment_time_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_fastest_pace_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_coordinates_key), false, false, true),
new DataField(resources.getString(R.string.stats_custom_layout_heart_rate_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_cadence_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_power_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_clock_key), false, false, false),
new DataField(resources.getString(R.string.stats_custom_layout_temperature_key), false, false, false)
);
private static List<TypedArray> getLayoutDataFields() {
return Stream.of(
R.array.stats_custom_layout_fields_default_value_0,
R.array.stats_custom_layout_fields_default_value_1,
R.array.stats_custom_layout_fields_default_value_2,
R.array.stats_custom_layout_fields_default_value_3,
R.array.stats_custom_layout_fields_default_value_4,
R.array.stats_custom_layout_fields_default_value_5,
R.array.stats_custom_layout_fields_default_value_6,
R.array.stats_custom_layout_fields_default_value_7,
R.array.stats_custom_layout_fields_default_value_8,
R.array.stats_custom_layout_fields_default_value_9,
R.array.stats_custom_layout_fields_default_value_10,
R.array.stats_custom_layout_fields_default_value_11,
R.array.stats_custom_layout_fields_default_value_12,
R.array.stats_custom_layout_fields_default_value_13,
R.array.stats_custom_layout_fields_default_value_14,
R.array.stats_custom_layout_fields_default_value_15,
R.array.stats_custom_layout_fields_default_value_16,
R.array.stats_custom_layout_fields_default_value_17,
R.array.stats_custom_layout_fields_default_value_18,
R.array.stats_custom_layout_fields_default_value_19,
R.array.stats_custom_layout_fields_default_value_20,
R.array.stats_custom_layout_fields_default_value_21
).map(id -> resources.obtainTypedArray(id))
.toList();
}
@SuppressLint("ResourceType")
private static String buildDefaultFields() {
List<TypedArray> fieldsArrays = getLayoutDataFields();
return fieldsArrays.stream().map(i -> i.getString(0) + CsvLayoutUtils.PROPERTY_SEPARATOR + i.getString(1)).collect(Collectors.joining(CsvLayoutUtils.ITEM_SEPARATOR))
+ CsvLayoutUtils.ITEM_SEPARATOR;
RecordingLayout defaultLayout = new RecordingLayout(resources.getString(R.string.stats_custom_layout_default_layout), 2);
defaultLayout.addFields(defaultFields);
return defaultLayout;
}
static String buildDefaultLayout() {
return resources.getString(R.string.stats_custom_layout_default_layout) + CsvLayoutUtils.ITEM_SEPARATOR + getLayoutColumnsByDefault() + CsvLayoutUtils.ITEM_SEPARATOR + buildDefaultFields();
return RecordingLayoutIO.toCSV(List.of(getDefaultLayout()));
}
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 RecordingLayout getCustomLayout() {
String selectedLayout = getSelectedLayoutName();
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 RecordingLayoutIO.fromCsv(csvLines[0], resources);
}
for (String line : csvLines) {
RecordingLayout recordingLayout = RecordingLayoutIO.fromCsv(line, resources);
if (recordingLayout.sameName(layoutSelected)) {
return recordingLayout;
}
}
List<RecordingLayout> recordingLayouts = RecordingLayoutIO.fromCsv(csvCustomLayouts, resources);
return RecordingLayoutIO.fromCsv(csvLines[0], resources);
RecordingLayout fallback = !recordingLayouts.isEmpty() ? recordingLayouts.get(0) : getDefaultLayout();
return recordingLayouts.stream()
.filter(it -> it.sameName(selectedLayout))
.findFirst()
.orElse(fallback);
}
public static void updateCustomLayouts(@NonNull List<RecordingLayout> recordingLayouts) {
@@ -891,26 +871,28 @@ public class PreferencesUtils {
}
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);
ArrayList<RecordingLayout> currentLayouts = new ArrayList<>(PreferencesUtils.getAllCustomLayouts());
RecordingLayout newCustomLayout = RecordingLayout.fromDefault(layoutName);
currentLayouts.add(newCustomLayout);
setString(R.string.stats_custom_layouts_key, RecordingLayoutIO.toCSV(currentLayouts));
}
public static void setDefaultLayout(String layoutName) {
@Nullable
public static String getSelectedLayoutName() {
return getString(R.string.stats_custom_layout_selected_layout_key, null);
}
public static void setSelectedLayoutName(String layoutName) {
setString(R.string.stats_custom_layout_selected_layout_key, layoutName);
}
public static List<RecordingLayout> getAllCustomLayouts() {
String csvCustomLayout = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
String[] csvLines = csvCustomLayout.split(CsvLayoutUtils.LINE_SEPARATOR);
String csvCustomLayouts = getString(R.string.stats_custom_layouts_key, buildDefaultLayout());
return Arrays.stream(csvLines).map(line -> RecordingLayoutIO.fromCsv(line, resources)).toList();
}
public static List<String> getAllCustomLayoutNames() {
return getAllCustomLayouts().stream()
.map(RecordingLayout::getName)
.toList();
return RecordingLayoutIO.fromCsv(csvCustomLayouts, resources);
}
public static void resetCustomLayoutPreferences() {
@@ -5,13 +5,26 @@ import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
//TODO refactor; rename ot CsvUtils
public final class CsvLayoutUtils {
@Deprecated //TODO private | package private
public static final String LINE_SEPARATOR = "\n";
@Deprecated //TODO private | package private
public static final String ITEM_SEPARATOR = ";";
@Deprecated //TODO private | package private
public static final String PROPERTY_SEPARATOR = ",";
private CsvLayoutUtils() {
}
@Nullable
public static List<String> getCsvLines(String csv) {
if (csv == null) {
return null;
}
return Arrays.stream(csv.split(CsvLayoutUtils.LINE_SEPARATOR)).toList();
}
/**
@@ -17,9 +17,18 @@ public class RecordingLayout implements Parcelable {
private int columnsPerRow;
private final List<DataField> dataFields = new ArrayList<>();
public static RecordingLayout fromDefault(String newName) {
RecordingLayout defaultLayout = PreferencesUtils.getDefaultLayout();
RecordingLayout recordingLayout = new RecordingLayout(newName, defaultLayout.getColumnsPerRow());
recordingLayout.addFields(defaultLayout.getFields());
return recordingLayout;
}
@Deprecated //TODO please set columnsPerRow explicitly
public RecordingLayout(String name) {
this.name = name;
this.columnsPerRow = PreferencesUtils.getLayoutColumnsByDefault();
this.columnsPerRow = PreferencesUtils.getDefaultLayout().getColumnsPerRow();
}
public RecordingLayout(String name, int columnsPerRow) {
@@ -90,11 +99,11 @@ public class RecordingLayout implements Parcelable {
}
public boolean sameName(RecordingLayout recordingLayout) {
return this.name.equalsIgnoreCase(recordingLayout.getName());
return sameName(recordingLayout.getName());
}
public boolean sameName(String name) {
return this.name.equalsIgnoreCase(name);
return this.name.equals(name);
}
public String toCsv() {
@@ -19,11 +19,17 @@ public class RecordingLayoutIO {
private static final String YES_VALUE = "1";
private static final String NOT_VALUE = "0";
public static RecordingLayout fromCsv(@NonNull String csvLine, @NonNull Resources resources) {
public static List<RecordingLayout> fromCsv(@NonNull String csv, @NonNull Resources resources) {
return CsvLayoutUtils.getCsvLines(csv).stream()
.map(line -> RecordingLayoutIO.fromCsvLine(line, resources))
.toList();
}
public static RecordingLayout fromCsvLine(@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 RecordingLayout(PreferencesUtils.getDefaultLayoutName());
return PreferencesUtils.getDefaultLayout();
}
RecordingLayout recordingLayout = new RecordingLayout(csvParts.get(0), Integer.parseInt(csvParts.get(1)));