diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java index ef8dc1cc80..1b9a865692 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java @@ -108,8 +108,8 @@ public final class AttachmentCompressionJob extends BaseJob { public void onRun() throws Exception { Log.d(TAG, "Running for: " + attachmentId); - AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); - DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId); + AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); + DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId); if (databaseAttachment == null) { throw new UndeliverableMessageException("Cannot find the specified attachment."); @@ -141,7 +141,7 @@ public final class AttachmentCompressionJob extends BaseJob { { try { if (MediaUtil.isVideo(attachment)) { - transcodeVideoIfNeededToDatabase(context, attachmentDatabase, attachment, constraints, EventBus.getDefault(), this::isCanceled); + attachment = transcodeVideoIfNeededToDatabase(context, attachmentDatabase, attachment, constraints, EventBus.getDefault(), this::isCanceled); if (!constraints.isSatisfied(context, attachment)) { throw new UndeliverableMessageException("Size constraints could not be met on video!"); } @@ -163,12 +163,12 @@ public final class AttachmentCompressionJob extends BaseJob { } } - private static void transcodeVideoIfNeededToDatabase(@NonNull Context context, - @NonNull AttachmentDatabase attachmentDatabase, - @NonNull DatabaseAttachment attachment, - @NonNull MediaConstraints constraints, - @NonNull EventBus eventBus, - @NonNull InMemoryTranscoder.CancelationSignal cancelationSignal) + private static @NonNull DatabaseAttachment transcodeVideoIfNeededToDatabase(@NonNull Context context, + @NonNull AttachmentDatabase attachmentDatabase, + @NonNull DatabaseAttachment attachment, + @NonNull MediaConstraints constraints, + @NonNull EventBus eventBus, + @NonNull InMemoryTranscoder.CancelationSignal cancelationSignal) throws UndeliverableMessageException { AttachmentDatabase.TransformProperties transformProperties = attachment.getTransformProperties(); @@ -179,7 +179,7 @@ public final class AttachmentCompressionJob extends BaseJob { if (transformProperties.isVideoEdited()) { throw new UndeliverableMessageException("Video edited, but transcode is not available"); } - return; + return attachment; } try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) { @@ -210,6 +210,11 @@ public final class AttachmentCompressionJob extends BaseJob { attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited()); attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId()); + DatabaseAttachment updatedAttachment = attachmentDatabase.getAttachment(attachment.getAttachmentId()); + if (updatedAttachment == null) { + throw new AssertionError(); + } + return updatedAttachment; } } } @@ -226,6 +231,7 @@ public final class AttachmentCompressionJob extends BaseJob { } catch (IOException | MmsException | VideoSizeException e) { throw new UndeliverableMessageException("Failed to transcode", e); } + return attachment; } private static MediaStream getResizedMedia(@NonNull Context context, diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java b/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java index e1b327fedd..408c41024e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/videoconverter/VideoThumbnailsRangeSelectorView.java @@ -5,6 +5,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; @@ -17,16 +18,21 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import org.thoughtcrime.securesms.R; +import java.util.Locale; import java.util.concurrent.TimeUnit; @RequiresApi(api = 23) public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView { private static final long MINIMUM_SELECTABLE_RANGE = TimeUnit.MILLISECONDS.toMicros(500); + private static final int ANIMATION_DURATION_MS = 100; - private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Rect tempDrawRect = new Rect(); + private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint paintGrey = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint thumbTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint thumbTimeBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Rect tempDrawRect = new Rect(); + private final RectF timePillRect = new RectF(); private Drawable chevronLeft; private Drawable chevronRight; @@ -42,8 +48,8 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView private long downMin; private long downMax; private Thumb dragThumb; + private Thumb lastDragThumb; private OnRangeChangeListener onRangeChangeListener; - @Px private int thumbSizePixels; @Px private int thumbTouchRadius; @Px private int cursorPixels; @@ -52,6 +58,11 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView @ColorInt private int thumbColorEdited; private long actualPosition; private long dragPosition; + @Px private int thumbHintTextSize; + @ColorInt private int thumbHintTextColor; + @ColorInt private int thumbHintBackgroundColor; + private long dragStartTimeMs; + private long dragEndTimeMs; public VideoThumbnailsRangeSelectorView(final Context context) { super(context); @@ -71,13 +82,19 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView private void init(final @Nullable AttributeSet attrs) { if (attrs != null) { TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.VideoThumbnailsRangeSelectorView, 0, 0); - - thumbSizePixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbWidth, 1); - cursorPixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_cursorWidth, 1); - thumbColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColor, 0xffff0000); - thumbColorEdited = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColorEdited, thumbColor); - cursorColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_cursorColor, thumbColor); - thumbTouchRadius = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbTouchRadius, 50); + try { + thumbSizePixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbWidth, 1); + cursorPixels = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_cursorWidth, 1); + thumbColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColor, 0xffff0000); + thumbColorEdited = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbColorEdited, thumbColor); + cursorColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_cursorColor, thumbColor); + thumbTouchRadius = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbTouchRadius, 50); + thumbHintTextSize = typedArray.getDimensionPixelSize(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintTextSize, 0); + thumbHintTextColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintTextColor, 0xffff0000); + thumbHintBackgroundColor = typedArray.getColor(R.styleable.VideoThumbnailsRangeSelectorView_thumbHintBackgroundColor, 0xff00ff00); + } finally { + typedArray.recycle(); + } } chevronLeft = VectorDrawableCompat.create(getResources(), R.drawable.ic_chevron_left_black_8dp, null); @@ -88,6 +105,12 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView paintGrey.setStrokeWidth(1); paint.setStrokeWidth(2); + + thumbTimeTextPaint.setTextSize(thumbHintTextSize); + thumbTimeTextPaint.setColor(thumbHintTextColor); + + thumbTimeBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE); + thumbTimeBackgroundPaint.setColor(thumbHintBackgroundColor); } @Override @@ -194,6 +217,16 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView chevronRight.draw(canvas); canvas.restore(); + // draw time hint pill + if (thumbHintTextSize > 0) { + if (dragStartTimeMs > 0 && (dragThumb == Thumb.MIN || dragThumb == Thumb.MAX)) { + drawTimeHint(canvas, drawableWidth, drawableHeight, dragThumb, false); + } + if (dragEndTimeMs > 0 && (lastDragThumb == Thumb.MIN || lastDragThumb == Thumb.MAX)) { + drawTimeHint(canvas, drawableWidth, drawableHeight, lastDragThumb, true); + } + } + // draw current position marker if (left <= cursor && cursor <= right && dragThumb != Thumb.MIN && dragThumb != Thumb.MAX) { canvas.translate(cursorPixels / 2, 0); @@ -203,6 +236,50 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView } } + private void drawTimeHint(Canvas canvas, int drawableWidth, int drawableHeight, Thumb dragThumb, boolean fadeOut) { + canvas.save(); + long microsecondValue = dragThumb == Thumb.MIN ? getMinValue() : getMaxValue(); + long seconds = TimeUnit.MICROSECONDS.toSeconds(microsecondValue); + String timeString = String.format(Locale.getDefault(), "%d:%02d", seconds / 60, seconds % 60); + float topBottomPadding = thumbHintTextSize * 0.5f; + float leftRightPadding = thumbHintTextSize * 0.75f; + + thumbTimeTextPaint.getTextBounds(timeString, 0, timeString.length(), tempDrawRect); + + timePillRect.set(tempDrawRect.left - leftRightPadding, tempDrawRect.top - topBottomPadding, tempDrawRect.right + leftRightPadding, tempDrawRect.bottom + topBottomPadding); + + float halfPillWidth = timePillRect.width() / 2f; + float halfPillHeight = timePillRect.height() / 2f; + + long animationTime = fadeOut ? ANIMATION_DURATION_MS - Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - dragEndTimeMs) + : Math.min(ANIMATION_DURATION_MS, System.currentTimeMillis() - dragStartTimeMs); + float animationPosition = animationTime / (float) ANIMATION_DURATION_MS; + float scaleIn = 0.2f * animationPosition + 0.8f; + int alpha = (int) (255 * animationPosition); + + if (dragThumb == Thumb.MAX) { + canvas.translate(Math.min(right, drawableWidth - halfPillWidth), 0); + } else { + canvas.translate(Math.max(left, halfPillWidth), 0); + } + canvas.translate(0, drawableHeight + halfPillHeight); + canvas.scale(scaleIn, scaleIn); + thumbTimeBackgroundPaint.setAlpha(alpha); + thumbTimeTextPaint.setAlpha(alpha); + canvas.translate(leftRightPadding - halfPillWidth, halfPillHeight); + canvas.drawRoundRect(timePillRect, halfPillHeight, halfPillHeight, thumbTimeBackgroundPaint); + canvas.drawText(timeString, 0, 0, thumbTimeTextPaint); + canvas.restore(); + + if (fadeOut && animationTime > 0 || !fadeOut && animationTime < ANIMATION_DURATION_MS) { + invalidate(); + } else { + if (fadeOut) { + lastDragThumb = null; + } + } + } + public long getMinValue() { return minValue == null ? 0 : minValue; } @@ -257,11 +334,13 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView public boolean onTouchEvent(MotionEvent event) { int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { - xDown = event.getX(); - downCursor = actualPosition; - downMin = getMinValue(); - downMax = getMaxValue(); - dragThumb = closestThumb(event.getX()); + xDown = event.getX(); + downCursor = actualPosition; + downMin = getMinValue(); + downMax = getMaxValue(); + dragThumb = closestThumb(event.getX()); + dragStartTimeMs = System.currentTimeMillis(); + invalidate(); return dragThumb != null; } @@ -297,7 +376,9 @@ public final class VideoThumbnailsRangeSelectorView extends VideoThumbnailsView } else { onRangeChangeListener.onRangeDragEnd(getMinValue(), getMaxValue(), getDuration(), dragThumb); } - dragThumb = null; + lastDragThumb = dragThumb; + dragEndTimeMs = System.currentTimeMillis(); + dragThumb = null; invalidate(); } return true; diff --git a/app/src/main/res/layout/video_editor_hud.xml b/app/src/main/res/layout/video_editor_hud.xml index 2ffa95a3ba..6439c37b1b 100644 --- a/app/src/main/res/layout/video_editor_hud.xml +++ b/app/src/main/res/layout/video_editor_hud.xml @@ -10,7 +10,8 @@ + android:layout_height="match_parent" + android:clipChildren="false"> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 22fe03645d..84a30d5a1c 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -482,6 +482,9 @@ + + +