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
path: root/app
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2020-11-03 19:19:38 +0300
committerStefan Niedermann <info@niedermann.it>2020-11-03 19:19:38 +0300
commit704b8c48d1fdfeee3d900287347d24e79a72aef8 (patch)
treec38ec082a0cda6e5ff4b11fc5bbf9009fd6e6b9b /app
parentcbbc9314263cd82e54af2beddece4215f5311856 (diff)
Performant image picker
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'app')
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java107
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java19
-rw-r--r--app/src/main/res/layout/fragment_card_edit_tab_attachments.xml30
4 files changed, 138 insertions, 63 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 990328f66..9edcf7a33 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
@@ -4,12 +4,10 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
-import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
-import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,8 +32,6 @@ import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -156,12 +152,13 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
public void onStateChanged(@NonNull View bottomSheet, int newState) {
switch (newState) {
case STATE_HIDDEN: {
- binding.bottomNavigation.setVisibility(GONE);
+ hidePicker();
+ binding.fab.show();
break;
}
case STATE_EXPANDED:
case STATE_HALF_EXPANDED: {
- binding.bottomNavigation.setVisibility(VISIBLE);
+ showPicker();
break;
}
}
@@ -172,20 +169,11 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
});
- GalleryAdapter galleryAdapter = new GalleryAdapter();
- List<Long> imageIds = Collections.emptyList();
if (SDK_INT >= LOLLIPOP) {
- final ContentResolver contentResolver = requireContext().getContentResolver();
- try (final Cursor outerCursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null)) {
- imageIds = new ArrayList<>(outerCursor.getCount());
- while (outerCursor.moveToNext()) {
- imageIds.add(outerCursor.getLong(outerCursor.getColumnIndex(MediaStore.Images.Media._ID)));
- }
- }
+ GalleryAdapter galleryAdapter = new GalleryAdapter(requireContext());
+ binding.pickerRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
+ binding.pickerRecyclerView.setAdapter(galleryAdapter);
}
- binding.pickerRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
- galleryAdapter.setImageIds(imageIds);
- binding.pickerRecyclerView.setAdapter(galleryAdapter);
final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int spanCount = (int) ((displayMetrics.widthPixels / displayMetrics.density) / getResources().getInteger(R.integer.max_dp_attachment_column));
@@ -224,7 +212,8 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
// picker = CardAttachmentPicker.newInstance();
// picker.show(getChildFragmentManager(), CardAttachmentPicker.class.getSimpleName());
mBottomSheetBehaviour.setState(STATE_HALF_EXPANDED);
- binding.bottomNavigation.setVisibility(VISIBLE);
+ showPicker();
+ binding.fab.hide();
});
binding.fab.show();
binding.attachmentsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -419,6 +408,24 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
}
+ private void hidePicker() {
+ binding.bottomNavigation
+ .animate()
+ .translationY(binding.bottomNavigation.getHeight())
+ .setDuration(300)
+ .start();
+ binding.bottomNavigation.setVisibility(GONE);
+ }
+
+ private void showPicker() {
+ binding.bottomNavigation.setVisibility(VISIBLE);
+ binding.bottomNavigation
+ .animate()
+ .translationY(0)
+ .setDuration(300)
+ .start();
+ }
+
@Override
public void onAttachmentDeleted(Attachment attachment) {
adapter.removeAttachment(attachment);
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 faad73e8a..d123cee74 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,35 +1,75 @@
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.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;
+import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.Q;
+import static androidx.recyclerview.widget.RecyclerView.NO_ID;
+
public class GalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder> {
@NonNull
- List<Long> imageIds = new ArrayList<>();
+ private final Context context;
+ private final int columnIndex;
+ private final int count;
+ @Nullable
+ private final Cursor cursor;
+ @NonNull
+ private final ContentResolver contentResolver;
+ @NonNull
+ private final Map<Integer, Pair<Long, FutureTask<Bitmap>>> itemCache = new HashMap<>();
+ private final ExecutorService bitmapFetcherExecutor = Executors.newCachedThreadPool();
+ private final ExecutorService bitmapWaiterExecutor = Executors.newCachedThreadPool();
- public GalleryAdapter() {
- setHasStableIds(true);
- }
+ public GalleryAdapter(@NonNull Context context) {
+ this.context = context;
+ this.contentResolver = context.getContentResolver();
+ log("Start Query");
+ @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();
- public void setImageIds(@NonNull Collection<Long> imageIds) {
- this.imageIds.clear();
- this.imageIds.addAll(imageIds);
- notifyDataSetChanged();
+ log("Cursor count = " + this.count);
+ notifyItemRangeInserted(0, this.count);
+ setHasStableIds(true);
}
@Override
public long getItemId(int position) {
- return super.getItemId(position);
+ Pair<Long, ?> itemAtPosition = getImageId(position);
+ return itemAtPosition == null ? NO_ID : itemAtPosition.first;
}
@NonNull
@@ -40,11 +80,50 @@ public class GalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder>
@Override
public void onBindViewHolder(@NonNull GalleryItemViewHolder holder, int position) {
- holder.bind(imageIds.get(position));
+ FutureTask<Bitmap> imageFuture = getImageId(position).second;
+ bitmapFetcherExecutor.execute(imageFuture);
+ bitmapWaiterExecutor.execute(() -> {
+ try {
+ final Bitmap image = imageFuture.get();
+ new Handler(Looper.getMainLooper()).post(() -> holder.bind(image));
+ } catch (ExecutionException | InterruptedException ignored) {
+ new Handler(Looper.getMainLooper()).post(() -> holder.bind(null));
+ }
+ });
}
@Override
public int getItemCount() {
- return this.imageIds.size();
+ return count;
+ }
+
+ private Pair<Long, FutureTask<Bitmap>> getImageId(int position) {
+ if (itemCache.containsKey(position)) {
+ return itemCache.get(position);
+ } else {
+ if (cursor == null) {
+ return new Pair<>(NO_ID, null);
+ }
+ if (cursor.moveToPosition(position)) {
+ long id = cursor.getLong(columnIndex);
+ return itemCache.put(position, new Pair<>(id, new FutureTask<>(() -> {
+ if (SDK_INT >= Q) {
+ return contentResolver.loadThumbnail(ContentUris.withAppendedId(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id), new Size(512, 384), null);
+ } else {
+ return MediaStore.Images.Thumbnails.getThumbnail(
+ contentResolver, id,
+ MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ })));
+ } else {
+ throw new NoSuchElementException("Could not find ID for position " + position);
+ }
+ }
+ }
+
+ private void log(String msg) {
+ DeckLog.log(msg);
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
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 7a409c748..7c4cf4a5a 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
@@ -1,30 +1,31 @@
package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
-import android.content.Context;
-import android.provider.MediaStore;
+import android.graphics.Bitmap;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
public class GalleryItemViewHolder extends RecyclerView.ViewHolder {
- ItemAttachmentImageBinding binding;
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ private final ItemAttachmentImageBinding binding;
public GalleryItemViewHolder(@NonNull ItemAttachmentImageBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
- public void bind(@NonNull Long imageId) {
- Context context = itemView.getContext();
- Glide.with(context)
- .load(MediaStore.Images.Thumbnails.getThumbnail(
- context.getContentResolver(), imageId,
- MediaStore.Images.Thumbnails.MINI_KIND, null))
+ public void bind(@Nullable Bitmap image) {
+ Glide.with(itemView.getContext())
+ .load(image)
.into(binding.preview);
}
}
diff --git a/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml b/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml
index c08656c60..cc02d4b3b 100644
--- a/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml
+++ b/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml
@@ -37,48 +37,36 @@
tools:visibility="visible" />
<LinearLayout
- android:orientation="vertical"
android:id="@+id/bottom_sheet_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:gravity="center"
+ android:orientation="vertical"
app:elevation="@dimen/spacer_1x"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<View
android:layout_width="20dp"
android:layout_height="4dp"
- android:background="@color/bg_info_box"
android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"/>
+ android:layout_marginBottom="10dp"
+ android:background="@color/bg_info_box" />
- <androidx.core.widget.NestedScrollView
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/pickerRecyclerView"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <LinearLayout
- android:id="@+id/bottom_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/pickerRecyclerView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- tools:listitem="@layout/support_simple_spinner_dropdown_item"/>
-
- </LinearLayout>
- </androidx.core.widget.NestedScrollView>
+ android:layout_height="wrap_content"
+ tools:listitem="@layout/support_simple_spinner_dropdown_item" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:gravity="bottom"
android:visibility="gone"
- android:gravity="bottom">
+ tools:visibility="visible">
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"