Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2020-11-04 16:08:50 +0300
committerStefan Niedermann <info@niedermann.it>2020-11-04 16:08:50 +0300
commit1ab635e7ab3e103230f78aa96bc715218a78c4f4 (patch)
tree817ccf51388690c84c2d0ca54e2b7a8cde775620 /app/src/main/java/it/niedermann/nextcloud/deck/ui
parentd525953eb5163d372c09bd59ca8320bc07258634 (diff)
Created different pickers
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud/deck/ui')
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java109
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java98
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java81
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactPickerItemViewHolder.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java72
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FilePickerItemViewHolder.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java115
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPickerItemViewHolder.java51
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java35
11 files changed, 620 insertions, 81 deletions
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 e3395ea35..607390c12 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
@@ -30,6 +30,7 @@ import androidx.core.app.SharedElementCallback;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
@@ -54,6 +55,9 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiv
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.AbstractPickerAdapter;
+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.GalleryAdapter;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.takephoto.TakePhotoActivity;
@@ -90,16 +94,19 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
private static final int REQUEST_CODE_ADD_FILE = 1;
private static final int REQUEST_CODE_ADD_FILE_PERMISSION = 2;
- private static final int REQUEST_CODE_CAMERA = 3;
- private static final int REQUEST_CODE_CAMERA_PERMISSION = 4;
- private static final int REQUEST_CODE_PICK_CONTACT = 5;
- private static final int REQUEST_CODE_PICK_CONTACT_PERMISSION = 6;
+ private static final int REQUEST_CODE_ADD_FILE_PICKER_PERMISSION = 3;
+ private static final int REQUEST_CODE_CAMERA = 4;
+ private static final int REQUEST_CODE_CAMERA_PERMISSION = 5;
+ private static final int REQUEST_CODE_GALLERY_PICKER_PERMISSION = 6;
+ private static final int REQUEST_CODE_PICK_CONTACT = 7;
+ private static final int REQUEST_CODE_PICK_CONTACT_PERMISSION = 8;
+ private static final int REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION = 9;
private SyncManager syncManager;
private CardAttachmentAdapter adapter;
private FloatingActionButton[] brandedViews;
- private GalleryAdapter galleryAdapter;
+ private AbstractPickerAdapter<?> pickerAdapter;
private boolean pickerAnimationInProgress = false;
private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@@ -221,20 +228,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
if (viewModel.canEdit()) {
- binding.fab.setOnClickListener(v -> {
- if (SDK_INT >= LOLLIPOP && galleryAdapter == null) {
- galleryAdapter = new GalleryAdapter(requireContext(), uri -> {
- // TODO show selected image in dialog and let it confirm first
- onActivityResult(REQUEST_CODE_ADD_FILE, RESULT_OK, new Intent().setData(uri));
- });
- binding.pickerRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
- binding.pickerRecyclerView.setAdapter(galleryAdapter);
- }
- mBottomSheetBehaviour.setState(STATE_HALF_EXPANDED);
- showPicker();
- backPressedCallback.setEnabled(true);
- requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
- });
+ binding.fab.setOnClickListener(v -> showGalleryPicker());
binding.fab.show();
binding.attachmentsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@@ -286,6 +280,55 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
}
+ public void showContactPicker() {
+ if (isPermissionRequestNeeded(READ_CONTACTS)) {
+ requestPermissions(new String[]{READ_CONTACTS}, REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION);
+ } else {
+ pickerAdapter = new ContactAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_CONTACT, RESULT_OK, new Intent().setData(uri)), this::pickContact);
+ binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ binding.pickerRecyclerView.setAdapter(pickerAdapter);
+ mBottomSheetBehaviour.setState(STATE_HALF_EXPANDED);
+ showPicker();
+ backPressedCallback.setEnabled(true);
+ requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
+ }
+ }
+
+ public void showGalleryPicker() {
+ if (isPermissionRequestNeeded(READ_EXTERNAL_STORAGE) || isPermissionRequestNeeded(CAMERA)) {
+ requestPermissions(new String[]{READ_EXTERNAL_STORAGE, CAMERA}, REQUEST_CODE_GALLERY_PICKER_PERMISSION);
+ } else {
+ if (SDK_INT >= LOLLIPOP) {
+ pickerAdapter = new GalleryAdapter(requireContext(), uri -> {
+ // TODO show selected image in dialog and let it confirm first
+ onActivityResult(REQUEST_CODE_ADD_FILE, RESULT_OK, new Intent().setData(uri));
+ }, this::pickCamera, getViewLifecycleOwner());
+ binding.pickerRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
+ binding.pickerRecyclerView.setAdapter(pickerAdapter);
+ }
+ mBottomSheetBehaviour.setState(STATE_HALF_EXPANDED);
+ showPicker();
+ backPressedCallback.setEnabled(true);
+ requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
+ }
+ }
+
+ public void showFilesPicker() {
+ if (isPermissionRequestNeeded(READ_EXTERNAL_STORAGE)) {
+ requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, REQUEST_CODE_ADD_FILE_PICKER_PERMISSION);
+ } else {
+ if (SDK_INT >= LOLLIPOP) {
+ pickerAdapter = new FileAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_ADD_FILE, RESULT_OK, new Intent().setData(uri)), this::pickFile);
+ binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ binding.pickerRecyclerView.setAdapter(pickerAdapter);
+ }
+ mBottomSheetBehaviour.setState(STATE_HALF_EXPANDED);
+ showPicker();
+ backPressedCallback.setEnabled(true);
+ requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
+ }
+ }
+
public void pickContact() {
if (isPermissionRequestNeeded(READ_CONTACTS)) {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_CODE_PICK_CONTACT_PERMISSION);
@@ -348,8 +391,8 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
@Override
public void onDestroy() {
- if (this.galleryAdapter != null) {
- this.galleryAdapter.onDestroy();
+ if (this.pickerAdapter != null) {
+ this.pickerAdapter.onDestroy();
this.binding.pickerRecyclerView.setAdapter(null);
}
super.onDestroy();
@@ -444,6 +487,30 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
break;
}
+ case REQUEST_CODE_ADD_FILE_PICKER_PERMISSION: {
+ if (checkSelfPermission(requireActivity(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+ showFilesPicker();
+ } else {
+ Toast.makeText(requireContext(), R.string.cannot_upload_files_without_permission, Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ case REQUEST_CODE_GALLERY_PICKER_PERMISSION: {
+ if (checkSelfPermission(requireActivity(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED && checkSelfPermission(requireActivity(), CAMERA) == PERMISSION_GRANTED) {
+ showGalleryPicker();
+ } else {
+ Toast.makeText(requireContext(), R.string.cannot_upload_files_without_permission, Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ case REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION: {
+ if (checkSelfPermission(requireActivity(), READ_CONTACTS) == PERMISSION_GRANTED) {
+ showContactPicker();
+ } else {
+ Toast.makeText(requireContext(), R.string.cannot_upload_files_without_permission, Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
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
new file mode 100644
index 000000000..e2c655916
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java
@@ -0,0 +1,98 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import static android.database.Cursor.FIELD_TYPE_BLOB;
+import static android.database.Cursor.FIELD_TYPE_FLOAT;
+import static android.database.Cursor.FIELD_TYPE_INTEGER;
+import static android.database.Cursor.FIELD_TYPE_NULL;
+import static android.database.Cursor.FIELD_TYPE_STRING;
+import static androidx.recyclerview.widget.RecyclerView.NO_ID;
+
+public abstract class AbstractPickerAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
+
+ protected static final int VIEW_TYPE_ITEM = 0;
+ protected static final int VIEW_TYPE_ITEM_PICKER = 1;
+
+ private final int count;
+ protected final int columnIndex;
+ private final int columnIndexType;
+ @NonNull
+ protected final Consumer<Uri> onSelect;
+ @NonNull
+ protected final Runnable onSelectPicker;
+ @NonNull
+ protected final Cursor cursor;
+ @NonNull
+ protected final ContentResolver contentResolver;
+
+ public AbstractPickerAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker, Uri subject, String idColumn, String sortOrder) {
+ this(context, onSelect, onSelectPicker, subject, idColumn, new String[]{idColumn}, sortOrder);
+ }
+
+ public AbstractPickerAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker, Uri subject, String idColumn, String[] requestedColumns, String sortOrder) {
+ this.onSelect = onSelect;
+ this.onSelectPicker = onSelectPicker;
+ this.contentResolver = context.getContentResolver();
+ this.cursor = Objects.requireNonNull(contentResolver.query(subject, requestedColumns, null, null, sortOrder));
+ this.columnIndex = cursor.getColumnIndex(idColumn);
+ cursor.moveToFirst();
+ this.columnIndexType = this.cursor.getType(columnIndex);
+ this.count = cursor.getCount();
+ setHasStableIds(true);
+ }
+
+ /**
+ * Moves the {@link #cursor} to the given position
+ */
+ @Override
+ public long getItemId(int position) {
+ if (cursor.moveToPosition(position - 1)) {
+ switch (columnIndexType) {
+ case FIELD_TYPE_INTEGER:
+ return cursor.getLong(columnIndex);
+ case FIELD_TYPE_NULL:
+ return NO_ID;
+ case FIELD_TYPE_FLOAT:
+ return String.valueOf(cursor.getFloat(columnIndex)).hashCode();
+ case FIELD_TYPE_STRING:
+ return cursor.getString(columnIndex).hashCode();
+ case FIELD_TYPE_BLOB:
+ return Arrays.hashCode(cursor.getBlob(columnIndex));
+ default:
+ throw new IllegalStateException("Unknown type for columnIndex \"" + columnIndex + "\": " + columnIndexType);
+ }
+ } else {
+ return NO_ID;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return count;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position == 0
+ ? VIEW_TYPE_ITEM_PICKER
+ : VIEW_TYPE_ITEM;
+ }
+
+ /**
+ * Call this method when the {@link AbstractPickerAdapter} is no longe need to free resources.
+ */
+ public void onDestroy() {
+ cursor.close();
+ }
+}
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
new file mode 100644
index 000000000..4c8bae92f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java
@@ -0,0 +1,81 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.ContactsContract;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+import it.niedermann.nextcloud.deck.databinding.ItemFilterUserBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
+
+import static android.provider.ContactsContract.Contacts.CONTENT_LOOKUP_URI;
+import static android.provider.ContactsContract.Contacts.CONTENT_URI;
+import static android.provider.ContactsContract.Contacts.DISPLAY_NAME;
+import static android.provider.ContactsContract.Contacts.LOOKUP_KEY;
+import static android.provider.ContactsContract.Contacts.SORT_KEY_PRIMARY;
+
+public class ContactAdapter extends AbstractPickerAdapter<RecyclerView.ViewHolder> {
+
+
+ private final int displayNameColumnIndex;
+ @NonNull
+ private final ExecutorService bitmapExecutor = Executors.newCachedThreadPool();
+
+ public ContactAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker) {
+ super(context, onSelect, onSelectPicker, CONTENT_URI, LOOKUP_KEY, new String[]{LOOKUP_KEY, DISPLAY_NAME}, SORT_KEY_PRIMARY);
+ displayNameColumnIndex = cursor.getColumnIndex(DISPLAY_NAME);
+ notifyItemRangeInserted(0, getItemCount());
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_PICKER:
+ return new ContactPickerItemViewHolder(ItemFilterUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new ContactItemViewHolder(ItemPickerUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_PICKER: {
+ ((ContactPickerItemViewHolder) holder).bind(onSelectPicker);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ bitmapExecutor.execute(() -> {
+ final ContactItemViewHolder viewHolder = (ContactItemViewHolder) holder;
+ final long id = getItemId(position);
+ final String name = cursor.getString(displayNameColumnIndex);
+ try (InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, Uri.withAppendedPath(CONTENT_LOOKUP_URI, cursor.getString(columnIndex)))) {
+ final Bitmap thumbnail = BitmapFactory.decodeStream(inputStream);
+ new Handler(Looper.getMainLooper()).post(() -> viewHolder.bind(ContentUris.withAppendedId(CONTENT_LOOKUP_URI, id), thumbnail, name, onSelect));
+ } catch (IOException ignored) {
+ new Handler(Looper.getMainLooper()).post(viewHolder::bindError);
+ }
+ });
+ break;
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000..272fcdca3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java
@@ -0,0 +1,43 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.function.Consumer;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
+
+public class ContactItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPickerUserBinding binding;
+
+ public ContactItemViewHolder(@NonNull ItemPickerUserBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull String displayName, @NonNull Consumer<Uri> onSelect) {
+ itemView.setOnClickListener((v) -> onSelect.accept(uri));
+ binding.displayName.setText(displayName);
+ Glide.with(itemView.getContext())
+ .load(image)
+ .placeholder(R.drawable.ic_person_grey600_24dp)
+ .apply(RequestOptions.circleCropTransform())
+ .into(binding.avatar);
+ }
+
+ public void bindError() {
+ itemView.setOnClickListener(null);
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_person_grey600_24dp)
+ .into(binding.avatar);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactPickerItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactPickerItemViewHolder.java
new file mode 100644
index 000000000..7978dde93
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactPickerItemViewHolder.java
@@ -0,0 +1,27 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemFilterUserBinding;
+
+public class ContactPickerItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemFilterUserBinding binding;
+
+ public ContactPickerItemViewHolder(@NonNull ItemFilterUserBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(Runnable onOpenMajorPicker) {
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_baseline_account_circle_24)
+ .into(binding.avatar);
+ binding.displayName.setText("Show all contacts");
+ itemView.setOnClickListener((v) -> onOpenMajorPicker.run());
+ }
+}
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
new file mode 100644
index 000000000..867081a4f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java
@@ -0,0 +1,72 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.MediaStore;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+import it.niedermann.nextcloud.deck.databinding.ItemFilterUserBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
+
+import static android.provider.BaseColumns._ID;
+import static android.provider.MediaStore.Files.FileColumns.TITLE;
+import static android.provider.MediaStore.MediaColumns.DATE_ADDED;
+import static android.provider.MediaStore.MediaColumns.SIZE;
+
+public class FileAdapter extends AbstractPickerAdapter<RecyclerView.ViewHolder> {
+
+ private final int displayNameColumnIndex;
+ private final int sizeColumnIndex;
+ @NonNull
+ private final ExecutorService bitmapExecutor = Executors.newCachedThreadPool();
+
+ public FileAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker) {
+ super(context, onSelect, onSelectPicker, MediaStore.Files.getContentUri("external"), _ID, new String[]{_ID, TITLE, SIZE}, DATE_ADDED + " DESC");
+ displayNameColumnIndex = cursor.getColumnIndex(TITLE);
+ sizeColumnIndex = cursor.getColumnIndex(SIZE);
+ notifyItemRangeInserted(0, getItemCount());
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_PICKER:
+ return new FilePickerItemViewHolder(ItemFilterUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new FileItemViewHolder(ItemPickerUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_PICKER: {
+ ((FilePickerItemViewHolder) holder).bind(onSelectPicker);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ bitmapExecutor.execute(() -> {
+ final long id = getItemId(position);
+ final String name = cursor.getString(displayNameColumnIndex);
+ final long size = cursor.getLong(sizeColumnIndex);
+ new Handler(Looper.getMainLooper()).post(() -> ((FileItemViewHolder) holder).bind(ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id), name, size, onSelect));
+ });
+ break;
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000..6273ea1dc
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java
@@ -0,0 +1,43 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.net.Uri;
+import android.text.format.Formatter;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.function.Consumer;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
+
+public class FileItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPickerUserBinding binding;
+
+ public FileItemViewHolder(@NonNull ItemPickerUserBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Uri uri, @NonNull String displayName, long size, @NonNull Consumer<Uri> onSelect) {
+ itemView.setOnClickListener((v) -> onSelect.accept(uri));
+ binding.displayName.setText(displayName);
+ binding.contactInformation.setText(Formatter.formatFileSize(itemView.getContext(), size));
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_attach_file_grey600_24dp)
+ .placeholder(R.drawable.ic_person_grey600_24dp)
+ .apply(RequestOptions.circleCropTransform())
+ .into(binding.avatar);
+ }
+
+ public void bindError() {
+ itemView.setOnClickListener(null);
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_person_grey600_24dp)
+ .into(binding.avatar);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FilePickerItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FilePickerItemViewHolder.java
new file mode 100644
index 000000000..2cf012acb
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FilePickerItemViewHolder.java
@@ -0,0 +1,27 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemFilterUserBinding;
+
+public class FilePickerItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemFilterUserBinding binding;
+
+ public FilePickerItemViewHolder(@NonNull ItemFilterUserBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(Runnable onOpenMajorPicker) {
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_attach_file_grey600_24dp)
+ .into(binding.avatar);
+ binding.displayName.setText("Show all files");
+ itemView.setOnClickListener((v) -> onOpenMajorPicker.run());
+ }
+}
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 902ff2cb7..209e4af90 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
@@ -1,10 +1,8 @@
package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
import android.annotation.SuppressLint;
-import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
@@ -15,92 +13,89 @@ import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
import java.io.IOException;
-import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPhotoPreviewBinding;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.Q;
-import static androidx.recyclerview.widget.RecyclerView.NO_ID;
+import static android.provider.BaseColumns._ID;
+import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
-public class GalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder> {
+public class GalleryAdapter extends AbstractPickerAdapter<RecyclerView.ViewHolder> {
- private final int columnIndex;
- private final int count;
@NonNull
- private final Consumer<Uri> onSelect;
+ private final ExecutorService bitmapExecutor = Executors.newCachedThreadPool();
@NonNull
- private final Cursor cursor;
- @NonNull
- private final ContentResolver contentResolver;
- @NonNull
- private final ExecutorService bitmapFetcherExecutor = Executors.newCachedThreadPool();
- @NonNull
- private final ExecutorService bitmapWaiterExecutor = Executors.newCachedThreadPool();
+ private final LifecycleOwner lifecycleOwner;
- public GalleryAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect) {
- this.onSelect = onSelect;
- this.contentResolver = context.getContentResolver();
- @SuppressLint("InlinedApi") final String sortOrder = (SDK_INT >= Q)
- ? MediaStore.Images.Media.DATE_TAKEN
- : MediaStore.Images.Media.DATE_ADDED;
- cursor = Objects.requireNonNull(contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.Media._ID}, null, null, sortOrder + " DESC"));
- this.columnIndex = cursor.getColumnIndex(MediaStore.Images.Media._ID);
- this.count = cursor.getCount();
- notifyItemRangeInserted(0, this.count);
- setHasStableIds(true);
- }
+ @SuppressLint("InlinedApi")
+ private static final String sortOrder = (SDK_INT >= Q)
+ ? MediaStore.Images.Media.DATE_TAKEN
+ : MediaStore.Images.Media.DATE_ADDED;
- @Override
- public long getItemId(int position) {
- if (cursor.moveToPosition(position)) {
- return cursor.getLong(columnIndex);
- } else {
- return NO_ID;
- }
+ public GalleryAdapter(@NonNull Context context, @NonNull Consumer<Uri> onSelect, @NonNull Runnable onSelectPicker, @NonNull LifecycleOwner lifecycleOwner) {
+ super(context, onSelect, onSelectPicker, EXTERNAL_CONTENT_URI, _ID, sortOrder + " DESC");
+ this.lifecycleOwner = lifecycleOwner;
+ notifyItemRangeInserted(0, getItemCount());
}
@NonNull
@Override
- public GalleryItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- return new GalleryItemViewHolder(ItemAttachmentImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_PICKER:
+ return new GalleryPickerItemViewHolder(ItemPhotoPreviewBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new GalleryItemViewHolder(ItemAttachmentImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+
}
@Override
- public void onBindViewHolder(@NonNull GalleryItemViewHolder holder, int position) {
- long id = getItemId(position);
- try {
- Bitmap thumbnail;
- if (SDK_INT >= Q) {
- thumbnail = contentResolver.loadThumbnail(ContentUris.withAppendedId(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id), new Size(512, 384), null);
- } else {
- thumbnail = MediaStore.Images.Thumbnails.getThumbnail(
- contentResolver, id,
- MediaStore.Images.Thumbnails.MINI_KIND, null);
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_PICKER: {
+ ((GalleryPickerItemViewHolder) holder).bind(onSelectPicker, lifecycleOwner);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ bitmapExecutor.execute(() -> {
+ final long id = getItemId(position);
+ try {
+ final Bitmap thumbnail;
+ if (SDK_INT >= Q) {
+ thumbnail = contentResolver.loadThumbnail(ContentUris.withAppendedId(
+ EXTERNAL_CONTENT_URI, id), new Size(512, 384), null);
+ } else {
+ thumbnail = MediaStore.Images.Thumbnails.getThumbnail(
+ contentResolver, id,
+ MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ new Handler(Looper.getMainLooper()).post(() -> ((GalleryItemViewHolder) holder).bind(ContentUris.withAppendedId(
+ EXTERNAL_CONTENT_URI, id), thumbnail, onSelect));
+ } catch (IOException ignored) {
+ new Handler(Looper.getMainLooper()).post(((GalleryItemViewHolder) holder)::bindError);
+ }
+ });
}
- new Handler(Looper.getMainLooper()).post(() -> holder.bind(ContentUris.withAppendedId(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id), thumbnail, onSelect));
- } catch (IOException ignored) {
- new Handler(Looper.getMainLooper()).post(holder::bindError);
}
}
@Override
- public int getItemCount() {
- return count;
- }
-
- /**
- * Call this method when the {@link GalleryAdapter} is no longe need to free resources.
- */
- public void onDestroy() {
- cursor.close();
+ public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+ if(holder instanceof GalleryPickerItemViewHolder) {
+ ((GalleryPickerItemViewHolder) holder).unbind();
+ }
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPickerItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPickerItemViewHolder.java
new file mode 100644
index 000000000..80e15fa7b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPickerItemViewHolder.java
@@ -0,0 +1,51 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutionException;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.databinding.ItemPhotoPreviewBinding;
+
+import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA;
+
+public class GalleryPickerItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPhotoPreviewBinding binding;
+ private ProcessCameraProvider cameraProvider;
+
+ public GalleryPickerItemViewHolder(@NonNull ItemPhotoPreviewBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Runnable onSelectPicker, @NonNull LifecycleOwner lifecycleOwner) {
+ itemView.setOnClickListener((v) -> onSelectPicker.run());
+
+ ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(itemView.getContext());
+ cameraProviderFuture.addListener(() -> {
+ try {
+ cameraProvider = cameraProviderFuture.get();
+ Preview previewUseCase = new Preview.Builder().build();
+ previewUseCase.setSurfaceProvider(binding.preview.getSurfaceProvider());
+ cameraProvider.bindToLifecycle(lifecycleOwner, DEFAULT_BACK_CAMERA, previewUseCase);
+ } catch (ExecutionException | InterruptedException e) {
+ DeckLog.logError(e);
+ }
+ }, ContextCompat.getMainExecutor(itemView.getContext()));
+ }
+
+
+ public void unbind() {
+ if(cameraProvider != null) {
+ cameraProvider.unbindAll();
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java
new file mode 100644
index 000000000..0912a07dd
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java
@@ -0,0 +1,35 @@
+package it.niedermann.nextcloud.deck.ui.view;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+public class SquareConstraintLayout extends ConstraintLayout {
+
+ public SquareConstraintLayout(Context context) {
+ super(context);
+ }
+
+ public SquareConstraintLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SquareConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public SquareConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Set a square layout.
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+
+} \ No newline at end of file