kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fade in media detail view.
rodzic
2856697109
commit
7ad6d95b27
|
@ -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<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height) {
|
||||
return setImageResource(glideRequests, uri, width, height, true, null);
|
||||
}
|
||||
|
||||
public ListenableFuture<Boolean> setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri, int width, int height, boolean animate, @Nullable RequestListener<Drawable> listener) {
|
||||
SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
|
||||
|
||||
GlideRequest request = glideRequests.load(new DecryptableUri(uri))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(withCrossFade());
|
||||
GlideRequest<Drawable> 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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<Media?>, 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<Media> = 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<Media>, 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
|
||||
|
|
|
@ -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<MediaDatabase.MediaRecord> = emptyList(),
|
||||
|
@ -9,6 +10,7 @@ data class MediaPreviewV2State(
|
|||
val showThread: Boolean = false,
|
||||
val allMediaInAlbumRail: Boolean = false,
|
||||
val leftIsRecent: Boolean = false,
|
||||
val albums: Map<Long, List<Media>> = mapOf(),
|
||||
) {
|
||||
enum class LoadState { INIT, DATA_LOADED, MEDIA_READY }
|
||||
}
|
||||
|
|
|
@ -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<Long, MutableList<Media>>, 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()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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> 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<Media> 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<PreviewData> getPreviewData() {
|
||||
return previewData;
|
||||
}
|
||||
|
||||
public static class PreviewData {
|
||||
private final List<Media> albumThumbnails;
|
||||
private final String caption;
|
||||
private final int activePosition;
|
||||
|
||||
public PreviewData(@NonNull List<Media> albumThumbnails, @Nullable String caption, int activePosition) {
|
||||
this.albumThumbnails = albumThumbnails;
|
||||
this.caption = caption;
|
||||
this.activePosition = activePosition;
|
||||
}
|
||||
|
||||
public @NonNull List<Media> getAlbumThumbnails() {
|
||||
return albumThumbnails;
|
||||
}
|
||||
|
||||
public @Nullable String getCaption() {
|
||||
return caption;
|
||||
}
|
||||
|
||||
public int getActivePosition() {
|
||||
return activePosition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MediaRailAdapter.MediaRailViewHolder> {
|
||||
|
||||
|
@ -26,19 +33,21 @@ public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.Medi
|
|||
private final List<Media> media;
|
||||
private final RailItemListener listener;
|
||||
private final StableIdGenerator<Media> 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<MediaRailAdapter.Medi
|
|||
public void onBindViewHolder(@NonNull MediaRailViewHolder viewHolder, int i) {
|
||||
switch (getItemViewType(i)) {
|
||||
case TYPE_MEDIA:
|
||||
((MediaViewHolder) viewHolder).bind(media.get(i), i == activePosition, glideRequests, listener, i - activePosition, editable, interactive);
|
||||
((MediaViewHolder) viewHolder).bind(media.get(i), i == activePosition, glideRequests, listener, i - activePosition, editable, interactive, imageLoadingListener);
|
||||
break;
|
||||
case TYPE_BUTTON:
|
||||
((ButtonViewHolder) viewHolder).bind(addListener);
|
||||
|
@ -159,9 +168,10 @@ public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.Medi
|
|||
|
||||
void bind(@NonNull Media media, boolean isActive, @NonNull GlideRequests glideRequests,
|
||||
@NonNull RailItemListener railItemListener, int distanceFromActive, boolean editable,
|
||||
boolean interactive)
|
||||
boolean interactive, @NonNull ImageLoadingListener listener)
|
||||
{
|
||||
image.setImageResource(glideRequests, media.getUri());
|
||||
listener.onRequest();
|
||||
image.setImageResource(glideRequests, media.getUri(), 0, 0, false, listener);
|
||||
image.setOnClickListener(v -> railItemListener.onRailItemClicked(distanceFromActive));
|
||||
|
||||
outline.setVisibility(isActive && interactive ? View.VISIBLE : View.GONE);
|
||||
|
@ -208,4 +218,32 @@ public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.Medi
|
|||
public interface RailItemAddListener {
|
||||
void onRailItemAddClicked();
|
||||
}
|
||||
|
||||
abstract static class ImageLoadingListener implements RequestListener<Drawable> {
|
||||
final private AtomicInteger activeJobs = new AtomicInteger();
|
||||
|
||||
void onRequest() {
|
||||
activeJobs.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
||||
int count = activeJobs.decrementAndGet();
|
||||
if (count == 0) {
|
||||
onAllRequestsFinished();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||
int count = activeJobs.decrementAndGet();
|
||||
if (count == 0) {
|
||||
onAllRequestsFinished();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract void onAllRequestsFinished();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/media_preview_album_rail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="11dp"
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:animateLayoutChanges="true"
|
||||
android:visibility="invisible"
|
||||
android:alpha="0"
|
||||
android:animateLayoutChanges="false"
|
||||
android:background="@color/signal_dark_colorSurface_87"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
|
|
Ładowanie…
Reference in New Issue