parent
15c6c372ba
commit
45583ea469
25 changed files with 399 additions and 856 deletions
|
@ -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
|
||||
|
|
|
@ -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<Slide> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<TransferControlView> 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<Drawable> buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
|
||||
GlideRequest<Drawable> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TransferControlView>) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Slide> slides;
|
||||
@Nullable private View current;
|
||||
|
||||
private final ProgressWheel progressWheel;
|
||||
private final View downloadDetails;
|
||||
private final TextView downloadDetailsText;
|
||||
|
||||
private final Map<Attachment, Float> networkProgress;
|
||||
private final Map<Attachment, Float> 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<Slide> 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<Slide> 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<Slide> 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<Slide> 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<Attachment, Float> uploadDownloadProgress, Map<Attachment, Float> 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Slide> = emptyList()
|
||||
private var current: MutableSet<View> = 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<Attachment, Progress>
|
||||
private val compressionProgress: MutableMap<Attachment, Progress>
|
||||
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<Slide>) {
|
||||
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<Slide>): 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<Slide>): 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<Slide> 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 {
|
||||
|
|
|
@ -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);}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="16dp" />
|
||||
<solid android:color="@color/signal_colorTransparentInverse4" />
|
||||
<solid android:color="@color/core_grey_02" />
|
||||
|
||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="24dp" />
|
||||
<solid android:color="@color/signal_colorTransparentInverse4" />
|
||||
|
||||
</shape>
|
|
@ -1,8 +0,0 @@
|
|||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners android:radius="1dp" />
|
||||
<solid android:color="@color/signal_light_colorOnCustom" />
|
||||
<size android:width="6dp" android:height="6dp" />
|
||||
</shape>
|
|
@ -15,8 +15,8 @@
|
|||
|
||||
<ViewStub
|
||||
android:id="@+id/album_transfer_controls_stub"
|
||||
android:layout_width="@dimen/album_total_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout="@layout/transfer_controls_stub" />
|
||||
|
||||
|
|
|
@ -71,10 +71,9 @@
|
|||
|
||||
<ViewStub
|
||||
android:id="@+id/transfer_controls_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="@color/transparent"
|
||||
android:layout="@layout/transfer_controls_stub" />
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.components.transfercontrols.TransferControlView
|
||||
<org.thoughtcrime.securesms.components.TransferControlView
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/transfer_controls"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:elevation="2dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
|
|
|
@ -1,135 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:parentTag="org.thoughtcrime.securesms.components.transfercontrols.TransferControlView"
|
||||
tools:viewBindingIgnore="true">
|
||||
tools:parentTag="org.thoughtcrime.securesms.components.TransferControlView">
|
||||
|
||||
<View
|
||||
android:id="@+id/secondary_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/transfer_controls_background"
|
||||
android:longClickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/download_details_text"
|
||||
app:layout_constraintEnd_toEndOf="@+id/download_details_text"
|
||||
app:layout_constraintStart_toStartOf="@+id/secondary_progress_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/download_details_text" />
|
||||
|
||||
<View
|
||||
android:id="@+id/primary_background"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/transfer_controls_play_background"
|
||||
android:longClickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/primary_details_text"
|
||||
app:layout_constraintStart_toStartOf="@+id/primary_progress_view"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.transfercontrols.TransferProgressView
|
||||
android:id="@+id/secondary_progress_view"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="-4dp"
|
||||
app:layout_constraintStart_toStartOf="@+id/vertical_guideline"
|
||||
app:layout_constraintTop_toTopOf="@+id/horizontal_guideline" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_details_text"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:layout_width="0dp"
|
||||
<com.pnikosis.materialishprogress.ProgressWheel
|
||||
android:id="@+id/progress_wheel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/transfer_control_view_progressbar_to_textview_margin"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:longClickable="false"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textColor="@color/signal_colorOnCustom"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toEndOf="@+id/secondary_view_space"
|
||||
app:layout_constraintTop_toTopOf="@+id/secondary_progress_view" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/secondary_view_space"
|
||||
android:layout_width="@dimen/transfer_control_view_progressbar_to_textview_margin"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toEndOf="@+id/secondary_progress_view"
|
||||
/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.transfercontrols.TransferProgressView
|
||||
android:id="@+id/primary_progress_view"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/primary_details_text"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
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"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/primary_details_text"
|
||||
style="@style/Signal.Text.Body"
|
||||
<LinearLayout
|
||||
android:id="@+id/download_details"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:padding="4dp"
|
||||
android:gravity="center"
|
||||
android:longClickable="false"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textColor="@color/signal_colorOnCustom"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/primary_progress_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/primary_progress_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/primary_progress_view" />
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/play_video_button"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_margin="12dp"
|
||||
android:contentDescription="@string/ThumbnailView_Play_video_description"
|
||||
android:paddingLeft="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:tint="@color/signal_colorOnCustom"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/primary_progress_view"
|
||||
app:layout_constraintEnd_toEndOf="@+id/primary_progress_view"
|
||||
app:layout_constraintStart_toStartOf="@+id/primary_progress_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/primary_progress_view"
|
||||
app:srcCompat="@drawable/triangle_right"
|
||||
tools:ignore="RtlHardcoded,RtlSymmetry"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/vertical_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="8dp" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/horizontal_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_begin="8dp" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:tint="@color/core_grey_60"
|
||||
app:srcCompat="@drawable/ic_arrow_down_circle_filled" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_details_text"
|
||||
style="@style/Signal.Text.Preview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:gravity="center"
|
||||
android:longClickable="false"
|
||||
android:textColor="@color/black" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -153,7 +153,6 @@
|
|||
<dimen name="transfer_top_padding">64dp</dimen>
|
||||
<dimen name="transfer_split_top_padding">32dp</dimen>
|
||||
<dimen name="transfer_item_spacing">24dp</dimen>
|
||||
<dimen name="transfer_control_view_progressbar_to_textview_margin">4dp</dimen>
|
||||
|
||||
<dimen name="create_payment_key_width">80dp</dimen>
|
||||
<dimen name="create_payment_key_height">70dp</dimen>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue