diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-11-05 21:44:04 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2020-11-05 21:44:04 +0300 |
commit | ee8171ceb74b85f07c6ee0092b5d566102b8a5a7 (patch) | |
tree | 9557701ba4900b77df87f53634d3bb4df8684884 /app/src/main/java/it/niedermann/nextcloud/deck | |
parent | 9f442181a8f1b405283b55df128ed4fa9dc6b42e (diff) |
Display attachments in a dialog to give the user a chance to cancel the upload
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud/deck')
14 files changed, 261 insertions, 62 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java index 74c89d830..34d2eb3f3 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java @@ -19,7 +19,7 @@ import com.bumptech.glide.Glide; import java.io.Serializable; import it.niedermann.nextcloud.deck.R; -import it.niedermann.nextcloud.deck.databinding.DialogAssigneeBinding; +import it.niedermann.nextcloud.deck.databinding.DialogPreviewBinding; import it.niedermann.nextcloud.deck.model.User; import it.niedermann.nextcloud.deck.ui.branding.BrandedDeleteAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; @@ -27,10 +27,11 @@ import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel; import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; +@Deprecated public class CardAssigneeDialog extends BrandedDialogFragment { private static final String KEY_USER = "user"; - private DialogAssigneeBinding binding; + private DialogPreviewBinding binding; private EditCardViewModel viewModel; @Nullable @@ -63,7 +64,7 @@ public class CardAssigneeDialog extends BrandedDialogFragment { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - binding = DialogAssigneeBinding.inflate(LayoutInflater.from(requireContext())); + binding = DialogPreviewBinding.inflate(LayoutInflater.from(requireContext())); viewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class); AlertDialog.Builder dialogBuilder = new BrandedDeleteAlertDialogBuilder(requireContext()); @@ -95,7 +96,7 @@ public class CardAssigneeDialog extends BrandedDialogFragment { .placeholder(circularProgressDrawable) .error(R.drawable.ic_person_grey600_24dp) .into(binding.avatar)); - binding.displayName.setText(user.getDisplayname()); + binding.title.setText(user.getDisplayname()); } @Override diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java index b137b1b7f..24b94ebde 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java @@ -55,6 +55,8 @@ import it.niedermann.nextcloud.deck.ui.card.attachments.picker.ContactAdapter; import it.niedermann.nextcloud.deck.ui.card.attachments.picker.FileAdapter; import it.niedermann.nextcloud.deck.ui.card.attachments.picker.FileAdapterLegacy; import it.niedermann.nextcloud.deck.ui.card.attachments.picker.GalleryAdapter; +import it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog.PreviewDialog; +import it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog.PreviewDialogViewModel; import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import it.niedermann.nextcloud.deck.ui.takephoto.TakePhotoActivity; import it.niedermann.nextcloud.deck.util.VCardUtil; @@ -84,7 +86,8 @@ import static java.net.HttpURLConnection.HTTP_CONFLICT; public class CardAttachmentsFragment extends BrandedFragment implements AttachmentDeletedListener, AttachmentClickedListener { private FragmentCardEditTabAttachmentsBinding binding; - private EditCardViewModel viewModel; + private EditCardViewModel editViewModel; + private PreviewDialogViewModel previewViewModel; private BottomSheetBehavior<LinearLayout> mBottomSheetBehaviour; private static final int REQUEST_CODE_PICK_FILE = 1; @@ -115,7 +118,8 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme Bundle savedInstanceState) { binding = FragmentCardEditTabAttachmentsBinding.inflate(inflater, container, false); - viewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class); + editViewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class); + previewViewModel = new ViewModelProvider(requireActivity()).get(PreviewDialogViewModel.class); binding.bottomNavigation.setOnNavigationItemSelectedListener(item -> { if (item.getItemId() == R.id.gallery) { showGalleryPicker(); @@ -129,7 +133,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme // This might be a zombie fragment with an empty EditCardViewModel after Android killed the activity (but not the fragment instance // See https://github.com/stefan-niedermann/nextcloud-deck/issues/478 - if (viewModel.getFullCard() == null) { + if (editViewModel.getFullCard() == null) { DeckLog.logError(new IllegalStateException("Cannot populate " + CardAttachmentsFragment.class.getSimpleName() + " because viewModel.getFullCard() is null")); return binding.getRoot(); } @@ -139,8 +143,8 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme getChildFragmentManager(), requireActivity().getMenuInflater(), this, - viewModel.getAccount(), - viewModel.getFullCard().getLocalId()); + editViewModel.getAccount(), + editViewModel.getFullCard().getLocalId()); binding.attachmentsList.setAdapter(adapter); adapter.isEmpty().observe(getViewLifecycleOwner(), (isEmpty) -> { @@ -198,7 +202,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme } }); binding.attachmentsList.setLayoutManager(glm); - if (!viewModel.isCreateMode()) { + if (!editViewModel.isCreateMode()) { // 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() { @@ -211,10 +215,10 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme } } }); - adapter.setAttachments(viewModel.getFullCard().getAttachments(), viewModel.getFullCard().getId()); + adapter.setAttachments(editViewModel.getFullCard().getAttachments(), editViewModel.getFullCard().getId()); } - if (viewModel.canEdit()) { + if (editViewModel.canEdit()) { binding.fab.setOnClickListener(v -> { if (SDK_INT < LOLLIPOP) { openNativeFilePicker(); @@ -293,7 +297,15 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme requestPermissions(new String[]{READ_EXTERNAL_STORAGE, CAMERA}, REQUEST_CODE_PICK_GALLERY_PERMISSION); } else { unbindPickerAdapter(); - pickerAdapter = new GalleryAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)), this::openNativeCameraPicker, getViewLifecycleOwner()); + pickerAdapter = new GalleryAdapter(requireContext(), (uri, pair) -> { + previewViewModel.prepareDialog(pair.first, pair.second); + PreviewDialog.newInstance().show(getChildFragmentManager(), PreviewDialog.class.getSimpleName()); + observeOnce(previewViewModel.getResult(), getViewLifecycleOwner(), (submitPositive) -> { + if (submitPositive) { + onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)); + } + }); + }, this::openNativeCameraPicker, getViewLifecycleOwner()); binding.pickerRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3)); binding.pickerRecyclerView.setAdapter(pickerAdapter); } @@ -306,7 +318,15 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme requestPermissions(new String[]{READ_CONTACTS}, REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION); } else { unbindPickerAdapter(); - pickerAdapter = new ContactAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_CONTACT, RESULT_OK, new Intent().setData(uri)), this::openNativeContactPicker); + pickerAdapter = new ContactAdapter(requireContext(), (uri, pair) -> { + previewViewModel.prepareDialog(pair.first, pair.second); + PreviewDialog.newInstance().show(getChildFragmentManager(), PreviewDialog.class.getSimpleName()); + observeOnce(previewViewModel.getResult(), getViewLifecycleOwner(), (submitPositive) -> { + if (submitPositive) { + onActivityResult(REQUEST_CODE_PICK_CONTACT, RESULT_OK, new Intent().setData(uri)); + } + }); + }, this::openNativeContactPicker); binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); binding.pickerRecyclerView.setAdapter(pickerAdapter); } @@ -324,7 +344,15 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme // // TODO Only usable with Scoped Storage // pickerAdapter = new FileAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)), this::openNativeFilePicker); // } else { - pickerAdapter = new FileAdapterLegacy(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)), this::openNativeFilePicker); + pickerAdapter = new FileAdapterLegacy(requireContext(), (uri, pair) -> { + previewViewModel.prepareDialog(pair.first, pair.second); + PreviewDialog.newInstance().show(getChildFragmentManager(), PreviewDialog.class.getSimpleName()); + observeOnce(previewViewModel.getResult(), getViewLifecycleOwner(), (submitPositive) -> { + if (submitPositive) { + onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)); + } + }); + }, this::openNativeFilePicker); // } binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); binding.pickerRecyclerView.setAdapter(pickerAdapter); @@ -337,7 +365,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme if (SDK_INT >= LOLLIPOP) { startActivityForResult(TakePhotoActivity.createIntent(requireContext()), REQUEST_CODE_PICK_CAMERA); } else { - ExceptionDialogFragment.newInstance(new UnsupportedOperationException("This feature requires Android 5"), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + ExceptionDialogFragment.newInstance(new UnsupportedOperationException("This feature requires Android 5"), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); } } @@ -388,7 +416,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme : requireContext().getContentResolver().getType(sourceUri)); mBottomSheetBehaviour.setState(STATE_HIDDEN); } catch (Exception e) { - ExceptionDialogFragment.newInstance(e, viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + ExceptionDialogFragment.newInstance(e, editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); } } break; @@ -416,15 +444,14 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme case ContentResolver.SCHEME_CONTENT: case ContentResolver.SCHEME_FILE: { DeckLog.verbose("--- found content URL " + sourceUri.getPath()); - final File fileToUpload = copyContentUriToTempFile(requireContext(), sourceUri, viewModel.getAccount().getId(), viewModel.getFullCard().getLocalId()); - for (Attachment existingAttachment : viewModel.getFullCard().getAttachments()) { + final File fileToUpload = copyContentUriToTempFile(requireContext(), sourceUri, editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId()); + for (Attachment existingAttachment : editViewModel.getFullCard().getAttachments()) { final String existingPath = existingAttachment.getLocalPath(); if (existingPath != null && existingPath.equals(fileToUpload.getAbsolutePath())) { BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show(); return; } } - final Instant now = Instant.now(); final Attachment a = new Attachment(); a.setMimetype(mimeType); @@ -436,24 +463,24 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme a.setLastModifiedLocal(now); a.setCreatedAt(now); a.setStatusEnum(DBStatus.LOCAL_EDITED); - viewModel.getFullCard().getAttachments().add(0, a); + editViewModel.getFullCard().getAttachments().add(0, a); adapter.addAttachment(a); - if (!viewModel.isCreateMode()) { - WrappedLiveData<Attachment> liveData = syncManager.addAttachmentToCard(viewModel.getAccount().getId(), viewModel.getFullCard().getLocalId(), a.getMimetype(), fileToUpload); + if (!editViewModel.isCreateMode()) { + WrappedLiveData<Attachment> liveData = syncManager.addAttachmentToCard(editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId(), a.getMimetype(), fileToUpload); observeOnce(liveData, getViewLifecycleOwner(), (next) -> { if (liveData.hasError()) { Throwable t = liveData.getError(); if (t instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) t).getStatusCode() == HTTP_CONFLICT) { // https://github.com/stefan-niedermann/nextcloud-deck/issues/534 - viewModel.getFullCard().getAttachments().remove(a); + editViewModel.getFullCard().getAttachments().remove(a); adapter.removeAttachment(a); BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show(); } else { - ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme", t), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme", t), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); } } else { - viewModel.getFullCard().getAttachments().remove(a); - viewModel.getFullCard().getAttachments().add(0, next); + editViewModel.getFullCard().getAttachments().remove(a); + editViewModel.getFullCard().getAttachments().add(0, next); adapter.replaceAttachment(a, next); } }); @@ -503,12 +530,12 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme @Override public void onAttachmentDeleted(Attachment attachment) { adapter.removeAttachment(attachment); - viewModel.getFullCard().getAttachments().remove(attachment); - if (!viewModel.isCreateMode() && attachment.getLocalId() != null) { - final WrappedLiveData<Void> deleteLiveData = syncManager.deleteAttachmentOfCard(viewModel.getAccount().getId(), viewModel.getFullCard().getLocalId(), attachment.getLocalId()); + editViewModel.getFullCard().getAttachments().remove(attachment); + if (!editViewModel.isCreateMode() && attachment.getLocalId() != null) { + final WrappedLiveData<Void> deleteLiveData = syncManager.deleteAttachmentOfCard(editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId(), attachment.getLocalId()); observeOnce(deleteLiveData, this, (next) -> { if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) { - ExceptionDialogFragment.newInstance(deleteLiveData.getError(), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + ExceptionDialogFragment.newInstance(deleteLiveData.getError(), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); } }); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java index 73058c757..743e3382e 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java @@ -5,13 +5,16 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.RequestBuilder; + import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import static android.database.Cursor.FIELD_TYPE_INTEGER; import static android.database.Cursor.FIELD_TYPE_NULL; @@ -29,7 +32,7 @@ public abstract class AbstractCursorPickerAdapter<T extends RecyclerView.ViewHol protected final int columnIndex; private final int columnIndexType; @NonNull - protected final Consumer<Uri> onSelect; + protected final BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect; @NonNull protected final Runnable openNativePicker; @NonNull @@ -44,15 +47,15 @@ public abstract class AbstractCursorPickerAdapter<T extends RecyclerView.ViewHol @NonNull protected final ExecutorService bindExecutor = Executors.newFixedThreadPool(1); - public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable openNativePicker, Uri subject, String idColumn, String sortOrder) { + public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, Uri subject, String idColumn, String sortOrder) { this(context, onSelect, openNativePicker, subject, idColumn, new String[]{idColumn}, sortOrder); } - public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable openNativePicker, Uri subject, String idColumn, String[] requestedColumns, String sortOrder) { + public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, Uri subject, String idColumn, String[] requestedColumns, String sortOrder) { this(context, onSelect, openNativePicker, idColumn, requireNonNull(context.getContentResolver().query(subject, requestedColumns, null, null, sortOrder))); } - public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable openNativePicker, String idColumn, @NonNull Cursor cursor) { + public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, String idColumn, @NonNull Cursor cursor) { this.contentResolver = context.getContentResolver(); this.onSelect = onSelect; this.openNativePicker = openNativePicker; diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java index d04065aa4..a17794841 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java @@ -18,4 +18,4 @@ public abstract class AbstractPickerAdapter<T extends RecyclerView.ViewHolder> e * Call this method when the {@link AbstractPickerAdapter} is no longer need to free resources. */ public abstract void onDestroy(); -} +}
\ No newline at end of file diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java index 874127103..e334cc492 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java @@ -9,15 +9,18 @@ import android.os.Handler; import android.os.Looper; import android.provider.ContactsContract; import android.text.TextUtils; +import android.util.Pair; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.RequestBuilder; + import java.io.IOException; import java.io.InputStream; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding; import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding; @@ -37,7 +40,7 @@ public class ContactAdapter extends AbstractCursorPickerAdapter<RecyclerView.Vie private final int lookupKeyColumnIndex; private final int displayNameColumnIndex; - public ContactAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker) { + public ContactAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable onSelectPicker) { super(context, onSelect, onSelectPicker, CONTENT_URI, _ID, new String[]{_ID, LOOKUP_KEY, DISPLAY_NAME}, SORT_KEY_PRIMARY); lookupKeyColumnIndex = cursor.getColumnIndex(LOOKUP_KEY); displayNameColumnIndex = cursor.getColumnIndex(DISPLAY_NAME); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java index b8d52a1d3..f403fed21 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java @@ -4,15 +4,17 @@ import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.text.TextUtils; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.request.RequestOptions; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding; @@ -30,9 +32,9 @@ public class ContactItemViewHolder extends RecyclerView.ViewHolder { this.binding = binding; } - public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull String displayName, @Nullable String contactInformation, @NonNull Consumer<Uri> onSelect) { - itemView.setOnClickListener((v) -> onSelect.accept(uri)); - binding.displayName.setText(displayName); + public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull String displayName, @Nullable String contactInformation, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect) { + itemView.setOnClickListener((v) -> onSelect.accept(uri, new Pair<>(displayName, image == null ? null : Glide.with(itemView.getContext()).load(image)))); + binding.title.setText(displayName); binding.contactInformation.setText(contactInformation); if (image == null) { binding.initials.setVisibility(VISIBLE); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java index 47dd3f7a3..46633df6c 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; +import android.util.Pair; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -13,7 +14,9 @@ import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.recyclerview.widget.RecyclerView; -import java.util.function.Consumer; +import com.bumptech.glide.RequestBuilder; + +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding; import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding; @@ -35,7 +38,7 @@ public class FileAdapter extends AbstractCursorPickerAdapter<RecyclerView.ViewHo private final int modifiedColumnIndex; private final int mimeTypeColumnIndex; - private FileAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker) { + private FileAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable onSelectPicker) { super(context, onSelect, onSelectPicker, _ID, requireNonNull(context.getContentResolver().query(EXTERNAL_CONTENT_URI, new String[]{_ID, TITLE, SIZE, DATE_MODIFIED, MIME_TYPE}, null, null, DATE_ADDED + " DESC"))); displayNameColumnIndex = cursor.getColumnIndex(TITLE); sizeColumnIndex = cursor.getColumnIndex(SIZE); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java index a338e9d75..5688ab15b 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java @@ -3,16 +3,19 @@ package it.niedermann.nextcloud.deck.ui.card.attachments.picker; import android.content.Context; import android.net.Uri; import android.os.Environment; +import android.util.Pair; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.RequestBuilder; + import java.io.File; import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding; import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding; @@ -31,11 +34,11 @@ public class FileAdapterLegacy extends AbstractPickerAdapter<RecyclerView.ViewHo @NonNull private final List<File> files; @NonNull - protected final Consumer<Uri> onSelect; + protected final BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect; @NonNull protected final Runnable openNativePicker; - public FileAdapterLegacy(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable openNativePicker) { + public FileAdapterLegacy(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker) { // TODO run in separate thread? this.context = context; this.onSelect = onSelect; diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java index 50733dfbe..f7d64aca8 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java @@ -1,12 +1,15 @@ package it.niedermann.nextcloud.deck.ui.card.attachments.picker; import android.net.Uri; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; -import java.util.function.Consumer; +import com.bumptech.glide.RequestBuilder; + +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding; @@ -24,8 +27,8 @@ public class FileItemViewHolder extends RecyclerView.ViewHolder { this.binding = binding; } - public void bind(@NonNull Uri uri, @NonNull String name, String mimeType, long size, long modified, @Nullable Consumer<Uri> onSelect) { - itemView.setOnClickListener(onSelect == null ? null : (v) -> onSelect.accept(uri)); + public void bind(@NonNull Uri uri, @NonNull String name, String mimeType, long size, long modified, @Nullable BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect) { + itemView.setOnClickListener(onSelect == null ? null : (v) -> onSelect.accept(uri, new Pair<>(name, null))); binding.filename.setText(name); binding.filesize.setText(formatFileSize(binding.filesize.getContext(), size)); binding.modified.setText(getRelativeDateTimeString(binding.modified.getContext(), modified)); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java index 0be85d9e5..7f5c254c3 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java @@ -8,6 +8,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; +import android.util.Pair; import android.util.Size; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -16,8 +17,10 @@ import androidx.annotation.NonNull; import androidx.lifecycle.LifecycleOwner; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.RequestBuilder; + import java.io.IOException; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding; import it.niedermann.nextcloud.deck.databinding.ItemPhotoPreviewBinding; @@ -37,7 +40,7 @@ public class GalleryAdapter extends AbstractCursorPickerAdapter<RecyclerView.Vie ? MediaStore.Images.Media.DATE_TAKEN : MediaStore.Images.Media.DATE_ADDED; - public GalleryAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable openNativePicker, @NonNull LifecycleOwner lifecycleOwner) { + public GalleryAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, @NonNull LifecycleOwner lifecycleOwner) { super(context, onSelect, openNativePicker, EXTERNAL_CONTENT_URI, _ID, sortOrder + " DESC"); this.lifecycleOwner = lifecycleOwner; notifyItemRangeInserted(0, getItemCount()); @@ -90,7 +93,7 @@ public class GalleryAdapter extends AbstractCursorPickerAdapter<RecyclerView.Vie @Override public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { super.onViewDetachedFromWindow(holder); - if(holder instanceof GalleryPhotoPreviewItemViewHolder) { + if (holder instanceof GalleryPhotoPreviewItemViewHolder) { ((GalleryPhotoPreviewItemViewHolder) holder).unbind(); } } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java index d83876f0e..346fca9c3 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java @@ -2,23 +2,22 @@ package it.niedermann.nextcloud.deck.ui.card.attachments.picker; import android.graphics.Bitmap; import android.net.Uri; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestBuilder; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding; public class GalleryItemViewHolder extends RecyclerView.ViewHolder { - private final ExecutorService executor = Executors.newCachedThreadPool(); private final ItemAttachmentImageBinding binding; public GalleryItemViewHolder(@NonNull ItemAttachmentImageBinding binding) { @@ -26,8 +25,8 @@ public class GalleryItemViewHolder extends RecyclerView.ViewHolder { this.binding = binding; } - public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull Consumer<Uri> onSelect) { - itemView.setOnClickListener((v) -> onSelect.accept(uri)); + public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect) { + itemView.setOnClickListener((v) -> onSelect.accept(uri, new Pair<>(null, Glide.with(itemView.getContext()).load(image)))); Glide.with(itemView.getContext()) .load(image) .placeholder(R.drawable.ic_image_grey600_24dp) diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java new file mode 100644 index 000000000..8ebdf1b50 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java @@ -0,0 +1,102 @@ +package it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModelProvider; +import androidx.swiperefreshlayout.widget.CircularProgressDrawable; + +import com.bumptech.glide.RequestBuilder; + +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.databinding.DialogPreviewBinding; +import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; +import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme; + +public class PreviewDialog extends BrandedDialogFragment { + + private DialogPreviewBinding binding; + private PreviewDialogViewModel viewModel; + private LiveData<RequestBuilder<?>> imageBuilder$; + private LiveData<String> title$; + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + viewModel = new ViewModelProvider(requireActivity()).get(PreviewDialogViewModel.class); + binding = DialogPreviewBinding.inflate(LayoutInflater.from(requireContext())); + + final Context context = requireContext(); + + this.imageBuilder$ = this.viewModel.getImageBuilder(); + this.imageBuilder$.observe(requireActivity(), builder -> { + if (builder == null) { + binding.avatar.setVisibility(GONE); + } else { + final CircularProgressDrawable circularProgressDrawable = new CircularProgressDrawable(context); + circularProgressDrawable.setStrokeWidth(5f); + circularProgressDrawable.setCenterRadius(30f); + circularProgressDrawable.setColorSchemeColors(isDarkTheme(context) ? Color.LTGRAY : Color.DKGRAY); + circularProgressDrawable.start(); + binding.avatar.setVisibility(VISIBLE); + binding.avatar.post(() -> builder + .placeholder(circularProgressDrawable) + .into(binding.avatar)); + } + }); + this.title$ = this.viewModel.getTitle(); + this.title$.observe(requireActivity(), title -> { + if (TextUtils.isEmpty(title)) { + binding.title.setVisibility(GONE); + } else { + binding.title.setVisibility(VISIBLE); + binding.title.setText(title); + } + }); + + return new BrandedAlertDialogBuilder(requireContext()) + .setPositiveButton(R.string.simple_attach, (d, w) -> { + viewModel.setResult(true); + dismiss(); + }) + .setNeutralButton(R.string.simple_close, (d, w) -> { + viewModel.setResult(false); + dismiss(); + }) + .setView(binding.getRoot()) + .create(); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + viewModel.setResult(false); + super.onCancel(dialog); + } + + @Override + public void applyBrand(int mainColor) { + } + + @Override + public void onDestroy() { + this.imageBuilder$.removeObservers(requireActivity()); + this.title$.removeObservers(requireActivity()); + super.onDestroy(); + } + + public static DialogFragment newInstance() { + return new PreviewDialog(); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java new file mode 100644 index 000000000..8ee8a0e08 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java @@ -0,0 +1,50 @@ +package it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.bumptech.glide.RequestBuilder; + +import static androidx.lifecycle.Transformations.distinctUntilChanged; + +public class PreviewDialogViewModel extends ViewModel { + + @NonNull + private final MutableLiveData<String> title$ = new MutableLiveData<>(); + @NonNull + private final MutableLiveData<RequestBuilder<?>> imageBuilder$ = new MutableLiveData<>(); + private MutableLiveData<Boolean> result$ = new MutableLiveData<>(); + + /** + * Call this before observing {@link #getResult()} to prepare the {@link PreviewDialog}. + */ + public void prepareDialog(@Nullable String title, @Nullable RequestBuilder<?> imageBuilder) { + this.result$ = new MutableLiveData<>(); + this.title$.setValue(title); + this.imageBuilder$.setValue(imageBuilder); + } + + /** + * This will be a new instance after each call of {@link #prepareDialog(String, RequestBuilder)}. + * + * @return {@link Boolean#TRUE} if a positive action has been submitted, {@link Boolean#FALSE} if the dialog has been canceled. + */ + public LiveData<Boolean> getResult() { + return this.result$; + } + + protected LiveData<String> getTitle() { + return distinctUntilChanged(this.title$); + } + + protected LiveData<RequestBuilder<?>> getImageBuilder() { + return distinctUntilChanged(this.imageBuilder$); + } + + protected void setResult(boolean submittedPositive) { + result$.setValue(submittedPositive); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java index 82230060c..4b75b985f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java @@ -84,14 +84,14 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us } void bind(@NonNull final User user) { - binding.displayName.setText(user.getDisplayname()); + binding.title.setText(user.getDisplayname()); ViewUtil.addAvatar(binding.avatar, account.getUrl(), user.getUid(), avatarSize, R.drawable.ic_person_grey600_24dp); itemView.setSelected(selectedUsers.contains(user)); bindClickListener(user); } public void bindNotAssigned() { - binding.displayName.setText(itemView.getContext().getString(R.string.simple_unassigned)); + binding.title.setText(itemView.getContext().getString(R.string.simple_unassigned)); Glide.with(itemView.getContext()) .load(R.drawable.ic_baseline_block_24) .into(binding.avatar); |