Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for picking documents from the picker. Updated with the A… #449

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Android CI

on:
pull_request:
branches: [main]
branches: [ main ]

jobs:
build:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.esafirm.imagepicker.features.imageloader.ImageLoader
import com.esafirm.imagepicker.features.imageloader.ImageType
import com.esafirm.imagepicker.listeners.OnFolderClickListener
import com.esafirm.imagepicker.model.Folder
import com.esafirm.imagepicker.model.FolderType

class FolderPickerAdapter(
context: Context,
Expand All @@ -31,11 +32,15 @@ class FolderPickerAdapter(
override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
val folder = folders.getOrNull(position) ?: return

imageLoader.loadImage(folder.images.first(), holder.image, ImageType.FOLDER)

holder.apply {
name.text = folder.folderName
number.text = folder.images.size.toString()

if (folder.type == FolderType.Local) {
imageLoader.loadImage(folder.images.first(), holder.image, ImageType.FOLDER)
number.text = folder.images.size.toString()
} else {
number.text = "Pick from Google Photos, Dropbox and other storages"
}
itemView.setOnClickListener { folderClickListener(folder) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.esafirm.imagepicker.features

import android.content.Intent
import com.esafirm.imagepicker.features.cameraonly.ImagePickerCameraOnly
import com.esafirm.imagepicker.model.Document
import com.esafirm.imagepicker.model.Image

object ImagePicker {
Expand All @@ -17,4 +18,8 @@ object ImagePicker {
fun getImages(intent: Intent?): List<Image>? {
return intent?.getParcelableArrayListExtra(IpCons.EXTRA_SELECTED_IMAGES)
}

fun getDocuments(intent: Intent?): List<Document>? {
return intent?.getParcelableArrayListExtra(IpCons.EXTRA_SELECTED_DOCUMENTS)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,9 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener
setResult(RESULT_OK, result)
finish()
}
}

override fun finishPickDocuments(result: Intent?) {
setResult(IpCons.DOCUMENT_PICKED_OK, result)
finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import com.esafirm.imagepicker.helper.ImagePickerPreferences
import com.esafirm.imagepicker.helper.ImagePickerUtils
import com.esafirm.imagepicker.helper.IpLogger
import com.esafirm.imagepicker.helper.state.fetch
import com.esafirm.imagepicker.model.Document
import com.esafirm.imagepicker.model.Folder
import com.esafirm.imagepicker.model.FolderType
import com.esafirm.imagepicker.model.Image

class ImagePickerFragment : Fragment() {
Expand Down Expand Up @@ -141,6 +143,7 @@ class ImagePickerFragment : Fragment() {
}

private fun subscribeToUiState() = presenter.getUiState().observe(this) { state ->

showLoading(state.isLoading)

state.error.fetch {
Expand Down Expand Up @@ -190,9 +193,13 @@ class ImagePickerFragment : Fragment() {
resources.configuration.orientation
).apply {
val selectListener = { isSelected: Boolean -> selectImage(isSelected) }

val folderClick = { bucket: Folder ->
setImageAdapter(bucket.images)
updateTitle()
if (bucket.type == FolderType.Shared) {
openDocumentsPickerPhotos()
} else {
setImageAdapter(bucket.images)
}
}

setupAdapters(passedSelectedImages, selectListener, folderClick)
Expand All @@ -205,6 +212,16 @@ class ImagePickerFragment : Fragment() {
}
}

private fun openDocumentsPickerPhotos() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"

}
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
startActivityForResult(intent, RC_DOCUMENTS_PICKER)
}

override fun onResume() {
super.onResume()
loadDataWithPermission()
Expand Down Expand Up @@ -321,7 +338,23 @@ class ImagePickerFragment : Fragment() {
} else if (resultCode == Activity.RESULT_CANCELED) {
presenter.abortCaptureImage(requireContext())
}
} else if (requestCode == RC_DOCUMENTS_PICKER) {
handleDocumentsPicker(data)
}
}

private fun handleDocumentsPicker(data: Intent?) {
val documents = mutableListOf<Document>()
data?.data?.also {
documents.add(Document(uri = it))
} ?: run {
val clipData = data?.clipData ?: return
for (clipIndex in 0 until clipData.itemCount) {
val uriAtIndex = clipData.getItemAt(clipIndex).uri
documents.add(Document(uriAtIndex))
}
}
interactionListener.finishPickDocuments(ImagePickerUtils.createResultIntentDocuments(documents))
}

/**
Expand Down Expand Up @@ -395,6 +428,7 @@ class ImagePickerFragment : Fragment() {
private const val STATE_KEY_SELECTED_IMAGES = "Key.SelectedImages"

private const val RC_CAPTURE = 2000
private const val RC_DOCUMENTS_PICKER = 2001

fun newInstance(config: ImagePickerConfig): ImagePickerFragment {
val args = Bundle().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface ImagePickerInteractionListener {
// removes Images whose files no longer exist.
fun finishPickImages(result: Intent?)

fun finishPickDocuments(result: Intent?)

/**
* Called when the user selects or deselects sn image. Also called in onCreateView.
* May include Images whose files no longer exist.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.esafirm.imagepicker.features

import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import androidx.activity.ComponentActivity
Expand All @@ -10,6 +11,7 @@ import com.esafirm.imagepicker.features.cameraonly.CameraOnlyConfig
import com.esafirm.imagepicker.features.common.BaseConfig
import com.esafirm.imagepicker.helper.ConfigUtils.checkConfig
import com.esafirm.imagepicker.helper.LocaleManager
import com.esafirm.imagepicker.model.Document
import com.esafirm.imagepicker.model.Image

/* --------------------------------------------------- */
Expand All @@ -28,6 +30,7 @@ class ImagePickerLauncher(
}

typealias ImagePickerCallback = (List<Image>) -> Unit
typealias DocumentPickerCallback = (List<Document>) -> Unit

fun Fragment.registerImagePicker(
context: () -> Context = { requireContext() },
Expand All @@ -36,11 +39,28 @@ fun Fragment.registerImagePicker(
return ImagePickerLauncher(context, createLauncher(callback))
}

/**
* Launcher for image picker.
* Usually you just want to specify the listener when images is picked
*
* ```kotlin
* val launcher = registerImagePicker {
* println(it.images)
* }
*
* launcher.launch(config)
* ```
*
* @param context The context for the launcher in form of lambda
* @param documentCallback callback when images from document picker is selected
* @param callback callback when images from internal picker is selected
*/
fun ComponentActivity.registerImagePicker(
context: () -> Context = { this },
callback: ImagePickerCallback
documentCallback: DocumentPickerCallback? = null,
callback: ImagePickerCallback,
): ImagePickerLauncher {
return ImagePickerLauncher(context, createLauncher(callback))
return ImagePickerLauncher(context, createLauncher(documentCallback, callback))
}

fun createImagePickerIntent(context: Context, config: BaseConfig): Intent {
Expand All @@ -67,8 +87,13 @@ private fun Fragment.createLauncher(callback: ImagePickerCallback) =
callback(images)
}

private fun ComponentActivity.createLauncher(callback: ImagePickerCallback) =
private fun ComponentActivity.createLauncher(documentCallback: DocumentPickerCallback?, callback: ImagePickerCallback) =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val images = ImagePicker.getImages(it.data) ?: emptyList()
callback(images)
if (it.resultCode == RESULT_OK) {
val images = ImagePicker.getImages(it.data) ?: emptyList()
callback(images)
} else if (it.resultCode == IpCons.DOCUMENT_PICKED_OK) {
val documents = ImagePicker.getDocuments(it.data) ?: emptyList()
documentCallback?.invoke(documents)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.esafirm.imagepicker.helper.state.LiveDataObservableState
import com.esafirm.imagepicker.helper.state.ObservableState
import com.esafirm.imagepicker.helper.state.asSingleEvent
import com.esafirm.imagepicker.model.Folder
import com.esafirm.imagepicker.model.FolderType
import com.esafirm.imagepicker.model.Image
import java.io.File

Expand Down Expand Up @@ -42,10 +43,14 @@ internal class ImagePickerPresenter(
imageLoader.abortLoadImages()
imageLoader.loadDeviceImages(config, object : ImageLoaderListener {
override fun onImageLoaded(images: List<Image>, folders: List<Folder>) {
val otherFolder = Folder("More ...")
otherFolder.type = FolderType.Shared
val modifiedFolders = folders.toMutableList()
modifiedFolders.add(otherFolder)
setState {
ImagePickerState(
images = images,
folders = folders,
folders = modifiedFolders,
isLoading = false,
isFolder = config.isFolderMode.asSingleEvent()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ object IpCons {
@Deprecated("You should use the new API to start image picker")
const val RC_IMAGE_PICKER = 0x229
const val EXTRA_SELECTED_IMAGES = "selectedImages"
const val DOCUMENT_PICKED_OK = 2001
const val EXTRA_SELECTED_DOCUMENTS = "selectedDocuments"
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,26 @@ class DefaultImageFileLoader(private val context: Context) : ImageFileLoader {
null
}
}

fun getImageFromURI(context: Context, uri: Uri): Image? {
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME
)

val cursor = context.contentResolver.query(
uri, projection, null, null, null
)
cursor?.let {
it.moveToFirst()
val idIndex = cursor.getColumnIndex(projection[0])
val nameIndex = cursor.getColumnIndex(projection[1])
val id = cursor.getLong(idIndex)
val name = cursor.getString(nameIndex)
it.close()
return Image(id, name, uri.path ?: "")
}
return null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.webkit.MimeTypeMap
import com.esafirm.imagepicker.features.ImagePickerSavePath
import com.esafirm.imagepicker.features.IpCons
import com.esafirm.imagepicker.helper.IpLogger.d
import com.esafirm.imagepicker.model.Document
import com.esafirm.imagepicker.model.Image
import java.io.File
import java.net.URLConnection
Expand Down Expand Up @@ -145,4 +146,11 @@ object ImagePickerUtils {
data.putParcelableArrayListExtra(IpCons.EXTRA_SELECTED_IMAGES, imageArrayList)
return data
}

fun createResultIntentDocuments(documents: List<Document>): Intent {
val data = Intent()
val documentsList = ArrayList(documents)
data.putParcelableArrayListExtra(IpCons.EXTRA_SELECTED_DOCUMENTS, documentsList)
return data
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.esafirm.imagepicker.model

import android.net.Uri
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Document(val uri: Uri) : Parcelable
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.esafirm.imagepicker.model

enum class FolderType {
Local,
Shared
}

class Folder(var folderName: String) {
var images: MutableList<Image> = mutableListOf()
var type: FolderType = FolderType.Local
}
6 changes: 6 additions & 0 deletions sample/src/main/java/com/esafirm/sample/CustomUIActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.appcompat.widget.Toolbar
import com.esafirm.imagepicker.features.ImagePickerConfig
import com.esafirm.imagepicker.features.ImagePickerFragment
import com.esafirm.imagepicker.features.ImagePickerInteractionListener
import com.esafirm.imagepicker.features.IpCons
import com.esafirm.imagepicker.features.cameraonly.CameraOnlyConfig
import com.esafirm.imagepicker.helper.ConfigUtils
import com.esafirm.imagepicker.helper.IpLogger
Expand Down Expand Up @@ -166,6 +167,11 @@ class CustomUIActivity : AppCompatActivity() {
finish()
}

override fun finishPickDocuments(result: Intent?) {
setResult(IpCons.DOCUMENT_PICKED_OK, result)
finish()
}

override fun selectionChanged(imageList: List<Image>?) {
if (imageList == null) error("Image list is null")

Expand Down