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 6f00df6c4..4ed01702e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -21,11 +21,14 @@ import androidx.annotation.UiThread; import androidx.appcompat.widget.AppCompatImageView; import com.bumptech.glide.RequestBuilder; +import com.bumptech.glide.TransitionOptions; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.FitCenter; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; +import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import org.signal.core.util.logging.Log; @@ -418,13 +421,21 @@ public class ThumbnailView extends FrameLayout { } public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height) { + return setImageResource(glideRequests, uri, width, height, true, null); + } + + public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height, boolean animate, @Nullable RequestListener listener) { SettableFuture future = new SettableFuture<>(); if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); - GlideRequest request = glideRequests.load(new DecryptableUri(uri)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(withCrossFade()); + GlideRequest request = glideRequests.load(new DecryptableUri(uri)) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .listener(listener); + + if (animate) { + request = request.transition(withCrossFade()); + } if (width > 0 && height > 0) { request = request.override(width, height); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java index e374e3f9b..17781edee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java @@ -51,11 +51,6 @@ public final class ImageMediaPreviewFragment extends MediaPreviewFragment { @Override public void pause() {} - @Override - public ViewGroup getBottomBarControls() { - return bottomBarControlView; - } - @Override public void setBottomButtonControls(MediaPreviewPlayerControlView playerControlView) { bottomBarControlView = playerControlView; diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java index d29edba9e..773cc9a76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java @@ -81,7 +81,6 @@ public abstract class MediaPreviewFragment extends Fragment { public abstract void cleanUp(); public abstract void pause(); - public abstract ViewGroup getBottomBarControls(); public abstract void setBottomButtonControls(MediaPreviewPlayerControlView playerControlView); private void checkMediaStillAvailable() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index 4677a668e..d06bb347a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -5,18 +5,24 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.Menu import android.view.View import android.view.ViewGroup +import android.view.ViewGroup.GONE import android.view.ViewGroup.MarginLayoutParams +import android.view.ViewGroup.VISIBLE +import android.view.animation.PathInterpolator import android.widget.Toast import androidx.core.app.ShareCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.MarginPageTransformer import androidx.viewpager2.widget.ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT @@ -34,6 +40,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs import org.thoughtcrime.securesms.database.MediaDatabase import org.thoughtcrime.securesms.databinding.FragmentMediaPreviewV2Binding +import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter.ImageLoadingListener import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.mms.GlideApp @@ -48,8 +55,8 @@ import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.SaveAttachmentTask import org.thoughtcrime.securesms.util.StorageUtil import org.thoughtcrime.securesms.util.ViewUtil +import org.thoughtcrime.securesms.util.visible import java.util.Locale -import java.util.Optional import java.util.concurrent.TimeUnit class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events { @@ -60,8 +67,8 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med private val viewModel: MediaPreviewV2ViewModel by viewModels() private val debouncer = Debouncer(2, TimeUnit.SECONDS) - private lateinit var fullscreenHelper: FullscreenHelper + private lateinit var albumRailAdapter: MediaRailAdapter override fun onAttach(context: Context) { super.onAttach(context) @@ -80,6 +87,7 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med initializeViewModel(args) initializeToolbar(binding.toolbar) initializeViewPager() + initializeAlbumRail() initializeFullScreenUi() anchorMarginsToBottomInsets(binding.mediaPreviewDetailsContainer) lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { @@ -123,24 +131,30 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med }) } - private fun initializeAlbumRail(recyclerView: RecyclerView, albumThumbnailMedia: List, albumPosition: Int) { - recyclerView.itemAnimator = null // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682 - val mediaRailAdapter = MediaRailAdapter( - GlideApp.with(this), - object : MediaRailAdapter.RailItemListener { - override fun onRailItemClicked(distanceFromActive: Int) { - binding.mediaPager.currentItem += distanceFromActive - } + private fun initializeAlbumRail() { + binding.mediaPreviewPlaybackControls.recyclerView.apply { + this.itemAnimator = null // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682 + PagerSnapHelper().attachToRecyclerView(this) + albumRailAdapter = MediaRailAdapter( + GlideApp.with(this@MediaPreviewV2Fragment), + object : MediaRailAdapter.RailItemListener { + override fun onRailItemClicked(distanceFromActive: Int) { + binding.mediaPager.currentItem += distanceFromActive + } - override fun onRailItemDeleteClicked(distanceFromActive: Int) { - throw UnsupportedOperationException("Callback unsupported.") + override fun onRailItemDeleteClicked(distanceFromActive: Int) { + throw UnsupportedOperationException("Callback unsupported.") + } + }, + false, + object : ImageLoadingListener() { + override fun onAllRequestsFinished() { + crossfadeViewIn(this@apply) + } } - }, - false - ) - mediaRailAdapter.setMedia(albumThumbnailMedia, albumPosition) - recyclerView.adapter = mediaRailAdapter - recyclerView.smoothScrollToPosition(albumPosition) + ) + this.adapter = albumRailAdapter + } } private fun initializeFullScreenUi() { @@ -187,6 +201,7 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med onMediaNotAvailable() return } + val currentPosition = currentState.position val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentPosition] @@ -197,16 +212,29 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med } } - val mediaType: MediaPreviewPlayerControlView.MediaMode = if (currentItem.attachment?.isVideoGif == true) { - MediaPreviewPlayerControlView.MediaMode.IMAGE - } else { - MediaPreviewPlayerControlView.MediaMode.fromString(currentItem.contentType) - } - binding.mediaPreviewPlaybackControls.setMediaMode(mediaType) + bindTextViews(currentItem, currentState.showThread) + bindMenuItems(currentItem) + bindMediaPreviewPlaybackControls(currentItem, getMediaPreviewFragmentFromChildFragmentManager(currentPosition)) - binding.toolbar.title = getTitleText(currentItem, currentState.showThread) + val albumThumbnailMedia: List = if (currentState.allMediaInAlbumRail) { + currentState.mediaRecords.mapNotNull { it.toMedia() } + } else { + currentState.albums[currentItem.attachment?.mmsId] ?: emptyList() + } + bindAlbumRail(albumThumbnailMedia, currentItem) + crossfadeViewIn(binding.mediaPreviewDetailsContainer) + } + + private fun bindTextViews(currentItem: MediaDatabase.MediaRecord, showThread: Boolean) { + binding.toolbar.title = getTitleText(currentItem, showThread) binding.toolbar.subtitle = getSubTitleText(currentItem) + val caption = currentItem.attachment?.caption + binding.mediaPreviewCaption.text = caption + binding.mediaPreviewCaption.visible = caption != null + } + + private fun bindMenuItems(currentItem: MediaDatabase.MediaRecord) { val menu: Menu = binding.toolbar.menu if (currentItem.threadId == MediaIntentFactory.NOT_IN_A_THREAD.toLong()) { menu.findItem(R.id.delete).isVisible = false @@ -222,38 +250,50 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med } return@setOnMenuItemClickListener true } - val albumThumbnailMedia = if (currentState.allMediaInAlbumRail) { - currentState.mediaRecords.map { it.toMedia() } + } + + private fun bindMediaPreviewPlaybackControls(currentItem: MediaDatabase.MediaRecord, currentFragment: MediaPreviewFragment?) { + val mediaType: MediaPreviewPlayerControlView.MediaMode = if (currentItem.attachment?.isVideoGif == true) { + MediaPreviewPlayerControlView.MediaMode.IMAGE } else { - currentState.mediaRecords - .filter { it.attachment != null && it.attachment!!.mmsId == currentItem.attachment?.mmsId } - .map { it.toMedia() } + MediaPreviewPlayerControlView.MediaMode.fromString(currentItem.contentType) } - - val caption = currentItem.attachment?.caption - - val albumRailEnabled = albumThumbnailMedia.size > 1 - - if (caption != null) { - binding.mediaPreviewCaption.text = caption - binding.mediaPreviewCaption.visibility = View.VISIBLE - } else { - binding.mediaPreviewCaption.visibility = View.GONE - } - + binding.mediaPreviewPlaybackControls.setMediaMode(mediaType) binding.mediaPreviewPlaybackControls.setShareButtonListener { share(currentItem) } binding.mediaPreviewPlaybackControls.setForwardButtonListener { forward(currentItem) } - - val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.findViewById(R.id.media_preview_album_rail) - if (albumRailEnabled) { - val albumPosition = albumThumbnailMedia.indexOfFirst { it?.uri == currentItem.attachment?.uri } - initializeAlbumRail(albumRail, albumThumbnailMedia, albumPosition) - } - albumRail.visibility = if (albumRailEnabled) View.VISIBLE else View.GONE - val currentFragment: MediaPreviewFragment? = getMediaPreviewFragmentFromChildFragmentManager(currentPosition) currentFragment?.setBottomButtonControls(binding.mediaPreviewPlaybackControls) } + private fun bindAlbumRail(albumThumbnailMedia: List, currentItem: MediaDatabase.MediaRecord) { + val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView + if (albumThumbnailMedia.size > 1) { + val albumPosition = albumThumbnailMedia.indexOfFirst { it.uri == currentItem.attachment?.uri } + if (albumRail.visibility == GONE) { + albumRail.visibility = View.INVISIBLE + } + albumRailAdapter.setMedia(albumThumbnailMedia, albumPosition) + albumRail.smoothScrollToPosition(albumPosition) + } else { + albumRail.visibility = View.GONE + albumRailAdapter.setMedia(emptyList()) + } + } + + private fun crossfadeViewIn(view: View, duration: Long = 200) { + if (!view.isVisible) { + val viewPropertyAnimator = view.animate() + .alpha(1f) + .setDuration(duration) + .withStartAction { + view.visibility = VISIBLE + } + if (Build.VERSION.SDK_INT >= 21) { + viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f) + } + viewPropertyAnimator.start() + } + } + private fun getMediaPreviewFragmentFromChildFragmentManager(currentPosition: Int) = childFragmentManager.findFragmentByTag("f$currentPosition") as? MediaPreviewFragment private fun getTitleText(mediaRecord: MediaDatabase.MediaRecord, showThread: Boolean): String { @@ -304,29 +344,6 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med } } - private fun MediaDatabase.MediaRecord.toMedia(): Media? { - val attachment = this.attachment - val uri = attachment?.uri - if (attachment == null || uri == null) { - return null - } - - return Media( - uri, - this.contentType, - this.date, - attachment.width, - attachment.height, - attachment.size, - 0, - attachment.isBorderless, - attachment.isVideoGif, - Optional.empty(), - Optional.ofNullable(attachment.caption), - Optional.empty() - ) - } - override fun singleTapOnMedia(): Boolean { fullscreenHelper.toggleUiVisibility() return true diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt index 13f6aa91c..43705a6c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.mediapreview import org.thoughtcrime.securesms.database.MediaDatabase +import org.thoughtcrime.securesms.mediasend.Media data class MediaPreviewV2State( val mediaRecords: List = emptyList(), @@ -9,6 +10,7 @@ data class MediaPreviewV2State( val showThread: Boolean = false, val allMediaInAlbumRail: Boolean = false, val leftIsRecent: Boolean = false, + val albums: Map> = mapOf(), ) { enum class LoadState { INIT, DATA_LOADED, MEDIA_READY } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt index 804b604e3..20c8aa495 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -13,8 +13,10 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.database.MediaDatabase +import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.util.AttachmentUtil import org.thoughtcrime.securesms.util.rx.RxStore +import java.util.Optional class MediaPreviewV2ViewModel : ViewModel() { private val TAG = Log.tag(MediaPreviewV2ViewModel::class.java) @@ -27,16 +29,26 @@ class MediaPreviewV2ViewModel : ViewModel() { fun fetchAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaDatabase.Sorting, forceRefresh: Boolean = false) { if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) { disposables += store.update(repository.getAttachments(startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State -> + val albums = result.records.fold(mutableMapOf()) { acc: MutableMap>, mediaRecord: MediaDatabase.MediaRecord -> + val attachment = mediaRecord.attachment + if (attachment != null) { + val convertedMedia = mediaRecord.toMedia() ?: return@fold acc + acc.getOrPut(attachment.mmsId) { mutableListOf() }.add(convertedMedia) + } + acc + } if (oldState.leftIsRecent) { oldState.copy( position = result.initialPosition, mediaRecords = result.records, + albums = albums, loadState = MediaPreviewV2State.LoadState.DATA_LOADED, ) } else { oldState.copy( position = result.records.size - result.initialPosition - 1, mediaRecords = result.records.reversed(), + albums = albums.mapValues { it.value.reversed() }, loadState = MediaPreviewV2State.LoadState.DATA_LOADED, ) } @@ -82,3 +94,26 @@ class MediaPreviewV2ViewModel : ViewModel() { } } } + +fun MediaDatabase.MediaRecord.toMedia(): Media? { + val attachment = this.attachment + val uri = attachment?.uri + if (attachment == null || uri == null) { + return null + } + + return Media( + uri, + this.contentType, + this.date, + attachment.width, + attachment.height, + attachment.size, + 0, + attachment.isBorderless, + attachment.isVideoGif, + Optional.empty(), + Optional.ofNullable(attachment.caption), + Optional.empty() + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java deleted file mode 100644 index 70a986365..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewViewModel.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.thoughtcrime.securesms.mediapreview; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; -import org.thoughtcrime.securesms.mediasend.Media; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; - -public class MediaPreviewViewModel extends ViewModel { - - private final MutableLiveData previewData = new MutableLiveData<>(); - - private boolean leftIsRecent; - - private @Nullable Cursor cursor; - - public void setCursor(@NonNull Context context, @Nullable Cursor cursor, boolean leftIsRecent) { - boolean firstLoad = (this.cursor == null) && (cursor != null); - - this.cursor = cursor; - this.leftIsRecent = leftIsRecent; - - if (firstLoad) { - setActiveAlbumRailItem(context, 0); - } - } - - public void setActiveAlbumRailItem(@NonNull Context context, int activePosition) { - if (cursor == null) { - previewData.postValue(new PreviewData(Collections.emptyList(), null, 0)); - return; - } - - activePosition = getCursorPosition(activePosition); - - cursor.moveToPosition(activePosition); - - MediaRecord activeRecord = MediaRecord.from(cursor); - LinkedList rail = new LinkedList<>(); - - Media activeMedia = toMedia(activeRecord); - if (activeMedia != null) rail.add(activeMedia); - - while (cursor.moveToPrevious()) { - MediaRecord record = MediaRecord.from(cursor); - if (record.getAttachment().getMmsId() == activeRecord.getAttachment().getMmsId()) { - Media media = toMedia(record); - if (media != null) rail.addFirst(media); - } else { - break; - } - } - - cursor.moveToPosition(activePosition); - - while (cursor.moveToNext()) { - MediaRecord record = MediaRecord.from(cursor); - if (record.getAttachment().getMmsId() == activeRecord.getAttachment().getMmsId()) { - Media media = toMedia(record); - if (media != null) rail.addLast(media); - } else { - break; - } - } - - if (!leftIsRecent) { - Collections.reverse(rail); - } - - previewData.postValue(new PreviewData(rail.size() > 1 ? rail : Collections.emptyList(), - activeRecord.getAttachment().getCaption(), - rail.indexOf(activeMedia))); - } - - public void resubmitPreviewData() { - previewData.postValue(previewData.getValue()); - } - - private int getCursorPosition(int position) { - if (cursor == null) { - return 0; - } - - if (leftIsRecent) return position; - else return cursor.getCount() - 1 - position; - } - - private @Nullable Media toMedia(@NonNull MediaRecord mediaRecord) { - Uri uri = mediaRecord.getAttachment().getUri(); - - if (uri == null) { - return null; - } - - return new Media(uri, - mediaRecord.getContentType(), - mediaRecord.getDate(), - mediaRecord.getAttachment().getWidth(), - mediaRecord.getAttachment().getHeight(), - mediaRecord.getAttachment().getSize(), - 0, - mediaRecord.getAttachment().isBorderless(), - mediaRecord.getAttachment().isVideoGif(), - Optional.empty(), - Optional.ofNullable(mediaRecord.getAttachment().getCaption()), - Optional.empty()); - } - - public LiveData getPreviewData() { - return previewData; - } - - public static class PreviewData { - private final List albumThumbnails; - private final String caption; - private final int activePosition; - - public PreviewData(@NonNull List albumThumbnails, @Nullable String caption, int activePosition) { - this.albumThumbnails = albumThumbnails; - this.caption = caption; - this.activePosition = activePosition; - } - - public @NonNull List getAlbumThumbnails() { - return albumThumbnails; - } - - public @Nullable String getCaption() { - return caption; - } - - public int getActivePosition() { - return activePosition; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java index 6161ff13a..fab5bebd1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaRailAdapter.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.mediapreview; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -8,6 +9,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.mediasend.Media; @@ -16,6 +22,7 @@ import org.thoughtcrime.securesms.util.adapter.StableIdGenerator; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public class MediaRailAdapter extends RecyclerView.Adapter { @@ -26,19 +33,21 @@ public class MediaRailAdapter extends RecyclerView.Adapter media; private final RailItemListener listener; private final StableIdGenerator stableIdGenerator; + private final ImageLoadingListener imageLoadingListener; - private RailItemAddListener addListener; - private int activePosition; - private boolean editable; - private boolean interactive; + private RailItemAddListener addListener; + private int activePosition; + private boolean editable; + private boolean interactive; - public MediaRailAdapter(@NonNull GlideRequests glideRequests, @NonNull RailItemListener listener, boolean editable) { - this.glideRequests = glideRequests; - this.media = new ArrayList<>(); - this.listener = listener; - this.editable = editable; - this.stableIdGenerator = new StableIdGenerator<>(); - this.interactive = true; + public MediaRailAdapter(@NonNull GlideRequests glideRequests, @NonNull RailItemListener listener, boolean editable, ImageLoadingListener imageLoadingListener) { + this.glideRequests = glideRequests; + this.media = new ArrayList<>(); + this.listener = listener; + this.editable = editable; + this.stableIdGenerator = new StableIdGenerator<>(); + this.interactive = true; + this.imageLoadingListener = imageLoadingListener; setHasStableIds(true); } @@ -60,7 +69,7 @@ public class MediaRailAdapter extends RecyclerView.Adapter railItemListener.onRailItemClicked(distanceFromActive)); outline.setVisibility(isActive && interactive ? View.VISIBLE : View.GONE); @@ -208,4 +218,32 @@ public class MediaRailAdapter extends RecyclerView.Adapter { + final private AtomicInteger activeJobs = new AtomicInteger(); + + void onRequest() { + activeJobs.incrementAndGet(); + } + + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + int count = activeJobs.decrementAndGet(); + if (count == 0) { + onAllRequestsFinished(); + } + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + int count = activeJobs.decrementAndGet(); + if (count == 0) { + onAllRequestsFinished(); + } + return false; + } + + abstract void onAllRequestsFinished(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java index 143456b9e..2f5f90f71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java @@ -142,11 +142,6 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { } } - @Override - public @Nullable ViewGroup getBottomBarControls() { - return videoView.getControlView(); - } - @Override public void setBottomButtonControls(@NonNull MediaPreviewPlayerControlView playerControlView) { videoView.setControlView(playerControlView); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index 84742cf58..1835a0e44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -6,7 +6,6 @@ import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.KeyEvent -import android.view.WindowManager import android.widget.FrameLayout import android.widget.TextView import androidx.activity.OnBackPressedCallback diff --git a/app/src/main/res/layout/exo_player_control_view.xml b/app/src/main/res/layout/exo_player_control_view.xml index 6c8085449..83f50950e 100644 --- a/app/src/main/res/layout/exo_player_control_view.xml +++ b/app/src/main/res/layout/exo_player_control_view.xml @@ -51,7 +51,7 @@