Implement new media send flow.

Update our media send flow to allow users to send multiple images/videos
at once. This change includes:

- New in-app media picker flow.
- Ability to caption images and videos.
- Image editing tools are made more prominent in the flow.
- Some fixes to the image editing tools.
This commit is contained in:
Greyson Parrelli
2018-11-20 09:59:23 -08:00
parent bae55f4b2f
commit 6fa7eca60b
83 changed files with 3270 additions and 247 deletions

View File

@@ -1,100 +0,0 @@
package org.thoughtcrime.securesms.mediapreview;
import android.support.annotation.NonNull;
import android.support.v7.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.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.ArrayList;
import java.util.List;
public class AlbumRailAdapter extends RecyclerView.Adapter<AlbumRailAdapter.AlbumRailViewHolder> {
private final GlideRequests glideRequests;
private final List<MediaRecord> records;
private final RailItemClickedListener listener;
private int activePosition;
public AlbumRailAdapter(@NonNull GlideRequests glideRequests, @NonNull RailItemClickedListener listener) {
this.glideRequests = glideRequests;
this.records = new ArrayList<>();
this.listener = listener;
setHasStableIds(true);
}
@NonNull
@Override
public AlbumRailViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new AlbumRailViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_preview_album_rail_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull AlbumRailViewHolder albumRailViewHolder, int i) {
albumRailViewHolder.bind(records.get(i), i == activePosition, glideRequests, listener, i - activePosition);
}
@Override
public void onViewRecycled(@NonNull AlbumRailViewHolder holder) {
holder.recycle();
}
@Override
public long getItemId(int position) {
return records.get(position).getAttachment().getAttachmentId().getUniqueId();
}
@Override
public int getItemCount() {
return records.size();
}
public void setRecords(@NonNull List<MediaRecord> records, int activePosition) {
this.activePosition = activePosition;
this.records.clear();
this.records.addAll(records);
notifyDataSetChanged();
}
static class AlbumRailViewHolder extends RecyclerView.ViewHolder {
private final ThumbnailView image;
AlbumRailViewHolder(@NonNull View itemView) {
super(itemView);
image = (ThumbnailView) itemView;
}
void bind(@NonNull MediaRecord record, boolean isActive, @NonNull GlideRequests glideRequests,
@NonNull RailItemClickedListener railItemClickedListener, int distanceFromActive)
{
if (record.getAttachment().getThumbnailUri() != null) {
image.setImageResource(glideRequests, record.getAttachment().getThumbnailUri());
} else if (record.getAttachment().getDataUri() != null) {
image.setImageResource(glideRequests, record.getAttachment().getDataUri());
} else {
image.clear(glideRequests);
}
image.setBackgroundResource(isActive ? R.drawable.album_rail_item_background : 0);
image.setOnClickListener(v -> railItemClickedListener.onRailItemClicked(distanceFromActive));
}
void recycle() {
image.setOnClickListener(null);
}
}
public interface RailItemClickedListener {
void onRailItemClicked(int distanceFromActive);
}
}

View File

