diff options
author | 2023-04-20 17:55:07 +0000 | |
---|---|---|
committer | 2023-04-20 17:55:07 +0000 | |
commit | 395868a876907174d4923823048bd1f81a1987a7 (patch) | |
tree | 21b86fd2e611ac10570a5f924b7b6eb42936fef1 /java/src | |
parent | 2b99e8fe664ff18349bad6bc66e5a3a45ca40ea7 (diff) | |
parent | 21cee78c93503c016f6d5cd16e8f5e24abf4910c (diff) |
Merge "Files + text in a separate preview layout." into udc-dev
Diffstat (limited to 'java/src')
5 files changed, 245 insertions, 88 deletions
diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java index 181fe117..21930fdb 100644 --- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java @@ -150,6 +150,15 @@ public final class ChooserContentPreviewUi { } ArrayList<FileInfo> files = new ArrayList<>(uris.size()); int previewCount = readFileInfo(contentResolver, typeClassifier, uris, files); + CharSequence text = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); + if (!TextUtils.isEmpty(text)) { + return new FilesPlusTextContentPreviewUi(files, + targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT), + actionFactory, + imageLoader, + typeClassifier, + headlineGenerator); + } if (previewCount == 0) { return new FileContentPreviewUi( files, @@ -158,7 +167,6 @@ public final class ChooserContentPreviewUi { } return new UnifiedContentPreviewUi( files, - targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT), actionFactory, imageLoader, typeClassifier, diff --git a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java new file mode 100644 index 00000000..5174234a --- /dev/null +++ b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.intentresolver.contentpreview; + +import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_FILE; +import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_IMAGE; + +import android.content.res.Resources; +import android.text.util.Linkify; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.intentresolver.R; +import com.android.intentresolver.widget.ActionRow; +import com.android.intentresolver.widget.ScrollableImagePreviewView; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * FilesPlusTextContentPreviewUi is shown when the user is sending 1 or more files along with + * non-empty EXTRA_TEXT. The text can be toggled with a checkbox. If a single image file is being + * shared, it is shown in a preview (otherwise the headline summary is the sole indication of the + * file content). + */ +class FilesPlusTextContentPreviewUi extends ContentPreviewUi { + private final List<FileInfo> mFiles; + private final CharSequence mText; + private final ChooserContentPreviewUi.ActionFactory mActionFactory; + private final ImageLoader mImageLoader; + private final MimeTypeClassifier mTypeClassifier; + private final HeadlineGenerator mHeadlineGenerator; + private final boolean mAllImages; + private final boolean mAllVideos; + + FilesPlusTextContentPreviewUi( + List<FileInfo> files, + CharSequence text, + ChooserContentPreviewUi.ActionFactory actionFactory, + ImageLoader imageLoader, + MimeTypeClassifier typeClassifier, + HeadlineGenerator headlineGenerator) { + mFiles = files; + mText = text; + mActionFactory = actionFactory; + mImageLoader = imageLoader; + mTypeClassifier = typeClassifier; + mHeadlineGenerator = headlineGenerator; + + boolean allImages = true; + boolean allVideos = true; + for (FileInfo fileInfo : mFiles) { + ScrollableImagePreviewView.PreviewType previewType = + getPreviewType(fileInfo.getMimeType()); + allImages = allImages && previewType == ScrollableImagePreviewView.PreviewType.Image; + allVideos = allVideos && previewType == ScrollableImagePreviewView.PreviewType.Video; + } + mAllImages = allImages; + mAllVideos = allVideos; + } + + @Override + public int getType() { + return shouldShowPreview() ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE; + } + + @Override + public ViewGroup display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent) { + ViewGroup layout = displayInternal(layoutInflater, parent); + displayModifyShareAction(layout, mActionFactory); + return layout; + } + + private ViewGroup displayInternal(LayoutInflater layoutInflater, ViewGroup parent) { + ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( + R.layout.chooser_grid_preview_files_text, parent, false); + ImageView imagePreview = + contentPreviewLayout.findViewById(R.id.image_view); + + final ActionRow actionRow = + contentPreviewLayout.findViewById(com.android.internal.R.id.chooser_action_row); + actionRow.setActions(createActions( + createImagePreviewActions(), + mActionFactory.createCustomActions())); + + if (shouldShowPreview()) { + mImageLoader.loadImage(mFiles.get(0).getPreviewUri(), bitmap -> { + if (bitmap == null) { + imagePreview.setVisibility(View.GONE); + } else { + imagePreview.setImageBitmap(bitmap); + } + }); + } else { + imagePreview.setVisibility(View.GONE); + } + + prepareTextPreview(contentPreviewLayout, mActionFactory); + updateHeadline(contentPreviewLayout); + + return contentPreviewLayout; + } + + private boolean shouldShowPreview() { + return mAllImages && mFiles.size() == 1 && mFiles.get(0).getPreviewUri() != null; + } + + private List<ActionRow.Action> createImagePreviewActions() { + ArrayList<ActionRow.Action> actions = new ArrayList<>(2); + //TODO: add copy action; + if (mFiles.size() == 1 && mAllImages) { + ActionRow.Action action = mActionFactory.createEditButton(); + if (action != null) { + actions.add(action); + } + } + return actions; + } + + private void updateHeadline(ViewGroup contentPreview) { + CheckBox includeText = contentPreview.requireViewById(R.id.include_text_action); + String headline; + if (includeText.getVisibility() == View.VISIBLE && includeText.isChecked()) { + if (mAllImages) { + headline = mHeadlineGenerator.getImagesWithTextHeadline(mText, mFiles.size()); + } else if (mAllVideos) { + headline = mHeadlineGenerator.getVideosWithTextHeadline(mText, mFiles.size()); + } else { + headline = mHeadlineGenerator.getFilesWithTextHeadline(mText, mFiles.size()); + } + } else { + if (mAllImages) { + headline = mHeadlineGenerator.getImagesHeadline(mFiles.size()); + } else if (mAllVideos) { + headline = mHeadlineGenerator.getVideosHeadline(mFiles.size()); + } else { + headline = mHeadlineGenerator.getItemsHeadline(mFiles.size()); + } + } + + displayHeadline(contentPreview, headline); + } + + private void prepareTextPreview( + ViewGroup contentPreview, + ChooserContentPreviewUi.ActionFactory actionFactory) { + final TextView textView = contentPreview.requireViewById(R.id.content_preview_text); + CheckBox includeText = contentPreview.requireViewById(R.id.include_text_action); + boolean isLink = HttpUriMatcher.isHttpUri(mText.toString()); + textView.setAutoLinkMask(isLink ? Linkify.WEB_URLS : 0); + textView.setText(mText); + + final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction(); + includeText.setChecked(true); + includeText.setText(isLink ? R.string.include_link : R.string.include_text); + shareTextAction.accept(false); + includeText.setOnCheckedChangeListener((view, isChecked) -> { + textView.setEnabled(isChecked); + shareTextAction.accept(!isChecked); + updateHeadline(contentPreview); + }); + includeText.setVisibility(View.VISIBLE); + } + + private ScrollableImagePreviewView.PreviewType getPreviewType(String mimeType) { + if (mTypeClassifier.isImageType(mimeType)) { + return ScrollableImagePreviewView.PreviewType.Image; + } + if (mTypeClassifier.isVideoType(mimeType)) { + return ScrollableImagePreviewView.PreviewType.Video; + } + return ScrollableImagePreviewView.PreviewType.File; + } +} diff --git a/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt b/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt index e32bb5c4..ad2a7ada 100644 --- a/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt +++ b/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt @@ -25,7 +25,11 @@ private const val PLURALS_COUNT = "count" interface HeadlineGenerator { fun getTextHeadline(text: CharSequence): String - fun getImageWithTextHeadline(text: CharSequence): String + fun getImagesWithTextHeadline(text: CharSequence, count: Int): String + + fun getVideosWithTextHeadline(text: CharSequence, count: Int): String + + fun getFilesWithTextHeadline(text: CharSequence, count: Int): String fun getImagesHeadline(count: Int): String diff --git a/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt b/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt index ae44294c..a6b782ad 100644 --- a/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt +++ b/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt @@ -16,6 +16,7 @@ package com.android.intentresolver.contentpreview +import android.annotation.StringRes import android.content.Context import com.android.intentresolver.R import android.util.PluralsMessageFormatter @@ -28,40 +29,49 @@ private const val PLURALS_COUNT = "count" */ class HeadlineGeneratorImpl(private val context: Context) : HeadlineGenerator { override fun getTextHeadline(text: CharSequence): String { - if (text.toString().isHttpUri()) { - return context.getString(R.string.sharing_link) - } - return context.getString(R.string.sharing_text) + return context.getString( + getTemplateResource(text, R.string.sharing_link, R.string.sharing_text)) } - override fun getImageWithTextHeadline(text: CharSequence): String { - if (text.toString().isHttpUri()) { - return context.getString(R.string.sharing_image_with_link) - } - return context.getString(R.string.sharing_image_with_text) + override fun getImagesWithTextHeadline(text: CharSequence, count: Int): String { + return getPluralString(getTemplateResource( + text, R.string.sharing_images_with_link, R.string.sharing_images_with_text), count) + } + + override fun getVideosWithTextHeadline(text: CharSequence, count: Int): String { + return getPluralString(getTemplateResource( + text, R.string.sharing_videos_with_link, R.string.sharing_videos_with_text), count) + } + + override fun getFilesWithTextHeadline(text: CharSequence, count: Int): String { + return getPluralString(getTemplateResource( + text, R.string.sharing_files_with_link, R.string.sharing_files_with_text), count) } override fun getImagesHeadline(count: Int): String { - return PluralsMessageFormatter.format( - context.resources, - mapOf(PLURALS_COUNT to count), - R.string.sharing_images - ) + return getPluralString(R.string.sharing_images, count) } override fun getVideosHeadline(count: Int): String { - return PluralsMessageFormatter.format( - context.resources, - mapOf(PLURALS_COUNT to count), - R.string.sharing_videos - ) + return getPluralString(R.string.sharing_videos, count) } override fun getItemsHeadline(count: Int): String { + return getPluralString(R.string.sharing_items, count) + } + + private fun getPluralString(@StringRes templateResource: Int, count: Int): String { return PluralsMessageFormatter.format( context.resources, mapOf(PLURALS_COUNT to count), - R.string.sharing_items + templateResource ) } + + @StringRes + private fun getTemplateResource( + text: CharSequence, @StringRes linkResource: Int, @StringRes nonLinkResource: Int + ): Int { + return if (text.toString().isHttpUri()) linkResource else nonLinkResource + } } diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java index 709ec566..6f1be116 100644 --- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java +++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java @@ -19,15 +19,10 @@ package com.android.intentresolver.contentpreview; import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_IMAGE; import android.content.res.Resources; -import android.text.TextUtils; -import android.text.util.Linkify; -import android.transition.TransitionManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.TextView; import androidx.annotation.Nullable; @@ -39,12 +34,10 @@ import com.android.intentresolver.widget.ScrollableImagePreviewView; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.function.Consumer; class UnifiedContentPreviewUi extends ContentPreviewUi { private final List<FileInfo> mFiles; @Nullable - private final CharSequence mText; private final ChooserContentPreviewUi.ActionFactory mActionFactory; private final ImageLoader mImageLoader; private final MimeTypeClassifier mTypeClassifier; @@ -53,14 +46,12 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { UnifiedContentPreviewUi( List<FileInfo> files, - @Nullable CharSequence text, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, MimeTypeClassifier typeClassifier, TransitionElementStatusCallback transitionElementStatusCallback, HeadlineGenerator headlineGenerator) { mFiles = files; - mText = text; mActionFactory = actionFactory; mImageLoader = imageLoader; mTypeClassifier = typeClassifier; @@ -131,20 +122,15 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { mFiles.size() - previews.size(), mImageLoader); - if (!TextUtils.isEmpty(mText) && mFiles.size() == 1 && allImages) { - setTextInImagePreviewVisibility(contentPreviewLayout, imagePreview, mActionFactory); - updateTextWithImageHeadline(contentPreviewLayout); + if (allImages) { + displayHeadline( + contentPreviewLayout, mHeadlineGenerator.getImagesHeadline(mFiles.size())); + } else if (allVideos) { + displayHeadline( + contentPreviewLayout, mHeadlineGenerator.getVideosHeadline(mFiles.size())); } else { - if (allImages) { - displayHeadline( - contentPreviewLayout, mHeadlineGenerator.getImagesHeadline(mFiles.size())); - } else if (allVideos) { - displayHeadline( - contentPreviewLayout, mHeadlineGenerator.getVideosHeadline(mFiles.size())); - } else { - displayHeadline( - contentPreviewLayout, mHeadlineGenerator.getItemsHeadline(mFiles.size())); - } + displayHeadline( + contentPreviewLayout, mHeadlineGenerator.getItemsHeadline(mFiles.size())); } return contentPreviewLayout; @@ -166,50 +152,6 @@ class UnifiedContentPreviewUi extends ContentPreviewUi { return actions; } - private void updateTextWithImageHeadline(ViewGroup contentPreview) { - CheckBox actionView = contentPreview.requireViewById(R.id.include_text_action); - if (actionView.getVisibility() == View.VISIBLE && actionView.isChecked()) { - displayHeadline(contentPreview, mHeadlineGenerator.getImageWithTextHeadline(mText)); - } else { - displayHeadline( - contentPreview, mHeadlineGenerator.getImagesHeadline(mFiles.size())); - } - } - - private void setTextInImagePreviewVisibility( - ViewGroup contentPreview, - ScrollableImagePreviewView imagePreview, - ChooserContentPreviewUi.ActionFactory actionFactory) { - final TextView textView = contentPreview - .requireViewById(com.android.internal.R.id.content_preview_text); - CheckBox actionView = contentPreview - .requireViewById(R.id.include_text_action); - textView.setVisibility(View.VISIBLE); - boolean isLink = HttpUriMatcher.isHttpUri(mText.toString()); - textView.setAutoLinkMask(isLink ? Linkify.WEB_URLS : 0); - textView.setText(mText); - - final int[] actionLabels = isLink - ? new int[] { R.string.include_link, R.string.exclude_link } - : new int[] { R.string.include_text, R.string.exclude_text }; - final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction(); - actionView.setChecked(true); - actionView.setText(actionLabels[1]); - shareTextAction.accept(false); - actionView.setOnCheckedChangeListener((view, isChecked) -> { - view.setText(actionLabels[isChecked ? 1 : 0]); - textView.setEnabled(isChecked); - if (imagePreview.getVisibility() == View.VISIBLE) { - // animate only only if we have preview - TransitionManager.beginDelayedTransition((ViewGroup) textView.getParent()); - textView.setVisibility(isChecked ? View.VISIBLE : View.GONE); - } - shareTextAction.accept(!isChecked); - updateTextWithImageHeadline(contentPreview); - }); - actionView.setVisibility(View.VISIBLE); - } - private ScrollableImagePreviewView.PreviewType getPreviewType(String mimeType) { if (mTypeClassifier.isImageType(mimeType)) { return ScrollableImagePreviewView.PreviewType.Image; |