mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-03 09:53:06 +02:00
TrackController: refactor button (delayed action) UI and use handler.
This commit is contained in:
@@ -18,17 +18,12 @@ package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.os.Handler;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.TrackControllerBinding;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||
@@ -42,26 +37,29 @@ import de.dennisguse.opentracks.util.UnitConversions;
|
||||
*
|
||||
* @author Jimmy Shih
|
||||
*/
|
||||
public class TrackController {
|
||||
public class TrackController implements View.OnTouchListener {
|
||||
|
||||
private static final String TAG = TrackController.class.getSimpleName();
|
||||
|
||||
private final Activity activity;
|
||||
private final TrackRecordingServiceConnection trackRecordingServiceConnection;
|
||||
private final Handler handlerUpdateTotalTime;
|
||||
private final Handler handlerUpdateTotalTime = new Handler();
|
||||
private final boolean alwaysShow;
|
||||
private ButtonDelay buttonDelay;
|
||||
|
||||
private Runnable buttonDelay;
|
||||
private final int buttonDelayDuration;
|
||||
|
||||
private final TrackControllerBinding viewBinding;
|
||||
private final TransitionDrawable transition;
|
||||
|
||||
private boolean isRecording;
|
||||
private boolean isPaused;
|
||||
private long totalTime = 0;
|
||||
|
||||
private long totalTimeTimestamp = 0;
|
||||
|
||||
private boolean isResumed = false;
|
||||
|
||||
private final Callback callback;
|
||||
|
||||
// A runnable to update the total time.
|
||||
private final Runnable updateTotalTimeRunnable = new Runnable() {
|
||||
public void run() {
|
||||
@@ -73,98 +71,98 @@ public class TrackController {
|
||||
};
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
TrackController(Activity activity, TrackControllerBinding viewBinding, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
|
||||
TrackController(Activity activity, TrackControllerBinding viewBinding, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, Callback callback) {
|
||||
this.activity = activity;
|
||||
this.viewBinding = viewBinding;
|
||||
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
|
||||
this.alwaysShow = alwaysShow;
|
||||
handlerUpdateTotalTime = new Handler();
|
||||
viewBinding.trackControllerRecord.setOnTouchListener((view, motionEvent) -> onRecordTouch(activity, recordListener, motionEvent));
|
||||
this.callback = callback;
|
||||
|
||||
viewBinding.trackControllerStop.setOnTouchListener((view, motionEvent) -> onStopTouch(activity, stopListener, motionEvent));
|
||||
buttonDelayDuration = activity.getResources().getInteger(R.integer.buttonDelayMillis);
|
||||
|
||||
viewBinding.trackControllerRecord.setOnTouchListener(this);
|
||||
viewBinding.trackControllerRecord.setOnClickListener((view) -> {
|
||||
if (buttonDelay != null || (isRecording && !isPaused)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback.recordStart();
|
||||
});
|
||||
|
||||
viewBinding.trackControllerStop.setOnTouchListener(this);
|
||||
transition = (TransitionDrawable) viewBinding.trackControllerContainer.getBackground();
|
||||
}
|
||||
|
||||
private boolean onRecordTouch(final Activity activity, final OnClickListener recordListener, final MotionEvent motionEvent) {
|
||||
if (isRecording && !isPaused) {
|
||||
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
buttonDelay = new ButtonDelay(activity, viewBinding.trackControllerRecord, R.drawable.ic_button_pause_anim, R.string.hold_to_pause, recordListener);
|
||||
new Thread(buttonDelay).start();
|
||||
return true;
|
||||
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
|
||||
viewBinding.trackControllerRecord.setImageResource(R.drawable.ic_button_pause);
|
||||
if (buttonDelay != null) {
|
||||
buttonDelay.canceled = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
recordListener.onClick(null);
|
||||
@SuppressLint("ClickableViewAccessibility") //We do accessibility manually.
|
||||
@Override
|
||||
public boolean onTouch(final View view, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buttonDelay != null && event.getAction() == MotionEvent.ACTION_UP) {
|
||||
handlerUpdateTotalTime.removeCallbacks(buttonDelay);
|
||||
buttonDelay = null;
|
||||
((ImageButton) view).setPressed(false);
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
showStatusSetDefaultText();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Trigger button pressed animation
|
||||
((ImageButton) view).setPressed(true);
|
||||
|
||||
//To pause a recording
|
||||
if (viewBinding.trackControllerRecord.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& isRecording && !isPaused) {
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(activity, 150);
|
||||
viewBinding.trackControllerStatus.setText(R.string.hold_to_pause);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
view.performClick();
|
||||
callback.recordStart(); //TODO Should be recordPause();
|
||||
|
||||
transition.resetTransition();
|
||||
|
||||
ActivityUtils.vibrate(activity, 1000);
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
//To stop a recording
|
||||
if (viewBinding.trackControllerStop.equals(view)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& isRecording) {
|
||||
|
||||
|
||||
transition.startTransition(buttonDelayDuration);
|
||||
|
||||
ActivityUtils.vibrate(activity, 150);
|
||||
viewBinding.trackControllerStatus.setText(R.string.hold_to_stop);
|
||||
|
||||
buttonDelay = () -> {
|
||||
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
|
||||
callback.recordStop();
|
||||
ActivityUtils.vibrate(activity, 1000);
|
||||
|
||||
transition.resetTransition();
|
||||
};
|
||||
handlerUpdateTotalTime.postDelayed(buttonDelay, buttonDelayDuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean onStopTouch(final Activity activity, final OnClickListener stopListener, final MotionEvent motionEvent) {
|
||||
if (isRecording) {
|
||||
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
buttonDelay = new ButtonDelay(activity, viewBinding.trackControllerStop, R.drawable.ic_button_stop_anim, R.string.hold_to_stop, stopListener);
|
||||
new Thread(buttonDelay).start();
|
||||
return true;
|
||||
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
|
||||
viewBinding.trackControllerStop.setImageResource(R.drawable.ic_button_stop);
|
||||
if (buttonDelay != null) {
|
||||
buttonDelay.canceled = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class ButtonDelay implements Runnable {
|
||||
|
||||
private boolean canceled = false;
|
||||
|
||||
private final ImageButton imageButton;
|
||||
private final Activity activity;
|
||||
private final OnClickListener clickListener;
|
||||
private final int delayMillis;
|
||||
private final Drawable drawable;
|
||||
private final int delayMessageId;
|
||||
|
||||
private ButtonDelay(final Activity activity, final ImageButton imageButton, final int animDrawableId, final int delayMessageId, final OnClickListener clickListener) {
|
||||
this.activity = activity;
|
||||
this.clickListener = clickListener;
|
||||
this.delayMillis = activity.getResources().getInteger(R.integer.buttonDelayMillis);
|
||||
this.imageButton = imageButton;
|
||||
this.drawable = ContextCompat.getDrawable(activity, animDrawableId);
|
||||
this.delayMessageId = delayMessageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
activity.runOnUiThread(() -> {
|
||||
imageButton.setImageDrawable(drawable);
|
||||
if (drawable instanceof AnimatedVectorDrawable) {
|
||||
((AnimatedVectorDrawable) drawable).start();
|
||||
}
|
||||
ActivityUtils.vibrate(activity, 150);
|
||||
ActivityUtils.toast(activity, delayMessageId, Toast.LENGTH_SHORT, Gravity.TOP);
|
||||
});
|
||||
|
||||
try {
|
||||
Thread.sleep(delayMillis);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
if (!canceled) {
|
||||
activity.runOnUiThread(() -> {
|
||||
clickListener.onClick(null);
|
||||
ActivityUtils.vibrate(activity, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(boolean recording, boolean paused) {
|
||||
if (!isResumed) {
|
||||
return;
|
||||
@@ -182,13 +180,12 @@ public class TrackController {
|
||||
viewBinding.trackControllerRecord.setImageResource(isRecording && !isPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
|
||||
viewBinding.trackControllerRecord.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record));
|
||||
|
||||
viewBinding.trackControllerStop.setImageResource(isRecording ? R.drawable.ic_button_stop : R.drawable.ic_button_stop_disabled);
|
||||
viewBinding.trackControllerStop.setEnabled(isRecording);
|
||||
|
||||
viewBinding.trackControllerStatus.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
|
||||
if (isRecording) {
|
||||
viewBinding.trackControllerStatus.setTextColor(activity.getResources().getColor(isPaused ? android.R.color.white : R.color.recording_text));
|
||||
viewBinding.trackControllerStatus.setText(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
showStatusSetDefaultText();
|
||||
}
|
||||
|
||||
handlerUpdateTotalTime.removeCallbacks(updateTotalTimeRunnable);
|
||||
@@ -228,4 +225,14 @@ public class TrackController {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
return trackRecordingService != null ? trackRecordingService.getTotalTime() : 0L;
|
||||
}
|
||||
|
||||
private void showStatusSetDefaultText() {
|
||||
viewBinding.trackControllerStatus.setText(isPaused ? R.string.generic_paused : R.string.generic_recording);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void recordStart();
|
||||
|
||||
void recordStop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -65,7 +64,7 @@ import de.dennisguse.opentracks.util.TrackIconUtils;
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller {
|
||||
public class TrackListActivity extends AbstractListActivity implements ConfirmDeleteDialogFragment.ConfirmDeleteCaller, TrackController.Callback {
|
||||
|
||||
private static final String TAG = TrackListActivity.class.getSimpleName();
|
||||
|
||||
@@ -133,14 +132,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem startGpsMenuItem;
|
||||
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
updateMenuItems(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this, true);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback when the trackRecordingServiceConnection binding changes.
|
||||
private final Runnable bindChangedCallback = new Runnable() {
|
||||
@Override
|
||||
@@ -175,27 +166,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
}
|
||||
};
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// Reset theme after splash
|
||||
@@ -211,7 +181,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
sharedPreferences = PreferencesUtils.getSharedPreferences(this);
|
||||
|
||||
trackRecordingServiceConnection = new TrackRecordingServiceConnection(bindChangedCallback);
|
||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, true, recordListener, stopListener);
|
||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, true, this);
|
||||
|
||||
|
||||
// Show trackController when search dialog is dismissed
|
||||
@@ -577,4 +547,30 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
resourceCursorAdapter.swapCursor(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStart() {
|
||||
if (!PreferencesUtils.isRecording(recordingTrackId)) {
|
||||
// Not recording -> Recording
|
||||
updateMenuItems(false, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class);
|
||||
startActivity(newIntent);
|
||||
} else if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(false, true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStop() {
|
||||
updateMenuItems(false, false);
|
||||
trackRecordingServiceConnection.stopRecording(TrackListActivity.this, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -42,7 +41,7 @@ import de.dennisguse.opentracks.util.TrackUtils;
|
||||
*/
|
||||
//NOTE: This activity does NOT react to preference changes of R.string.recording_track_id_key.
|
||||
//This mode of communication should be removed anyhow.
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface {
|
||||
public class TrackRecordingActivity extends AbstractActivity implements ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller, TrackActivityDataHubInterface, TrackController.Callback {
|
||||
|
||||
public static final String EXTRA_TRACK_ID = "track_id";
|
||||
|
||||
@@ -126,36 +125,6 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
private MenuItem insertMarkerMenuItem;
|
||||
private MenuItem markerListMenuItem;
|
||||
|
||||
private final OnClickListener recordListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnClickListener stopListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
updateMenuItems(true);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -185,7 +154,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
viewBinding.trackDetailActivityViewPager.setCurrentItem(savedInstanceState.getInt(CURRENT_TAB_TAG_KEY));
|
||||
}
|
||||
|
||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, false, recordListener, stopListener);
|
||||
trackController = new TrackController(this, viewBinding.trackControllerContainer, trackRecordingServiceConnection, false, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -363,6 +332,32 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
||||
TrackUtils.updateTrack(this, track, null, category, null, contentProviderUtils);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStart() {
|
||||
if (recordingTrackPaused) {
|
||||
// Paused -> Resume
|
||||
updateMenuItems(false);
|
||||
trackRecordingServiceConnection.resumeTrack();
|
||||
trackController.update(true, false);
|
||||
} else {
|
||||
// Recording -> Paused
|
||||
updateMenuItems(true);
|
||||
trackRecordingServiceConnection.pauseTrack();
|
||||
trackController.update(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordStop() {
|
||||
trackRecordingServiceConnection.stopRecording(TrackRecordingActivity.this, true);
|
||||
Intent newIntent = IntentUtils.newIntent(TrackRecordingActivity.this, TrackRecordedActivity.class)
|
||||
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId);
|
||||
startActivity(newIntent);
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
|
||||
updateMenuItems(true);
|
||||
finish();
|
||||
}
|
||||
|
||||
private class CustomFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
public CustomFragmentPagerAdapter(@NonNull FragmentManager fm, int behavior) {
|
||||
|
||||
Reference in New Issue
Block a user