diff options
| author | 2023-08-08 20:43:32 -0700 | |
|---|---|---|
| committer | 2023-08-15 21:31:27 +0000 | |
| commit | 3864b3fb003144dad57924ae0143fa5a4df6c849 (patch) | |
| tree | e469bb9fbba2e1dbecc1f44c657e80a1d174114f /java/src | |
| parent | e3360fa4f978bdea5a974189b72949a79c148d22 (diff) | |
Add loading state to the image preview UI
Preview headline loading label is based on the target intent's mime
type.
Circular indeterminated indicator gets shown for loading previews.
Bug: 292157413
Test: manual testing with ShareTest app; unit tests.
Change-Id: I55ec1036a65720fa73c18828fc7a7686de70fa57
Diffstat (limited to 'java/src')
3 files changed, 86 insertions, 40 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java index e8367c4e..18cbb8af 100644 --- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java @@ -163,10 +163,12 @@ public final class ChooserContentPreviewUi { UnifiedContentPreviewUi unifiedContentPreviewUi = new UnifiedContentPreviewUi( isSingleImageShare, + targetIntent.getType(), actionFactory, imageLoader, typeClassifier, transitionElementStatusCallback, + previewData.getUriCount(), headlineGenerator); previewData.getFileMetadataForImagePreview(mLifecycle, unifiedContentPreviewUi::setFiles); return unifiedContentPreviewUi; diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java index 6385f2b6..5db5020e 100644 --- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java @@ -37,11 +37,14 @@ import java.util.Objects; class UnifiedContentPreviewUi extends ContentPreviewUi { private final boolean mShowEditAction; + @Nullable + private final String mIntentMimeType; private final ChooserContentPreviewUi.ActionFactory mActionFactory; private final ImageLoader mImageLoader; private final MimeTypeClassifier mTypeClassifier; private final TransitionElementStatusCallback mTransitionElementStatusCallback; private final HeadlineGenerator mHeadlineGenerator; + private final int mItemCount; @Nullable private List<FileInfo> mFiles; @Nullable @@ -49,16 +52,20 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { UnifiedContentPreviewUi( boolean isSingleImage, + @Nullable String intentMimeType, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, MimeTypeClassifier typeClassifier, TransitionElementStatusCallback transitionElementStatusCallback, + int itemCount, HeadlineGenerator headlineGenerator) { mShowEditAction = isSingleImage; + mIntentMimeType = intentMimeType; mActionFactory = actionFactory; mImageLoader = imageLoader; mTypeClassifier = typeClassifier; mTransitionElementStatusCallback = transitionElementStatusCallback; + mItemCount = itemCount; mHeadlineGenerator = headlineGenerator; } @@ -96,11 +103,19 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { ScrollableImagePreviewView imagePreview = mContentPreviewView.requireViewById(R.id.scrollable_image_preview); + imagePreview.setImageLoader(mImageLoader); imagePreview.setOnNoPreviewCallback(() -> imagePreview.setVisibility(View.GONE)); imagePreview.setTransitionElementStatusCallback(mTransitionElementStatusCallback); if (mFiles != null) { updatePreviewWithFiles(mContentPreviewView, mFiles); + } else { + displayHeadline( + mContentPreviewView, + mItemCount, + mTypeClassifier.isImageType(mIntentMimeType), + mTypeClassifier.isVideoType(mIntentMimeType)); + imagePreview.setLoading(mItemCount); } return mContentPreviewView; @@ -138,14 +153,18 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { } } - imagePreview.setPreviews(previews, count - previews.size(), mImageLoader); + imagePreview.setPreviews(previews, count - previews.size()); + displayHeadline(contentPreviewView, count, allImages, allVideos); + } + private void displayHeadline( + ViewGroup layout, int count, boolean allImages, boolean allVideos) { if (allImages) { - displayHeadline(contentPreviewView, mHeadlineGenerator.getImagesHeadline(count)); + displayHeadline(layout, mHeadlineGenerator.getImagesHeadline(count)); } else if (allVideos) { - displayHeadline(contentPreviewView, mHeadlineGenerator.getVideosHeadline(count)); + displayHeadline(layout, mHeadlineGenerator.getVideosHeadline(count)); } else { - displayHeadline(contentPreviewView, mHeadlineGenerator.getFilesHeadline(count)); + displayHeadline(layout, mHeadlineGenerator.getFilesHeadline(count)); } } } diff --git a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt index 583a2887..d9844d7b 100644 --- a/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt +++ b/java/src/com/android/intentresolver/widget/ScrollableImagePreviewView.kt @@ -158,22 +158,28 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { return null } - fun setPreviews(previews: List<Preview>, otherItemCount: Int, imageLoader: CachingImageLoader) { - previewAdapter.reset(0, imageLoader) + fun setImageLoader(imageLoader: CachingImageLoader) { + previewAdapter.imageLoader = imageLoader + } + + fun setLoading(totalItemCount: Int) { + previewAdapter.reset(totalItemCount) + } + + fun setPreviews(previews: List<Preview>, otherItemCount: Int) { + previewAdapter.reset(previews.size + otherItemCount) batchLoader?.cancel() batchLoader = BatchPreviewLoader( - imageLoader, + previewAdapter.imageLoader ?: error("Image loader is not set"), previews, otherItemCount, - onReset = { totalItemCount -> - previewAdapter.reset(totalItemCount, imageLoader) - }, onUpdate = previewAdapter::addPreviews, onCompletion = { if (!previewAdapter.hasPreviews) { onNoPreviewCallback?.run() } + previewAdapter.markLoaded() } ) .apply { @@ -262,10 +268,11 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { context.resources.getString(R.string.video_preview_a11y_description) private val filePreviewDescription = context.resources.getString(R.string.file_preview_a11y_description) - private var imageLoader: CachingImageLoader? = null + var imageLoader: CachingImageLoader? = null private var firstImagePos = -1 private var totalItemCount: Int = 0 + private var isLoading = false private val hasOtherItem get() = previews.size < totalItemCount val hasPreviews: Boolean @@ -273,61 +280,78 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { var transitionStatusElementCallback: TransitionElementStatusCallback? = null - fun reset(totalItemCount: Int, imageLoader: CachingImageLoader) { - this.imageLoader = imageLoader + fun reset(totalItemCount: Int) { firstImagePos = -1 previews.clear() this.totalItemCount = maxOf(0, totalItemCount) + isLoading = this.totalItemCount > 0 notifyDataSetChanged() } + fun markLoaded() { + if (!isLoading) return + isLoading = false + if (hasOtherItem) { + notifyItemChanged(previews.size) + } else { + notifyItemRemoved(previews.size) + } + } + fun addPreviews(newPreviews: Collection<Preview>) { if (newPreviews.isEmpty()) return val insertPos = previews.size val hadOtherItem = hasOtherItem + val wasEmpty = previews.isEmpty() previews.addAll(newPreviews) if (firstImagePos < 0) { val pos = newPreviews.indexOfFirst { it.type == PreviewType.Image } if (pos >= 0) firstImagePos = insertPos + pos } - notifyItemRangeInserted(insertPos, newPreviews.size) - when { - hadOtherItem && previews.size >= totalItemCount -> { - notifyItemRemoved(previews.size) - } - !hadOtherItem && previews.size < totalItemCount -> { - notifyItemInserted(previews.size) + if (wasEmpty) { + // we don't want any item animation in that case + notifyDataSetChanged() + } else { + notifyItemRangeInserted(insertPos, newPreviews.size) + when { + hadOtherItem && !hasOtherItem -> { + notifyItemRemoved(previews.size) + } + !hadOtherItem && hasOtherItem -> { + notifyItemInserted(previews.size) + } } } } override fun onCreateViewHolder(parent: ViewGroup, itemType: Int): ViewHolder { val view = LayoutInflater.from(context).inflate(itemType, parent, false) - return if (itemType == R.layout.image_preview_other_item) { - OtherItemViewHolder(view) - } else { - PreviewViewHolder( - view, - imagePreviewDescription, - videoPreviewDescription, - filePreviewDescription, - ) + return when (itemType) { + R.layout.image_preview_other_item -> OtherItemViewHolder(view) + R.layout.image_preview_loading_item -> LoadingItemViewHolder(view) + else -> + PreviewViewHolder( + view, + imagePreviewDescription, + videoPreviewDescription, + filePreviewDescription, + ) } } - override fun getItemCount(): Int = previews.size + if (hasOtherItem) 1 else 0 + override fun getItemCount(): Int = previews.size + if (isLoading || hasOtherItem) 1 else 0 - override fun getItemViewType(position: Int): Int { - return if (position == previews.size) { - R.layout.image_preview_other_item - } else { - R.layout.image_preview_image_item + override fun getItemViewType(position: Int): Int = + when { + position == previews.size && isLoading -> R.layout.image_preview_loading_item + position == previews.size -> R.layout.image_preview_other_item + else -> R.layout.image_preview_image_item } - } override fun onBindViewHolder(vh: ViewHolder, position: Int) { when (vh) { is OtherItemViewHolder -> vh.bind(totalItemCount - previews.size) + is LoadingItemViewHolder -> vh.bind() is PreviewViewHolder -> vh.bind( previews[position], @@ -466,6 +490,11 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { override fun unbind() = Unit } + private class LoadingItemViewHolder(view: View) : ViewHolder(view) { + fun bind() = Unit + override fun unbind() = Unit + } + private class SpacingDecoration(private val innerSpacing: Int, private val outerSpacing: Int) : ItemDecoration() { override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: State) { @@ -487,7 +516,6 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { private val imageLoader: CachingImageLoader, previews: List<Preview>, otherItemCount: Int, - private val onReset: (Int) -> Unit, private val onUpdate: (List<Preview>) -> Unit, private val onCompletion: () -> Unit, ) { @@ -527,9 +555,6 @@ class ScrollableImagePreviewView : RecyclerView, ImagePreviewView { } } .collect { - if (blockStart == 0) { - onReset(totalItemCount) - } val updates = ArrayList<Preview>(blockEnd - blockStart) while (blockStart < blockEnd) { if (previewWidths[blockStart] > 0) { |