diff --git a/capture-sdk/sdk/src/main/AndroidManifest.xml b/capture-sdk/sdk/src/main/AndroidManifest.xml index 06676c4b5d..dc5ba0412a 100644 --- a/capture-sdk/sdk/src/main/AndroidManifest.xml +++ b/capture-sdk/sdk/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -18,6 +19,16 @@ + + + + + + + \ No newline at end of file diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/camera/CameraFragmentImpl.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/camera/CameraFragmentImpl.java index 9c463df50a..4f4c0f3993 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/camera/CameraFragmentImpl.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/camera/CameraFragmentImpl.java @@ -456,6 +456,8 @@ private void setFileChooserFragmentResultListener() { public void handleFileChooserResult(@NonNull FileChooserResult result) { if (result instanceof FileChooserResult.FilesSelected) { importDocumentFromIntent(((FileChooserResult.FilesSelected) result).getDataIntent()); + } else if(result instanceof FileChooserResult.FilesSelectedUri) { + importDocumentFromUriList(((FileChooserResult.FilesSelectedUri) result).getList()); } else if (result instanceof FileChooserResult.Error) { final GiniCaptureError error = ((FileChooserResult.Error) result).getError(); final String message = "Document import failed: " + error.getMessage(); @@ -1113,6 +1115,13 @@ private void importDocumentFromIntent(@NonNull final Intent data) { } } + private void importDocumentFromUriList(List uriList) { + if (mFragment.getActivity() == null) + return; + + handleMultiPageDocumentAndCallListener(mFragment.getActivity(), new Intent(Intent.ACTION_PICK), uriList); + } + private boolean isImage(@NonNull final Intent data, @NonNull final Activity activity) { return IntentHelper.hasMimeTypeWithPrefix(data, activity, MimeType.IMAGE_PREFIX.asString()); } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/FileChooserFragment.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/FileChooserFragment.kt index 996a41d5f0..c9cc710387 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/FileChooserFragment.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/FileChooserFragment.kt @@ -7,6 +7,7 @@ import android.app.Dialog import android.content.Context import android.content.Intent import android.content.pm.ResolveInfo +import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Parcelable @@ -15,7 +16,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.fragment.app.setFragmentResult import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager @@ -30,6 +33,7 @@ import net.gini.android.capture.R import net.gini.android.capture.databinding.GcFragmentFileChooserBinding import net.gini.android.capture.internal.fileimport.providerchooser.ProvidersAdapter import net.gini.android.capture.internal.fileimport.providerchooser.ProvidersAppItem +import net.gini.android.capture.internal.fileimport.providerchooser.ProvidersAppWrapperItem import net.gini.android.capture.internal.fileimport.providerchooser.ProvidersItem import net.gini.android.capture.internal.fileimport.providerchooser.ProvidersSectionItem import net.gini.android.capture.internal.fileimport.providerchooser.ProvidersSeparatorItem @@ -52,6 +56,7 @@ class FileChooserFragment : BottomSheetDialogFragment() { private var docImportEnabledFileTypes: DocumentImportEnabledFileTypes? = null private var binding: GcFragmentFileChooserBinding by autoCleared() private var chooseFileLauncher: ActivityResultLauncher? = null + private var pickMedia: ActivityResultLauncher? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -139,6 +144,32 @@ class FileChooserFragment : BottomSheetDialogFragment() { }) } } + + val photoPickType = + if (FeatureConfiguration.isMultiPageEnabled()) + ActivityResultContracts.PickMultipleVisualMedia() + else + ActivityResultContracts.PickVisualMedia() + + + pickMedia = registerForActivityResult(photoPickType) { uri -> + findNavController().popBackStack() + if (uri != null) { + val lst = when(uri){ + is Uri -> listOf(uri) + is List<*> -> uri.filterIsInstance().takeIf { it.size == uri.size } + ?: throw IllegalArgumentException("List contains non-Uri elements") + else -> throw IllegalArgumentException("uri is neither Uri nor List") + } + setFragmentResult(REQUEST_KEY, Bundle().apply { + putParcelable(RESULT_KEY, FileChooserResult.FilesSelectedUri(lst)) + }) + } else { + setFragmentResult(REQUEST_KEY, Bundle().apply { + putParcelable(RESULT_KEY, FileChooserResult.Cancelled) + }) + } + } } private fun getGridSpanCount(): Int = @@ -151,15 +182,18 @@ class FileChooserFragment : BottomSheetDialogFragment() { private fun populateFileProviders() { val providerItems: MutableList = ArrayList() - var imageProviderItems: List = ArrayList() + var imageProviderItems: List = arrayListOf() var pdfProviderItems: List = ArrayList() if (shouldShowImageProviders()) { val imagePickerResolveInfos = queryImagePickers(requireContext()) val imageProviderResolveInfos = queryImageProviders(requireContext()) - imageProviderItems = getImageProviderItems( - imagePickerResolveInfos, - imageProviderResolveInfos - ) + + imageProviderItems = + if (ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(requireContext())) { + getPhotoPickerProvider() + } else { + getImageProviderItems(imagePickerResolveInfos, imageProviderResolveInfos) + } } if (shouldShowPdfProviders()) { val pdfProviderResolveInfos = queryPdfProviders(requireContext()) @@ -172,15 +206,31 @@ class FileChooserFragment : BottomSheetDialogFragment() { providerItems.addAll(pdfProviderItems) (binding.gcFileProviders.layoutManager as GridLayoutManager).spanSizeLookup = ProvidersSpanSizeLookup(providerItems, getGridSpanCount()) - binding.gcFileProviders.adapter = ProvidersAdapter(requireContext(), providerItems) { item -> launchApp(item) } + binding.gcFileProviders.adapter = ProvidersAdapter(requireContext(), providerItems) { + item -> launchApp(item) + } } - private fun launchApp(item: ProvidersAppItem) { - item.intent.setClassName( - item.resolveInfo.activityInfo.packageName, - item.resolveInfo.activityInfo.name - ) - chooseFileLauncher?.launch(item.intent) + private fun launchApp(item: ProvidersItem) { + when (item) { + is ProvidersAppItem -> { + item.intent.setClassName( + item.resolveInfo.activityInfo.packageName, + item.resolveInfo.activityInfo.name + ) + chooseFileLauncher?.launch(item.intent) + } + + is ProvidersAppWrapperItem -> { + pickMedia?.launch( + PickVisualMediaRequest( + ActivityResultContracts.PickVisualMedia.SingleMimeType( + MimeType.IMAGE_WILDCARD.asString() + ) + ) + ) + } + } } private fun getImageProviderItems( @@ -204,6 +254,23 @@ class FileChooserFragment : BottomSheetDialogFragment() { } } + private fun getPhotoPickerProvider(): List { + val providerList = + ContextCompat.getDrawable(requireContext(), (R.drawable.gc_photo_tip_align)) + ?.let { image -> + listOf( + ProvidersSectionItem(getString(R.string.gc_file_chooser_fotos_section_header)), + ProvidersAppWrapperItem( + image, + getString(R.string.gc_file_chooser_fotos_section_header) + ) + ) + } ?: run { + emptyList() + } + return providerList + } + private fun getPdfProviderItems(pdfProviderResolveInfos: List): List = mutableListOf().apply { if (pdfProviderResolveInfos.isNotEmpty()) { @@ -311,6 +378,7 @@ class FileChooserFragment : BottomSheetDialogFragment() { @Parcelize sealed class FileChooserResult : Parcelable { data class FilesSelected(val dataIntent: Intent) : FileChooserResult() + data class FilesSelectedUri(val list: List) : FileChooserResult() data class Error(val error: GiniCaptureError) : FileChooserResult() object Cancelled : FileChooserResult() } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAdapter.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAdapter.java index 7a672cb21f..37270307fa 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAdapter.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAdapter.java @@ -40,12 +40,14 @@ public int getItemViewType(final int position) { @Override public ProvidersItemViewHolder onCreateViewHolder(final ViewGroup parent, - final int viewType) { + final int viewType) { switch (ProvidersItem.FileProviderItemType.fromOrdinal(viewType)) { case SECTION: return createSectionItemViewHolder(parent); case APP: - return createAppItemViewHolder(parent); + return createAppItemViewHolder(parent, ProvidersItem.FileProviderItemType.APP); + case APP_WRAPPER_PHOTO_PICKER: + return createAppItemViewHolder(parent, ProvidersItem.FileProviderItemType.APP_WRAPPER_PHOTO_PICKER); case SEPARATOR: return createSeparatorItemViewHolder(parent); default: @@ -62,10 +64,12 @@ private ProvidersItemViewHolder createSectionItemViewHolder( } @NonNull - private ProvidersItemViewHolder createAppItemViewHolder(@NonNull final ViewGroup parent) { + private ProvidersItemViewHolder createAppItemViewHolder( + @NonNull final ViewGroup parent, + @NonNull ProvidersItem.FileProviderItemType providerItemType) { final View itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.gc_item_file_provider_app, parent, false); - return new ProvidersAppItemViewHolder(itemView); + return new ProvidersAppItemViewHolder(itemView, providerItemType); } private ProvidersItemViewHolder createSeparatorItemViewHolder(@NonNull final ViewGroup parent) { @@ -83,6 +87,8 @@ public void onBindViewHolder(final ProvidersItemViewHolder holder, final int pos case APP: bindAppItemViewHolder((ProvidersAppItemViewHolder) holder, position); break; + case APP_WRAPPER_PHOTO_PICKER: + bindAppWrapperItemViewHolder((ProvidersAppItemViewHolder) holder, position); case SEPARATOR: break; default: @@ -101,13 +107,22 @@ private void bindAppItemViewHolder(@NonNull final ProvidersAppItemViewHolder hol final ProvidersAppItem item = (ProvidersAppItem) mItems.get(position); holder.icon.setImageDrawable(item.getResolveInfo().loadIcon(mContext.getPackageManager())); holder.label.setText(item.getResolveInfo().loadLabel(mContext.getPackageManager())); - holder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { - final ProvidersAppItem item = - (ProvidersAppItem) mItems.get(holder.getAdapterPosition()); - mItemSelectedListener.onItemSelected(item); - } + holder.itemView.setOnClickListener(view -> { + final ProvidersAppItem item1 = + (ProvidersAppItem) mItems.get(holder.getAdapterPosition()); + mItemSelectedListener.onItemSelected(item1); + }); + } + + private void bindAppWrapperItemViewHolder(@NonNull final ProvidersAppItemViewHolder holder, + final int position) { + final ProvidersAppWrapperItem item = (ProvidersAppWrapperItem) mItems.get(position); + holder.icon.setImageDrawable(item.getDrawableIcon()); + holder.label.setText(item.getText()); + holder.itemView.setOnClickListener(view -> { + final ProvidersAppWrapperItem item1 = + (ProvidersAppWrapperItem) mItems.get(holder.getAdapterPosition()); + mItemSelectedListener.onItemSelected(item1); }); } @@ -123,8 +138,8 @@ private static class ProvidersAppItemViewHolder extends ProvidersItemViewHolder @NonNull final TextView label; - ProvidersAppItemViewHolder(@NonNull final View itemView) { - super(itemView, ProvidersItem.FileProviderItemType.APP); + ProvidersAppItemViewHolder(@NonNull final View itemView, @NonNull ProvidersItem.FileProviderItemType providerItemType) { + super(itemView, providerItemType); icon = itemView.findViewById(R.id.gc_app_icon); label = itemView.findViewById(R.id.gc_app_label); } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppItemSelectedListener.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppItemSelectedListener.java index 9324d0de5b..f6e16751ef 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppItemSelectedListener.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppItemSelectedListener.java @@ -9,5 +9,5 @@ */ public interface ProvidersAppItemSelectedListener { - void onItemSelected(@NonNull final ProvidersAppItem item); + void onItemSelected(@NonNull final ProvidersItem item); } diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppWrapperItem.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppWrapperItem.java new file mode 100644 index 0000000000..7dee3c5b82 --- /dev/null +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersAppWrapperItem.java @@ -0,0 +1,31 @@ +package net.gini.android.capture.internal.fileimport.providerchooser; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; + +/** + * Internal use only. + * + * @suppress + */ +public class ProvidersAppWrapperItem extends ProvidersItem { + + private final Drawable mIcon; + private final String mText; + + public ProvidersAppWrapperItem(@NonNull final Drawable icon, @NonNull final String text) { + super(FileProviderItemType.APP_WRAPPER_PHOTO_PICKER); + mIcon = icon; + mText = text; + } + + public Drawable getDrawableIcon() { + return mIcon; + } + + public String getText() { + return mText; + } + +} diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersItem.java b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersItem.java index 97749a19a3..408c20573b 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersItem.java +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/internal/fileimport/providerchooser/ProvidersItem.java @@ -53,7 +53,8 @@ FileProviderItemType getType() { enum FileProviderItemType { SECTION, SEPARATOR, - APP; + APP, + APP_WRAPPER_PHOTO_PICKER; static FileProviderItemType fromOrdinal(final int ordinal) { if (ordinal >= values().length) {