From 45583ea4696790b5284ab3b633c24fa12d717e65 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 22 Sep 2023 16:13:19 -0300 Subject: [PATCH] Revert "Instant Video Playback UI" This reverts commit f8283acfae3b78593db4a39507547b43891112d9. --- .../securesms/attachments/Attachment.java | 6 +- .../components/AlbumThumbnailView.java | 42 ++- .../components/ConversationItemThumbnail.kt | 12 +- .../ConversationItemThumbnailState.kt | 7 +- .../securesms/components/ThumbnailView.java | 56 ++-- .../ThumbnailViewTransferControlsState.kt | 10 +- .../components/TransferControlView.java | 273 +++++++++++++++++ .../transfercontrols/TransferControlView.kt | 289 ------------------ .../transfercontrols/TransferProgressView.kt | 169 ---------- .../conversation/ConversationItem.java | 93 +----- .../securesms/jobs/AttachmentDownloadJob.java | 43 +-- .../securesms/jobs/AttachmentUploadJob.java | 16 +- .../securesms/jobs/PushSendJob.java | 12 +- .../securesms/util/MediaUtil.java | 9 - .../drawable/transfer_controls_background.xml | 2 +- .../transfer_controls_play_background.xml | 9 - .../drawable/transfer_controls_stop_icon.xml | 8 - .../main/res/layout/album_thumbnail_view.xml | 4 +- app/src/main/res/layout/thumbnail_view.xml | 5 +- .../res/layout/transfer_controls_stub.xml | 4 +- .../res/layout/transfer_controls_view.xml | 167 +++------- app/src/main/res/values/dimens.xml | 1 - .../api/messages/SignalServiceAttachment.java | 1 - .../internal/push/PushServiceSocket.java | 5 +- .../push/http/DigestingRequestBodyTest.java | 12 +- 25 files changed, 399 insertions(+), 856 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt delete mode 100644 app/src/main/res/drawable/transfer_controls_play_background.xml delete mode 100644 app/src/main/res/drawable/transfer_controls_stop_icon.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java index 13980699a0..7973ef911a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/Attachment.java @@ -172,11 +172,7 @@ public abstract class Attachment { @Nullable public byte[] getIncrementalDigest() { - if (incrementalDigest != null && incrementalDigest.length > 0) { - return incrementalDigest; - } else { - return null; - } + return incrementalDigest; } @Nullable diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java index 5b64fa17a2..2143327932 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java @@ -12,7 +12,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideClickListener; @@ -55,7 +54,7 @@ public class AlbumThumbnailView extends FrameLayout { inflate(getContext(), R.layout.album_thumbnail_view, this); albumCellContainer = findViewById(R.id.album_cell_container); - transferControls = new Stub<>(findViewById(R.id.album_transfer_controls_stub)); + transferControls = new Stub<>(findViewById(R.id.album_transfer_controls_stub)); } public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List slides, boolean showControls) { @@ -65,13 +64,12 @@ public class AlbumThumbnailView extends FrameLayout { if (showControls) { transferControls.get().setShowDownloadText(true); + transferControls.get().setSlides(slides); transferControls.get().setDownloadClickListener(v -> { if (downloadClickListener != null) { downloadClickListener.onClick(v, slides); } }); - transferControls.get().setSlides(slides); - transferControls.setVisibility(VISIBLE); } else { if (transferControls.resolved()) { transferControls.get().setVisibility(GONE); @@ -87,7 +85,6 @@ public class AlbumThumbnailView extends FrameLayout { showSlides(glideRequests, slides); applyCorners(); - forceLayout(); } public void setCellBackgroundColor(@ColorInt int color) { @@ -120,25 +117,22 @@ public class AlbumThumbnailView extends FrameLayout { private void inflateLayout(int sizeClass) { albumCellContainer.removeAllViews(); - int resId = switch (sizeClass) { - case 2 -> R.layout.album_thumbnail_2; - case 3 -> R.layout.album_thumbnail_3; - case 4 -> R.layout.album_thumbnail_4; - case 5 -> R.layout.album_thumbnail_5; - default -> R.layout.album_thumbnail_many; - }; - - inflate(getContext(), resId, albumCellContainer); - if (transferControls.resolved()) { - int size = switch (sizeClass) { - case 2 -> R.dimen.album_2_total_height; - case 3 -> R.dimen.album_3_total_height; - case 4 -> R.dimen.album_4_total_height; - default -> R.dimen.album_5_total_height; - }; - final ViewGroup.LayoutParams params = transferControls.get().getLayoutParams(); - params.height = getContext().getResources().getDimensionPixelSize(size); - transferControls.get().setLayoutParams(params); + switch (sizeClass) { + case 2: + inflate(getContext(), R.layout.album_thumbnail_2, albumCellContainer); + break; + case 3: + inflate(getContext(), R.layout.album_thumbnail_3, albumCellContainer); + break; + case 4: + inflate(getContext(), R.layout.album_thumbnail_4, albumCellContainer); + break; + case 5: + inflate(getContext(), R.layout.album_thumbnail_5, albumCellContainer); + break; + default: + inflate(getContext(), R.layout.album_thumbnail_many, albumCellContainer); + break; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt index 21180bfe78..7ee62e28cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnail.kt @@ -255,17 +255,9 @@ class ConversationItemThumbnail @JvmOverloads constructor( state.applyState(thumbnail, album) } - fun setPlayVideoClickListener(listener: SlideClickListener?) { + fun setProgressWheelClickListener(listener: SlideClickListener?) { state = state.copy( - thumbnailViewState = state.thumbnailViewState.copy(playVideoClickListener = listener) - ) - - state.applyState(thumbnail, album) - } - - fun setCancelDownloadClickListener(listener: SlidesClickedListener?) { - state = state.copy( - thumbnailViewState = state.thumbnailViewState.copy(cancelDownloadClickListener = listener) + thumbnailViewState = state.thumbnailViewState.copy(progressWheelClickListener = listener) ) state.applyState(thumbnail, album) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt index 42c5546a76..f8ef85a79a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemThumbnailState.kt @@ -31,9 +31,7 @@ data class ConversationItemThumbnailState( @IgnoredOnParcel private val downloadClickListener: SlidesClickedListener? = null, @IgnoredOnParcel - private val cancelDownloadClickListener: SlidesClickedListener? = null, - @IgnoredOnParcel - private val playVideoClickListener: SlideClickListener? = null, + private val progressWheelClickListener: SlideClickListener? = null, @IgnoredOnParcel private val longClickListener: OnLongClickListener? = null, private val visibility: Int = View.GONE, @@ -59,8 +57,7 @@ data class ConversationItemThumbnailState( thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft) thumbnailView.get().setThumbnailClickListener(clickListener) thumbnailView.get().setDownloadClickListener(downloadClickListener) - thumbnailView.get().setCancelDownloadClickListener(cancelDownloadClickListener) - thumbnailView.get().setPlayVideoClickListener(playVideoClickListener) + thumbnailView.get().setProgressWheelClickListener(progressWheelClickListener) thumbnailView.get().setOnLongClickListener(longClickListener) thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java index 02d5a400b6..b20a2a6096 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -31,7 +31,6 @@ import org.signal.core.util.logging.Log; import org.signal.glide.transforms.SignalDownsampleStrategy; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.blurhash.BlurHash; -import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView; import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequest; @@ -42,6 +41,7 @@ import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.stories.StoryTextPostModel; +import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; @@ -80,13 +80,12 @@ public class ThumbnailView extends FrameLayout { private final CornerMask cornerMask; - private ThumbnailViewTransferControlsState transferControlsState = new ThumbnailViewTransferControlsState(); + private ThumbnailViewTransferControlsState transferControlsState = new ThumbnailViewTransferControlsState(); private Stub transferControlViewStub; - private SlideClickListener thumbnailClickListener = null; - private SlidesClickedListener downloadClickListener = null; - private SlidesClickedListener cancelDownloadClickListener = null; - private SlideClickListener playVideoClickListener = null; - private Slide slide = null; + private SlideClickListener thumbnailClickListener = null; + private SlidesClickedListener downloadClickListener = null; + private SlideClickListener progressWheelClickListener = null; + private Slide slide = null; public ThumbnailView(Context context) { @@ -368,11 +367,10 @@ public class ThumbnailView extends FrameLayout { } transferControlsState = transferControlsState.withSlide(slide) - .withDownloadClickListener(new DownloadClickDispatcher()) - .withCancelDownloadClickListener(new CancelClickDispatcher()); + .withDownloadClickListener(new DownloadClickDispatcher()); - if (MediaUtil.isInstantVideoSupported(slide)) { - transferControlsState = transferControlsState.withInstantPlaybackClickListener(new ProgressWheelClickDispatcher()); + if (FeatureFlags.instantVideoPlayback()) { + transferControlsState = transferControlsState.withProgressWheelClickListener(new ProgressWheelClickDispatcher()); } transferControlsState.applyState(transferControlViewStub); @@ -527,12 +525,8 @@ public class ThumbnailView extends FrameLayout { this.downloadClickListener = listener; } - public void setCancelDownloadClickListener(SlidesClickedListener listener) { - this.cancelDownloadClickListener = listener; - } - - public void setPlayVideoClickListener(SlideClickListener listener) { - this.playVideoClickListener = listener; + public void setProgressWheelClickListener(SlideClickListener listener) { + this.progressWheelClickListener = listener; } public void clear(GlideRequests glideRequests) { @@ -574,9 +568,9 @@ public class ThumbnailView extends FrameLayout { private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(Objects.requireNonNull(slide.getUri()))) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE) - .transition(withCrossFade())); + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .downsample(SignalDownsampleStrategy.CENTER_OUTSIDE_NO_UPSCALE) + .transition(withCrossFade())); boolean doNotShowMissingThumbnailImage = Build.VERSION.SDK_INT < 23; @@ -631,7 +625,7 @@ public class ThumbnailView extends FrameLayout { if (Util.equals(slide, other)) { if (slide != null && other != null) { - byte[] digestLeft = slide.asAttachment().getDigest(); + byte[] digestLeft = slide.asAttachment().getDigest(); byte[] digestRight = other.asAttachment().getDigest(); return Arrays.equals(digestLeft, digestRight); @@ -676,26 +670,14 @@ public class ThumbnailView extends FrameLayout { } } - private class CancelClickDispatcher implements View.OnClickListener { - @Override - public void onClick(View view) { - Log.i(TAG, "onClick() for cancel button"); - if (cancelDownloadClickListener != null && slide != null) { - cancelDownloadClickListener.onClick(view, Collections.singletonList(slide)); - } else { - Log.w(TAG, "Received a cancel button click, but unable to execute it. slide: " + slide + " cancelDownloadClickListener: " + cancelDownloadClickListener); - } - } - } - private class ProgressWheelClickDispatcher implements View.OnClickListener { @Override public void onClick(View view) { - Log.i(TAG, "onClick() for instant video playback"); - if (playVideoClickListener != null && slide != null) { - playVideoClickListener.onClick(view, slide); + Log.i(TAG, "onClick() for progress wheel"); + if (progressWheelClickListener != null && slide != null) { + progressWheelClickListener.onClick(view, slide); } else { - Log.w(TAG, "Received an instant video click, but unable to execute it. slide: " + slide + " progressWheelClickListener: " + playVideoClickListener); + Log.w(TAG, "Received a progress wheel click, but unable to execute it. slide: " + slide + " progressWheelClickListener: " + progressWheelClickListener); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt index b3577a810c..c8d60ea185 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailViewTransferControlsState.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.components import android.view.View.OnClickListener -import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.util.views.Stub @@ -13,8 +12,7 @@ data class ThumbnailViewTransferControlsState( val isClickable: Boolean = true, val slide: Slide? = null, val downloadClickedListener: OnClickListener? = null, - val cancelDownloadClickedListener: OnClickListener? = null, - val instantPlaybackClickListener: OnClickListener? = null, + val progressWheelClickedListener: OnClickListener? = null, val showDownloadText: Boolean = true ) { @@ -22,8 +20,7 @@ data class ThumbnailViewTransferControlsState( fun withClickable(isClickable: Boolean): ThumbnailViewTransferControlsState = copy(isClickable = isClickable) fun withSlide(slide: Slide?): ThumbnailViewTransferControlsState = copy(slide = slide) fun withDownloadClickListener(downloadClickedListener: OnClickListener): ThumbnailViewTransferControlsState = copy(downloadClickedListener = downloadClickedListener) - fun withCancelDownloadClickListener(cancelClickListener: OnClickListener): ThumbnailViewTransferControlsState = copy(cancelDownloadClickedListener = cancelClickListener) - fun withInstantPlaybackClickListener(instantPlaybackClickListener: OnClickListener): ThumbnailViewTransferControlsState = copy(instantPlaybackClickListener = instantPlaybackClickListener) + fun withProgressWheelClickListener(progressWheelClickedListener: OnClickListener): ThumbnailViewTransferControlsState = copy(progressWheelClickedListener = progressWheelClickedListener) fun withDownloadText(showDownloadText: Boolean): ThumbnailViewTransferControlsState = copy(showDownloadText = showDownloadText) fun applyState(transferControlView: Stub) { @@ -34,9 +31,8 @@ data class ThumbnailViewTransferControlsState( transferControlView.get().setSlide(slide) } transferControlView.get().setDownloadClickListener(downloadClickedListener) + transferControlView.get().setProgressWheelClickListener(progressWheelClickedListener) transferControlView.get().setShowDownloadText(showDownloadText) - transferControlView.get().setCancelClickListener(cancelDownloadClickedListener) - transferControlView.get().setInstantPlaybackClickListener(instantPlaybackClickListener) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java new file mode 100644 index 0000000000..4d04d72c15 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/TransferControlView.java @@ -0,0 +1,273 @@ +package org.thoughtcrime.securesms.components; + +import android.animation.LayoutTransition; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.annimon.stream.Stream; +import com.pnikosis.materialishprogress.ProgressWheel; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.database.AttachmentTable; +import org.thoughtcrime.securesms.events.PartProgressEvent; +import org.thoughtcrime.securesms.mms.Slide; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public final class TransferControlView extends FrameLayout { + + private static final String TAG = "TransferControlView"; + private static final int UPLOAD_TASK_WEIGHT = 1; + + /** + * A weighting compared to {@link #UPLOAD_TASK_WEIGHT} + */ + private static final int COMPRESSION_TASK_WEIGHT = 3; + + @Nullable private List slides; + @Nullable private View current; + + private final ProgressWheel progressWheel; + private final View downloadDetails; + private final TextView downloadDetailsText; + + private final Map networkProgress; + private final Map compresssionProgress; + + public TransferControlView(Context context) { + this(context, null); + } + + public TransferControlView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TransferControlView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + inflate(context, R.layout.transfer_controls_view, this); + + setLongClickable(false); + setBackground(ContextCompat.getDrawable(context, R.drawable.transfer_controls_background)); + setVisibility(GONE); + setLayoutTransition(new LayoutTransition()); + + this.networkProgress = new HashMap<>(); + this.compresssionProgress = new HashMap<>(); + + this.progressWheel = findViewById(R.id.progress_wheel); + this.downloadDetails = findViewById(R.id.download_details); + this.downloadDetailsText = findViewById(R.id.download_details_text); + } + + @Override + public void setFocusable(boolean focusable) { + super.setFocusable(focusable); + downloadDetails.setFocusable(focusable); + } + + @Override + public void setClickable(boolean clickable) { + super.setClickable(clickable); + downloadDetails.setClickable(clickable); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + EventBus.getDefault().unregister(this); + } + + public void setSlide(final @NonNull Slide slides) { + setSlides(Collections.singletonList(slides)); + } + + public void setSlides(final @NonNull List slides) { + if (slides.isEmpty()) { + throw new IllegalArgumentException("Must provide at least one slide."); + } + + this.slides = slides; + + if (!isUpdateToExistingSet(slides)) { + networkProgress.clear(); + compresssionProgress.clear(); + Stream.of(slides).forEach(s -> networkProgress.put(s.asAttachment(), 0f)); + } + + for (Slide slide : slides) { + if (slide.asAttachment().getTransferState() == AttachmentTable.TRANSFER_PROGRESS_DONE) { + networkProgress.put(slide.asAttachment(), 1f); + } + } + + switch (getTransferState(slides)) { + case AttachmentTable.TRANSFER_PROGRESS_STARTED: + showProgressSpinner(calculateProgress(networkProgress, compresssionProgress)); + break; + case AttachmentTable.TRANSFER_PROGRESS_PENDING: + case AttachmentTable.TRANSFER_PROGRESS_FAILED: + String downloadText = getDownloadText(this.slides); + if (!Objects.equals(downloadText, downloadDetailsText.getText().toString())) { + downloadDetailsText.setText(getDownloadText(this.slides)); + } + + display(downloadDetails); + break; + default: + display(null); + break; + } + } + + public void showProgressSpinner() { + showProgressSpinner(calculateProgress(networkProgress, compresssionProgress)); + } + + public void showProgressSpinner(float progress) { + if (progress == 0) { + progressWheel.spin(); + } else { + progressWheel.setInstantProgress(progress); + } + + display(progressWheel); + } + + public void setDownloadClickListener(final @Nullable OnClickListener listener) { + downloadDetails.setOnClickListener(listener); + } + + public void setProgressWheelClickListener(final @Nullable OnClickListener listener) { + progressWheel.setOnClickListener(listener); + } + + public void clear() { + clearAnimation(); + setVisibility(GONE); + if (current != null) { + current.clearAnimation(); + current.setVisibility(GONE); + } + current = null; + slides = null; + } + + public void setShowDownloadText(boolean showDownloadText) { + downloadDetailsText.setVisibility(showDownloadText ? VISIBLE : GONE); + forceLayout(); + } + + private boolean isUpdateToExistingSet(@NonNull List slides) { + if (slides.size() != networkProgress.size()) { + return false; + } + + for (Slide slide : slides) { + if (!networkProgress.containsKey(slide.asAttachment())) { + return false; + } + } + + return true; + } + + static int getTransferState(@NonNull List slides) { + int transferState = AttachmentTable.TRANSFER_PROGRESS_DONE; + boolean allFailed = true; + + for (Slide slide : slides) { + if (slide.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE) { + allFailed = false; + if (slide.getTransferState() == AttachmentTable.TRANSFER_PROGRESS_PENDING && transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) { + transferState = slide.getTransferState(); + } else { + transferState = Math.max(transferState, slide.getTransferState()); + } + } + } + return allFailed ? AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE : transferState; + } + + private String getDownloadText(@NonNull List slides) { + if (slides.size() == 1) { + return slides.get(0).getContentDescription(getContext()); + } else { + int downloadCount = Stream.of(slides).reduce(0, (count, slide) -> slide.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_DONE ? count + 1 : count); + return getContext().getResources().getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount); + } + } + + private void display(@Nullable final View view) { + if (current == view) { + return; + } + + if (current != null) { + current.setVisibility(GONE); + } + + if (view != null) { + view.setVisibility(VISIBLE); + setVisibility(VISIBLE); + } else { + setVisibility(GONE); + } + + current = view; + } + + private static float calculateProgress(@NonNull Map uploadDownloadProgress, Map compresssionProgress) { + float totalDownloadProgress = 0; + float totalCompressionProgress = 0; + + for (float progress : uploadDownloadProgress.values()) { + totalDownloadProgress += progress; + } + + for (float progress : compresssionProgress.values()) { + totalCompressionProgress += progress; + } + + float weightedProgress = UPLOAD_TASK_WEIGHT * totalDownloadProgress + COMPRESSION_TASK_WEIGHT * totalCompressionProgress; + float weightedTotal = UPLOAD_TASK_WEIGHT * uploadDownloadProgress.size() + COMPRESSION_TASK_WEIGHT * compresssionProgress.size(); + + return weightedProgress / weightedTotal; + } + + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) + public void onEventAsync(final PartProgressEvent event) { + final Attachment attachment = event.attachment; + if (networkProgress.containsKey(attachment)) { + float proportionCompleted = ((float) event.progress) / event.total; + + if (event.type == PartProgressEvent.Type.COMPRESSION) { + compresssionProgress.put(attachment, proportionCompleted); + } else { + networkProgress.put(attachment, proportionCompleted); + } + + progressWheel.setInstantProgress(calculateProgress(networkProgress, compresssionProgress)); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt deleted file mode 100644 index f6814eb542..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.thoughtcrime.securesms.components.transfercontrols - -import android.animation.LayoutTransition -import android.annotation.SuppressLint -import android.content.Context -import android.text.format.Formatter -import android.util.AttributeSet -import android.view.View -import android.widget.Space -import android.widget.TextView -import androidx.appcompat.widget.AppCompatImageView -import androidx.constraintlayout.widget.ConstraintLayout -import com.annimon.stream.Stream -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode -import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.attachments.Attachment -import org.thoughtcrime.securesms.database.AttachmentTable -import org.thoughtcrime.securesms.events.PartProgressEvent -import org.thoughtcrime.securesms.mms.Slide -import org.thoughtcrime.securesms.util.MediaUtil -import org.thoughtcrime.securesms.util.ThrottledDebouncer -import org.thoughtcrime.securesms.util.ViewUtil -import org.thoughtcrime.securesms.util.visible - -class TransferControlView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { - private var slides: List = emptyList() - private var current: MutableSet = HashSet() - private var playableWhileDownloading = false - private var showDownloadText = true - private val downloadDetails: View - private val downloadDetailsText: TextView - private val primaryDetailsText: TextView - private val secondaryViewSpace: Space - private val playVideoButton: AppCompatImageView - private val primaryProgressView: TransferProgressView - private val secondaryProgressView: TransferProgressView - private val networkProgress: MutableMap - private val compressionProgress: MutableMap - private val debouncer: ThrottledDebouncer = ThrottledDebouncer(8) // frame time for 120 Hz - - init { - inflate(context, R.layout.transfer_controls_view, this) - isLongClickable = false - visibility = GONE - layoutTransition = LayoutTransition() - networkProgress = HashMap() - compressionProgress = HashMap() - primaryProgressView = findViewById(R.id.primary_progress_view) - secondaryProgressView = findViewById(R.id.secondary_progress_view) - playVideoButton = findViewById(R.id.play_video_button) - downloadDetails = findViewById(R.id.secondary_background) - downloadDetailsText = findViewById(R.id.download_details_text) - secondaryViewSpace = findViewById(R.id.secondary_view_space) - primaryDetailsText = findViewById(R.id.primary_details_text) - } - - override fun setFocusable(focusable: Boolean) { - super.setFocusable(focusable) - progressView.isFocusable = focusable - if (playVideoButton.visibility == VISIBLE) { - playVideoButton.isFocusable = focusable - } - } - - override fun setClickable(clickable: Boolean) { - super.setClickable(clickable) - secondaryProgressView.isClickable = secondaryProgressView.visible && clickable - primaryProgressView.isClickable = primaryProgressView.visible && clickable - playVideoButton.isClickable = playVideoButton.visible && clickable - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this) - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - EventBus.getDefault().unregister(this) - } - - fun setSlide(slides: Slide) { - setSlides(listOf(slides)) - } - - fun setSlides(slides: List) { - require(slides.isNotEmpty()) { "Must provide at least one slide." } - this.slides = slides - if (!isUpdateToExistingSet(slides)) { - networkProgress.clear() - compressionProgress.clear() - slides.forEach { networkProgress[it.asAttachment()] = Progress(0L, it.fileSize) } - } - var allStreamableOrDone = true - for (slide in slides) { - val attachment = slide.asAttachment() - if (attachment.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) { - networkProgress[attachment] = Progress(1L, attachment.size) - } else if (!MediaUtil.isInstantVideoSupported(slide)) { - allStreamableOrDone = false - } - } - playableWhileDownloading = allStreamableOrDone - setPlayableWhileDownloading(playableWhileDownloading) - val uploading = slides.any { it.asAttachment().uploadTimestamp == 0L } - when (getTransferState(slides)) { - AttachmentTable.TRANSFER_PROGRESS_STARTED -> showProgressSpinner(calculateProgress(), uploading) - AttachmentTable.TRANSFER_PROGRESS_PENDING -> { - updateDownloadText() - progressView.setStopped(false) - this.visible = true - } - - AttachmentTable.TRANSFER_PROGRESS_FAILED -> { - downloadDetailsText.setText(R.string.NetworkFailure__retry) - progressView.setStopped(false) - this.visible = true - } - - else -> this.visible = false - } - } - - private val progressView: TransferProgressView - get() = if (playableWhileDownloading) { - secondaryProgressView - } else { - primaryProgressView - } - - @JvmOverloads - fun showProgressSpinner(progress: Float = calculateProgress(), uploading: Boolean = false) { - if (uploading || progress == 0f) { - progressView.setUploading(progress) - } else { - progressView.setDownloading(progress) - } - } - - fun setDownloadClickListener(listener: OnClickListener?) { - primaryProgressView.startClickListener = listener - secondaryProgressView.startClickListener = listener - } - - fun setCancelClickListener(listener: OnClickListener?) { - primaryProgressView.cancelClickListener = listener - secondaryProgressView.cancelClickListener = listener - } - - fun setInstantPlaybackClickListener(onPlayClickedListener: OnClickListener?) { - playVideoButton.setOnClickListener(onPlayClickedListener) - } - - fun clear() { - clearAnimation() - visibility = GONE - if (current.isNotEmpty()) { - for (v in current) { - v.clearAnimation() - v.visibility = GONE - } - } - current.clear() - slides = emptyList() - } - - fun setShowDownloadText(showDownloadText: Boolean) { - this.showDownloadText = showDownloadText - updateDownloadText() - } - - private fun isUpdateToExistingSet(slides: List): Boolean { - if (slides.size != networkProgress.size) { - return false - } - for (slide in slides) { - if (!networkProgress.containsKey(slide.asAttachment())) { - return false - } - } - return true - } - - private fun updateDownloadText() { - val byteCount = slides.sumOf { it.asAttachment().size } - downloadDetailsText.text = Formatter.formatShortFileSize(context, byteCount) - downloadDetailsText.invalidate() - - if (slides.size > 1) { - val downloadCount = Stream.of(slides).reduce(0) { count: Int, slide: Slide -> if (slide.transferState != AttachmentTable.TRANSFER_PROGRESS_DONE) count + 1 else count } - primaryDetailsText.text = context.resources.getQuantityString(R.plurals.TransferControlView_n_items, downloadCount, downloadCount) - primaryDetailsText.visible = showDownloadText - } else { - primaryDetailsText.text = "" - primaryDetailsText.visible = false - } - } - - @SuppressLint("SetTextI18n") - private fun updateDownloadProgressText(isCompression: Boolean) { - val context = context - val progress = if (isCompression) compressionProgress.values.sumOf { it.completed } else networkProgress.values.sumOf { it.completed } - val total = if (isCompression) compressionProgress.values.sumOf { it.total } else networkProgress.values.sumOf { it.total } - val progressText = Formatter.formatShortFileSize(context, progress) - val totalText = Formatter.formatShortFileSize(context, total) - downloadDetailsText.text = "$progressText/$totalText" - downloadDetailsText.invalidate() - } - - @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) - fun onEventAsync(event: PartProgressEvent) { - val attachment = event.attachment - if (networkProgress.containsKey(attachment)) { - val proportionCompleted = event.progress.toFloat() / event.total - if (event.type == PartProgressEvent.Type.COMPRESSION) { - compressionProgress[attachment] = Progress.fromEvent(event) - } else { - networkProgress[attachment] = Progress.fromEvent(event) - } - debouncer.publish { - val progress = calculateProgress() - if (attachment.uploadTimestamp == 0L) { - progressView.setUploading(progress) - } else { - progressView.setDownloading(progress) - } - updateDownloadProgressText(event.type == PartProgressEvent.Type.COMPRESSION) - } - } - } - - private fun setPlayableWhileDownloading(playableWhileDownloading: Boolean) { - playVideoButton.visible = playableWhileDownloading - secondaryProgressView.visible = playableWhileDownloading - secondaryViewSpace.visible = !playableWhileDownloading // exists because constraint layout was being very weird about margins and this was the only way - primaryProgressView.visibility = if (playableWhileDownloading) INVISIBLE else VISIBLE - val textPadding = if (playableWhileDownloading) 0 else context.resources.getDimensionPixelSize(R.dimen.transfer_control_view_progressbar_to_textview_margin) - ViewUtil.setPaddingStart(downloadDetailsText, textPadding) - } - - private fun calculateProgress(): Float { - val totalDownloadProgress: Float = networkProgress.values.map { it.completed.toFloat() / it.total }.sum() - val totalCompressionProgress: Float = compressionProgress.values.map { it.completed.toFloat() / it.total }.sum() - val weightedProgress = UPLOAD_TASK_WEIGHT * totalDownloadProgress + COMPRESSION_TASK_WEIGHT * totalCompressionProgress - val weightedTotal = (UPLOAD_TASK_WEIGHT * networkProgress.size + COMPRESSION_TASK_WEIGHT * compressionProgress.size).toFloat() - return weightedProgress / weightedTotal - } - - companion object { - private const val TAG = "TransferControlView" - private const val UPLOAD_TASK_WEIGHT = 1 - - /** - * A weighting compared to [.UPLOAD_TASK_WEIGHT] - */ - private const val COMPRESSION_TASK_WEIGHT = 3 - - @JvmStatic - fun getTransferState(slides: List): Int { - var transferState = AttachmentTable.TRANSFER_PROGRESS_DONE - var allFailed = true - for (slide in slides) { - if (slide.transferState != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE) { - allFailed = false - transferState = if (slide.transferState == AttachmentTable.TRANSFER_PROGRESS_PENDING && transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) { - slide.transferState - } else { - transferState.coerceAtLeast(slide.transferState) - } - } - } - return if (allFailed) AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE else transferState - } - } - - data class Progress(val completed: Long, val total: Long) { - companion object { - fun fromEvent(event: PartProgressEvent): Progress { - return Progress(event.progress, event.total) - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt deleted file mode 100644 index a20a2d77e3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferProgressView.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.components.transfercontrols - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.RectF -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import android.view.View -import androidx.core.content.ContextCompat -import androidx.core.graphics.withTranslation -import org.signal.core.util.dp -import org.signal.core.util.logging.Log -import org.thoughtcrime.securesms.R -import kotlin.math.roundToInt - -class TransferProgressView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, - defStyleRes: Int = 0 -) : View(context, attrs, defStyleAttr, defStyleRes) { - companion object { - const val TAG = "TransferProgressView" - private const val PROGRESS_ARC_STROKE_WIDTH = 2f - private const val ICON_INSET_PERCENT = 0.2f - } - - private val progressRect = RectF() - private val stopIconRect = RectF() - private val progressPaint = progressPaint() - private val stopIconPaint = stopIconPaint() - private val trackPaint = trackPaint() - - private var progressPercent = 0f - private var currentState = State.READY_TO_DOWNLOAD - - private val downloadDrawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_down_24) - private val uploadDrawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_up_16) - - var startClickListener: OnClickListener? = null - var cancelClickListener: OnClickListener? = null - - init { - val tint = ContextCompat.getColor(context, R.color.signal_colorOnCustom) - val filter = PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP) - downloadDrawable?.colorFilter = filter - uploadDrawable?.colorFilter = filter - } - - override fun requestLayout() { - super.requestLayout() - Log.d(TAG, "Requesting new layout.", Exception()) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - when (currentState) { - State.IN_PROGRESS_CANCELABLE, State.IN_PROGRESS_NON_CANCELABLE -> drawProgress(canvas, progressPercent) - State.READY_TO_UPLOAD -> sizeAndDrawDrawable(canvas, uploadDrawable) - State.READY_TO_DOWNLOAD -> sizeAndDrawDrawable(canvas, downloadDrawable) - } - } - - fun setDownloading(progress: Float) { - if (currentState != State.IN_PROGRESS_CANCELABLE) { - currentState = State.IN_PROGRESS_CANCELABLE - setOnClickListener(cancelClickListener) - } - progressPercent = progress - } - - fun setUploading(progress: Float) { - if (currentState != State.IN_PROGRESS_NON_CANCELABLE) { - currentState = State.IN_PROGRESS_NON_CANCELABLE - setOnClickListener(null) - } - progressPercent = progress - } - - fun setStopped(isUpload: Boolean) { - val newState = if (isUpload) State.READY_TO_UPLOAD else State.READY_TO_DOWNLOAD - if (currentState != newState) { - currentState = newState - setOnClickListener(startClickListener) - } - progressPercent = 0f - } - - private fun drawProgress(canvas: Canvas, progressPercent: Float) { - if (currentState == State.IN_PROGRESS_CANCELABLE) { - val miniIcon = height < 32.dp - val stopIconCornerRadius = if (miniIcon) 1f.dp else 4f.dp - val iconSize: Float = if (miniIcon) 6.6f.dp else 16f.dp - stopIconRect.set(0f, 0f, iconSize, iconSize) - - canvas.withTranslation(width / 2 - (iconSize / 2), height / 2 - (iconSize / 2)) { - drawRoundRect(stopIconRect, stopIconCornerRadius, stopIconCornerRadius, stopIconPaint) - } - } - - val widthDp = PROGRESS_ARC_STROKE_WIDTH.dp - val inset = 2.dp - progressRect.top = widthDp + inset - progressRect.left = widthDp + inset - progressRect.right = (width - widthDp) - inset - progressRect.bottom = (height - widthDp) - inset - - canvas.drawArc(progressRect, 0f, 360f, false, trackPaint) - canvas.drawArc(progressRect, 270f, 360f * progressPercent, false, progressPaint) - } - - private fun stopIconPaint(): Paint { - val stopIconPaint = Paint() - stopIconPaint.color = ContextCompat.getColor(context, R.color.signal_colorOnCustom) - stopIconPaint.isAntiAlias = true - stopIconPaint.style = Paint.Style.FILL - return stopIconPaint - } - - private fun trackPaint(): Paint { - val trackPaint = Paint() - trackPaint.color = ContextCompat.getColor(context, R.color.signal_colorTransparent2) - trackPaint.isAntiAlias = true - trackPaint.style = Paint.Style.STROKE - trackPaint.strokeWidth = PROGRESS_ARC_STROKE_WIDTH.dp - return trackPaint - } - - private fun progressPaint(): Paint { - val progressPaint = Paint() - progressPaint.color = ContextCompat.getColor(context, R.color.signal_colorOnCustom) - progressPaint.isAntiAlias = true - progressPaint.style = Paint.Style.STROKE - progressPaint.strokeWidth = PROGRESS_ARC_STROKE_WIDTH.dp - return progressPaint - } - - private fun sizeAndDrawDrawable(canvas: Canvas, drawable: Drawable?) { - if (drawable == null) { - Log.w(TAG, "Could not load icon for $currentState") - return - } - - drawable.setBounds( - (width * ICON_INSET_PERCENT).roundToInt(), - (height * ICON_INSET_PERCENT).roundToInt(), - (width * (1 - ICON_INSET_PERCENT)).roundToInt(), - (height * (1 - ICON_INSET_PERCENT)).roundToInt() - ) - - drawable.draw(canvas) - } - - private enum class State { - IN_PROGRESS_CANCELABLE, - IN_PROGRESS_NON_CANCELABLE, - READY_TO_UPLOAD, - READY_TO_DOWNLOAD - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 6f30cf1de9..bde2d8016c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -63,16 +63,12 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.common.collect.Sets; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; import org.signal.core.util.DimensionUnit; import org.signal.core.util.StringUtil; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallLinkRootKey; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.badges.BadgeImageView; import org.thoughtcrime.securesms.badges.gifts.GiftMessageView; @@ -110,10 +106,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.events.PartProgressEvent; import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy; import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer; -import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob; import org.thoughtcrime.securesms.jobs.MmsDownloadJob; import org.thoughtcrime.securesms.jobs.MmsSendJob; @@ -247,8 +241,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener(); private final AttachmentDownloadClickListener downloadClickListener = new AttachmentDownloadClickListener(); - private final PlayVideoClickListener playVideoClickListener = new PlayVideoClickListener(); - private final AttachmentCancelClickListener attachmentCancelClickListener = new AttachmentCancelClickListener(); + private final ProgressWheelClickListener progressWheelClickListener = new ProgressWheelClickListener(); private final SlideClickPassthroughListener singleDownloadClickListener = new SlideClickPassthroughListener(downloadClickListener); private final SharedContactEventListener sharedContactEventListener = new SharedContactEventListener(); private final SharedContactClickListener sharedContactClickListener = new SharedContactClickListener(); @@ -1179,8 +1172,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo mediaThumbnailStub.require().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(linkPreview.getThumbnail().get())), showControls, false); mediaThumbnailStub.require().setThumbnailClickListener(new LinkPreviewThumbnailClickListener()); mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.require().setCancelDownloadClickListener(attachmentCancelClickListener); - mediaThumbnailStub.require().setPlayVideoClickListener(playVideoClickListener); + mediaThumbnailStub.require().setProgressWheelClickListener(progressWheelClickListener); mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener); linkPreviewStub.get().setLinkPreview(glideRequests, linkPreview, false); @@ -1320,8 +1312,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo false); mediaThumbnailStub.require().setThumbnailClickListener(new ThumbnailClickListener()); mediaThumbnailStub.require().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.require().setCancelDownloadClickListener(attachmentCancelClickListener); - mediaThumbnailStub.require().setPlayVideoClickListener(playVideoClickListener); + mediaThumbnailStub.require().setProgressWheelClickListener(progressWheelClickListener); mediaThumbnailStub.require().setOnLongClickListener(passthroughClickListener); mediaThumbnailStub.require().setOnClickListener(passthroughClickListener); mediaThumbnailStub.require().showShade(messageRecord.isDisplayBodyEmpty(getContext()) && !hasExtraText(messageRecord)); @@ -2452,84 +2443,18 @@ public final class ConversationItem extends RelativeLayout implements BindableCo } } - private class AttachmentCancelClickListener implements SlidesClickedListener { - @Override - public void onClick(View v, List slides) { - Log.i(TAG, "onClick() for attachment cancellation"); - final JobManager jobManager = ApplicationDependencies.getJobManager(); - if (messageRecord.isMmsNotification()) { - Log.i(TAG, "Canceling MMS attachments download"); - jobManager.cancel("mms-operation"); - } else { - Log.i(TAG, "Canceling push attachment downloads for " + slides.size() + " items"); - - for (Slide slide : slides) { - final String queue = AttachmentDownloadJob.constructQueueString(((DatabaseAttachment) slide.asAttachment()).getAttachmentId()); - jobManager.cancelAllInQueue(queue); - } - } - } - } - - private class PlayVideoClickListener implements SlideClickListener { - private static final float MINIMUM_DOWNLOADED_THRESHOLD = 0.05f; - private View parentView; - private Slide activeSlide; + private class ProgressWheelClickListener implements SlideClickListener { @Override public void onClick(View v, Slide slide) { - if (messageRecord.isOutgoing()) { - Log.d(TAG, "Video player button for outgoing slide clicked."); - return; - } - if (MediaUtil.isInstantVideoSupported(slide)) { - final DatabaseAttachment databaseAttachment = (DatabaseAttachment) slide.asAttachment(); - if (databaseAttachment.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_STARTED) { - final AttachmentId attachmentId = databaseAttachment.getAttachmentId(); - final JobManager jobManager = ApplicationDependencies.getJobManager(); - final String queue = AttachmentDownloadJob.constructQueueString(attachmentId); - setup(v, slide); - jobManager.add(new AttachmentDownloadJob(messageRecord.getId(), - attachmentId, - true)); - jobManager.addListener(queue, (job, jobState) -> { - if (jobState.isComplete()) { - cleanup(); - } - }); - } else { - launchMediaPreview(v, slide); - cleanup(); - } + final boolean isIncremental = slide.asAttachment().getIncrementalDigest() != null; + final boolean contentTypeSupported = MediaUtil.isVideoType(slide.getContentType()); + if (FeatureFlags.instantVideoPlayback() && isIncremental && contentTypeSupported) { + launchMediaPreview(v, slide); } else { - Log.d(TAG, "Non-eligible slide clicked."); + Log.d(TAG, "Non-eligible slide clicked: " + "\tisIncremental: " + isIncremental + "\tcontentTypeSupported: " + contentTypeSupported); } } - - private void setup(View v, Slide slide) { - parentView = v; - activeSlide = slide; - if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this); - } - - private void cleanup() { - parentView = null; - activeSlide = null; - if (EventBus.getDefault().isRegistered(this)) { - EventBus.getDefault().unregister(this); - } - } - - @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) - public void onEventAsync(PartProgressEvent event) { - float progressPercent = ((float) event.progress) / event.total; - final View currentParentView = parentView; - final Slide currentActiveSlide = activeSlide; - if (progressPercent >= MINIMUM_DOWNLOADED_THRESHOLD && currentParentView != null && currentActiveSlide != null) { - cleanup(); - launchMediaPreview(currentParentView, currentActiveSlide); - } - } } private class SlideClickPassthroughListener implements SlideClickListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index be483272dc..62074097c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -19,9 +19,9 @@ import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.PartProgressEvent; +import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobLogger; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.notifications.v2.ConversationId; @@ -33,7 +33,6 @@ import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; @@ -70,11 +69,11 @@ public final class AttachmentDownloadJob extends BaseJob { public AttachmentDownloadJob(long messageId, AttachmentId attachmentId, boolean manual) { this(new Job.Parameters.Builder() - .setQueue(constructQueueString(attachmentId)) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .setMaxAttempts(Parameters.UNLIMITED) - .build(), + .setQueue("AttachmentDownloadJob" + attachmentId.getRowId() + "-" + attachmentId.getUniqueId()) + .addConstraint(NetworkConstraint.KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(Parameters.UNLIMITED) + .build(), messageId, attachmentId, manual); @@ -110,8 +109,8 @@ public final class AttachmentDownloadJob extends BaseJob { final AttachmentTable database = SignalDatabase.attachments(); final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId); final DatabaseAttachment attachment = database.getAttachment(attachmentId); - final boolean pending = attachment != null && attachment.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_DONE - && attachment.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE; + final boolean pending = attachment != null && attachment.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_DONE + && attachment.getTransferState() != AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE; if (pending && (manual || AttachmentUtil.isAutoDownloadPermitted(context, attachment))) { Log.i(TAG, "onAdded() Marking attachment progress as 'started'"); @@ -131,8 +130,8 @@ public final class AttachmentDownloadJob extends BaseJob { public void doWork() throws IOException, RetryLaterException { Log.i(TAG, "onRun() messageId: " + messageId + " partRowId: " + partRowId + " partUniqueId: " + partUniqueId + " manual: " + manual); - final AttachmentTable database = SignalDatabase.attachments(); - final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId); + final AttachmentTable database = SignalDatabase.attachments(); + final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId); final DatabaseAttachment attachment = database.getAttachment(attachmentId); if (attachment == null) { @@ -196,20 +195,11 @@ public final class AttachmentDownloadJob extends BaseJob { } SignalServiceMessageReceiver messageReceiver = ApplicationDependencies.getSignalServiceMessageReceiver(); SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment); - InputStream stream = messageReceiver.retrieveAttachment(pointer, - attachmentFile, - maxReceiveSize, - new SignalServiceAttachment.ProgressListener() { - @Override - public void onAttachmentProgress(long total, long progress) { - EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)); - } + InputStream stream = messageReceiver.retrieveAttachment(pointer, + attachmentFile, + maxReceiveSize, + (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress))); - @Override - public boolean shouldCancel() { - return isCanceled(); - } - }); database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream); } catch (RangeException e) { Log.w(TAG, "Range exception, file size " + attachmentFile.length(), e); @@ -308,13 +298,8 @@ public final class AttachmentDownloadJob extends BaseJob { } } - public static String constructQueueString(AttachmentId attachmentId) { - return "AttachmentDownloadJob" + attachmentId.getRowId() + "-" + attachmentId.getUniqueId(); - } - @VisibleForTesting static class InvalidPartException extends Exception { InvalidPartException(String s) {super(s);} - InvalidPartException(Exception e) {super(e);} } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index 14d1b6348f..7fc444c323 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -213,18 +213,10 @@ public final class AttachmentUploadJob extends BaseJob { .withCaption(attachment.getCaption()) .withCancelationSignal(this::isCanceled) .withResumableUploadSpec(resumableUploadSpec) - .withListener(new SignalServiceAttachment.ProgressListener() { - @Override - public void onAttachmentProgress(long total, long progress) { - EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)); - if (notification != null) { - notification.setProgress(total, progress); - } - } - - @Override - public boolean shouldCancel() { - return isCanceled(); + .withListener((total, progress) -> { + EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)); + if (notification != null) { + notification.setProgress(total, progress); } }); if (MediaUtil.isImageType(attachment.getContentType())) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index b451c95cec..80b1992637 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -200,17 +200,7 @@ public abstract class PushSendJob extends SendJob { .withWidth(attachment.getWidth()) .withHeight(attachment.getHeight()) .withCaption(attachment.getCaption()) - .withListener(new SignalServiceAttachment.ProgressListener() { - @Override - public void onAttachmentProgress(long total, long progress) { - EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)); - } - - @Override - public boolean shouldCancel() { - return isCanceled(); - } - }) + .withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress))) .build(); } catch (IOException ioe) { Log.w(TAG, "Couldn't open attachment", ioe); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java index 31137f7b0a..23688d752d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java @@ -468,15 +468,6 @@ public class MediaUtil { return mediaMetadataRetriever.getFrameAtTime(timeUs); } - public static boolean isInstantVideoSupported(Slide slide) { - if (!FeatureFlags.instantVideoPlayback()) { - return false; - } - final boolean isIncremental = slide.asAttachment().getIncrementalDigest() != null; - final boolean contentTypeSupported = isVideoType(slide.getContentType()); - return isIncremental && contentTypeSupported; - } - public static @Nullable String getDiscreteMimeType(@NonNull String mimeType) { final String[] sections = mimeType.split("/", 2); return sections.length > 1 ? sections[0] : null; diff --git a/app/src/main/res/drawable/transfer_controls_background.xml b/app/src/main/res/drawable/transfer_controls_background.xml index 484118b2d3..1ba1aa2ece 100644 --- a/app/src/main/res/drawable/transfer_controls_background.xml +++ b/app/src/main/res/drawable/transfer_controls_background.xml @@ -4,6 +4,6 @@ android:shape="rectangle"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/transfer_controls_play_background.xml b/app/src/main/res/drawable/transfer_controls_play_background.xml deleted file mode 100644 index 0d8a28c973..0000000000 --- a/app/src/main/res/drawable/transfer_controls_play_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/transfer_controls_stop_icon.xml b/app/src/main/res/drawable/transfer_controls_stop_icon.xml deleted file mode 100644 index cd6dae63d3..0000000000 --- a/app/src/main/res/drawable/transfer_controls_stop_icon.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/album_thumbnail_view.xml b/app/src/main/res/layout/album_thumbnail_view.xml index eb14c5852a..147bb5850d 100644 --- a/app/src/main/res/layout/album_thumbnail_view.xml +++ b/app/src/main/res/layout/album_thumbnail_view.xml @@ -15,8 +15,8 @@ diff --git a/app/src/main/res/layout/thumbnail_view.xml b/app/src/main/res/layout/thumbnail_view.xml index 68c093dc97..0dde744ed1 100644 --- a/app/src/main/res/layout/thumbnail_view.xml +++ b/app/src/main/res/layout/thumbnail_view.xml @@ -71,10 +71,9 @@ diff --git a/app/src/main/res/layout/transfer_controls_stub.xml b/app/src/main/res/layout/transfer_controls_stub.xml index b5c7ecdf2f..de0156a813 100644 --- a/app/src/main/res/layout/transfer_controls_stub.xml +++ b/app/src/main/res/layout/transfer_controls_stub.xml @@ -1,11 +1,11 @@ - diff --git a/app/src/main/res/layout/transfer_controls_view.xml b/app/src/main/res/layout/transfer_controls_view.xml index 2fd7053799..ed8d4a3f95 100644 --- a/app/src/main/res/layout/transfer_controls_view.xml +++ b/app/src/main/res/layout/transfer_controls_view.xml @@ -1,135 +1,56 @@ - + tools:parentTag="org.thoughtcrime.securesms.components.TransferControlView"> - - - - - - - - - - - + app:matProg_barColor="@color/core_ultramarine" + app:matProg_rimColor="@color/core_grey_05" + app:matProg_linearProgress="true" + app:matProg_spinSpeed="0.2" + app:matProg_barWidth="2dp" + app:matProg_rimWidth="2dp" + app:matProg_circleRadius="24dp" + tools:visibility="visible"/> - - - - - - - - - + android:visibility="gone" + tools:visibility="visible"> + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b980ce74e9..79d93ecd4c 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -153,7 +153,6 @@ 64dp 32dp 24dp - 4dp 80dp 70dp diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java index 665e1820ce..c7c3692676 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceAttachment.java @@ -176,6 +176,5 @@ public abstract class SignalServiceAttachment { * @param progress The amount that has been transmitted/received in bytes thus far */ public void onAttachmentProgress(long total, long progress); - boolean shouldCancel(); } } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index f14bcb0a30..a739e8972d 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -1445,7 +1445,7 @@ public class PushServiceSocket { } } - private void downloadFromCdn(File destination, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener) + private void downloadFromCdn(File destination, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener) throws IOException, MissingConfigurationException { try (FileOutputStream outputStream = new FileOutputStream(destination, true)) { @@ -1502,9 +1502,6 @@ public class PushServiceSocket { if (listener != null) { listener.onAttachmentProgress(body.contentLength() + offset, totalRead); - if (listener.shouldCancel()) { - call.cancel(); - } } } } else if (response.code() == 416) { diff --git a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java index edd2b34c56..edd5aa3064 100644 --- a/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java +++ b/libsignal/service/src/test/java/org/whispersystems/signalservice/internal/push/http/DigestingRequestBodyTest.java @@ -3,7 +3,6 @@ package org.whispersystems.signalservice.internal.push.http; import org.junit.Test; import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream; import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.internal.util.Util; import java.io.ByteArrayInputStream; @@ -71,15 +70,6 @@ public class DigestingRequestBodyTest { } private DigestingRequestBody getBody(long contentStart) { - return new DigestingRequestBody(new ByteArrayInputStream(input), outputStreamFactory, "application/octet", CONTENT_LENGTH, new SignalServiceAttachment.ProgressListener() { - @Override - public void onAttachmentProgress(long total, long progress) { - // no-op - } - - @Override public boolean shouldCancel() { - return false; - } - }, () -> false, contentStart); + return new DigestingRequestBody(new ByteArrayInputStream(input), outputStreamFactory, "application/octet", CONTENT_LENGTH, (a, b) -> {}, () -> false, contentStart); } } \ No newline at end of file