@@ -5,10 +5,13 @@ import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.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;
@@ -22,9 +25,15 @@ public class MediaPreviewViewModel extends ViewModel {
private @Nullable Cursor cursor;
public void setCursor(@Nullable Cursor cursor, boolean leftIsRecent) {
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) {
@@ -37,15 +46,17 @@ public class MediaPreviewViewModel extends ViewModel {
cursor.moveToPosition(activePosition);
MediaRecord activeRecord = MediaRecord.from(context, cursor);
LinkedList<MediaRecord> rail = new LinkedList<>();
MediaRecord activeRecord = MediaRecord.from(context, cursor);
LinkedList<Media> rail = new LinkedList<>();
rail.add(activeRecord);
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()) {
rail.addFirst(record);
Media media = toMedia(record);
if (media != null) rail.addFirst(media);
} else {
break;
}
@@ -56,7 +67,8 @@ public class MediaPreviewViewModel extends ViewModel {
while (cursor.moveToNext()) {
MediaRecord record = MediaRecord.from(context, cursor);
if (record.getAttachment().getMmsId() == activeRecord.getAttachment().getMmsId()) {
rail.addLast(record);
Media media = toMedia(record);
if (media != null) rail.addLast(media);
} else {
break;
}
@@ -68,7 +80,7 @@ public class MediaPreviewViewModel extends ViewModel {
previewData.postValue(new PreviewData(rail.size() > 1 ? rail : Collections.emptyList(),
activeRecord.getAttachment().getCaption(),
rail.indexOf(activeRecord)));
rail.indexOf(activeMedia)));
}
private int getCursorPosition(int position) {
@@ -80,22 +92,39 @@ public class MediaPreviewViewModel extends ViewModel {
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(),
Optional.absent(),
Optional.fromNullable(mediaRecord.getAttachment().getCaption()));
}
public LiveData<PreviewData> getPreviewData() {
return previewData;
}
public static class PreviewData {
private final List<MediaRecord> albumThumbnails;
private final String caption;
private final int activePosition;
private final List<Media> albumThumbnails;
private final String caption;
private final int activePosition;
public PreviewData(@NonNull List<MediaRecord> albumThumbnails, @Nullable String caption, 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<MediaRecord> getAlbumThumbnails() {
public @NonNull List<Media> getAlbumThumbnails() {
return albumThumbnails;
}

View File

@@ -0,0 +1,108 @@
package org.thoughtcrime.securesms.mediapreview;
import android.support.annotation.NonNull;
import android.support.v7.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 java.util.ArrayList;
import java.util.List;
public class MediaRailAdapter extends RecyclerView.Adapter<MediaRailAdapter.MediaRailViewHolder> {
private final GlideRequests glideRequests;
private final List<Media> media;
private final RailItemListener listener;
private final boolean deleteEnabled;
private int activePosition;
public MediaRailAdapter(@NonNull GlideRequests glideRequests, @NonNull RailItemListener listener, boolean deleteEnabled) {
this.glideRequests = glideRequests;
this.media = new ArrayList<>();
this.listener = listener;
this.deleteEnabled = deleteEnabled;
}
@NonNull
@Override
public MediaRailViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new MediaRailViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_preview_album_rail_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull MediaRailViewHolder mediaRailViewHolder, int i) {
mediaRailViewHolder.bind(media.get(i), i == activePosition, glideRequests, listener, i - activePosition, deleteEnabled);
}
@Override
public void onViewRecycled(@NonNull MediaRailViewHolder holder) {
holder.recycle();
}
@Override
public int getItemCount() {
return media.size();
}
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();
}
static class MediaRailViewHolder extends RecyclerView.ViewHolder {
private final ThumbnailView image;
private final View deleteButton;
MediaRailViewHolder(@NonNull View itemView) {
super(itemView);
image = itemView.findViewById(R.id.rail_item_image);
deleteButton = itemView.findViewById(R.id.rail_item_delete);
}
void bind(@NonNull Media media, boolean isActive, @NonNull GlideRequests glideRequests,
@NonNull RailItemListener railItemListener, int distanceFromActive, boolean deleteEnabled)
{
image.setImageResource(glideRequests, media.getUri());
image.setBackgroundResource(isActive ? R.drawable.media_rail_item_background : 0);
image.setOnClickListener(v -> railItemListener.onRailItemClicked(distanceFromActive));
if (deleteEnabled && isActive) {
deleteButton.setVisibility(View.VISIBLE);
deleteButton.setOnClickListener(v -> railItemListener.onRailItemDeleteClicked(distanceFromActive));
} else {
deleteButton.setVisibility(View.GONE);
}
}
void recycle() {
image.setOnClickListener(null);
deleteButton.setOnClickListener(null);
}
}
public interface RailItemListener {
void onRailItemClicked(int distanceFromActive);
void onRailItemDeleteClicked(int distanceFromActive);
}
}