diff --git a/build.gradle b/build.gradle index 3b6de8f69..d80bed607 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.3.1' + classpath 'com.android.tools.build:gradle:8.1.4' } } diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index 5a6559e39..05f171686 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -47,6 +47,7 @@ import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.databinding.TrackListBinding; +import de.dennisguse.opentracks.databinding.TrackRecordingBinding; import de.dennisguse.opentracks.services.RecordingStatus; import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; @@ -81,6 +82,8 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co private TrackListBinding viewBinding; + private TrackRecordingBinding recordingBinding; + // Preferences private UnitSystem unitSystem = UnitSystem.defaultUnitSystem(); @@ -150,6 +153,8 @@ protected void onCreate(Bundle savedInstanceState) { requestRequiredPermissions(); + recordingBinding = TrackRecordingBinding.inflate(getLayoutInflater()); + recordingStatusConnection = new TrackRecordingServiceConnection(bindChangedCallback); viewBinding.friendsButton.setOnClickListener((view)->startActivity(IntentUtils.newIntent(this, FriendsActivity.class))); @@ -158,6 +163,14 @@ protected void onCreate(Bundle savedInstanceState) { viewBinding.leaderboardButton.setOnClickListener((view) -> startActivity(IntentUtils.newIntent(this, LeaderboardActivity.class))); viewBinding.sensorStartButton.setOnClickListener((view) -> { LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + + //Check if location is actually null +/* if (locationManager == null) { + // Handle the scenario where LocationManager is null + Toast.makeText(this, "Unable to access location services.", Toast.LENGTH_SHORT).show(); + return; + }*/ + if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } else { @@ -191,6 +204,19 @@ protected void onCreate(Bundle savedInstanceState) { startActivity(newIntent); }); }); + + recordingBinding.trackRecordingPause.setOnLongClickListener((view) -> { + if (!recordingStatus.isRecording()) { + return false; + } + + // Recording -> Pause + ActivityUtils.vibrate(this, 1000); + updateGpsMenuItem(true, false); + recordingBinding.trackRecordingPause.setImageResource(R.drawable.ic_button_pause); + return true; + }); + viewBinding.trackListFabAction.setOnLongClickListener((view) -> { if (!recordingStatus.isRecording()) { return false; @@ -200,8 +226,23 @@ protected void onCreate(Bundle savedInstanceState) { ActivityUtils.vibrate(this, 1000); updateGpsMenuItem(false, false); recordingStatusConnection.stopRecording(TrackListActivity.this); - viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24); - viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark)); +/* viewBinding.trackListFabAction.setImageResource(R.drawable.ic_baseline_record_24); + viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.red_dark));*/ + viewBinding.trackListFabAction.setImageResource(R.drawable.ic_button_start_arrow); + return true; + }); + + recordingBinding.trackRecordingPause.setOnLongClickListener((view) -> { + if (recordingStatus.isRecording()) { + return false; + } + + // Pause -> Recording + ActivityUtils.vibrate(this, 1000); + updateGpsMenuItem(true, true); + TrackRecordingServiceConnection.execute(this, (service, connection) -> { + service.resumeTrack(getRecordingTrackId()); + }); return true; }); @@ -441,8 +482,9 @@ public void onGpsStatusChanged(GpsStatusValue newStatus) { } private void setFloatButton() { - viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24); - viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks : R.color.red_dark)); +// viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_baseline_stop_24 : R.drawable.ic_baseline_record_24); + viewBinding.trackListFabAction.setImageResource(recordingStatus.isRecording() ? R.drawable.ic_button_stop : R.drawable.ic_button_start_arrow); +// viewBinding.trackListFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, recordingStatus.isRecording() ? R.color.opentracks : R.color.red_dark)); } private void onRecordingStatusChanged(RecordingStatus status) { diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index 499e8661f..f77f3c599 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -147,7 +147,6 @@ private void saveSelectedValue(int value) { @Override protected void onStart() { super.onStart(); - trackDataHub.start(); } diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index 3390484d1..904e3b9a6 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -76,6 +76,8 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc private RecordingStatus recordingStatus = TrackRecordingService.STATUS_DEFAULT; + public Track track; + private final TrackRecordingServiceConnection.Callback bindChangedCallback = (service, unused) -> { service.getRecordingStatusObservable() .observe(TrackRecordingActivity.this, this::onRecordingStatusChanged); @@ -104,7 +106,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc } if (key == null) return; - runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu); //TODO Should not be necessary +// runOnUiThread(TrackRecordingActivity.this::invalidateOptionsMenu); //TODO Should not be necessary }; @Override @@ -132,22 +134,47 @@ protected void onCreate(Bundle savedInstanceState) { viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY)); } - viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24); +/* viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_baseline_stop_24); viewBinding.trackRecordingFabAction.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.opentracks)); - viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks)); - viewBinding.trackRecordingFabAction.setOnLongClickListener((view) -> { - ActivityUtils.vibrate(this, 1000); + viewBinding.trackRecordingFabAction.setBackgroundColor(ContextCompat.getColor(this, R.color.opentracks));*/ + + + viewBinding.trackRecordingFabAction.setImageResource(R.drawable.ic_button_stop); + + viewBinding.trackRecordingFabAction.setOnClickListener((view) -> { + ActivityUtils.vibrate(this, 200); trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this); + String time_setting = getString(R.string.stats_time_units_key); + String[] parts = time_setting.split("\\s+"); + String firstPart = parts[0]; +// int time_key = Integer.parseInt(firstPart); +// long totalTimeSeconds = track.getTrackStatistics().getTotalTime().getSeconds(); +// if(totalTimeSeconds < time_key) { +// Log.i("HELLO", "HELLO"); +// } Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackStoppedActivity.class) .putExtra(TrackStoppedActivity.EXTRA_TRACK_ID, trackId); startActivity(newIntent); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); finish(); - return true; }); - viewBinding.trackRecordingFabAction.setOnClickListener((view) -> Toast.makeText(TrackRecordingActivity.this, getString(R.string.hold_to_stop), Toast.LENGTH_LONG).show()); - setSupportActionBar(viewBinding.bottomAppBar); + viewBinding.trackRecordingPause.setImageResource(R.drawable.ic_baseline_record_pause); + viewBinding.trackRecordingPause.setOnClickListener((view) -> { + ActivityUtils.vibrate(this, 200); + trackRecordingServiceConnection.pauseRecording(TrackRecordingActivity.this); + view.setVisibility(View.GONE); + }); + + viewBinding.trackRecordingResume.setImageResource(R.drawable.ic_button_resume); + viewBinding.trackRecordingResume.setOnClickListener((view) -> { + ActivityUtils.vibrate(this, 200); + trackRecordingServiceConnection.resumeRecording(trackId); + viewBinding.trackRecordingPause.setVisibility(View.VISIBLE); + }); + + + setSupportActionBar(viewBinding.bottomAppBar); } @Override diff --git a/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java index ddb916775..f4ce0047b 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackStoppedActivity.java @@ -6,6 +6,9 @@ import android.util.Pair; import android.view.View; import android.widget.ArrayAdapter; +import android.widget.Toast; + +import androidx.preference.ListPreference; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.models.ActivityType; @@ -14,13 +17,18 @@ import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.databinding.TrackStoppedBinding; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; +import de.dennisguse.opentracks.services.TrackDeleteService; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.settings.DefaultsSettingsFragment; import de.dennisguse.opentracks.settings.PreferencesUtils; +import de.dennisguse.opentracks.settings.TimeUnitSystem; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.util.ExportUtils; import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.TrackUtils; +import androidx.preference.PreferenceManager; public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { @@ -34,6 +42,10 @@ public class TrackStoppedActivity extends AbstractTrackDeleteActivity implements private boolean isDiscarding = false; + public ListPreference statsTimePreferences; + public DefaultsSettingsFragment dfs; + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -91,6 +103,27 @@ protected void onCreate(Bundle savedInstanceState) { } viewBinding.finishButton.setOnClickListener(v -> { + TimeUnitSystem timeUnitSystem = PreferencesUtils.getTimeUnit(); + Log.i("timeUnitSystem", timeUnitSystem.toString()); + + String customTime = DefaultsSettingsFragment.getCustomTime(this); + System.out.println("CustomTime value: " + customTime); + if (customTime != null) { + System.out.println("Custom time valid"); + int time_key = Integer.parseInt(customTime); + long totalTimeSeconds = track.getTrackStatistics().getTotalTime().getSeconds(); + if(totalTimeSeconds < time_key) { + System.out.println("Track deleted"); + onConfirmDeleteDone(trackId); + } + } else { + String[] parts = timeUnitSystem.toString().split("_"); + int time_key = convertInt(parts[0]); + long totalTimeSeconds = track.getTrackStatistics().getTotalTime().getSeconds(); + if (totalTimeSeconds < time_key) { + onConfirmDeleteDone(trackId); + } + } storeTrackMetaData(contentProviderUtils, track); ExportUtils.postWorkoutExport(this, trackId); finish(); @@ -110,6 +143,18 @@ private void storeTrackMetaData(ContentProviderUtils contentProviderUtils, Track contentProviderUtils); } + public int convertInt(String s) { + int val = 0; + if (s.equals("FIVE")) { + val = 5; + } else if (s.equals("TEN")) { + val = 10; + } else if (s.equals("TWENTY")) { + val = 20; + } + return val; + } + @Override public void onBackPressed() { if (isDiscarding) { diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java index 5c5ce6639..6de5bc487 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java @@ -25,11 +25,15 @@ import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; +import java.util.Objects; + import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.TrackRecordedActivity; import de.dennisguse.opentracks.data.ContentProviderUtils; @@ -138,7 +142,7 @@ public void onDestroyView() { } public void loadStatistics() { - if (isResumed()) { + if (isResumed() && getActivity() != null) { getActivity().runOnUiThread(() -> { if (isResumed()) { Track track = contentProviderUtils.getTrack(trackId); @@ -164,6 +168,10 @@ public void loadStatistics() { } }); } + //add logs + else { + Log.w(TAG, "Activity is null when loading statistics"); + } } private void loadTrackDescription(@NonNull Track track) { @@ -173,6 +181,17 @@ private void loadTrackDescription(@NonNull Track track) { } private void updateUI() { + + // Check if track is null + if (track == null) { + Log.e(TAG, "track cannot be null"); + Toast.makeText(getContext(), "Error: Track cannot be null. Please retry.", Toast.LENGTH_SHORT).show(); + + requireActivity().finish(); + return; + } + + TrackStatistics trackStatistics = track.getTrackStatistics(); // Set total distance { diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java index c0b299e8c..3eb7da62f 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingManager.java @@ -109,6 +109,17 @@ boolean resumeExistingTrack(@NonNull Track.Id resumeTrackId) { return true; } + void pauseCurrentTrack() { + if (trackId == null) { + Log.w(TAG, "No trackId to pause."); + return; + } + + TrackPoint segmentEnd = trackPointCreator.createSegmentEnd(); + insertTrackPoint(segmentEnd, true); + handler.removeCallbacks(ON_IDLE); + } + void endCurrentTrack() { TrackPoint segmentEnd = trackPointCreator.createSegmentEnd(); insertTrackPoint(segmentEnd, true); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index ad2e62722..e5f511278 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -16,6 +16,8 @@ package de.dennisguse.opentracks.services; +import static java.security.AccessController.getContext; + import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; @@ -26,6 +28,7 @@ import android.os.PowerManager.WakeLock; import android.util.Log; import android.util.Pair; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -35,8 +38,13 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.text.SimpleDateFormat; import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.List; +import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.data.models.Distance; import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Track; @@ -46,8 +54,10 @@ import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.services.handlers.TrackPointCreator; import de.dennisguse.opentracks.settings.PreferencesUtils; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.PermissionRequester; import de.dennisguse.opentracks.util.SystemUtils; +import de.dennisguse.opentracks.viewmodels.GenericStatisticsViewHolder; public class TrackRecordingService extends Service implements TrackPointCreator.Callback, SharedPreferences.OnSharedPreferenceChangeListener, TrackRecordingManager.IdleObserver { @@ -187,6 +197,23 @@ public void resumeTrack(Track.Id trackId) { startRecording(); } + public void pauseRecording() { + trackRecordingManager.pauseCurrentTrack(); + // Set recording status +// updateRecordingStatus(STATUS_DEFAULT); + stopUpdateRecordingData(); + +// GenericStatisticsViewHolder.TotalTime.pause(); +// GenericStatisticsViewHolder.MovingTime.pause(); +// GenericStatisticsViewHolder.Distance.pause(); +// GenericStatisticsViewHolder.SpeedOrPace.pause(); + } + + public void resumeRecording() { + + } + + private void startRecording() { // Update instance variables handler.postDelayed(updateRecordingData, RECORDING_DATA_UPDATE_INTERVAL.toMillis()); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java index 8738ac7cd..2e1b44f4a 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceConnection.java @@ -24,11 +24,14 @@ import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.util.Log; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import de.dennisguse.opentracks.BuildConfig; +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.stats.TrackStatistics; /** * Wrapper for the track recording service. @@ -89,13 +92,18 @@ public void bind(@NonNull Context context) { * Unbinds the service (but leave it running). */ //TODO This is often called for one-shot operations and should be refactored as unbinding is required. + // TEST TODO public void unbind(Context context) { - try { - context.unbindService(serviceConnection); - } catch (IllegalArgumentException e) { - // Means not bound to the service. OK to ignore. + if (trackRecordingService != null) { + try { + context.unbindService(serviceConnection); + } catch (IllegalArgumentException e) { + // Means not bound to the service. OK to ignore. + } + setTrackRecordingService(null); + } else { + Log.w(TAG, "TrackRecordingService is not bound; no need to unbind."); } - setTrackRecordingService(null); } public void stopService(Context context) { @@ -130,6 +138,22 @@ public void stopRecording(@NonNull Context context) { unbindAndStop(context); } + public void pauseRecording(@NonNull Context context) { + if (trackRecordingService == null) { + Log.e(TAG, "TrackRecordingService not connected."); + } else { + trackRecordingService.pauseRecording(); + } + } + + public void resumeRecording(Track.Id trackId) { + if (trackRecordingService == null) { + Log.e(TAG, "TrackRecordingService not connected."); + } else { + trackRecordingService.resumeTrack(trackId); + } + } + public interface Callback { void onConnected(TrackRecordingService service, TrackRecordingServiceConnection self); } diff --git a/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java b/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java index e2f192cc4..c9c4001a1 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java +++ b/src/main/java/de/dennisguse/opentracks/settings/DefaultsSettingsFragment.java @@ -1,16 +1,28 @@ package de.dennisguse.opentracks.settings; +import static androidx.preference.PreferenceDialogFragmentCompat.*; + +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.preference.ListPreference; import androidx.preference.Preference; +import android.content.Context; +import android.text.InputType; +import android.widget.EditText; + +import androidx.preference.PreferenceDialogFragmentCompat; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.models.ActivityType; import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment; +import de.dennisguse.opentracks.settings.bluetooth.BluetoothLeSensorPreference; public class DefaultsSettingsFragment extends PreferenceFragmentCompat implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller { @@ -21,11 +33,36 @@ public class DefaultsSettingsFragment extends PreferenceFragmentCompat implement if (PreferencesUtils.isKey(R.string.stats_units_key, key)) { getActivity().runOnUiThread(this::updateUnits); } + if (PreferencesUtils.isKey(R.string.stats_time_units_key, key)) { + getActivity().runOnUiThread(this::updateTimeUnits); + } }; + + public String custom_time; + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.settings_defaults); + + ListPreference statsTimeUnitsPreference = findPreference(getString(R.string.stats_time_units_key)); + + custom_time = PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getString("custom_time_unit", null); + + // Set up the listener + statsTimeUnitsPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { + if (newValue.equals("CUSTOM")) { + displayCustomInputDialog(); + // Update the displayed value of the ListPreference + ((ListPreference) preference).setValue(newValue.toString()); + } + // Allow the ListPreference to handle the change + return true; + } + }); } @Override @@ -38,6 +75,7 @@ public void onStart() { public void onResume() { super.onResume(); PreferencesUtils.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + updateTimeUnits(); //Make sure that time is kept updateUnits(); } @@ -64,6 +102,28 @@ public void onDisplayPreferenceDialog(Preference preference) { super.onDisplayPreferenceDialog(preference); } + //Modify the default time units for activities + private void updateTimeUnits() { + //Acquire time units from Preferences + TimeUnitSystem time = PreferencesUtils.getTimeUnit(); + SharedPreferences preferences = getContext().getSharedPreferences("default_time_unit", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + + editor.putInt(getString(R.string.stats_time_units_key), time.getPreferenceId()); + editor.apply(); + + ListPreference statsTimePreferences = findPreference((getString(R.string.stats_time_units_key))); + + int entriesID = switch (time) { + case FIVE_SEC, TEN_SEC, TWENTY_SEC, CUSTOM -> R.array.stats_time_units_options; + }; + + String[] entries = getResources().getStringArray(entriesID); + statsTimePreferences.setEntries(entries); + + HackUtils.invalidatePreference(statsTimePreferences); + } + private void updateUnits() { UnitSystem unitSystem = PreferencesUtils.getUnitSystem(); @@ -89,4 +149,46 @@ public void onChooseActivityTypeDone(ActivityType activityType) { activityPreferenceDialog.updateUI(activityType); } } + + + //Custom Dialog + protected void displayCustomInputDialog() { + // Show dialog box for custom input + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); + builder.setTitle("Custom Time Unit"); + + // Set the edit text box for user to enter + final EditText cus_Input = new EditText(requireContext()); + //cus_Input.setInputType(InputType.TYPE_CLASS_NUMBER); //make sure it's a number + + builder.setView(cus_Input); + + //Buttons cancel or just say ok + builder.setPositiveButton("OK", (dialog, which) -> { + // Store the custom value + custom_time = cus_Input.getText().toString(); + saveCustomTimeUnit(custom_time); // Call the method to persist the value + }); + + if (custom_time != null) { + cus_Input.setText(custom_time); + } + builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel()); + + //show final result + builder.show(); + } + + private void saveCustomTimeUnit(String value) { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(requireContext()); + sharedPref.edit().putString("custom_time_unit", value).apply(); + custom_time = value; + System.out.println("Custom Time set to: " + value); + } + + public static String getCustomTime(Context context) { + System.out.println("getCustomTime called in DefaultsSettingsFragment"); + return PreferenceManager.getDefaultSharedPreferences(context) + .getString("custom_time_unit", null); + } } diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java index 531aa4fbf..65cbbe2e5 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java @@ -219,6 +219,19 @@ public static UnitSystem getUnitSystem() { .orElse(UnitSystem.defaultUnitSystem()); //TODO This AGAIN defines the default } + //TODO Get time wanted + public static TimeUnitSystem getTimeUnit() { + final String STATS_TIME_UNIT_DEFAULT = resources.getString(R.string.stats_time_default); + + final String VALUE = getString(R.string.stats_time_units_key, STATS_TIME_UNIT_DEFAULT); + return Arrays.stream(TimeUnitSystem.values()) + .filter(d -> VALUE.equals(resources.getString(d.getPreferenceId(), STATS_TIME_UNIT_DEFAULT))) + .findFirst() + .orElse(TimeUnitSystem.defaultUnitSystem()); //TODO This AGAIN defines the default + } + + + public static void setUnit(UnitSystem unitSystem) { setString(R.string.stats_units_key, unitSystem.getPreferenceId()); } diff --git a/src/main/java/de/dennisguse/opentracks/settings/TimeUnitSystem.java b/src/main/java/de/dennisguse/opentracks/settings/TimeUnitSystem.java new file mode 100644 index 000000000..0bfa18376 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/settings/TimeUnitSystem.java @@ -0,0 +1,27 @@ +package de.dennisguse.opentracks.settings; + +import de.dennisguse.opentracks.R; + +public enum TimeUnitSystem { + FIVE_SEC(R.string.stats_time_unit_five), + + TEN_SEC(R.string.stats_time_unit_ten), + TWENTY_SEC(R.string.stats_time_unit_twenty), + + CUSTOM(R.string.stats_time_unit_custom); + + private final int preference; + + TimeUnitSystem(int preference) { + this.preference = preference; + } + + public int getPreferenceId() { + return preference; + } + + @Deprecated //TODO used to initialize before loading from preferences; should be loaded first + public static TimeUnitSystem defaultUnitSystem() { + return TEN_SEC; + } +} diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index f76a9c125..4dd0c863a 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -16,6 +16,9 @@ package de.dennisguse.opentracks.stats; +import android.util.Log; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -32,6 +35,7 @@ import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.data.models.TrackPoint; +import de.dennisguse.opentracks.viewmodels.GenericStatisticsViewHolder; /** * Statistical data about a {@link Track}. @@ -47,6 +51,8 @@ public class TrackStatistics { // The min and max altitude (meters) seen on this track. private final ExtremityMonitor altitudeExtremities = new ExtremityMonitor(); + public static boolean pause = false; + // The total number of runs in a season. private int totalRunsSeason; @@ -64,6 +70,7 @@ public class TrackStatistics { // The slope percentage in a season. private double slopePercentageSeason; + // The track start time. private Instant startTime; // The track stop time. @@ -83,7 +90,9 @@ public class TrackStatistics { private boolean isIdle; - //==========================================================================// + + public boolean resumed = false; + private Duration chairliftWaitingTime; private Distance chairliftElevationGain; private Speed chairliftConstantSpeed; @@ -149,9 +158,6 @@ public void setWaitingTimeSpeed(Speed waitingTimeSpeed) { private boolean skiing; private boolean chairlift; - //===========================================================================// - - public TrackStatistics() { reset(); } @@ -334,7 +340,26 @@ public void setMovingTime(Duration movingTime) { } public void addMovingTime(TrackPoint trackPoint, TrackPoint lastTrackPoint) { - addMovingTime(Duration.between(lastTrackPoint.getTime(), trackPoint.getTime())); + if (pause) { + return; + } + if (resumed) { + // Calculate the elapsed time since the pause + Duration elapsedTimeSincePause = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); + + // Update the moving time with the elapsed pause time + movingTime = movingTime.plus(elapsedTimeSincePause); + + // Reset the resumed flag + resumed = false; + } else { + // Standard moving time update logic (add the time between track points) + Duration time = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); + if (time.isNegative()) { + throw new RuntimeException("Moving time cannot be negative"); + } + movingTime = movingTime.plus(time); + } } //================================================================================// @@ -361,7 +386,7 @@ public void addMovingTime(Duration time) { if (time.isNegative()) { throw new RuntimeException("Moving time cannot be negative"); } - movingTime = movingTime.plus(time); + movingTime = movingTime.plus(time); } public Duration getStoppedTime() { diff --git a/src/main/java/de/dennisguse/opentracks/ui/TrackListAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/TrackListAdapter.java index 4dd48e637..37104617d 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/TrackListAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/ui/TrackListAdapter.java @@ -209,7 +209,8 @@ public void bind(Cursor cursor) { boolean isRecordingThisTrackRecording = trackId.equals(recordingStatus.trackId()); if (isRecordingThisTrackRecording) { - iconId = R.drawable.ic_track_recording; + //iconId = R.drawable.ic_track_recording; + iconId = R.drawable.ic_button_start_arrow; iconDesc = R.string.image_record; } diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java index 226a19f3c..a6ae613cd 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/GenericStatisticsViewHolder.java @@ -1,9 +1,15 @@ package de.dennisguse.opentracks.viewmodels; +import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; +import android.widget.Toast; + +import java.security.AccessControlContext; +import java.time.Duration; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.TrackRecordingActivity; import de.dennisguse.opentracks.data.models.DistanceFormatter; import de.dennisguse.opentracks.data.models.Speed; import de.dennisguse.opentracks.data.models.SpeedFormatter; @@ -12,6 +18,7 @@ import de.dennisguse.opentracks.sensors.sensorData.SensorDataSet; import de.dennisguse.opentracks.services.RecordingData; import de.dennisguse.opentracks.settings.UnitSystem; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.ui.customRecordingLayout.DataField; import de.dennisguse.opentracks.util.StringUtils; @@ -30,8 +37,22 @@ public void configureUI(DataField dataField) { public static class Distance extends GenericStatisticsViewHolder { + private static boolean paused = false; + private static String savedTime; + + public static void pause() { + paused = true; + } + + public static void resume() { + paused = false; + } + @Override public void onChanged(UnitSystem unitSystem, RecordingData data) { + if (paused) { + return; // Don't update UI if paused + } Pair valueAndUnit = DistanceFormatter.Builder() .setUnit(unitSystem) .build(getContext()).getDistanceParts(data.getTrackStatistics().getTotalDistance()); @@ -56,8 +77,37 @@ public void onChanged(UnitSystem unitSystem, RecordingData data) { public static class MovingTime extends GenericStatisticsViewHolder { + private static boolean paused = false; + private static boolean resume = false; + private static boolean fix = true; + + public static Duration currentTime; + private static String savedTime; + + public static void pause() { + paused = true; + TrackStatistics.pause = true; + } + public static void resume() { + paused = false; + resume = true; + } + @Override public void onChanged(UnitSystem unitSystem, RecordingData data) { + if (paused) { + if (fix) { + currentTime = data.getTrackStatistics().getMovingTime(); + } + fix = false; + return; // Don't update UI if paused + } + if (resume) { + data.getTrackStatistics().setMovingTime(currentTime); + resume = false; + TrackStatistics.pause = false; + return; + } String value = StringUtils.formatElapsedTime(data.getTrackStatistics().getMovingTime()); getBinding().statsValue.setText(value); @@ -65,8 +115,14 @@ public void onChanged(UnitSystem unitSystem, RecordingData data) { } } + public abstract static class SpeedOrPace extends GenericStatisticsViewHolder { + private static boolean paused = false; + private static String savedTime; + public static void pause() { + paused = true; + } private final boolean reportSpeed; public SpeedOrPace(boolean reportSpeed) { @@ -75,6 +131,9 @@ public SpeedOrPace(boolean reportSpeed) { @Override public void onChanged(UnitSystem unitSystem, RecordingData data) { + if (paused) { + return; // Don't update UI if paused + } SpeedFormatter localSpeedFormatter = SpeedFormatter.Builder() .setUnit(unitSystem) .setReportSpeedOrPace(reportSpeed) diff --git a/src/main/res/drawable/ic_baseline_record_pause.xml b/src/main/res/drawable/ic_baseline_record_pause.xml new file mode 100644 index 000000000..d19183016 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_record_pause.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_baseline_record_resume.xml b/src/main/res/drawable/ic_baseline_record_resume.xml new file mode 100644 index 000000000..d19183016 --- /dev/null +++ b/src/main/res/drawable/ic_baseline_record_resume.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/ic_button_pause.xml b/src/main/res/drawable/ic_button_pause.xml new file mode 100644 index 000000000..6dfd06491 --- /dev/null +++ b/src/main/res/drawable/ic_button_pause.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/src/main/res/drawable/ic_button_resume.xml b/src/main/res/drawable/ic_button_resume.xml index 5b2a085b2..b86180f3c 100644 --- a/src/main/res/drawable/ic_button_resume.xml +++ b/src/main/res/drawable/ic_button_resume.xml @@ -4,19 +4,11 @@ android:viewportWidth="125" android:viewportHeight="125"> + android:pathData="M62.5,62.5m-62.5,0a62.5,62.5 0,1 1,125 0a62.5,62.5 0,1 1,-125 0" + android:strokeWidth="0" + android:fillColor="#ff6d00"/> - + android:pathData="M85.52,62.5l-17.53,8.97 -17.53,8.97v-35.87l17.53,8.97 17.53,8.97Z" + android:strokeWidth="0" + android:fillColor="#fff"/> diff --git a/src/main/res/drawable/ic_button_start_arrow.xml b/src/main/res/drawable/ic_button_start_arrow.xml new file mode 100644 index 000000000..e4797150b --- /dev/null +++ b/src/main/res/drawable/ic_button_start_arrow.xml @@ -0,0 +1,14 @@ + + + + diff --git a/src/main/res/drawable/ic_button_stop.xml b/src/main/res/drawable/ic_button_stop.xml new file mode 100644 index 000000000..45d5b4387 --- /dev/null +++ b/src/main/res/drawable/ic_button_stop.xml @@ -0,0 +1,15 @@ + + + + diff --git a/src/main/res/layout/track_list.xml b/src/main/res/layout/track_list.xml index b06e15020..0ee3c2cf3 100644 --- a/src/main/res/layout/track_list.xml +++ b/src/main/res/layout/track_list.xml @@ -166,7 +166,8 @@ limitations under the License. android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/image_record" - android:src="@drawable/ic_baseline_record_24" + android:foreground="@drawable/ic_button_start_arrow" + android:src="@drawable/ic_button_start_arrow" app:layout_anchor="@id/bottom_app_bar" /> - + \ No newline at end of file diff --git a/src/main/res/layout/track_recorded.xml b/src/main/res/layout/track_recorded.xml index 06658308e..835b48e8a 100644 --- a/src/main/res/layout/track_recorded.xml +++ b/src/main/res/layout/track_recorded.xml @@ -47,7 +47,6 @@ limitations under the License. android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginBottom="100dp" - android:contentDescription="@string/calculate_moving_average" app:layout_anchor="@id/bottom_app_bar_layout" app:layout_anchorGravity="end|bottom" app:srcCompat="@drawable/ic_baseline_add_24" /> diff --git a/src/main/res/layout/track_recording.xml b/src/main/res/layout/track_recording.xml index 360a056dc..c96addfe2 100644 --- a/src/main/res/layout/track_recording.xml +++ b/src/main/res/layout/track_recording.xml @@ -40,12 +40,38 @@ app:navigationIcon="@drawable/ic_logo_color_24dp" app:menu="@menu/track_record" /> + + + + + - + \ No newline at end of file diff --git a/src/main/res/layout/track_stopped.xml b/src/main/res/layout/track_stopped.xml index 4aca4c419..cedc474ac 100644 --- a/src/main/res/layout/track_stopped.xml +++ b/src/main/res/layout/track_stopped.xml @@ -204,6 +204,7 @@ android:contentDescription="@string/image_record" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/buttons_guideline" + app:layout_constraintHorizontal_bias="0.515" app:layout_constraintStart_toEndOf="@id/buttons_guideline" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_button_resume" /> diff --git a/src/main/res/values/settings.xml b/src/main/res/values/settings.xml index fc116b292..8b1702f07 100644 --- a/src/main/res/values/settings.xml +++ b/src/main/res/values/settings.xml @@ -168,6 +168,27 @@ @string/stats_rate_pace + + timeUnits + timeUnits + @string/settings_stats_time_unit_ten + + @string/stats_time_unit_five + @string/stats_time_unit_ten + @string/stats_time_unit_twenty + @string/stats_time_unit_custom + + FIVE_SEC + TEN_SEC + TWENTY_SEC + CUSTOM + + @string/settings_stats_time_unit_five + @string/settings_stats_time_unit_ten + @string/settings_stats_time_unit_twenty + @string/settings_stats_time_unit_custom + + statsUnits @string/stats_units_metric diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 035664fba..8059295cf 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -276,6 +276,8 @@ limitations under the License. Photo marker Record Stop + Pause + Resume Discard Track @@ -362,7 +364,7 @@ limitations under the License. All About OpenTracks Project Default Units and Activity - Preferred Units, Default Activity, Track Default Name + Preferred Units, Default Activity, Track Default Name, Preferred Minimum Time User Interface Appearance, Recording Layout, Theme @@ -555,6 +557,13 @@ limitations under the License. Nautical (NM, ft) Preferred units + + Preferred minimum time + 5 seconds + 10 seconds + 20 seconds + Customize + Public API Remote control and statistics API for other apps diff --git a/src/main/res/xml/settings_defaults.xml b/src/main/res/xml/settings_defaults.xml index 778b55528..bbaf1dd53 100644 --- a/src/main/res/xml/settings_defaults.xml +++ b/src/main/res/xml/settings_defaults.xml @@ -19,6 +19,20 @@ android:key="@string/stats_rate_key" android:title="@string/settings_stats_rate_title" app:useSimpleSummaryProvider="true" /> + + + + + + + +