fix #241 stop button lock (#362)

* fix #241 stop button lock

Adds a delay and animation to the stop and pause buttons

- short vibration when starting to push the buttons
- long vibration when action was executed
- Toast to show "Hold to stop/pause recording"
This commit is contained in:
Peter Storch
2020-08-15 21:50:06 +02:00
committed by GitHub
parent a87d2e5b09
commit 18546fd3cc
14 changed files with 143 additions and 110 deletions
+1
View File
@@ -43,6 +43,7 @@ limitations under the License.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -16,15 +16,21 @@
package de.dennisguse.opentracks;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.os.Handler;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions;
@@ -46,6 +52,7 @@ public class TrackController {
private final ImageButton recordImageButton;
private final ImageButton stopImageButton;
private final boolean alwaysShow;
private ButtonDelay buttonDelay;
private boolean isRecording;
private boolean isPaused;
@@ -65,6 +72,7 @@ public class TrackController {
}
};
@SuppressLint("ClickableViewAccessibility")
TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
this.activity = activity;
this.trackRecordingServiceConnection = trackRecordingServiceConnection;
@@ -75,10 +83,85 @@ public class TrackController {
totalTimeTextView = activity.findViewById(R.id.track_controller_total_time);
recordImageButton = activity.findViewById(R.id.track_controller_record);
recordImageButton.setOnClickListener(recordListener);
recordImageButton.setOnTouchListener((view, motionEvent) -> onRecordTouch(activity, recordListener, motionEvent));
stopImageButton = activity.findViewById(R.id.track_controller_stop);
stopImageButton.setOnClickListener(stopListener);
stopImageButton.setOnTouchListener((view, motionEvent) -> onStopTouch(activity, stopListener, motionEvent));
}
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, recordImageButton, 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 ) {
recordImageButton.setImageResource(R.drawable.ic_button_pause);
if (buttonDelay != null) {
buttonDelay.canceled = true;
}
return true;
}
} else if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
recordListener.onClick(null);
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, stopImageButton, 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 ) {
stopImageButton.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 AnimatedVectorDrawable animatedDrawable;
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.animatedDrawable = (AnimatedVectorDrawable) activity.getDrawable(animDrawableId);
this.delayMessageId = delayMessageId;
}
@Override
public void run() {
imageButton.setImageDrawable(animatedDrawable);
activity.runOnUiThread(animatedDrawable::start);
ActivityUtils.vibrate(activity, 150);
activity.runOnUiThread(()-> 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) {
@@ -95,10 +178,10 @@ public class TrackController {
return;
}
recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.button_pause : R.drawable.button_record);
recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
recordImageButton.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record));
stopImageButton.setImageResource(isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled);
stopImageButton.setImageResource(isRecording ? R.drawable.ic_button_stop : R.drawable.ic_button_stop_disabled);
stopImageButton.setEnabled(isRecording);
statusTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
@@ -4,13 +4,18 @@ import android.app.Activity;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.widget.SearchView;
@@ -21,6 +26,8 @@ import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.SearchListActivity;
import de.dennisguse.opentracks.TrackController;
import static android.content.Context.VIBRATOR_SERVICE;
public class ActivityUtils {
private static final String TAG = ActivityUtils.class.getSimpleName();
@@ -129,4 +136,20 @@ public class ActivityUtils {
}
});
}
public static void vibrate(Activity activity, int milliseconds) {
final Vibrator vibrator = (Vibrator) activity.getSystemService(VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(milliseconds);
}
}
public static void toast(final Activity activity, final int resId, final int duration, final int gravity) {
final Toast toast = Toast.makeText(activity, resId, duration);
toast.setGravity(gravity, 0, 0);
toast.show();
}
}
@@ -0,0 +1,5 @@
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/buttonDelayMillis"
android:propertyName="fillAlpha"
android:valueFrom="1.0"
android:valueTo="0.0" />
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_button_pause_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/ic_button_pause" />
</selector>
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_button_stop_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/ic_button_stop" />
</selector>
@@ -24,4 +24,9 @@
android:strokeWidth="4.82273865"
android:strokeAlpha="1"
android:strokeColor="#00000000" />
<path android:name="fade_out_circle"
android:pathData="M62.5,62.5m-0,-62a50,50 0,1 1,-0 123a50,50 0,1 1,-0 -123"
android:fillAlpha="0.0"
android:fillColor="#a31a1f">
</path>
</vector>
@@ -0,0 +1,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_button_pause" >
<target
android:name="fade_out_circle"
android:animation="@animator/button_animator" />
</animated-vector>
@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="82dp"
android:height="82dp"
android:viewportWidth="125"
android:viewportHeight="125">
<path
android:fillAlpha="1"
android:fillColor="#00000000"
android:pathData="M62.5,62.5m-59.984,0a59.984,59.984 0,1 1,119.968 0a59.984,59.984 0,1 1,-119.968 0"
android:strokeWidth="5.03185225"
android:strokeAlpha="1"
android:strokeColor="#a31a1f" />
<path
android:fillAlpha="0.4"
android:fillColor="#414141"
android:pathData="M62.5,62.5m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"
android:strokeWidth="6.9172821"
android:strokeAlpha="1"
android:strokeColor="#00000000" />
<path
android:fillAlpha="0.87843137"
android:fillColor="#efefef"
android:pathData="M54,54.059h5v17.941h-5z"
android:strokeWidth="4.82273912"
android:strokeAlpha="1"
android:strokeColor="#00000000" />
<path
android:fillAlpha="0.87843137"
android:fillColor="#efefef"
android:pathData="M66,54h5v17.941h-5z"
android:strokeWidth="4.82273865"
android:strokeAlpha="1"
android:strokeColor="#00000000" />
</vector>
+5
View File
@@ -25,4 +25,9 @@
android:strokeWidth="1.04023623"
android:strokeAlpha="1"
android:strokeColor="#414141" />
<path android:name="fade_out_circle"
android:pathData="M62.5,62.5m-0,-62a50,50 0,1 1,-0 123a50,50 0,1 1,-0 -123"
android:fillAlpha="0.0"
android:fillColor="#a31a1f">
</path>
</vector>
@@ -0,0 +1,6 @@
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_button_stop" >
<target
android:name="fade_out_circle"
android:animation="@animator/button_animator" />
</animated-vector>
@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="82dp"
android:height="82dp"
android:viewportWidth="125"
android:viewportHeight="125">
<path
android:fillColor="#00000000"
android:pathData="M2.516,63.009a60.239,59.73 134.856,1 0,119.967 -1.017a60.239,59.73 134.856,1 0,-119.967 1.017z"
android:strokeWidth="5.0319"
android:strokeColor="#555" />
<path android:pathData="M62.5,62.5m-0,-50a50,50 0,1 1,-0 100a50,50 0,1 1,-0 -100">
<aapt:attr name="android:fillColor">
<gradient
android:endX="62.5"
android:endY="112.5"
android:startX="62.5"
android:startY="12.5"
android:type="linear">
<item
android:color="#FF404040"
android:offset="0" />
<item
android:color="#FF555555"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#dbdbdb"
android:pathData="M53.02,54.02h17.46v17.46h-17.46z"
android:strokeWidth="1.0402"
android:strokeColor="#414141" />
</vector>
+2
View File
@@ -231,4 +231,6 @@
<bool name="import_prevent_reimport_default">true</bool>
<string name="settings_recording_key" translatable="false">settingsRecordingKey</string>
<integer name="buttonDelayMillis">1500</integer>
</resources>
+3
View File
@@ -584,4 +584,7 @@ limitations under the License.
<!-- Share image -->
<string name="share_image_subject">I would like to share an image with you</string>
<string name="share_image_body">I think you might be interested in this image.</string>
<string name="hold_to_stop">Hold to stop recording</string>
<string name="hold_to_pause">Hold to pause recording</string>
</resources>