From f180824ec20453fe281d859bdd04aac53684ae16 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Sat, 7 Mar 2020 19:24:43 +0100 Subject: Make attachment viewer swipeable --- .../nextcloud/deck/ui/AttachmentsActivity.java | 47 ++-- .../deck/ui/attachments/AttachmentAdapter.java | 74 +++++++ .../nextcloud/deck/ui/card/ActivityAdapter.java | 79 ------- .../nextcloud/deck/ui/card/AttachmentAdapter.java | 245 --------------------- .../deck/ui/card/CardActivityAdapter.java | 79 +++++++ .../deck/ui/card/CardActivityFragment.java | 2 +- .../deck/ui/card/CardAttachmentAdapter.java | 245 +++++++++++++++++++++ .../deck/ui/card/CardAttachmentsFragment.java | 20 +- app/src/main/res/layout/activity_attachments.xml | 18 +- app/src/main/res/layout/item_attachment.xml | 7 + .../main/res/layout/item_attachment_default.xml | 1 - app/src/main/res/layout/item_attachment_image.xml | 1 - app/src/main/res/values/setup.xml | 2 +- 13 files changed, 458 insertions(+), 362 deletions(-) create mode 100644 app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java delete mode 100644 app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ActivityAdapter.java delete mode 100644 app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AttachmentAdapter.java create mode 100644 app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityAdapter.java create mode 100644 app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentAdapter.java create mode 100644 app/src/main/res/layout/item_attachment.xml diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/AttachmentsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/AttachmentsActivity.java index 411acdb90..93799e138 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/AttachmentsActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/AttachmentsActivity.java @@ -4,8 +4,7 @@ import android.os.Bundle; import android.view.MotionEvent; import androidx.appcompat.app.AppCompatActivity; - -import com.bumptech.glide.Glide; +import androidx.recyclerview.widget.RecyclerView; import java.util.List; @@ -13,8 +12,8 @@ import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.databinding.ActivityAttachmentsBinding; import it.niedermann.nextcloud.deck.model.Attachment; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; +import it.niedermann.nextcloud.deck.ui.attachments.AttachmentAdapter; import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; -import it.niedermann.nextcloud.deck.util.AttachmentUtil; import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_ACCOUNT_ID; import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_LOCAL_ID; @@ -51,26 +50,34 @@ public class AttachmentsActivity extends AppCompatActivity { List attachments = fullCard.getAttachments(); if (fullCard.getAttachments().size() == 0) { DeckLog.logError(new IllegalStateException(AttachmentsActivity.class.getSimpleName() + " called, but card " + fullCard.getLocalId() + "has no attachments")); - finish(); - } else { - if (currentAttachment != 0L) { - for (Attachment a : attachments) { - if (a.getLocalId() == currentAttachment) { - Glide.with(this) - .load(AttachmentUtil.getUrl(account.getUrl(), fullCard.getId(), a.getId())) - .into(binding.image); - binding.toolbar.setTitle(a.getBasename()); - break; - } + supportFinishAfterTransition(); + return; + } + RecyclerView.Adapter adapter = new AttachmentAdapter(account, fullCard.getId(), attachments); + binding.viewPager.setAdapter(adapter); + if (currentAttachment != 0L) { + for (int i = 0; i < attachments.size(); i++) { + if (attachments.get(i).getLocalId() == currentAttachment) { + binding.viewPager.setCurrentItem(i, false); + break; } - } else { - DeckLog.logError(new IllegalStateException("No " + BUNDLE_KEY_CURRENT_ATTACHMENT_LOCAL_ID + " was provided. Falling back to displaying first image.")); - Glide.with(this) - .load(AttachmentUtil.getUrl(account.getUrl(), fullCard.getId(), attachments.get(0).getId())) - .into(binding.image); - binding.toolbar.setTitle(attachments.get(0).getBasename()); } } + // TODO + // https://android-developers.googleblog.com/2018/02/continuous-shared-element-transitions.html?m=1 + // https://github.com/android/animation-samples/blob/master/GridToPager/app/src/main/java/com/google/samples/gridtopager/fragment/ImagePagerFragment.java +// setEnterSharedElementCallback(new SharedElementCallback() { +// @Override +// public void onMapSharedElements(List names, Map sharedElements) { +// View view = binding.viewPager.getRootView(); +// if (view == null) { +// return; +// } +// // Map the first shared element name to the child ImageView. +// sharedElements.put(names.get(0), view.findViewById(R.id.image)); +// Log.v("SHARED", "names" + names); +// } +// }); })); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java new file mode 100644 index 000000000..1d7307ccd --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java @@ -0,0 +1,74 @@ +package it.niedermann.nextcloud.deck.ui.attachments; + +import android.content.Context; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; + +import java.util.List; + +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.databinding.ItemAttachmentBinding; +import it.niedermann.nextcloud.deck.model.Account; +import it.niedermann.nextcloud.deck.model.Attachment; +import it.niedermann.nextcloud.deck.util.AttachmentUtil; + +public class AttachmentAdapter extends RecyclerView.Adapter { + + private final Account account; + private final long cardRemoteId; + @NonNull + private List attachments; + private Context context; + + public AttachmentAdapter(@NonNull Account account, long cardRemoteId, @NonNull List attachments) { + super(); + this.attachments = attachments; + this.account = account; + this.cardRemoteId = cardRemoteId; + } + + @NonNull + @Override + public AttachmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + this.context = parent.getContext(); + return new AttachmentViewHolder(ItemAttachmentBinding.inflate(LayoutInflater.from(context), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull AttachmentViewHolder holder, int position) { + Attachment attachment = attachments.get(position); + String uri = AttachmentUtil.getUrl(account.getUrl(), cardRemoteId, attachment.getId()); + if (attachment.getMimetype() != null) { + if (attachment.getMimetype().startsWith("image")) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + holder.binding.preview.setTransitionName(context.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId()))); + } + holder.binding.preview.setImageResource(R.drawable.ic_image_grey600_24dp); + Glide.with(context) + .load(uri) + .error(R.drawable.ic_image_grey600_24dp) + .into(holder.binding.preview); + } + } + } + + @Override + public int getItemCount() { + return attachments.size(); + } + + static class AttachmentViewHolder extends RecyclerView.ViewHolder { + private ItemAttachmentBinding binding; + + private AttachmentViewHolder(ItemAttachmentBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ActivityAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ActivityAdapter.java deleted file mode 100644 index ab43b7634..000000000 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ActivityAdapter.java +++ /dev/null @@ -1,79 +0,0 @@ -package it.niedermann.nextcloud.deck.ui.card; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import it.niedermann.nextcloud.deck.R; -import it.niedermann.nextcloud.deck.databinding.ItemActivityBinding; -import it.niedermann.nextcloud.deck.model.enums.ActivityType; -import it.niedermann.nextcloud.deck.model.ocs.Activity; -import it.niedermann.nextcloud.deck.util.DateUtil; - -public class ActivityAdapter extends RecyclerView.Adapter { - - @NonNull - private List activities; - private Context context; - - public ActivityAdapter(@NonNull List activities) { - super(); - this.activities = activities; - } - - @NonNull - @Override - public ActivitiesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - this.context = parent.getContext(); - ItemActivityBinding binding = ItemActivityBinding.inflate(LayoutInflater.from(context), parent, false); - return new ActivitiesViewHolder(binding); - } - - @Override - public void onBindViewHolder(@NonNull ActivitiesViewHolder holder, int position) { - Activity activity = activities.get(position); - holder.binding.date.setText(DateUtil.getRelativeDateTimeString(context, activity.getLastModified().getTime())); - holder.binding.subject.setText(activity.getSubject()); - switch (ActivityType.findById(activity.getType())) { - case DECK: - break; - case CHANGE: - holder.binding.type.setImageResource(R.drawable.type_change_36dp); - break; - case ADD: - holder.binding.type.setImageResource(R.drawable.type_add_color_36dp); - break; - case DELETE: - holder.binding.type.setImageResource(R.drawable.type_delete_color_36dp); - break; - case ARCHIVE: - break; - case HISTORY: - break; - case FILES: - break; - case COMMENT: - break; - } - } - - @Override - public int getItemCount() { - return activities.size(); - } - - static class ActivitiesViewHolder extends RecyclerView.ViewHolder { - private ItemActivityBinding binding; - - private ActivitiesViewHolder(ItemActivityBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } - -} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AttachmentAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AttachmentAdapter.java deleted file mode 100644 index 7cd953787..000000000 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AttachmentAdapter.java +++ /dev/null @@ -1,245 +0,0 @@ -package it.niedermann.nextcloud.deck.ui.card; - -import android.app.Activity; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.text.format.Formatter; -import android.transition.Explode; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MenuInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.core.app.ActivityOptionsCompat; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; - -import java.util.List; - -import it.niedermann.nextcloud.deck.R; -import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding; -import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding; -import it.niedermann.nextcloud.deck.model.Account; -import it.niedermann.nextcloud.deck.model.Attachment; -import it.niedermann.nextcloud.deck.model.enums.DBStatus; -import it.niedermann.nextcloud.deck.ui.AttachmentsActivity; -import it.niedermann.nextcloud.deck.util.AttachmentUtil; -import it.niedermann.nextcloud.deck.util.DateUtil; -import it.niedermann.nextcloud.deck.util.DeleteDialogBuilder; - -import static android.content.Context.CLIPBOARD_SERVICE; -import static androidx.constraintlayout.widget.Constraints.TAG; -import static it.niedermann.nextcloud.deck.ui.AttachmentsActivity.BUNDLE_KEY_CURRENT_ATTACHMENT_LOCAL_ID; -import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_ACCOUNT_ID; -import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_LOCAL_ID; - -public class AttachmentAdapter extends RecyclerView.Adapter { - - public static final int VIEW_TYPE_DEFAULT = 2; - public static final int VIEW_TYPE_IMAGE = 1; - - private final MenuInflater menuInflator; - private final Account account; - private final long cardRemoteId; - private final long cardLocalId; - @NonNull - private List attachments; - @NonNull - private AttachmentDeletedListener attachmentDeletedListener; - private Context context; - - AttachmentAdapter(@NonNull MenuInflater menuInflator, @NonNull AttachmentDeletedListener attachmentDeletedListener, @NonNull Account account, long cardLocalId, long cardRemoteId, @NonNull List attachments) { - super(); - this.menuInflator = menuInflator; - this.attachmentDeletedListener = attachmentDeletedListener; - this.attachments = attachments; - this.account = account; - this.cardRemoteId = cardRemoteId; - this.cardLocalId = cardLocalId; - } - - @NonNull - @Override - public AttachmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - this.context = parent.getContext(); - //noinspection SwitchStatementWithTooFewBranches - switch (viewType) { - case VIEW_TYPE_IMAGE: { - return new ImageAttachmentViewHolder(ItemAttachmentImageBinding.inflate(LayoutInflater.from(context), parent, false)); - } - default: { - return new DefaultAttachmentViewHolder(ItemAttachmentDefaultBinding.inflate(LayoutInflater.from(context), parent, false)); - } - } - } - - @Override - public void onBindViewHolder(@NonNull AttachmentViewHolder holder, int position) { - Attachment attachment = attachments.get(position); - int viewType = getItemViewType(position); - String uri = AttachmentUtil.getUrl(account.getUrl(), cardRemoteId, attachment.getId()); - holder.setNotSyncedYetStatus(attachment.getStatusEnum() == DBStatus.UP_TO_DATE); - holder.getRootView().setOnCreateContextMenuListener((menu, v, menuInfo) -> { - menuInflator.inflate(R.menu.attachment_menu, menu); - menu.findItem(R.id.delete).setOnMenuItemClickListener(item -> { - new DeleteDialogBuilder(context) - .setTitle(context.getString(R.string.delete_something, attachment.getFilename())) - .setMessage(R.string.attachment_delete_message) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.simple_delete, (dialog, which) -> attachmentDeletedListener.onAttachmentDeleted(attachment)) - .show(); - return false; - }); - menu.findItem(android.R.id.copyUrl).setOnMenuItemClickListener(item -> { - final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE); - ClipData clipData = ClipData.newPlainText(attachment.getFilename(), uri); - if (clipboardManager != null) { - clipboardManager.setPrimaryClip(clipData); - Toast.makeText(context, R.string.simple_copied, Toast.LENGTH_SHORT).show(); - } else { - Log.e(TAG, "clipboardManager is null"); - } - return false; - }); - }); - - if (attachment.getMimetype() != null) { - if (attachment.getMimetype().startsWith("image")) { - Glide.with(context) - .load(uri) - .error(R.drawable.ic_image_grey600_24dp) - .into(holder.getPreview()); - holder.getPreview().setImageResource(R.drawable.ic_image_grey600_24dp); - holder.getPreview().getRootView().setOnClickListener((v) -> { - Intent intent = new Intent(context, AttachmentsActivity.class); - intent.putExtra(BUNDLE_KEY_ACCOUNT_ID, account.getId()); - intent.putExtra(BUNDLE_KEY_LOCAL_ID, cardLocalId); - intent.putExtra(BUNDLE_KEY_CURRENT_ATTACHMENT_LOCAL_ID, attachment.getLocalId()); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && context instanceof Activity) { - ((Activity) context).getWindow().setSharedElementEnterTransition(new Explode()); - context.startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) context, holder.getPreview(), context.getString(R.string.transition_attachment_preview)).toBundle()); - } else { - context.startActivity(intent); - } - }); - } else if (attachment.getMimetype().startsWith("audio")) { - holder.getPreview().setImageResource(R.drawable.ic_music_note_grey600_24dp); - } else if (attachment.getMimetype().startsWith("video")) { - holder.getPreview().setImageResource(R.drawable.ic_local_movies_grey600_24dp); - } - } - - //noinspection SwitchStatementWithTooFewBranches - switch (viewType) { - case VIEW_TYPE_IMAGE: { - ImageAttachmentViewHolder imageHolder = (ImageAttachmentViewHolder) holder; - break; - } - default: { - DefaultAttachmentViewHolder defaultHolder = (DefaultAttachmentViewHolder) holder; - defaultHolder.binding.filename.getRootView().setOnClickListener((event) -> { - Intent openURL = new Intent(Intent.ACTION_VIEW); - openURL.setData(Uri.parse(AttachmentUtil.getUrl(account.getUrl(), cardRemoteId, attachment.getId()))); - context.startActivity(openURL); - }); - defaultHolder.binding.filename.setText(attachment.getBasename()); - defaultHolder.binding.filesize.setText(Formatter.formatFileSize(context, attachment.getFilesize())); - if (attachment.getLastModifiedLocal() != null) { - defaultHolder.binding.modified.setText(DateUtil.getRelativeDateTimeString(context, attachment.getLastModifiedLocal().getTime())); - defaultHolder.binding.modified.setVisibility(View.VISIBLE); - } else if (attachment.getLastModified() != null) { - defaultHolder.binding.modified.setText(DateUtil.getRelativeDateTimeString(context, attachment.getLastModified().getTime())); - defaultHolder.binding.modified.setVisibility(View.VISIBLE); - } else { - defaultHolder.binding.modified.setVisibility(View.GONE); - } - break; - } - } - } - - @Override - public int getItemViewType(int position) { - String mimeType = attachments.get(position).getMimetype(); - return (mimeType != null && mimeType.startsWith("image")) ? VIEW_TYPE_IMAGE : VIEW_TYPE_DEFAULT; - } - - @Override - public int getItemCount() { - return attachments.size(); - } - - static abstract class AttachmentViewHolder extends RecyclerView.ViewHolder { - AttachmentViewHolder(@NonNull View itemView) { - super(itemView); - } - - abstract protected View getRootView(); - - abstract protected ImageView getPreview(); - - abstract protected void setNotSyncedYetStatus(boolean synced); - } - - static class DefaultAttachmentViewHolder extends AttachmentViewHolder { - ItemAttachmentDefaultBinding binding; - - private DefaultAttachmentViewHolder(ItemAttachmentDefaultBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - - @Override - protected View getRootView() { - return binding.getRoot(); - } - - @Override - protected ImageView getPreview() { - return binding.preview; - } - - @Override - protected void setNotSyncedYetStatus(boolean synced) { - binding.notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE); - } - } - - static class ImageAttachmentViewHolder extends AttachmentViewHolder { - private ItemAttachmentImageBinding binding; - - private ImageAttachmentViewHolder(ItemAttachmentImageBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - - @Override - protected View getRootView() { - return binding.getRoot(); - } - - @Override - protected ImageView getPreview() { - return binding.preview; - } - - @Override - protected void setNotSyncedYetStatus(boolean synced) { - binding.notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE); - } - } - - public interface AttachmentDeletedListener { - void onAttachmentDeleted(Attachment attachment); - } -} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityAdapter.java new file mode 100644 index 000000000..b2a8a7aa4 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityAdapter.java @@ -0,0 +1,79 @@ +package it.niedermann.nextcloud.deck.ui.card; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.databinding.ItemActivityBinding; +import it.niedermann.nextcloud.deck.model.enums.ActivityType; +import it.niedermann.nextcloud.deck.model.ocs.Activity; +import it.niedermann.nextcloud.deck.util.DateUtil; + +public class CardActivityAdapter extends RecyclerView.Adapter { + + @NonNull + private List activities; + private Context context; + + public CardActivityAdapter(@NonNull List activities) { + super(); + this.activities = activities; + } + + @NonNull + @Override + public ActivitiesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + this.context = parent.getContext(); + ItemActivityBinding binding = ItemActivityBinding.inflate(LayoutInflater.from(context), parent, false); + return new ActivitiesViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull ActivitiesViewHolder holder, int position) { + Activity activity = activities.get(position); + holder.binding.date.setText(DateUtil.getRelativeDateTimeString(context, activity.getLastModified().getTime())); + holder.binding.subject.setText(activity.getSubject()); + switch (ActivityType.findById(activity.getType())) { + case DECK: + break; + case CHANGE: + holder.binding.type.setImageResource(R.drawable.type_change_36dp); + break; + case ADD: + holder.binding.type.setImageResource(R.drawable.type_add_color_36dp); + break; + case DELETE: + holder.binding.type.setImageResource(R.drawable.type_delete_color_36dp); + break; + case ARCHIVE: + break; + case HISTORY: + break; + case FILES: + break; + case COMMENT: + break; + } + } + + @Override + public int getItemCount() { + return activities.size(); + } + + static class ActivitiesViewHolder extends RecyclerView.ViewHolder { + private ItemActivityBinding binding; + + private ActivitiesViewHolder(ItemActivityBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityFragment.java index c2bcad532..9c4aaf5c2 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardActivityFragment.java @@ -70,7 +70,7 @@ public class CardActivityFragment extends Fragment { } else { binding.emptyContentView.setVisibility(View.GONE); binding.activitiesList.setVisibility(View.VISIBLE); - RecyclerView.Adapter adapter = new ActivityAdapter(activities); + RecyclerView.Adapter adapter = new CardActivityAdapter(activities); binding.activitiesList.setAdapter(adapter); } })); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentAdapter.java new file mode 100644 index 000000000..7b07abbfd --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentAdapter.java @@ -0,0 +1,245 @@ +package it.niedermann.nextcloud.deck.ui.card; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.text.format.Formatter; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityOptionsCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; + +import java.util.List; + +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding; +import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding; +import it.niedermann.nextcloud.deck.model.Account; +import it.niedermann.nextcloud.deck.model.Attachment; +import it.niedermann.nextcloud.deck.model.enums.DBStatus; +import it.niedermann.nextcloud.deck.ui.AttachmentsActivity; +import it.niedermann.nextcloud.deck.util.AttachmentUtil; +import it.niedermann.nextcloud.deck.util.DateUtil; +import it.niedermann.nextcloud.deck.util.DeleteDialogBuilder; + +import static android.content.Context.CLIPBOARD_SERVICE; +import static androidx.constraintlayout.widget.Constraints.TAG; +import static it.niedermann.nextcloud.deck.ui.AttachmentsActivity.BUNDLE_KEY_CURRENT_ATTACHMENT_LOCAL_ID; +import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_ACCOUNT_ID; +import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_LOCAL_ID; + +public class CardAttachmentAdapter extends RecyclerView.Adapter { + + public static final int VIEW_TYPE_DEFAULT = 2; + public static final int VIEW_TYPE_IMAGE = 1; + + private final MenuInflater menuInflator; + private final Account account; + private final long cardRemoteId; + private final long cardLocalId; + @NonNull + private List attachments; + @NonNull + private AttachmentDeletedListener attachmentDeletedListener; + private Context context; + + CardAttachmentAdapter(@NonNull MenuInflater menuInflator, @NonNull AttachmentDeletedListener attachmentDeletedListener, @NonNull Account account, long cardLocalId, long cardRemoteId, @NonNull List attachments) { + super(); + this.menuInflator = menuInflator; + this.attachmentDeletedListener = attachmentDeletedListener; + this.attachments = attachments; + this.account = account; + this.cardRemoteId = cardRemoteId; + this.cardLocalId = cardLocalId; + } + + @NonNull + @Override + public AttachmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + this.context = parent.getContext(); + //noinspection SwitchStatementWithTooFewBranches + switch (viewType) { + case VIEW_TYPE_IMAGE: { + return new ImageAttachmentViewHolder(ItemAttachmentImageBinding.inflate(LayoutInflater.from(context), parent, false)); + } + default: { + return new DefaultAttachmentViewHolder(ItemAttachmentDefaultBinding.inflate(LayoutInflater.from(context), parent, false)); + } + } + } + + @Override + public void onBindViewHolder(@NonNull AttachmentViewHolder holder, int position) { + Attachment attachment = attachments.get(position); + int viewType = getItemViewType(position); + String uri = AttachmentUtil.getUrl(account.getUrl(), cardRemoteId, attachment.getId()); + holder.setNotSyncedYetStatus(attachment.getStatusEnum() == DBStatus.UP_TO_DATE); + holder.getRootView().setOnCreateContextMenuListener((menu, v, menuInfo) -> { + menuInflator.inflate(R.menu.attachment_menu, menu); + menu.findItem(R.id.delete).setOnMenuItemClickListener(item -> { + new DeleteDialogBuilder(context) + .setTitle(context.getString(R.string.delete_something, attachment.getFilename())) + .setMessage(R.string.attachment_delete_message) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.simple_delete, (dialog, which) -> attachmentDeletedListener.onAttachmentDeleted(attachment)) + .show(); + return false; + }); + menu.findItem(android.R.id.copyUrl).setOnMenuItemClickListener(item -> { + final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE); + ClipData clipData = ClipData.newPlainText(attachment.getFilename(), uri); + if (clipboardManager != null) { + clipboardManager.setPrimaryClip(clipData); + Toast.makeText(context, R.string.simple_copied, Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "clipboardManager is null"); + } + return false; + }); + }); + + if (attachment.getMimetype() != null) { + if (attachment.getMimetype().startsWith("image")) { + Glide.with(context) + .load(uri) + .error(R.drawable.ic_image_grey600_24dp) + .into(holder.getPreview()); + holder.getPreview().setImageResource(R.drawable.ic_image_grey600_24dp); + holder.getPreview().getRootView().setOnClickListener((v) -> { + Intent intent = new Intent(context, AttachmentsActivity.class); + intent.putExtra(BUNDLE_KEY_ACCOUNT_ID, account.getId()); + intent.putExtra(BUNDLE_KEY_LOCAL_ID, cardLocalId); + intent.putExtra(BUNDLE_KEY_CURRENT_ATTACHMENT_LOCAL_ID, attachment.getLocalId()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && context instanceof Activity) { + String transitionName = context.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId())); + holder.getPreview().setTransitionName(transitionName); + context.startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) context, holder.getPreview(), transitionName).toBundle()); + } else { + context.startActivity(intent); + } + }); + } else if (attachment.getMimetype().startsWith("audio")) { + holder.getPreview().setImageResource(R.drawable.ic_music_note_grey600_24dp); + } else if (attachment.getMimetype().startsWith("video")) { + holder.getPreview().setImageResource(R.drawable.ic_local_movies_grey600_24dp); + } + } + + //noinspection SwitchStatementWithTooFewBranches + switch (viewType) { + case VIEW_TYPE_IMAGE: { + ImageAttachmentViewHolder imageHolder = (ImageAttachmentViewHolder) holder; + break; + } + default: { + DefaultAttachmentViewHolder defaultHolder = (DefaultAttachmentViewHolder) holder; + defaultHolder.binding.filename.getRootView().setOnClickListener((event) -> { + Intent openURL = new Intent(Intent.ACTION_VIEW); + openURL.setData(Uri.parse(AttachmentUtil.getUrl(account.getUrl(), cardRemoteId, attachment.getId()))); + context.startActivity(openURL); + }); + defaultHolder.binding.filename.setText(attachment.getBasename()); + defaultHolder.binding.filesize.setText(Formatter.formatFileSize(context, attachment.getFilesize())); + if (attachment.getLastModifiedLocal() != null) { + defaultHolder.binding.modified.setText(DateUtil.getRelativeDateTimeString(context, attachment.getLastModifiedLocal().getTime())); + defaultHolder.binding.modified.setVisibility(View.VISIBLE); + } else if (attachment.getLastModified() != null) { + defaultHolder.binding.modified.setText(DateUtil.getRelativeDateTimeString(context, attachment.getLastModified().getTime())); + defaultHolder.binding.modified.setVisibility(View.VISIBLE); + } else { + defaultHolder.binding.modified.setVisibility(View.GONE); + } + break; + } + } + } + + @Override + public int getItemViewType(int position) { + String mimeType = attachments.get(position).getMimetype(); + return (mimeType != null && mimeType.startsWith("image")) ? VIEW_TYPE_IMAGE : VIEW_TYPE_DEFAULT; + } + + @Override + public int getItemCount() { + return attachments.size(); + } + + static abstract class AttachmentViewHolder extends RecyclerView.ViewHolder { + AttachmentViewHolder(@NonNull View itemView) { + super(itemView); + } + + abstract protected View getRootView(); + + abstract protected ImageView getPreview(); + + abstract protected void setNotSyncedYetStatus(boolean synced); + } + + static class DefaultAttachmentViewHolder extends AttachmentViewHolder { + ItemAttachmentDefaultBinding binding; + + private DefaultAttachmentViewHolder(ItemAttachmentDefaultBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + @Override + protected View getRootView() { + return binding.getRoot(); + } + + @Override + protected ImageView getPreview() { + return binding.preview; + } + + @Override + protected void setNotSyncedYetStatus(boolean synced) { + binding.notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE); + } + } + + static class ImageAttachmentViewHolder extends AttachmentViewHolder { + private ItemAttachmentImageBinding binding; + + private ImageAttachmentViewHolder(ItemAttachmentImageBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + @Override + protected View getRootView() { + return binding.getRoot(); + } + + @Override + protected ImageView getPreview() { + return binding.preview; + } + + @Override + protected void setNotSyncedYetStatus(boolean synced) { + binding.notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE); + } + } + + public interface AttachmentDeletedListener { + void onAttachmentDeleted(Attachment attachment); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentsFragment.java index 33e5a6306..875ecb281 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAttachmentsFragment.java @@ -31,7 +31,7 @@ import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_BOARD_ import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_CAN_EDIT; import static it.niedermann.nextcloud.deck.ui.card.CardAdapter.BUNDLE_KEY_LOCAL_ID; -public class CardAttachmentsFragment extends Fragment implements AttachmentAdapter.AttachmentDeletedListener { +public class CardAttachmentsFragment extends Fragment implements CardAttachmentAdapter.AttachmentDeletedListener { private static final String TAG = CardAttachmentsFragment.class.getCanonicalName(); private FragmentCardEditTabAttachmentsBinding binding; @@ -66,7 +66,7 @@ public class CardAttachmentsFragment extends Fragment implements AttachmentAdapt this.binding.emptyContentView.setVisibility(View.GONE); this.binding.attachmentsList.setVisibility(View.VISIBLE); syncManager.readAccount(accountId).observe(getViewLifecycleOwner(), (Account account) -> { - RecyclerView.Adapter adapter = new AttachmentAdapter( + RecyclerView.Adapter adapter = new CardAttachmentAdapter( requireActivity().getMenuInflater(), this, account, @@ -74,15 +74,25 @@ public class CardAttachmentsFragment extends Fragment implements AttachmentAdapt fullCard.getCard().getId(), fullCard.getAttachments()); binding.attachmentsList.setAdapter(adapter); + + // TODO + // https://android-developers.googleblog.com/2018/02/continuous-shared-element-transitions.html?m=1 + // https://github.com/android/animation-samples/blob/master/GridToPager/app/src/main/java/com/google/samples/gridtopager/fragment/ImagePagerFragment.java +// setExitSharedElementCallback(new SharedElementCallback() { +// @Override +// public void onMapSharedElements(List names, Map sharedElements) { +// Log.v("SHARED", "names" + names); +// } +// }); GridLayoutManager glm = new GridLayoutManager(getActivity(), 3); glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { switch (adapter.getItemViewType(position)) { - case AttachmentAdapter.VIEW_TYPE_IMAGE: + case CardAttachmentAdapter.VIEW_TYPE_IMAGE: return 1; - case AttachmentAdapter.VIEW_TYPE_DEFAULT: + case CardAttachmentAdapter.VIEW_TYPE_DEFAULT: return 3; default: return 1; @@ -94,7 +104,7 @@ public class CardAttachmentsFragment extends Fragment implements AttachmentAdapt } }); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && canEdit) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && canEdit) { binding.fab.setOnClickListener(v -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, diff --git a/app/src/main/res/layout/activity_attachments.xml b/app/src/main/res/layout/activity_attachments.xml index 4d1fabe17..2e9f68414 100644 --- a/app/src/main/res/layout/activity_attachments.xml +++ b/app/src/main/res/layout/activity_attachments.xml @@ -23,14 +23,14 @@ + android:layout_height="match_parent" /> - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_attachment.xml b/app/src/main/res/layout/item_attachment.xml new file mode 100644 index 000000000..08a31cf7d --- /dev/null +++ b/app/src/main/res/layout/item_attachment.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_attachment_default.xml b/app/src/main/res/layout/item_attachment_default.xml index 11d147d92..f58783559 100644 --- a/app/src/main/res/layout/item_attachment_default.xml +++ b/app/src/main/res/layout/item_attachment_default.xml @@ -20,7 +20,6 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="@dimen/standard_half_margin" - android:transitionName="@string/transition_attachment_preview" app:srcCompat="@drawable/ic_attach_file_grey600_24dp" /> 4 - transition_attachment_preview + transition_attachment_preview_%1$s \ No newline at end of file -- cgit v1.2.3