diff --git a/.idea/misc.xml b/.idea/misc.xml index 860da66a5..505618cb6 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java new file mode 100644 index 000000000..a27e036a8 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/AnnouncementsSettingsFragment.java @@ -0,0 +1,33 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; + +import androidx.preference.ListPreference; +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.R; + +public class AnnouncementsSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_announcements); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_announcements_title); + } + + @Override + public void onResume() { + super.onResume(); + + ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key)); + voiceFrequency.setEntries(PreferencesUtils.getVoiceAnnouncementFrequencyEntries()); + + ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key)); + voiceDistance.setEntries(PreferencesUtils.getVoiceAnnouncementDistanceEntries()); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java new file mode 100644 index 000000000..ab2e405bb --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java @@ -0,0 +1,83 @@ +package de.dennisguse.opentracks.settings; + +import android.content.SharedPreferences; +import android.os.Bundle; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; +import de.dennisguse.opentracks.util.HackUtils; + +public class DefaultsSettingsFragment extends PreferenceFragmentCompat implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { + + // Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData. + private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog; + + private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> { + if (PreferencesUtils.isKey(R.string.stats_units_key, key)) { + getActivity().runOnUiThread(this::updateUnits); + } + }; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_defaults); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_defaults_title); + } + + @Override + public void onResume() { + super.onResume(); + PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + updateUnits(); + } + + @Override + public void onPause() { + super.onPause(); + PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + } + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + DialogFragment dialogFragment = null; + if (preference instanceof ActivityTypePreference) { + activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey()); + dialogFragment = activityPreferenceDialog; + } + + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0); + dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName()); + return; + } + + super.onDisplayPreferenceDialog(preference); + } + + private void updateUnits() { + boolean metricUnits = PreferencesUtils.isMetricUnits(); + + ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key)); + String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options); + statsRatePreferences.setEntries(entries); + + HackUtils.invalidatePreference(statsRatePreferences); + } + + @Override + public void onChooseActivityTypeDone(String iconValue) { + if (activityPreferenceDialog != null) { + activityPreferenceDialog.updateUI(iconValue); + } + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java new file mode 100644 index 000000000..6db9a604d --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/GpsSettingsFragment.java @@ -0,0 +1,78 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; + +import androidx.preference.ListPreference; +import androidx.preference.PreferenceFragmentCompat; + +import java.time.Duration; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.util.StringUtils; + +public class GpsSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_gps); + + findPreference(getString(R.string.recording_distance_interval_key)) + .setSummaryProvider( + preference -> { + boolean metricUnits = PreferencesUtils.isMetricUnits(); + Distance distance = PreferencesUtils.getRecordingDistanceInterval(); + return getString(R.string.settings_recording_location_frequency_summary, StringUtils.formatDistance(getContext(), distance, metricUnits)); + } + ); + + findPreference(getString(R.string.max_recording_distance_key)) + .setSummaryProvider( + preference -> { + boolean metricUnits = PreferencesUtils.isMetricUnits(); + Distance distance = PreferencesUtils.getMaxRecordingDistance(); + return getString(R.string.settings_recording_max_recording_distance_summary, StringUtils.formatDistance(getContext(), distance, metricUnits)); + } + ); + + findPreference(getString(R.string.recording_gps_accuracy_key)) + .setSummaryProvider( + preference -> { + boolean metricUnits = PreferencesUtils.isMetricUnits(); + Distance distance = PreferencesUtils.getThresholdHorizontalAccuracy(); + return getString(R.string.settings_recording_min_required_accuracy_summary, StringUtils.formatDistance(getContext(), distance, metricUnits)); + } + ); + + findPreference(getString(R.string.min_recording_interval_key)) + .setSummaryProvider( + preference -> { + Duration interval = PreferencesUtils.getMinRecordingInterval(); + return getString(R.string.settings_recording_location_frequency_summary, getString(R.string.value_integer_second, interval.getSeconds())); + } + ); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_gps_title); + } + + @Override + public void onResume() { + super.onResume(); + + ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key)); + minRecordingInterval.setEntries(PreferencesUtils.getMinRecordingIntervalEntries()); + + ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key)); + recordingDistanceInterval.setEntries(PreferencesUtils.getRecordingDistanceIntervalEntries()); + + ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key)); + maxRecordingDistance.setEntries(PreferencesUtils.getMaxRecordingDistanceEntries()); + + ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key)); + recordingGpsAccuracy.setEntries(PreferencesUtils.getThresholdHorizontalAccuracyEntries()); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java new file mode 100644 index 000000000..7934a2487 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/ImportExportSettingsFragment.java @@ -0,0 +1,72 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; + +import androidx.documentfile.provider.DocumentFile; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import java.util.Locale; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.io.file.TrackFileFormat; + +public class ImportExportSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_import_export); + + setExportTrackFileFormatOptions(); + setExportDirectorySummary(); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_import_export_title); + } + + @Override + public void onResume() { + super.onResume(); + + setExportDirectorySummary(); + + Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key)); + instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext())); + } + + private void setExportTrackFileFormatOptions() { + final TrackFileFormat[] trackFileFormats = { + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, + TrackFileFormat.GPX + }; + String[] entries = new String[trackFileFormats.length]; + String[] entryValues = new String[trackFileFormats.length]; + + for (int i = 0; i < entries.length; i++) { + TrackFileFormat trackFileFormat = trackFileFormats[i]; + String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case + int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos; + entries[i] = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId)); + entryValues[i] = trackFileFormat.name(); + } + + ListPreference listPreference = findPreference(getString(R.string.export_trackfileformat_key)); + listPreference.setEntries(entries); + listPreference.setEntryValues(entryValues); + } + + private void setExportDirectorySummary() { + Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key)); + instantExportDirectoryPreference.setSummaryProvider(preference -> { + DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(getContext()); + //Use same value for not set as Androidx ListPreference and EditTextPreference + return directory != null ? directory.getName() : getString(R.string.not_set); + }); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java new file mode 100644 index 000000000..8f71f15d4 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/MainSettingsFragment.java @@ -0,0 +1,132 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; +import android.util.Log; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.services.TrackRecordingService; +import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; + +public class MainSettingsFragment extends PreferenceFragmentCompat { + + private static final String TAG = MainSettingsFragment.class.getSimpleName(); + + private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT; + private TrackRecordingServiceConnection trackRecordingServiceConnection; + private final Runnable bindServiceCallback = new Runnable() { + @Override + public void run() { + TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); + if (service == null) { + Log.w(TAG, "could not get TrackRecordingService"); + return; + } + + service.getRecordingStatusObservable() + .observe(MainSettingsFragment.this, status -> onRecordingStatusChanged(status)); + + } + }; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings); + + trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindServiceCallback); + + findPreference(getString(R.string.settings_defaults_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_defaults_key)); + return true; + }); + + findPreference(getString(R.string.settings_ui_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_ui_key)); + return true; + }); + + findPreference(getString(R.string.settings_gps_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_gps_key)); + return true; + }); + + findPreference(getString(R.string.settings_sensors_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_sensors_key)); + return true; + }); + + findPreference(getString(R.string.settings_announcements_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_announcements_key)); + return true; + }); + + findPreference(getString(R.string.settings_import_export_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_import_export_key)); + return true; + }); + + findPreference(getString(R.string.settings_open_tracks_key)).setOnPreferenceClickListener(preference -> { + ((SettingsActivity) getActivity()).openScreen(getString(R.string.settings_open_tracks_key)); + return true; + }); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.menu_settings); + } + + @Override + public void onResume() { + super.onResume(); + trackRecordingServiceConnection.bind(getContext()); + updatePrefsDependOnRecording(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + trackRecordingServiceConnection.unbind(getContext()); + } + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + DialogFragment dialogFragment = null; + + if (preference instanceof ResetDialogPreference) { + dialogFragment = ResetDialogPreference.ResetPreferenceDialog.newInstance(preference.getKey()); + } + + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0); + dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName()); + return; + } + + super.onDisplayPreferenceDialog(preference); + } + + private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) { + this.recordingStatus = status; + if (isAdded()) { + updatePrefsDependOnRecording(); + } + } + + private void updatePrefsDependOnRecording() { + Preference importExportPreference = findPreference(getString(R.string.settings_import_export_key)); + Preference resetPreference = findPreference(getString(R.string.settings_reset_key)); + + boolean isRecording = recordingStatus.isRecording(); + + importExportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : getString(R.string.settings_import_export_summary)); + importExportPreference.setEnabled(!isRecording); + + resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : getString(R.string.settings_reset_summary)); + resetPreference.setEnabled(!isRecording); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/OpenTracksSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/OpenTracksSettingsFragment.java new file mode 100644 index 000000000..8b28ed677 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/OpenTracksSettingsFragment.java @@ -0,0 +1,24 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; +import android.util.Log; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.R; + +public class OpenTracksSettingsFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_open_tracks); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_open_tracks_title); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/SensorsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/SensorsSettingsFragment.java new file mode 100644 index 000000000..3925fc37f --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/SensorsSettingsFragment.java @@ -0,0 +1,71 @@ +package de.dennisguse.opentracks.settings; + +import android.os.Bundle; +import android.util.Log; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.EditTextPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference; +import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference; +import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference; +import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference; +import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference; +import de.dennisguse.opentracks.util.BluetoothUtils; + +public class SensorsSettingsFragment extends PreferenceFragmentCompat { + + private final static String TAG = SensorsSettingsFragment.class.getSimpleName(); + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_sensors); + setWheelCircumferenceInputFilter(); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_sensors_title); + } + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + DialogFragment dialogFragment = null; + if (preference instanceof BluetoothLeHeartRatePreference) { + dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES); + } else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) { + dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID); + } else if (preference instanceof BluetoothLeCyclingPowerPreference) { + dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID); + } else if (preference instanceof BluetoothLeRunningSpeedAndCadencePreference) { + dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID); + } + + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0); + dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName()); + return; + } + + super.onDisplayPreferenceDialog(preference); + } + + private void setWheelCircumferenceInputFilter() { + EditTextPreference wheelPreference = findPreference(getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key)); + wheelPreference.setOnPreferenceChangeListener((preference, newValue) -> { + if (newValue instanceof String) { + try { + int newValueInt = Integer.parseInt((String) newValue); + return newValueInt > 500 && newValueInt < 4000; + } catch (NumberFormatException e) { + Log.w(TAG, "Entered string is no number."); + } + } + return false; + }); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java index 45c1aca45..4d90187e2 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java @@ -1,48 +1,25 @@ package de.dennisguse.opentracks.settings; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; -import android.util.Log; import android.view.View; -import android.widget.Toast; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.DialogFragment; -import androidx.preference.EditTextPreference; -import androidx.preference.ListPreference; -import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; -import java.time.Duration; -import java.util.Locale; - import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; -import de.dennisguse.opentracks.io.file.TrackFileFormat; -import de.dennisguse.opentracks.services.TrackRecordingService; -import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; -import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingCadenceAndSpeedPreference; -import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeCyclingPowerPreference; -import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeHeartRatePreference; -import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeRunningSpeedAndCadencePreference; -import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference; -import de.dennisguse.opentracks.util.BluetoothUtils; -import de.dennisguse.opentracks.util.HackUtils; -import de.dennisguse.opentracks.util.StringUtils; -public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, ResetDialogPreference.ResetCallback { +public class SettingsActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { - private static final String TAG = SettingsActivity.class.getSimpleName(); public static final String EXTRAS_CHECK_EXPORT_DIRECTORY = "Check Export Directory"; - - private PrefsFragment prefsFragment; private boolean checkExportDirectory = false; + public static final String FRAGMENT_KEY = "fragmentKey"; + + private PreferenceFragmentCompat fragment = null; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -51,18 +28,14 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity if (intent != null && intent.hasExtra(EXTRAS_CHECK_EXPORT_DIRECTORY)) { checkExportDirectory = true; } - onReset(); - } - @Override - protected View getRootView() { - return getLayoutInflater().inflate(R.layout.settings, null); - } + if (savedInstanceState != null) { + fragment = (PreferenceFragmentCompat) getSupportFragmentManager().getFragment(savedInstanceState, FRAGMENT_KEY); + } else { + fragment = new MainSettingsFragment(); + } - @Override - protected void setupActionBarBack(Toolbar toolbar) { - super.setupActionBarBack(toolbar); - toolbar.setTitle(R.string.menu_settings); + getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).commit(); } @Override @@ -82,269 +55,50 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity } @Override - public void onChooseActivityTypeDone(String iconValue) { - prefsFragment.setDefaultActivity(iconValue); + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + getSupportFragmentManager().putFragment(outState, FRAGMENT_KEY, fragment); } @Override - public void onReset() { - prefsFragment = new PrefsFragment(); - getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, prefsFragment).commit(); + protected View getRootView() { + return getLayoutInflater().inflate(R.layout.settings, null); } - public static class PrefsFragment extends PreferenceFragmentCompat { + private PreferenceFragmentCompat getPreferenceScreen(String key) { + PreferenceFragmentCompat fragment = null; - private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> { - if (PreferencesUtils.isKey(R.string.stats_units_key, key)) { - getActivity().runOnUiThread(this::updateUnits); - } - if (PreferencesUtils.isKey(R.string.night_mode_key, key)) { - getActivity().runOnUiThread(PreferencesUtils::applyNightMode); - } - }; - - private TrackRecordingServiceConnection trackRecordingServiceConnection; - private TrackRecordingService.RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT; - - private final Runnable bindServiceCallback = new Runnable() { - @Override - public void run() { - TrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound(); - if (service == null) { - Log.w(TAG, "could not get TrackRecordingService"); - return; - } - - service.getRecordingStatusObservable() - .observe(PrefsFragment.this, status -> onRecordingStatusChanged(status)); - - } - }; - - // Used to forward update from ChooseActivityTypeDialogFragment; TODO Could be replaced with LiveData. - private ActivityTypePreference.ActivityPreferenceDialog activityPreferenceDialog; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindServiceCallback); - - try { - setPreferencesFromResource(R.xml.settings, rootKey); - } catch (ClassCastException e) { - // Some sharedPreference is broken: delete all and reset them; it is just a last resort... - Toast.makeText(getContext(), R.string.settings_error_initial_values_restored, Toast.LENGTH_LONG).show(); - PreferencesUtils.resetPreferences(getContext(), true); - - setPreferencesFromResource(R.xml.settings, rootKey); - } - - setExportTrackFileFormatOptions(); - setWheelCircumferenceInputFilter(); - - Preference instantExportDirectoryPreference = findPreference(getString(R.string.settings_default_export_directory_key)); - instantExportDirectoryPreference.setSummaryProvider(preference -> { - DocumentFile directory = PreferencesUtils.getDefaultExportDirectoryUri(getContext()); - //Use same value for not set as Androidx ListPreference and EditTextPreference - return directory != null ? directory.getName() : getString(R.string.not_set); - }); - - findPreference(getString(R.string.recording_distance_interval_key)) - .setSummaryProvider( - preference -> { - boolean metricUnits = PreferencesUtils.isMetricUnits(); - Distance distance = PreferencesUtils.getRecordingDistanceInterval(); - return getString(R.string.settings_recording_location_frequency_summary, StringUtils.formatDistance(getContext(), distance, metricUnits)); - } - ); - findPreference(getString(R.string.max_recording_distance_key)) - .setSummaryProvider( - preference -> { - boolean metricUnits = PreferencesUtils.isMetricUnits(); - Distance distance = PreferencesUtils.getMaxRecordingDistance(); - return getString(R.string.settings_recording_max_recording_distance_summary, StringUtils.formatDistance(getContext(), distance, metricUnits)); - } - ); - findPreference(getString(R.string.recording_gps_accuracy_key)) - .setSummaryProvider( - preference -> { - boolean metricUnits = PreferencesUtils.isMetricUnits(); - Distance distance = PreferencesUtils.getThresholdHorizontalAccuracy(); - return getString(R.string.settings_recording_min_required_accuracy_summary, StringUtils.formatDistance(getContext(), distance, metricUnits)); - } - ); - findPreference(getString(R.string.min_recording_interval_key)) - .setSummaryProvider( - preference -> { - Duration interval = PreferencesUtils.getMinRecordingInterval(); - return getString(R.string.settings_recording_location_frequency_summary, getString(R.string.value_integer_second, interval.getSeconds())); - } - ); - - Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key)); - customLayoutPreference.setOnPreferenceClickListener((preference) -> { - Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class); - startActivity(intent); - return true; - }); + if (key.equals(getString(R.string.settings_defaults_key))) { + fragment = new DefaultsSettingsFragment(); + } else if (key.equals(getString(R.string.settings_ui_key))) { + fragment = new UserInterfaceSettingsFragment(); + } else if (key.equals(getString(R.string.settings_gps_key))) { + fragment = new GpsSettingsFragment(); + } else if (key.equals(getString(R.string.settings_sensors_key))) { + fragment = new SensorsSettingsFragment(); + } else if (key.equals(getString(R.string.settings_announcements_key))) { + fragment = new AnnouncementsSettingsFragment(); + } else if (key.equals(getString(R.string.settings_import_export_key))) { + fragment = new ImportExportSettingsFragment(); + } else if (key.equals(getString(R.string.settings_open_tracks_key))) { + fragment = new OpenTracksSettingsFragment(); } - @Override - public void onResume() { - super.onResume(); + return fragment; + } - trackRecordingServiceConnection.bind(getContext()); + public PreferenceFragmentCompat openScreen(String key) { + fragment = getPreferenceScreen(key); + getSupportFragmentManager().beginTransaction().replace(R.id.settings_fragment, fragment).addToBackStack(key).commit(); + return fragment; + } - PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - - updateUnits(); - updatePrefsDependOnRecording(); - updateBluetooth(); - updatePostWorkoutExport(); - } - - private void updatePostWorkoutExport() { - Preference instantExportEnabledPreference = findPreference(getString(R.string.post_workout_export_enabled_key)); - instantExportEnabledPreference.setEnabled(PreferencesUtils.isDefaultExportDirectoryUri(getContext())); - } - - @Override - public void onPause() { - super.onPause(); - PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); - } - - @Override - public void onDisplayPreferenceDialog(Preference preference) { - DialogFragment dialogFragment = null; - if (preference instanceof ResetDialogPreference) { - dialogFragment = ResetDialogPreference.ResetPreferenceDialog.newInstance(preference.getKey()); - } else if (preference instanceof ActivityTypePreference) { - activityPreferenceDialog = ActivityTypePreference.ActivityPreferenceDialog.newInstance(preference.getKey()); - dialogFragment = activityPreferenceDialog; - } else if (preference instanceof BluetoothLeHeartRatePreference) { - dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.HEART_RATE_SUPPORTING_DEVICES); - } else if (preference instanceof BluetoothLeCyclingCadenceAndSpeedPreference) { - dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID); - } else if (preference instanceof BluetoothLeCyclingPowerPreference) { - dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.CYCLING_POWER_UUID); - } else if (preference instanceof BluetoothLeRunningSpeedAndCadencePreference) { - dialogFragment = BluetoothLeSensorPreference.BluetoothLeSensorPreferenceDialog.newInstance(preference.getKey(), BluetoothUtils.RUNNING_RUNNING_SPEED_CADENCE_UUID); - } - - if (dialogFragment != null) { - dialogFragment.setTargetFragment(this, 0); - dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName()); - return; - } - - super.onDisplayPreferenceDialog(preference); - } - - @Override - public void onDestroy() { - super.onDestroy(); - trackRecordingServiceConnection.unbind(getContext()); - } - - public void setDefaultActivity(String iconValue) { - if (activityPreferenceDialog != null) { - activityPreferenceDialog.updateUI(iconValue); - } - } - - private void updatePrefsDependOnRecording() { - Preference resetPreference = findPreference(getString(R.string.settings_reset_key)); - Preference importPreference = findPreference(getString(R.string.settings_import)); - Preference exportPreference = findPreference(getString(R.string.settings_export)); - - boolean isRecording = recordingStatus.isRecording(); - resetPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); - importPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); - exportPreference.setSummary(isRecording ? getString(R.string.settings_not_while_recording) : ""); - - resetPreference.setEnabled(!isRecording); - importPreference.setEnabled(!isRecording); - exportPreference.setEnabled(!isRecording); - } - - private void updateBluetooth() { - // Disable Bluetooth preference if device does not have Bluetooth - BluetoothLeSensorPreference bluetoothPreference = findPreference(getString(R.string.settings_sensor_bluetooth_heart_rate_key)); - bluetoothPreference.setVisible(BluetoothUtils.hasBluetooth(getContext())); - } - - private void updateUnits() { - boolean metricUnits = PreferencesUtils.isMetricUnits(); - - ListPreference voiceFrequency = findPreference(getString(R.string.voice_announcement_frequency_key)); - voiceFrequency.setEntries(PreferencesUtils.getVoiceAnnouncementFrequencyEntries()); - - ListPreference voiceDistance = findPreference(getString(R.string.voice_announcement_distance_key)); - voiceDistance.setEntries(PreferencesUtils.getVoiceAnnouncementDistanceEntries()); - - ListPreference minRecordingInterval = findPreference(getString(R.string.min_recording_interval_key)); - minRecordingInterval.setEntries(PreferencesUtils.getMinRecordingIntervalEntries()); - - ListPreference recordingDistanceInterval = findPreference(getString(R.string.recording_distance_interval_key)); - recordingDistanceInterval.setEntries(PreferencesUtils.getRecordingDistanceIntervalEntries()); - - ListPreference maxRecordingDistance = findPreference(getString(R.string.max_recording_distance_key)); - maxRecordingDistance.setEntries(PreferencesUtils.getMaxRecordingDistanceEntries()); - - ListPreference recordingGpsAccuracy = findPreference(getString(R.string.recording_gps_accuracy_key)); - recordingGpsAccuracy.setEntries(PreferencesUtils.getThresholdHorizontalAccuracyEntries()); - - ListPreference statsRatePreferences = findPreference(getString(R.string.stats_rate_key)); - String[] entries = getResources().getStringArray(metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options); - statsRatePreferences.setEntries(entries); - - HackUtils.invalidatePreference(statsRatePreferences); - } - - - private void setExportTrackFileFormatOptions() { - final TrackFileFormat[] trackFileFormats = { - TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, - TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, - TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, - TrackFileFormat.GPX - }; - String[] entries = new String[trackFileFormats.length]; - String[] entryValues = new String[trackFileFormats.length]; - - for (int i = 0; i < entries.length; i++) { - TrackFileFormat trackFileFormat = trackFileFormats[i]; - String trackFileFormatUpperCase = trackFileFormat.getExtension().toUpperCase(Locale.US); //ASCII upper case - int photoMessageId = trackFileFormat.includesPhotos() ? R.string.export_with_photos : R.string.export_without_photos; - entries[i] = String.format("%s (%s)", trackFileFormatUpperCase, getString(photoMessageId)); - entryValues[i] = trackFileFormat.name(); - } - - ListPreference listPreference = findPreference(getString(R.string.export_trackfileformat_key)); - listPreference.setEntries(entries); - listPreference.setEntryValues(entryValues); - } - - private void setWheelCircumferenceInputFilter() { - EditTextPreference wheelPreference = findPreference(getString(R.string.settings_sensor_bluetooth_cycling_speed_wheel_circumference_key)); - wheelPreference.setOnPreferenceChangeListener((preference, newValue) -> { - if (newValue instanceof String) { - try { - int newValueInt = Integer.parseInt((String) newValue); - return newValueInt > 500 && newValueInt < 4000; - } catch (NumberFormatException e) { - Log.w(TAG, "Entered string is no number."); - } - } - return false; - }); - } - - private void onRecordingStatusChanged(TrackRecordingService.RecordingStatus status) { - this.recordingStatus = status; - if (!status.isRecording() && isAdded()) { - updatePrefsDependOnRecording(); - } + @Override + public void onChooseActivityTypeDone(String iconValue) { + try { + ((ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller) this.fragment).onChooseActivityTypeDone(iconValue); + } catch (ClassCastException e) { + throw new ClassCastException(this.fragment.getClass().getSimpleName() + " must implement " + ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller.class.getSimpleName()); } } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/UserInterfaceSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/UserInterfaceSettingsFragment.java new file mode 100644 index 000000000..f4c9cf61f --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/UserInterfaceSettingsFragment.java @@ -0,0 +1,67 @@ +package de.dennisguse.opentracks.settings; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import de.dennisguse.opentracks.R; + +public class UserInterfaceSettingsFragment extends PreferenceFragmentCompat { + + private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (sharedPreferences, key) -> { + if (PreferencesUtils.isKey(R.string.night_mode_key, key)) { + getActivity().runOnUiThread(PreferencesUtils::applyNightMode); + } + }; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.settings_user_interface); + + Preference customLayoutPreference = findPreference(getString(R.string.stats_custom_layout_key)); + customLayoutPreference.setOnPreferenceClickListener((preference) -> { + Intent intent = new Intent(getContext(), SettingsCustomLayoutActivity.class); + startActivity(intent); + return true; + }); + } + + @Override + public void onStart() { + super.onStart(); + ((SettingsActivity) getActivity()).getSupportActionBar().setTitle(R.string.settings_ui_title); + } + + @Override + public void onResume() { + super.onResume(); + PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + } + + @Override + public void onPause() { + super.onPause(); + PreferencesUtils.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + } + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + DialogFragment dialogFragment = null; + + if (preference instanceof ResetDialogPreference) { + dialogFragment = ResetDialogPreference.ResetPreferenceDialog.newInstance(preference.getKey()); + } + + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0); + dialogFragment.show(getParentFragmentManager(), getClass().getSimpleName()); + return; + } + + super.onDisplayPreferenceDialog(preference); + } +} diff --git a/src/main/res/drawable/ic_baseline_autorenew_24.xml b/src/main/res/drawable/ic_baseline_autorenew_24.xml new file mode 100644 index 000000000..3c6d07f47 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_autorenew_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_import_export_24.xml b/src/main/res/drawable/ic_baseline_import_export_24.xml new file mode 100644 index 000000000..bc5c0ad68 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_import_export_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_info_24.xml b/src/main/res/drawable/ic_baseline_info_24.xml new file mode 100644 index 000000000..17255b7ae --- /dev/null +++ b/src/main/res/drawable/ic_baseline_info_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_satellite_alt_24.xml b/src/main/res/drawable/ic_baseline_satellite_alt_24.xml new file mode 100644 index 000000000..6c0db85cc --- /dev/null +++ b/src/main/res/drawable/ic_baseline_satellite_alt_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_sensors_24.xml b/src/main/res/drawable/ic_baseline_sensors_24.xml new file mode 100644 index 000000000..1f867db64 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_sensors_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_volume_up_24.xml b/src/main/res/drawable/ic_baseline_volume_up_24.xml new file mode 100644 index 000000000..cc288cde0 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_volume_up_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/src/main/res/drawable/ic_ui.xml b/src/main/res/drawable/ic_ui.xml new file mode 100644 index 000000000..98923690b --- /dev/null +++ b/src/main/res/drawable/ic_ui.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_units.xml b/src/main/res/drawable/ic_units.xml new file mode 100644 index 000000000..3f70646ba --- /dev/null +++ b/src/main/res/drawable/ic_units.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index 92b6d2b48..4826f0e1f 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -10,7 +10,41 @@ trackdetail_fullscreen_while_recording false + + settingsOpenTracks + Open Tracks + All About OpenTracks Project + + settingsDefaults + Default Units and Activity + Preferred Units, Default Activity, Track Default Name + + settingsUI + User Interface + Appearance, Recording Layout, Theme + + settingsGps + GPS + Time interval, Distance interval, Max Distance, Accuracy + + settingsSensorsGps + Bluetooth Sensors + Cycling Sensors, Wheel Circumference, Running Sensors + + settingsAnnouncements + Voice Announcements + Time, Distance, Voice Speed + + settingsImportExport + Import/Export + Import, Export, Auto Export + + + Units + Activity\'s Defaults + settingsReset + Reset Settings to Default Values settingsLayoutReset bluetoothSensor diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2e78f7971..6e9d81f89 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -373,20 +373,24 @@ limitations under the License. Reset your layouts? All layout settings have been reverted to the default values - Reset All Settings + Reset All settings will be reverted to the default values. This will not delete any tracks on the device. Reset now? All settings have been reverted to the default values Sorry, but an error occurred. All settings have been reverted to the default values. + Generic + Cycling + Running + Bluetooth Connected to %1$s Disconnected from %1$s - Cycling: distance and speed - Cycling: cadence - Running: distance, speed, and cadence - Wheel circumference (mm) + Distance and Speed + Cadence + Distance, Speed, and Cadence + Wheel Circumference (mm) Preferred rate @@ -394,6 +398,10 @@ limitations under the License. Metric (km, m) Preferred units + + Import + Export + Prevent re-import of tracks I think you might be interested in this track.\n\n%1$s diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/settings.xml index 01610a2e2..fb17e3aec 100644 --- a/src/main/res/xml/settings.xml +++ b/src/main/res/xml/settings.xml @@ -1,226 +1,59 @@ - - - - - - + - - - - + - - - - + - - - - - - - - - - - + - - - - - - - - + - - - - - + - - - - - - - - - - - - - - + - - - - + - - - - - - \ No newline at end of file + diff --git a/src/main/res/xml/settings_announcements.xml b/src/main/res/xml/settings_announcements.xml new file mode 100644 index 000000000..dbfd44c7a --- /dev/null +++ b/src/main/res/xml/settings_announcements.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/settings_defaults.xml b/src/main/res/xml/settings_defaults.xml new file mode 100644 index 000000000..778b55528 --- /dev/null +++ b/src/main/res/xml/settings_defaults.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/settings_gps.xml b/src/main/res/xml/settings_gps.xml new file mode 100644 index 000000000..2c661d073 --- /dev/null +++ b/src/main/res/xml/settings_gps.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/settings_import_export.xml b/src/main/res/xml/settings_import_export.xml new file mode 100644 index 000000000..88dd01ede --- /dev/null +++ b/src/main/res/xml/settings_import_export.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/settings_open_tracks.xml b/src/main/res/xml/settings_open_tracks.xml new file mode 100644 index 000000000..8ec3f6322 --- /dev/null +++ b/src/main/res/xml/settings_open_tracks.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/settings_sensors.xml b/src/main/res/xml/settings_sensors.xml new file mode 100644 index 000000000..e64f425c1 --- /dev/null +++ b/src/main/res/xml/settings_sensors.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/settings_user_interface.xml b/src/main/res/xml/settings_user_interface.xml new file mode 100644 index 000000000..6d251fcb7 --- /dev/null +++ b/src/main/res/xml/settings_user_interface.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file