Move all files to natural position.

This commit is contained in:
Alan Evans
2020-01-06 10:52:48 -05:00
parent 0df36047e7
commit 9ebe920195
3016 changed files with 6 additions and 36 deletions

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}