diff --git a/src/main/java/de/dennisguse/opentracks/TrackController.java b/src/main/java/de/dennisguse/opentracks/TrackController.java index 0a72f0abf..641baf4a2 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackController.java +++ b/src/main/java/de/dennisguse/opentracks/TrackController.java @@ -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(); + } } diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index 349b7f091..ed3fe4db1 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -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); + } } diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index a19f543d0..079b790f2 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -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) { diff --git a/src/main/res/animator/button_animator.xml b/src/main/res/animator/button_animator.xml deleted file mode 100644 index 2f3e87783..000000000 --- a/src/main/res/animator/button_animator.xml +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/src/main/res/drawable/button_stop.xml b/src/main/res/drawable/button_stop.xml new file mode 100644 index 000000000..ce380562e --- /dev/null +++ b/src/main/res/drawable/button_stop.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/ic_button_pause.xml b/src/main/res/drawable/ic_button_pause.xml index 675e69873..1a3b62da5 100644 --- a/src/main/res/drawable/ic_button_pause.xml +++ b/src/main/res/drawable/ic_button_pause.xml @@ -24,9 +24,4 @@ android:strokeWidth="4.82273865" android:strokeAlpha="1" android:strokeColor="#00000000" /> - diff --git a/src/main/res/drawable/ic_button_pause_anim.xml b/src/main/res/drawable/ic_button_pause_anim.xml deleted file mode 100644 index 16851e029..000000000 --- a/src/main/res/drawable/ic_button_pause_anim.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/res/drawable/ic_button_stop_anim.xml b/src/main/res/drawable/ic_button_stop_anim.xml deleted file mode 100644 index 22e792815..000000000 --- a/src/main/res/drawable/ic_button_stop_anim.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/res/drawable/track_controller_background.xml b/src/main/res/drawable/track_controller_background.xml new file mode 100644 index 000000000..c6074c9ec --- /dev/null +++ b/src/main/res/drawable/track_controller_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/layout-land/track_controller.xml b/src/main/res/layout-land/track_controller.xml index e4ba7f618..29df62576 100644 --- a/src/main/res/layout-land/track_controller.xml +++ b/src/main/res/layout-land/track_controller.xml @@ -19,7 +19,7 @@ limitations under the License. android:id="@+id/track_controller_container" android:layout_width="wrap_content" android:layout_height="match_parent" - android:background="@color/track_controller_background" + android:background="@color/track_controller_background_normal" android:orientation="vertical" android:paddingLeft="8dp" android:paddingRight="8dp"> @@ -30,7 +30,8 @@ limitations under the License. android:layout_height="0dp" android:layout_weight="10" android:background="@android:color/transparent" - android:contentDescription="@string/image_record" /> + android:contentDescription="@string/image_record" + android:src="@drawable/ic_button_record" /> + android:src="@drawable/button_stop" /> \ No newline at end of file diff --git a/src/main/res/layout/track_controller.xml b/src/main/res/layout/track_controller.xml index 617545ecf..716c39475 100644 --- a/src/main/res/layout/track_controller.xml +++ b/src/main/res/layout/track_controller.xml @@ -13,13 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - + @@ -29,32 +30,36 @@ limitations under the License. android:layout_height="wrap_content" android:layout_weight="10" android:background="@android:color/transparent" - android:contentDescription="@string/image_record" /> + android:contentDescription="@string/image_record" + android:src="@drawable/ic_button_record" /> + android:layout_gravity="center" + android:layout_weight="6"> + android:gravity="center" + android:textColor="@android:color/white" + tools:text="0:12:34" /> + android:gravity="center" + android:singleLine="true" + tools:text="@string/generic_recording" /> + android:src="@drawable/button_stop" /> \ No newline at end of file diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index e3403afa0..7ce31f0d3 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -35,7 +35,8 @@ limitations under the License. #B2000000 #00000000 #FF000000 - #FF0E0E0E + #FF0E0E0E + #3F0000 #272727 \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index df7e807b4..268f80b6b 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -631,8 +631,8 @@ limitations under the License. I would like to share an image with you I think you might be interested in this image. - Hold to stop recording - Hold to pause recording + Hold to stop + Hold to pause Last track exported Export track to storage after recording is finished