kopia lustrzana https://github.com/ryukoposting/Signal-Android
rodzic
52a5fb8ea2
commit
fb8e81cf50
|
@ -513,7 +513,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||
mediaIds = state.sharedMediaIds,
|
||||
onMediaRecordClick = { mediaRecord, isLtr ->
|
||||
startActivityForResult(
|
||||
MediaIntentFactory.intentFromMediaRecord(requireContext(), mediaRecord, isLtr),
|
||||
MediaIntentFactory.intentFromMediaRecord(requireContext(), mediaRecord, isLtr, allMediaInRail = true),
|
||||
REQUEST_CODE_RETURN_FROM_MEDIA
|
||||
)
|
||||
}
|
||||
|
|
|
@ -52,7 +52,8 @@ object MediaIntentFactory {
|
|||
fun intentFromMediaRecord(
|
||||
context: Context,
|
||||
mediaRecord: MediaRecord,
|
||||
leftIsRecent: Boolean
|
||||
leftIsRecent: Boolean,
|
||||
allMediaInRail: Boolean
|
||||
): Intent {
|
||||
val attachment: DatabaseAttachment = mediaRecord.attachment!!
|
||||
return create(
|
||||
|
@ -65,7 +66,7 @@ object MediaIntentFactory {
|
|||
attachment.size,
|
||||
attachment.caption,
|
||||
leftIsRecent,
|
||||
allMediaInRail = true,
|
||||
allMediaInRail = allMediaInRail,
|
||||
sorting = MediaDatabase.Sorting.Newest,
|
||||
isVideoGif = attachment.isVideoGif
|
||||
)
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.core.os.bundleOf
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import org.thoughtcrime.securesms.attachments.Attachment
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
|
||||
class MediaPreviewV2Adapter(val fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||
|
@ -37,6 +38,10 @@ class MediaPreviewV2Adapter(val fragment: Fragment) : FragmentStateAdapter(fragm
|
|||
return fragment
|
||||
}
|
||||
|
||||
fun findItemPosition(media: Media): Int {
|
||||
return items.indexOfFirst { it.uri == media.uri }
|
||||
}
|
||||
|
||||
fun updateBackingItems(newItems: Collection<Attachment>) {
|
||||
if (newItems != items) {
|
||||
items = newItems.toList()
|
||||
|
|
|
@ -24,7 +24,8 @@ 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.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.MarginPageTransformer
|
||||
import androidx.viewpager2.widget.ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT
|
||||
|
@ -42,7 +43,9 @@ 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.mediapreview.mediarail.CenterDecoration
|
||||
import org.thoughtcrime.securesms.mediapreview.mediarail.MediaRailAdapter
|
||||
import org.thoughtcrime.securesms.mediapreview.mediarail.MediaRailAdapter.ImageLoadingListener
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
|
@ -137,20 +140,11 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
|
||||
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)
|
||||
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
||||
addItemDecoration(CenterDecoration(0))
|
||||
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.")
|
||||
}
|
||||
},
|
||||
false,
|
||||
{ media -> jumpViewPagerToMedia(media) },
|
||||
object : ImageLoadingListener() {
|
||||
override fun onAllRequestsFinished() {
|
||||
crossfadeViewIn(this@apply)
|
||||
|
@ -281,14 +275,40 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
if (albumRail.visibility == GONE) {
|
||||
albumRail.visibility = View.INVISIBLE
|
||||
}
|
||||
albumRailAdapter.setMedia(albumThumbnailMedia, albumPosition)
|
||||
albumRail.smoothScrollToPosition(albumPosition)
|
||||
|
||||
albumRailAdapter.currentItemPosition = albumPosition
|
||||
albumRailAdapter.submitList(albumThumbnailMedia)
|
||||
scrollAlbumRailToCurrentAdapterPosition()
|
||||
} else {
|
||||
albumRail.visibility = View.GONE
|
||||
albumRailAdapter.setMedia(emptyList())
|
||||
albumRailAdapter.submitList(emptyList())
|
||||
albumRailAdapter.imageLoadingListener.reset()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollAlbumRailToCurrentAdapterPosition() {
|
||||
val currentItemPosition = albumRailAdapter.currentItemPosition
|
||||
val currentList = albumRailAdapter.currentList
|
||||
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
|
||||
var selectedItemWidth = -1
|
||||
for (i in currentList.indices) {
|
||||
val isSelected = i == currentItemPosition
|
||||
val stableId = albumRailAdapter.getItemId(i)
|
||||
val viewHolder = albumRail.findViewHolderForItemId(stableId) as? MediaRailAdapter.MediaRailViewHolder
|
||||
if (viewHolder != null) {
|
||||
viewHolder.setSelectedItem(isSelected)
|
||||
if (isSelected) {
|
||||
selectedItemWidth = viewHolder.itemView.width
|
||||
}
|
||||
}
|
||||
}
|
||||
val offsetFromStart = (albumRail.width - selectedItemWidth) / 2
|
||||
val smoothScroller = OffsetSmoothScroller(requireContext(), offsetFromStart)
|
||||
smoothScroller.targetPosition = currentItemPosition
|
||||
val layoutManager = albumRail.layoutManager as LinearLayoutManager
|
||||
layoutManager.startSmoothScroll(smoothScroller)
|
||||
}
|
||||
|
||||
private fun crossfadeViewIn(view: View, duration: Long = 200) {
|
||||
if (!view.isVisible) {
|
||||
val viewPropertyAnimator = view.animate()
|
||||
|
@ -297,6 +317,11 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
.withStartAction {
|
||||
view.visibility = VISIBLE
|
||||
}
|
||||
.withEndAction {
|
||||
if (view == binding.mediaPreviewPlaybackControls.recyclerView) {
|
||||
scrollAlbumRailToCurrentAdapterPosition()
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f)
|
||||
}
|
||||
|
@ -306,6 +331,12 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
|
||||
private fun getMediaPreviewFragmentFromChildFragmentManager(currentPosition: Int) = childFragmentManager.findFragmentByTag("f$currentPosition") as? MediaPreviewFragment
|
||||
|
||||
private fun jumpViewPagerToMedia(media: Media) {
|
||||
val viewPagerAdapter = binding.mediaPager.adapter as MediaPreviewV2Adapter
|
||||
val position = viewPagerAdapter.findItemPosition(media)
|
||||
binding.mediaPager.setCurrentItem(position, true)
|
||||
}
|
||||
|
||||
private fun getTitleText(mediaRecord: MediaDatabase.MediaRecord, showThread: Boolean): String {
|
||||
val recipient: Recipient = Recipient.live(mediaRecord.recipientId).get()
|
||||
val defaultFromString: String = if (mediaRecord.isOutgoing) {
|
||||
|
@ -482,6 +513,16 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med
|
|||
viewModel.onDestroyView()
|
||||
}
|
||||
|
||||
private class OffsetSmoothScroller(context: Context, val offset: Int) : LinearSmoothScroller(context) {
|
||||
override fun getHorizontalSnapPreference(): Int {
|
||||
return SNAP_TO_START
|
||||
}
|
||||
|
||||
override fun calculateDxToMakeVisible(view: View?, snapPreference: Int): Int {
|
||||
return offset + super.calculateDxToMakeVisible(view, snapPreference)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ARGS_KEY: String = "args"
|
||||
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
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;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
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> {
|
||||
|
||||
private static final int TYPE_MEDIA = 1;
|
||||
private static final int TYPE_BUTTON = 2;
|
||||
|
||||
private final GlideRequests glideRequests;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MediaRailViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int type) {
|
||||
switch (type) {
|
||||
case TYPE_MEDIA:
|
||||
return new MediaViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.mediarail_media_item, viewGroup, false));
|
||||
case TYPE_BUTTON:
|
||||
return new ButtonViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.mediarail_button_item, viewGroup, false));
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported view type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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, imageLoadingListener);
|
||||
break;
|
||||
case TYPE_BUTTON:
|
||||
((ButtonViewHolder) viewHolder).bind(addListener);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported view type: " + getItemViewType(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (editable && position == getItemCount() - 1) {
|
||||
return TYPE_BUTTON;
|
||||
} else {
|
||||
return TYPE_MEDIA;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull MediaRailViewHolder holder) {
|
||||
holder.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return editable ? media.size() + 1 : media.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
switch (getItemViewType(position)) {
|
||||
case TYPE_MEDIA:
|
||||
return stableIdGenerator.getId(media.get(position));
|
||||
case TYPE_BUTTON:
|
||||
return Long.MAX_VALUE;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported view type: " + getItemViewType(position));
|
||||
}
|
||||
}
|
||||
|
||||
public void setMedia(@NonNull List<Media> media) {
|
||||
setMedia(media, activePosition);
|
||||
}
|
||||
|
||||
public void setMedia(@NonNull List<Media> records, int activePosition) {
|
||||
this.activePosition = activePosition;
|
||||
|
||||
this.media.clear();
|
||||
this.media.addAll(records);
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setActivePosition(int activePosition) {
|
||||
this.activePosition = activePosition;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setAddButtonListener(@Nullable RailItemAddListener addListener) {
|
||||
this.addListener = addListener;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setEditable(boolean editable) {
|
||||
this.editable = editable;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setInteractive(boolean interactive) {
|
||||
this.interactive = interactive;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
static abstract class MediaRailViewHolder extends RecyclerView.ViewHolder {
|
||||
public MediaRailViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
abstract void recycle();
|
||||
}
|
||||
|
||||
static class MediaViewHolder extends MediaRailViewHolder {
|
||||
|
||||
private final ThumbnailView image;
|
||||
private final View outline;
|
||||
private final View deleteButton;
|
||||
private final View captionIndicator;
|
||||
|
||||
MediaViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
image = itemView.findViewById(R.id.rail_item_image);
|
||||
outline = itemView.findViewById(R.id.rail_item_outline);
|
||||
deleteButton = itemView.findViewById(R.id.rail_item_delete);
|
||||
captionIndicator = itemView.findViewById(R.id.rail_item_caption);
|
||||
}
|
||||
|
||||
void bind(@NonNull Media media, boolean isActive, @NonNull GlideRequests glideRequests,
|
||||
@NonNull RailItemListener railItemListener, int distanceFromActive, boolean editable,
|
||||
boolean interactive, @NonNull ImageLoadingListener listener)
|
||||
{
|
||||
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);
|
||||
|
||||
captionIndicator.setVisibility(media.getCaption().isPresent() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (editable && isActive && interactive) {
|
||||
deleteButton.setVisibility(View.VISIBLE);
|
||||
deleteButton.setOnClickListener(v -> railItemListener.onRailItemDeleteClicked(distanceFromActive));
|
||||
} else {
|
||||
deleteButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
void recycle() {
|
||||
image.setOnClickListener(null);
|
||||
deleteButton.setOnClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
static class ButtonViewHolder extends MediaRailViewHolder {
|
||||
|
||||
public ButtonViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void bind(@Nullable RailItemAddListener addListener) {
|
||||
if (addListener != null) {
|
||||
itemView.setOnClickListener(v -> addListener.onRailItemAddClicked());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void recycle() {
|
||||
itemView.setOnClickListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
public interface RailItemListener {
|
||||
void onRailItemClicked(int distanceFromActive);
|
||||
void onRailItemDeleteClicked(int distanceFromActive);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.thoughtcrime.securesms.mediapreview.mediarail
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.annotation.Px
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
/**
|
||||
* From: <a href="https://stackoverflow.com/a/53510142">https://stackoverflow.com/a/53510142</a>
|
||||
*/
|
||||
class CenterDecoration(@Px private val spacing: Int) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private var firstViewWidth = -1
|
||||
private var lastViewWidth = -1
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
val adapterPosition = (view.layoutParams as RecyclerView.LayoutParams).absoluteAdapterPosition
|
||||
val layoutManager = parent.layoutManager as LinearLayoutManager
|
||||
if (adapterPosition == 0) {
|
||||
if (view.width != firstViewWidth) {
|
||||
view.doOnPreDraw { parent.invalidateItemDecorations() }
|
||||
}
|
||||
firstViewWidth = view.width
|
||||
outRect.left = parent.width / 2 - view.width / 2
|
||||
if (layoutManager.itemCount > 1) {
|
||||
outRect.right = spacing / 2
|
||||
} else {
|
||||
outRect.right = outRect.left
|
||||
}
|
||||
} else if (adapterPosition == layoutManager.itemCount - 1) {
|
||||
if (view.width != lastViewWidth) {
|
||||
view.doOnPreDraw { parent.invalidateItemDecorations() }
|
||||
}
|
||||
lastViewWidth = view.width
|
||||
outRect.right = parent.width / 2 - view.width / 2
|
||||
outRect.left = spacing / 2
|
||||
} else {
|
||||
outRect.left = spacing / 2
|
||||
outRect.right = spacing / 2
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
package org.thoughtcrime.securesms.mediapreview.mediarail
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
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
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.adapter.StableIdGenerator
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* This is the RecyclerView.Adapter for the row of thumbnails present in the media viewer screen.
|
||||
*/
|
||||
class MediaRailAdapter(private val glideRequests: GlideRequests, listener: RailItemListener, imageLoadingListener: ImageLoadingListener) : ListAdapter<Media, MediaRailAdapter.MediaRailViewHolder>(MediaDiffer()) {
|
||||
val imageLoadingListener: ImageLoadingListener
|
||||
|
||||
var currentItemPosition: Int = -1
|
||||
|
||||
private val listener: RailItemListener
|
||||
private val stableIdGenerator: StableIdGenerator<Media>
|
||||
|
||||
init {
|
||||
this.listener = listener
|
||||
stableIdGenerator = StableIdGenerator()
|
||||
this.imageLoadingListener = imageLoadingListener
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, type: Int): MediaRailViewHolder {
|
||||
return MediaRailViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.mediarail_media_item, viewGroup, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(viewHolder: MediaRailViewHolder, i: Int) {
|
||||
viewHolder.bind(getItem(i), i == currentItemPosition, glideRequests, listener, imageLoadingListener)
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: MediaRailViewHolder) {
|
||||
holder.recycle()
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return stableIdGenerator.getId(getItem(position))
|
||||
}
|
||||
|
||||
class MediaRailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val image: ThumbnailView
|
||||
private val outline: View
|
||||
private val captionIndicator: View
|
||||
|
||||
init {
|
||||
image = itemView.findViewById(R.id.rail_item_image)
|
||||
outline = itemView.findViewById(R.id.rail_item_outline)
|
||||
captionIndicator = itemView.findViewById(R.id.rail_item_caption)
|
||||
}
|
||||
|
||||
fun bind(
|
||||
media: Media,
|
||||
isCurrentlySelected: Boolean,
|
||||
glideRequests: GlideRequests,
|
||||
railItemListener: RailItemListener,
|
||||
listener: ImageLoadingListener
|
||||
) {
|
||||
listener.onRequest()
|
||||
image.setImageResource(glideRequests, media.uri, 0, 0, false, listener)
|
||||
image.setOnClickListener { railItemListener.onRailItemClicked(media) }
|
||||
captionIndicator.visibility = if (media.caption.isPresent) View.VISIBLE else View.GONE
|
||||
setSelectedItem(isCurrentlySelected)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
image.setOnClickListener(null)
|
||||
}
|
||||
|
||||
fun setSelectedItem(isActive: Boolean) {
|
||||
outline.visible = isActive
|
||||
}
|
||||
}
|
||||
|
||||
fun interface RailItemListener {
|
||||
fun onRailItemClicked(media: Media)
|
||||
}
|
||||
|
||||
abstract class ImageLoadingListener : RequestListener<Drawable?> {
|
||||
private val activeJobs = AtomicInteger()
|
||||
fun onRequest() {
|
||||
activeJobs.incrementAndGet()
|
||||
}
|
||||
|
||||
final override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable?>, isFirstResource: Boolean): Boolean {
|
||||
val count = activeJobs.decrementAndGet()
|
||||
if (count == 0) {
|
||||
onAllRequestsFinished()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
final override fun onResourceReady(resource: Drawable?, model: Any, target: Target<Drawable?>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
||||
val count = activeJobs.decrementAndGet()
|
||||
if (count == 0) {
|
||||
onAllRequestsFinished()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
activeJobs.set(0)
|
||||
}
|
||||
|
||||
abstract fun onAllRequestsFinished()
|
||||
}
|
||||
|
||||
class MediaDiffer : DiffUtil.ItemCallback<Media>() {
|
||||
override fun areItemsTheSame(oldItem: Media, newItem: Media): Boolean {
|
||||
return oldItem.uri == newItem.uri
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Media, newItem: Media): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,7 +50,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_marginBottom="11dp"
|
||||
|
@ -58,7 +58,6 @@
|
|||
android:layout_marginEnd="12dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:layout_height="64dp" />
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/core_grey_95">
|
||||
|
||||
<org.thoughtcrime.securesms.components.viewpager.HackyViewPager
|
||||
android:id="@+id/media_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/media_preview_details_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="@drawable/image_preview_shade"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<org.thoughtcrime.securesms.components.MaxHeightScrollView
|
||||
android:id="@+id/media_preview_caption_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="32dp"
|
||||
android:animateLayoutChanges="true"
|
||||
app:scrollView_maxHeight="120dp">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/media_preview_caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:textColor="@color/core_white"
|
||||
android:gravity="bottom"
|
||||
tools:text="With great power comes great responsibility." />
|
||||
|
||||
</org.thoughtcrime.securesms.components.MaxHeightScrollView>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/media_preview_album_rail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
tools:layout_height="64dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/media_preview_playback_controls_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/media_preview_bar_background"
|
||||
app:elevation="0dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/toolbar_cutout_spacer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:theme="?actionBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -4,8 +4,8 @@
|
|||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="46dp"
|
||||
android:layout_width="@dimen/media_rail_item_size"
|
||||
android:layout_height="@dimen/media_rail_item_size"
|
||||
android:layout_margin="4dp"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
|
@ -42,14 +42,4 @@
|
|||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/rail_item_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|top"
|
||||
android:layout_margin="1dp"
|
||||
android:src="@drawable/ic_x_28"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -243,4 +243,5 @@
|
|||
<dimen name="exo_media_preview_button_height">48dp</dimen>
|
||||
<dimen name="media_preview_button_horizontal_margin">8dp</dimen>
|
||||
<dimen name="media_preview_lottie_button_dimen">36dp</dimen>
|
||||
<dimen name="media_rail_item_size">46dp</dimen>
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue