forked from upstream-mirrors/OpenTracks
Recording stats layout customizable.
Users can select the order, visibility and importance (primary or secondary) of all stats for recording layout from the Settings. Also, users can select the number of columns per row in the recording layout: 1, 2 or 3. Finally, show altitude/coordinate from settings have been removed (not needed anymore). Fixes #659. Fixes #302. Fixes #820.
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
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,41 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
|
||||
public class PreferencesOpenHelper {
|
||||
private final Context context;
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
private static final int PREFERENCES_VERSION = 1;
|
||||
|
||||
private PreferencesOpenHelper(Context context, SharedPreferences sharedPreferences) {
|
||||
this.context = context;
|
||||
this.sharedPreferences = sharedPreferences;
|
||||
}
|
||||
|
||||
public static PreferencesOpenHelper newInstance(Context context, SharedPreferences sharedPreferences) {
|
||||
return new PreferencesOpenHelper(context, sharedPreferences);
|
||||
}
|
||||
|
||||
public void checkForUpgrade() {
|
||||
int lastVersion = PreferencesUtils.getInt(sharedPreferences, context, R.string.prefs_last_version_key, 0);
|
||||
if (PREFERENCES_VERSION > lastVersion) {
|
||||
onUpgrade();
|
||||
}
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
PreferencesUtils.setInt(sharedPreferences, context, R.string.prefs_last_version_key, PREFERENCES_VERSION);
|
||||
switch (PREFERENCES_VERSION) {
|
||||
case 1:
|
||||
upgradeFrom0to1();
|
||||
}
|
||||
}
|
||||
|
||||
private void upgradeFrom0to1() {
|
||||
PreferencesUtils.setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, PreferencesUtils.buildDefaultLayout(context));
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -26,8 +28,16 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
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.io.file.TrackFileFormat;
|
||||
|
||||
/**
|
||||
@@ -44,7 +54,9 @@ public class PreferencesUtils {
|
||||
|
||||
@Deprecated //Should only be used to get a sharedPreference for more than one interaction!
|
||||
public static SharedPreferences getSharedPreferences(Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
PreferencesOpenHelper.newInstance(context, sharedPreferences).checkForUpgrade();
|
||||
return sharedPreferences;
|
||||
}
|
||||
|
||||
public static String getDefaultActivity(SharedPreferences sharedPreferences, Context context) {
|
||||
@@ -80,7 +92,7 @@ public class PreferencesUtils {
|
||||
return sharedPreferences.getBoolean(getKey(context, keyId), defaultValue);
|
||||
}
|
||||
|
||||
private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
|
||||
static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) {
|
||||
try {
|
||||
return sharedPreferences.getInt(getKey(context, keyId), defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
@@ -123,6 +135,12 @@ public class PreferencesUtils {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
static void setInt(SharedPreferences sharedPreferences, Context context, int keyId, int value) {
|
||||
Editor editor = sharedPreferences.edit();
|
||||
editor.putInt(getKey(context, keyId), value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean isMetricUnits(SharedPreferences sharedPreferences, Context context) {
|
||||
final String STATS_UNIT = context.getString(R.string.stats_units_default);
|
||||
return STATS_UNIT.equals(getString(sharedPreferences, context, R.string.stats_units_key, STATS_UNIT));
|
||||
@@ -196,16 +214,6 @@ public class PreferencesUtils {
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_fullscreen_while_recording_key, DEFAULT);
|
||||
}
|
||||
|
||||
public static boolean isShowStatsAltitude(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_ALTITUDE = context.getResources().getBoolean(R.bool.stats_show_altitude_default);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_grade_altitude_key, STATS_SHOW_ALTITUDE);
|
||||
}
|
||||
|
||||
public static boolean isStatsShowCoordinate(SharedPreferences sharedPreferences, Context context) {
|
||||
final boolean STATS_SHOW_COORDINATE = context.getResources().getBoolean(R.bool.stats_show_coordinate_default);
|
||||
return getBoolean(sharedPreferences, context, R.string.stats_show_coordinate_key, STATS_SHOW_COORDINATE);
|
||||
}
|
||||
|
||||
public static int getVoiceFrequency(SharedPreferences sharedPreferences, Context context) {
|
||||
final int VOICE_FREQUENCY_DEFAULT = Integer.parseInt(context.getResources().getString(R.string.voice_frequency_default));
|
||||
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
||||
@@ -318,4 +326,78 @@ public class PreferencesUtils {
|
||||
public static boolean isDefaultExportDirectoryUri(SharedPreferences sharedPreferences, Context context) {
|
||||
return getDefaultExportDirectoryUri(sharedPreferences, context) != null;
|
||||
}
|
||||
|
||||
public static int getLayoutColumns(SharedPreferences sharedPreferences, Context context) {
|
||||
return getInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, context.getResources().getInteger(R.integer.stats_custom_layout_columns_default));
|
||||
}
|
||||
|
||||
public static void setLayoutColumns(SharedPreferences sharedPreferences, Context context, int columns) {
|
||||
setInt(sharedPreferences, context, R.string.stats_custom_layout_columns_key, columns);
|
||||
}
|
||||
|
||||
private static List<TypedArray> getMultiTypedArray(Context context, String key) {
|
||||
List<TypedArray> typedArrays = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Class<R.array> resource = R.array.class;
|
||||
Field field;
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
field = resource.getField(key + context.getString(R.string.stats_custom_layout_fields_default_value_separator) + i);
|
||||
typedArrays.add(context.getResources().obtainTypedArray(field.getInt(null)));
|
||||
i++;
|
||||
} while (field != null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, e.getMessage());
|
||||
}
|
||||
|
||||
return typedArrays;
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
static String buildDefaultLayout(Context context) {
|
||||
List<TypedArray> fieldsArrays = getMultiTypedArray(context, "stats_custom_layout_fields_default_value");
|
||||
return context.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;
|
||||
}
|
||||
|
||||
public static Layout getCustomLayout(SharedPreferences sharedPreferences, Context context) {
|
||||
String csvCustomLayout = getString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, buildDefaultLayout(context));
|
||||
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(context, fieldParts[0]), fieldParts[1].equals(DataField.YES_VALUE), fieldParts[2].equals(DataField.YES_VALUE), fieldParts[0].equals(context.getString(R.string.stats_custom_layout_coordinates_key)));
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public static void setCustomLayout(SharedPreferences sharedPreferences, Context context, Layout layout) {
|
||||
List<DataField> fields = layout.getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String csv = layout.getProfile() + CsvConstants.LINE_SEPARATOR
|
||||
+ fields.stream().map(DataField::toCsv).collect(Collectors.joining(CsvConstants.LINE_SEPARATOR)) + CsvConstants.LINE_SEPARATOR;
|
||||
setString(sharedPreferences, context, R.string.stats_custom_layout_fields_key, csv);
|
||||
}
|
||||
|
||||
public static void resetCustomLayoutPreferences(Context context) {
|
||||
SharedPreferences settings = getSharedPreferences(context);
|
||||
if (settings.contains(context.getString(R.string.stats_custom_layout_fields_key))) {
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.remove(context.getString(R.string.stats_custom_layout_fields_key));
|
||||
editor.commit();
|
||||
}
|
||||
if (settings.contains(context.getString(R.string.stats_custom_layout_columns_key))) {
|
||||
SharedPreferences.Editor editor = settings.edit();
|
||||
editor.remove(context.getString(R.string.stats_custom_layout_columns_key));
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.dennisguse.opentracks.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Layout;
|
||||
|
||||
public class StatisticsUtils {
|
||||
|
||||
public static String emptyValue(@NonNull Context context, @NonNull String statTitle) {
|
||||
if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_total_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_moving_time_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_pace_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_fastest_pace_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_time);
|
||||
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_distance_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_max_speed_key, statTitle) || PreferencesUtils.isKey(context, R.string.stats_custom_layout_average_moving_speed_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_float);
|
||||
} else if (PreferencesUtils.isKey(context, R.string.stats_custom_layout_coordinates_key, statTitle)) {
|
||||
return context.getString(R.string.stats_empty_value_coordinates);
|
||||
} else {
|
||||
return context.getString(R.string.stats_empty_value_integer);
|
||||
}
|
||||
}
|
||||
|
||||
public static Layout filterVisible(Layout layout, boolean visible) {
|
||||
Layout result = new Layout(layout.getProfile());
|
||||
result.addFields(layout.getFields().stream().filter(f -> f.isVisible() == visible).collect(Collectors.toList()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -124,10 +124,22 @@ public class StringUtils {
|
||||
/**
|
||||
* Formats a coordinate
|
||||
*
|
||||
* @param context the context's object.
|
||||
* @param coordinate the coordinate
|
||||
*/
|
||||
public static String formatCoordinate(double coordinate) {
|
||||
return Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE;
|
||||
public static String formatCoordinate(Context context, double coordinate) {
|
||||
return context.getString(R.string.location_coordinate, Location.convert(coordinate, Location.FORMAT_DEGREES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a complete coordinate (latitude, longitude)
|
||||
*
|
||||
* @param context the context's object.
|
||||
* @param latitude the coordinate's latitude.
|
||||
* @param longitude the coordinate's longitude.
|
||||
*/
|
||||
public static String formatCoordinate(Context context, double latitude, double longitude) {
|
||||
return context.getString(R.string.location_latitude_longitude, Location.convert(latitude, Location.FORMAT_DEGREES), Location.convert(longitude, Location.FORMAT_DEGREES));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +203,33 @@ public class StringUtils {
|
||||
return new Pair<>(context.getString(R.string.time, minutes, seconds), unitString);
|
||||
}
|
||||
|
||||
public static Pair<String, String> getHeartRateParts(Context context, Float heartrate_bpm) {
|
||||
String value = context.getString(R.string.value_none);
|
||||
if (heartrate_bpm != null) {
|
||||
value = StringUtils.formatDecimal(heartrate_bpm, 0);
|
||||
}
|
||||
|
||||
return new Pair<>(value, context.getString(R.string.sensor_unit_beats_per_minute));
|
||||
}
|
||||
|
||||
public static Pair<String, String> getCadenceParts(Context context, Float cadence_rpm) {
|
||||
String value = context.getString(R.string.value_none);
|
||||
if (cadence_rpm != null) {
|
||||
value = StringUtils.formatDecimal(cadence_rpm, 0);
|
||||
}
|
||||
|
||||
return new Pair<>(value, context.getString(R.string.sensor_unit_rounds_per_minute));
|
||||
}
|
||||
|
||||
public static Pair<String, String> getPowerParts(Context context, Float power_w) {
|
||||
String value = context.getString(R.string.value_none);
|
||||
if (power_w != null) {
|
||||
value = StringUtils.formatDecimal(power_w, 0);
|
||||
}
|
||||
|
||||
return new Pair<>(value, context.getString(R.string.sensor_unit_power));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string for category.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user