mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Move all files to natural position.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.ZoomingImageView;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
public final class ImageMediaPreviewFragment extends MediaPreviewFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
ZoomingImageView zoomingImageView = (ZoomingImageView) inflater.inflate(R.layout.media_preview_image_fragment, container, false);
|
||||
GlideRequests glideRequests = GlideApp.with(requireActivity());
|
||||
Bundle arguments = requireArguments();
|
||||
Uri uri = arguments.getParcelable(DATA_URI);
|
||||
String contentType = arguments.getString(DATA_CONTENT_TYPE);
|
||||
|
||||
if (!MediaUtil.isImageType(contentType)) {
|
||||
throw new AssertionError("This fragment can only display images");
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
zoomingImageView.setImageUri(glideRequests, uri, contentType);
|
||||
|
||||
zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia());
|
||||
|
||||
return zoomingImageView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
public abstract class MediaPreviewFragment extends Fragment {
|
||||
|
||||
static final String DATA_URI = "DATA_URI";
|
||||
static final String DATA_SIZE = "DATA_SIZE";
|
||||
static final String DATA_CONTENT_TYPE = "DATA_CONTENT_TYPE";
|
||||
static final String AUTO_PLAY = "AUTO_PLAY";
|
||||
|
||||
protected Events events;
|
||||
|
||||
public static MediaPreviewFragment newInstance(@NonNull Attachment attachment, boolean autoPlay) {
|
||||
return newInstance(attachment.getDataUri(), attachment.getContentType(), attachment.getSize(), autoPlay);
|
||||
}
|
||||
|
||||
public static MediaPreviewFragment newInstance(@NonNull Uri dataUri, @NonNull String contentType, long size, boolean autoPlay) {
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(MediaPreviewFragment.DATA_URI, dataUri);
|
||||
args.putString(MediaPreviewFragment.DATA_CONTENT_TYPE, contentType);
|
||||
args.putLong(MediaPreviewFragment.DATA_SIZE, size);
|
||||
args.putBoolean(MediaPreviewFragment.AUTO_PLAY, autoPlay);
|
||||
|
||||
MediaPreviewFragment fragment = createCorrectFragmentType(contentType);
|
||||
|
||||
fragment.setArguments(args);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private static MediaPreviewFragment createCorrectFragmentType(@NonNull String contentType) {
|
||||
if (MediaUtil.isVideo(contentType)) {
|
||||
return new VideoMediaPreviewFragment();
|
||||
} else if (MediaUtil.isImageType(contentType)) {
|
||||
return new ImageMediaPreviewFragment();
|
||||
} else {
|
||||
throw new AssertionError("Unexpected media type: " + contentType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
if (!(context instanceof Events)) {
|
||||
throw new AssertionError("Activity must support " + Events.class);
|
||||
}
|
||||
|
||||
events = (Events) context;
|
||||
}
|
||||
|
||||
public void cleanUp() {
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
}
|
||||
|
||||
public @Nullable View getPlaybackControls() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public interface Events {
|
||||
|
||||
boolean singleTapOnMedia();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
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(context, cursor);
|
||||
LinkedList<Media> rail = new LinkedList<>();
|
||||
|
||||
Media activeMedia = toMedia(activeRecord);
|
||||
if (activeMedia != null) rail.add(activeMedia);
|
||||
|
||||
while (cursor.moveToPrevious()) {
|
||||
MediaRecord record = MediaRecord.from(context, 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(context, 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().getThumbnailUri() != null ? mediaRecord.getAttachment().getThumbnailUri()
|
||||
: mediaRecord.getAttachment().getDataUri();
|
||||
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Media(uri,
|
||||
mediaRecord.getContentType(),
|
||||
mediaRecord.getDate(),
|
||||
mediaRecord.getAttachment().getWidth(),
|
||||
mediaRecord.getAttachment().getHeight(),
|
||||
mediaRecord.getAttachment().getSize(),
|
||||
Optional.absent(),
|
||||
Optional.fromNullable(mediaRecord.getAttachment().getCaption()));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
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;
|
||||
|
||||
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 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;
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
image.setImageResource(glideRequests, media.getUri());
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.thoughtcrime.securesms.mediapreview;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.VideoSlide;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.video.VideoPlayer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class VideoMediaPreviewFragment extends MediaPreviewFragment {
|
||||
|
||||
private static final String TAG = Log.tag(VideoMediaPreviewFragment.class);
|
||||
|
||||
private VideoPlayer videoView;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View itemView = inflater.inflate(R.layout.media_preview_video_fragment, container, false);
|
||||
Bundle arguments = requireArguments();
|
||||
Uri uri = arguments.getParcelable(DATA_URI);
|
||||
String contentType = arguments.getString(DATA_CONTENT_TYPE);
|
||||
long size = arguments.getLong(DATA_SIZE);
|
||||
boolean autoPlay = arguments.getBoolean(AUTO_PLAY);
|
||||
|
||||
if (!MediaUtil.isVideo(contentType)) {
|
||||
throw new AssertionError("This fragment can only display video");
|
||||
}
|
||||
|
||||
videoView = itemView.findViewById(R.id.video_player);
|
||||
|
||||
videoView.setWindow(requireActivity().getWindow());
|
||||
videoView.setVideoSource(new VideoSlide(getContext(), uri, size), autoPlay);
|
||||
|
||||
videoView.setOnClickListener(v -> events.singleTapOnMedia());
|
||||
|
||||
return itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
if (videoView != null) {
|
||||
videoView.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
if (videoView != null) {
|
||||
videoView.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getPlaybackControls() {
|
||||
return videoView != null ? videoView.getControlView() : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user