mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-27 10:20:18 +00:00
Voice Note Locking.
Limit of 60 minutes, after which it's cancelled.
This commit is contained in:
@@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.DimenRes;
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
@@ -38,7 +39,6 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class InputPanel extends LinearLayout
|
||||
implements MicrophoneRecorderView.Listener,
|
||||
@@ -58,6 +58,7 @@ public class InputPanel extends LinearLayout
|
||||
private View quickAudioToggle;
|
||||
private View buttonToggle;
|
||||
private View recordingContainer;
|
||||
private View recordLockCancel;
|
||||
|
||||
private MicrophoneRecorderView microphoneRecorderView;
|
||||
private SlideToCancel slideToCancel;
|
||||
@@ -93,10 +94,15 @@ public class InputPanel extends LinearLayout
|
||||
this.quickAudioToggle = findViewById(R.id.quick_audio_toggle);
|
||||
this.buttonToggle = findViewById(R.id.button_toggle);
|
||||
this.recordingContainer = findViewById(R.id.recording_container);
|
||||
this.recordTime = new RecordTime(findViewById(R.id.record_time));
|
||||
this.recordLockCancel = findViewById(R.id.record_cancel);
|
||||
this.slideToCancel = new SlideToCancel(findViewById(R.id.slide_to_cancel));
|
||||
this.microphoneRecorderView = findViewById(R.id.recorder_view);
|
||||
this.microphoneRecorderView.setListener(this);
|
||||
this.recordTime = new RecordTime(findViewById(R.id.record_time),
|
||||
TimeUnit.HOURS.toSeconds(1),
|
||||
() -> microphoneRecorderView.cancelAction());
|
||||
|
||||
this.recordLockCancel.setOnClickListener(v -> microphoneRecorderView.cancelAction());
|
||||
|
||||
if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
|
||||
emojiToggle.setVisibility(View.GONE);
|
||||
@@ -181,21 +187,21 @@ public class InputPanel extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordPressed(float startPositionX) {
|
||||
public void onRecordPressed() {
|
||||
if (listener != null) listener.onRecorderStarted();
|
||||
recordTime.display();
|
||||
slideToCancel.display(startPositionX);
|
||||
slideToCancel.display();
|
||||
|
||||
if (emojiVisible) ViewUtil.fadeOut(emojiToggle, FADE_TIME, View.INVISIBLE);
|
||||
ViewUtil.fadeOut(composeText, FADE_TIME, View.INVISIBLE);
|
||||
ViewUtil.fadeOut(quickCameraToggle, FADE_TIME, View.INVISIBLE);
|
||||
ViewUtil.fadeOut(quickAudioToggle, FADE_TIME, View.INVISIBLE);
|
||||
ViewUtil.fadeOut(buttonToggle, FADE_TIME, View.INVISIBLE);
|
||||
buttonToggle.animate().alpha(0).setDuration(FADE_TIME).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordReleased(float x) {
|
||||
long elapsedTime = onRecordHideEvent(x);
|
||||
public void onRecordReleased() {
|
||||
long elapsedTime = onRecordHideEvent();
|
||||
|
||||
if (listener != null) {
|
||||
Log.d(TAG, "Elapsed time: " + elapsedTime);
|
||||
@@ -209,8 +215,8 @@ public class InputPanel extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordMoved(float x, float absoluteX) {
|
||||
slideToCancel.moveTo(x);
|
||||
public void onRecordMoved(float offsetX, float absoluteX) {
|
||||
slideToCancel.moveTo(offsetX);
|
||||
|
||||
int direction = ViewCompat.getLayoutDirection(this);
|
||||
float position = absoluteX / recordingContainer.getWidth();
|
||||
@@ -223,11 +229,19 @@ public class InputPanel extends LinearLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordCanceled(float x) {
|
||||
onRecordHideEvent(x);
|
||||
public void onRecordCanceled() {
|
||||
onRecordHideEvent();
|
||||
if (listener != null) listener.onRecorderCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordLocked() {
|
||||
slideToCancel.hide();
|
||||
recordLockCancel.setVisibility(View.VISIBLE);
|
||||
buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start();
|
||||
if (listener != null) listener.onRecorderLocked();
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
this.microphoneRecorderView.cancelAction();
|
||||
}
|
||||
@@ -239,8 +253,10 @@ public class InputPanel extends LinearLayout
|
||||
quickCameraToggle.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private long onRecordHideEvent(float x) {
|
||||
ListenableFuture<Void> future = slideToCancel.hide(x);
|
||||
private long onRecordHideEvent() {
|
||||
recordLockCancel.setVisibility(View.GONE);
|
||||
|
||||
ListenableFuture<Void> future = slideToCancel.hide();
|
||||
long elapsedTime = recordTime.hide();
|
||||
|
||||
future.addListener(new AssertedSuccessListener<Void>() {
|
||||
@@ -250,7 +266,7 @@ public class InputPanel extends LinearLayout
|
||||
ViewUtil.fadeIn(composeText, FADE_TIME);
|
||||
ViewUtil.fadeIn(quickCameraToggle, FADE_TIME);
|
||||
ViewUtil.fadeIn(quickAudioToggle, FADE_TIME);
|
||||
ViewUtil.fadeIn(buttonToggle, FADE_TIME);
|
||||
buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -276,9 +292,17 @@ public class InputPanel extends LinearLayout
|
||||
return getResources().getDimensionPixelSize(dimenRes);
|
||||
}
|
||||
|
||||
public boolean isRecordingInLockedMode() {
|
||||
return microphoneRecorderView.isRecordingLocked();
|
||||
}
|
||||
|
||||
public void releaseRecordingLock() {
|
||||
microphoneRecorderView.unlockAction();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onRecorderStarted();
|
||||
void onRecorderLocked();
|
||||
void onRecorderFinished();
|
||||
void onRecorderCanceled();
|
||||
void onRecorderPermissionRequired();
|
||||
@@ -290,23 +314,19 @@ public class InputPanel extends LinearLayout
|
||||
|
||||
private final View slideToCancelView;
|
||||
|
||||
private float startPositionX;
|
||||
|
||||
public SlideToCancel(View slideToCancelView) {
|
||||
SlideToCancel(View slideToCancelView) {
|
||||
this.slideToCancelView = slideToCancelView;
|
||||
}
|
||||
|
||||
public void display(float startPositionX) {
|
||||
this.startPositionX = startPositionX;
|
||||
public void display() {
|
||||
ViewUtil.fadeIn(this.slideToCancelView, FADE_TIME);
|
||||
}
|
||||
|
||||
public ListenableFuture<Void> hide(float x) {
|
||||
public ListenableFuture<Void> hide() {
|
||||
final SettableFuture<Void> future = new SettableFuture<>();
|
||||
float offset = getOffset(x);
|
||||
|
||||
AnimationSet animation = new AnimationSet(true);
|
||||
animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, offset,
|
||||
animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, slideToCancelView.getTranslationX(),
|
||||
Animation.ABSOLUTE, 0,
|
||||
Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, 0));
|
||||
@@ -323,8 +343,7 @@ public class InputPanel extends LinearLayout
|
||||
return future;
|
||||
}
|
||||
|
||||
public void moveTo(float x) {
|
||||
float offset = getOffset(x);
|
||||
void moveTo(float offset) {
|
||||
Animation animation = new TranslateAnimation(Animation.ABSOLUTE, offset,
|
||||
Animation.ABSOLUTE, offset,
|
||||
Animation.RELATIVE_TO_SELF, 0,
|
||||
@@ -336,49 +355,55 @@ public class InputPanel extends LinearLayout
|
||||
|
||||
slideToCancelView.startAnimation(animation);
|
||||
}
|
||||
|
||||
private float getOffset(float x) {
|
||||
return ViewCompat.getLayoutDirection(slideToCancelView) == ViewCompat.LAYOUT_DIRECTION_LTR ?
|
||||
-Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class RecordTime implements Runnable {
|
||||
|
||||
private final TextView recordTimeView;
|
||||
private final AtomicLong startTime = new AtomicLong(0);
|
||||
private final long limitSeconds;
|
||||
private final Runnable onLimitHit;
|
||||
private long startTime;
|
||||
|
||||
private RecordTime(TextView recordTimeView) {
|
||||
private RecordTime(TextView recordTimeView, long limitSeconds, Runnable onLimitHit) {
|
||||
this.recordTimeView = recordTimeView;
|
||||
this.limitSeconds = limitSeconds;
|
||||
this.onLimitHit = onLimitHit;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void display() {
|
||||
this.startTime.set(System.currentTimeMillis());
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.recordTimeView.setText(DateUtils.formatElapsedTime(0));
|
||||
ViewUtil.fadeIn(this.recordTimeView, FADE_TIME);
|
||||
Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public long hide() {
|
||||
long elapsedtime = System.currentTimeMillis() - startTime.get();
|
||||
this.startTime.set(0);
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
this.startTime = 0;
|
||||
ViewUtil.fadeOut(this.recordTimeView, FADE_TIME, View.INVISIBLE);
|
||||
return elapsedtime;
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@MainThread
|
||||
public void run() {
|
||||
long localStartTime = startTime.get();
|
||||
long localStartTime = startTime;
|
||||
if (localStartTime > 0) {
|
||||
long elapsedTime = System.currentTimeMillis() - localStartTime;
|
||||
recordTimeView.setText(DateUtils.formatElapsedTime(TimeUnit.MILLISECONDS.toSeconds(elapsedTime)));
|
||||
Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
long elapsedSeconds = TimeUnit.MILLISECONDS.toSeconds(elapsedTime);
|
||||
if (elapsedSeconds >= limitSeconds) {
|
||||
onLimitHit.run();
|
||||
} else {
|
||||
recordTimeView.setText(DateUtils.formatElapsedTime(elapsedSeconds));
|
||||
Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface MediaListener {
|
||||
public void onMediaSelected(@NonNull Uri uri, String contentType);
|
||||
void onMediaSelected(@NonNull Uri uri, String contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components;
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.util.AttributeSet;
|
||||
@@ -12,6 +13,7 @@ import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationSet;
|
||||
import android.view.animation.AnticipateOvershootInterpolator;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
@@ -22,13 +24,20 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener {
|
||||
public final class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener {
|
||||
|
||||
enum State {
|
||||
NOT_RUNNING,
|
||||
RUNNING_HELD,
|
||||
RUNNING_LOCKED
|
||||
}
|
||||
|
||||
public static final int ANIMATION_DURATION = 200;
|
||||
|
||||
private FloatingRecordButton floatingRecordButton;
|
||||
private LockDropTarget lockDropTarget;
|
||||
private @Nullable Listener listener;
|
||||
private boolean actionInProgress;
|
||||
private @NonNull State state = State.NOT_RUNNING;
|
||||
|
||||
public MicrophoneRecorderView(Context context) {
|
||||
super(context);
|
||||
@@ -42,22 +51,49 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
ImageView recordButtonFab = ViewUtil.findById(this, R.id.quick_audio_fab);
|
||||
this.floatingRecordButton = new FloatingRecordButton(getContext(), recordButtonFab);
|
||||
floatingRecordButton = new FloatingRecordButton(getContext(), findViewById(R.id.quick_audio_fab));
|
||||
lockDropTarget = new LockDropTarget (getContext(), findViewById(R.id.lock_drop_target));
|
||||
|
||||
View recordButton = ViewUtil.findById(this, R.id.quick_audio_toggle);
|
||||
recordButton.setOnTouchListener(this);
|
||||
}
|
||||
|
||||
public void cancelAction() {
|
||||
if (this.actionInProgress) {
|
||||
this.actionInProgress = false;
|
||||
this.floatingRecordButton.hide(this.floatingRecordButton.lastPositionX);
|
||||
if (state != State.NOT_RUNNING) {
|
||||
state = State.NOT_RUNNING;
|
||||
hideUi();
|
||||
|
||||
if (listener != null) listener.onRecordCanceled(this.floatingRecordButton.lastPositionX);
|
||||
if (listener != null) listener.onRecordCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRecordingLocked() {
|
||||
return state == State.RUNNING_LOCKED;
|
||||
}
|
||||
|
||||
private void lockAction() {
|
||||
if (state == State.RUNNING_HELD) {
|
||||
state = State.RUNNING_LOCKED;
|
||||
hideUi();
|
||||
|
||||
if (listener != null) listener.onRecordLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public void unlockAction() {
|
||||
if (state == State.RUNNING_LOCKED) {
|
||||
state = State.NOT_RUNNING;
|
||||
hideUi();
|
||||
|
||||
if (listener != null) listener.onRecordReleased();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideUi() {
|
||||
floatingRecordButton.hide();
|
||||
lockDropTarget.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, final MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
@@ -65,23 +101,29 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) {
|
||||
if (listener != null) listener.onRecordPermissionRequired();
|
||||
} else {
|
||||
this.actionInProgress = true;
|
||||
this.floatingRecordButton.display(event.getX());
|
||||
if (listener != null) listener.onRecordPressed(event.getX());
|
||||
state = State.RUNNING_HELD;
|
||||
floatingRecordButton.display(event.getX(), event.getY());
|
||||
lockDropTarget.display();
|
||||
if (listener != null) listener.onRecordPressed();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (this.actionInProgress) {
|
||||
this.actionInProgress = false;
|
||||
this.floatingRecordButton.hide(event.getX());
|
||||
if (listener != null) listener.onRecordReleased(event.getX());
|
||||
if (this.state == State.RUNNING_HELD) {
|
||||
state = State.NOT_RUNNING;
|
||||
hideUi();
|
||||
if (listener != null) listener.onRecordReleased();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (this.actionInProgress) {
|
||||
this.floatingRecordButton.moveTo(event.getX());
|
||||
if (listener != null) listener.onRecordMoved(event.getX(), event.getRawX());
|
||||
if (this.state == State.RUNNING_HELD) {
|
||||
this.floatingRecordButton.moveTo(event.getX(), event.getY());
|
||||
if (listener != null) listener.onRecordMoved(floatingRecordButton.lastOffsetX, event.getRawX());
|
||||
|
||||
int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target);
|
||||
if (floatingRecordButton.lastOffsetY <= dimensionPixelSize) {
|
||||
lockAction();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -94,10 +136,11 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onRecordPressed(float x);
|
||||
void onRecordReleased(float x);
|
||||
void onRecordCanceled(float x);
|
||||
void onRecordMoved(float x, float absoluteX);
|
||||
void onRecordPressed();
|
||||
void onRecordReleased();
|
||||
void onRecordCanceled();
|
||||
void onRecordLocked();
|
||||
void onRecordMoved(float offsetX, float absoluteX);
|
||||
void onRecordPermissionRequired();
|
||||
}
|
||||
|
||||
@@ -106,81 +149,73 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
private final ImageView recordButtonFab;
|
||||
|
||||
private float startPositionX;
|
||||
private float lastPositionX;
|
||||
private float startPositionY;
|
||||
private float lastOffsetX;
|
||||
private float lastOffsetY;
|
||||
|
||||
public FloatingRecordButton(Context context, ImageView recordButtonFab) {
|
||||
FloatingRecordButton(Context context, ImageView recordButtonFab) {
|
||||
this.recordButtonFab = recordButtonFab;
|
||||
this.recordButtonFab.getBackground().setColorFilter(context.getResources()
|
||||
.getColor(R.color.red_500),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
public void display(float x) {
|
||||
void display(float x, float y) {
|
||||
this.startPositionX = x;
|
||||
this.lastPositionX = x;
|
||||
this.startPositionY = y;
|
||||
|
||||
recordButtonFab.setVisibility(View.VISIBLE);
|
||||
|
||||
float translation = ViewCompat.getLayoutDirection(recordButtonFab) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR ? -.25f : .25f;
|
||||
|
||||
AnimationSet animation = new AnimationSet(true);
|
||||
animation.addAnimation(new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, 0,
|
||||
Animation.RELATIVE_TO_SELF, -.25f,
|
||||
Animation.RELATIVE_TO_SELF, -.25f));
|
||||
animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, 0,
|
||||
Animation.ABSOLUTE, 0,
|
||||
Animation.ABSOLUTE, 0,
|
||||
Animation.ABSOLUTE, 0));
|
||||
|
||||
animation.addAnimation(new ScaleAnimation(.5f, 1f, .5f, 1f,
|
||||
Animation.RELATIVE_TO_SELF, .5f,
|
||||
Animation.RELATIVE_TO_SELF, .5f));
|
||||
|
||||
animation.setFillBefore(true);
|
||||
animation.setFillAfter(true);
|
||||
animation.setDuration(ANIMATION_DURATION);
|
||||
animation.setInterpolator(new OvershootInterpolator());
|
||||
|
||||
recordButtonFab.startAnimation(animation);
|
||||
}
|
||||
|
||||
public void moveTo(float x) {
|
||||
this.lastPositionX = x;
|
||||
void moveTo(float x, float y) {
|
||||
lastOffsetX = getXOffset(x);
|
||||
lastOffsetY = getYOffset(y);
|
||||
|
||||
float offset = getOffset(x);
|
||||
if (Math.abs(lastOffsetX) > Math.abs(lastOffsetY)) {
|
||||
lastOffsetY = 0;
|
||||
} else {
|
||||
lastOffsetX = 0;
|
||||
}
|
||||
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, offset,
|
||||
Animation.ABSOLUTE, offset,
|
||||
Animation.RELATIVE_TO_SELF, -.25f,
|
||||
Animation.RELATIVE_TO_SELF, -.25f);
|
||||
|
||||
translateAnimation.setDuration(0);
|
||||
translateAnimation.setFillAfter(true);
|
||||
translateAnimation.setFillBefore(true);
|
||||
|
||||
recordButtonFab.startAnimation(translateAnimation);
|
||||
recordButtonFab.setTranslationX(lastOffsetX);
|
||||
recordButtonFab.setTranslationY(lastOffsetY);
|
||||
}
|
||||
|
||||
public void hide(float x) {
|
||||
this.lastPositionX = x;
|
||||
|
||||
float offset = getOffset(x);
|
||||
void hide() {
|
||||
recordButtonFab.setTranslationX(0);
|
||||
recordButtonFab.setTranslationY(0);
|
||||
if (recordButtonFab.getVisibility() != VISIBLE) return;
|
||||
|
||||
AnimationSet animation = new AnimationSet(false);
|
||||
Animation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, offset,
|
||||
Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, lastOffsetX,
|
||||
Animation.ABSOLUTE, 0,
|
||||
Animation.RELATIVE_TO_SELF, -.25f,
|
||||
Animation.RELATIVE_TO_SELF, -.25f);
|
||||
Animation.ABSOLUTE, lastOffsetY,
|
||||
Animation.ABSOLUTE, 0);
|
||||
|
||||
scaleAnimation.setInterpolator(new AnticipateOvershootInterpolator(1.5f));
|
||||
translateAnimation.setInterpolator(new DecelerateInterpolator());
|
||||
animation.addAnimation(scaleAnimation);
|
||||
animation.addAnimation(translateAnimation);
|
||||
animation.setDuration(ANIMATION_DURATION);
|
||||
animation.setFillBefore(true);
|
||||
animation.setFillAfter(false);
|
||||
animation.setInterpolator(new AnticipateOvershootInterpolator(1.5f));
|
||||
|
||||
recordButtonFab.setVisibility(View.GONE);
|
||||
@@ -188,16 +223,48 @@ public class MicrophoneRecorderView extends FrameLayout implements View.OnTouchL
|
||||
recordButtonFab.startAnimation(animation);
|
||||
}
|
||||
|
||||
private float getOffset(float x) {
|
||||
private float getXOffset(float x) {
|
||||
return ViewCompat.getLayoutDirection(recordButtonFab) == ViewCompat.LAYOUT_DIRECTION_LTR ?
|
||||
-Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX);
|
||||
}
|
||||
|
||||
private int getWidthAdjustment() {
|
||||
int width = recordButtonFab.getWidth() / 4;
|
||||
return ViewCompat.getLayoutDirection(recordButtonFab) == ViewCompat.LAYOUT_DIRECTION_LTR ? -width : width;
|
||||
private float getYOffset(float y) {
|
||||
return Math.min(0, y - this.startPositionY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class LockDropTarget {
|
||||
|
||||
private final View lockDropTarget;
|
||||
private final int dropTargetPosition;
|
||||
|
||||
LockDropTarget(Context context, View lockDropTarget) {
|
||||
this.lockDropTarget = lockDropTarget;
|
||||
this.dropTargetPosition = context.getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target);
|
||||
}
|
||||
|
||||
void display() {
|
||||
lockDropTarget.setScaleX(1);
|
||||
lockDropTarget.setScaleY(1);
|
||||
lockDropTarget.setAlpha(0);
|
||||
lockDropTarget.setTranslationY(0);
|
||||
lockDropTarget.setVisibility(VISIBLE);
|
||||
lockDropTarget.animate()
|
||||
.setStartDelay(ANIMATION_DURATION * 2)
|
||||
.setDuration(ANIMATION_DURATION)
|
||||
.setInterpolator(new DecelerateInterpolator())
|
||||
.translationY(dropTargetPosition)
|
||||
.alpha(1)
|
||||
.start();
|
||||
}
|
||||
|
||||
void hide() {
|
||||
lockDropTarget.animate()
|
||||
.setStartDelay(0)
|
||||
.setDuration(ANIMATION_DURATION)
|
||||
.setInterpolator(new LinearInterpolator())
|
||||
.scaleX(0).scaleY(0)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public class SendButton extends ImageButton
|
||||
}
|
||||
|
||||
private TransportOptions initializeTransportOptions(boolean media) {
|
||||
if (isInEditMode()) return null;
|
||||
|
||||
TransportOptions transportOptions = new TransportOptions(getContext(), media);
|
||||
transportOptions.addOnTransportChangedListener(this);
|
||||
|
||||
|
||||
@@ -1995,6 +1995,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void sendMessage() {
|
||||
if (inputPanel.isRecordingInLockedMode()) {
|
||||
inputPanel.releaseRecordingLock();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Recipient recipient = getRecipient();
|
||||
|
||||
@@ -2176,6 +2181,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void updateToggleButtonState() {
|
||||
if (inputPanel.isRecordingInLockedMode()) {
|
||||
buttonToggle.display(sendButton);
|
||||
quickAttachmentToggle.show();
|
||||
inlineAttachmentToggle.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (composeText.getText().length() == 0 && !attachmentManager.isAttachmentPresent()) {
|
||||
buttonToggle.display(attachButton);
|
||||
quickAttachmentToggle.show();
|
||||
@@ -2232,8 +2244,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
audioRecorder.startRecording();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecorderLocked() {
|
||||
updateToggleButtonState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecorderFinished() {
|
||||
updateToggleButtonState();
|
||||
Vibrator vibrator = ServiceUtil.getVibrator(this);
|
||||
vibrator.vibrate(20);
|
||||
|
||||
@@ -2274,6 +2292,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void onRecorderCanceled() {
|
||||
updateToggleButtonState();
|
||||
Vibrator vibrator = ServiceUtil.getVibrator(this);
|
||||
vibrator.vibrate(50);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user