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

Fix stuff #370

Merged
merged 9 commits into from
Jun 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ buildscript {
ext.kotlin_version = '1.4.32'
ext.core_ktx_version = '1.3.2'
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:4.2.1'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
Expand All @@ -16,7 +16,7 @@ ext {
sdk = [
compileSdk: 30,
targetSdk : 30,
minSdk : 14
minSdk : 21
]
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
6 changes: 6 additions & 0 deletions imagepicker/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@

</application>

<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.esafirm.imagepicker.R
import com.esafirm.imagepicker.adapter.ImagePickerAdapter.ImageViewHolder
Expand Down Expand Up @@ -71,7 +70,8 @@ class ImagePickerAdapter(

if (ImagePickerUtils.isVideoFormat(image)) {
if (!videoDurationHolder.containsKey(image.id)) {
val uri = Uri.withAppendedPath(MediaStore.Files.getContentUri("external"), "" + image.id)
val uri =
Uri.withAppendedPath(MediaStore.Files.getContentUri("external"), "" + image.id)
videoDurationHolder[image.id] = ImagePickerUtils.getVideoDurationLabel(
context, uri
)
Expand Down Expand Up @@ -141,7 +141,7 @@ class ImagePickerAdapter(
this.imageSelectedListener = imageSelectedListener
}

fun getItem(position: Int) = listDiffer.currentList.getOrNull(position)
private fun getItem(position: Int) = listDiffer.currentList.getOrNull(position)

class ImageViewHolder(itemView: View) : ViewHolder(itemView) {
val imageView: ImageView = itemView.image_view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package com.esafirm.imagepicker.features

import com.esafirm.imagepicker.helper.state.ObservableState
import com.esafirm.imagepicker.helper.state.SingleEvent
import com.esafirm.imagepicker.helper.state.asSingleEvent
import com.esafirm.imagepicker.model.Folder
import com.esafirm.imagepicker.model.Image

data class ImagePickerState(
val images: List<Image> = emptyList(),
val folders: List<Folder> = emptyList(),
// TODO: handle the transitions between folder and images in the view state as well
val isFolder: SingleEvent<Boolean>? = null,
val isLoading: Boolean = false,
val error: SingleEvent<Throwable>? = null,
val finishPickImage: SingleEvent<List<Image>>? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener

if (savedInstanceState != null) {
// The fragment has been restored.
imagePickerFragment = supportFragmentManager.findFragmentById(R.id.ef_imagepicker_fragment_placeholder) as ImagePickerFragment
imagePickerFragment =
supportFragmentManager.findFragmentById(R.id.ef_imagepicker_fragment_placeholder) as ImagePickerFragment
} else {
imagePickerFragment = ImagePickerFragment.newInstance(currentConfig)
val ft = supportFragmentManager.beginTransaction()
Expand Down Expand Up @@ -140,6 +141,7 @@ class ImagePickerActivity : AppCompatActivity(), ImagePickerInteractionListener
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_CANCELED) {
cameraModule.removeImage(this)
setResult(RESULT_CANCELED)
finish()
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
Expand Down Expand Up @@ -45,6 +46,17 @@ class ImagePickerFragment : Fragment() {
requireArguments().getParcelable(ImagePickerConfig::class.java.simpleName)!!
}

private val requestPermissionLauncher =
registerForActivityResult(RequestPermission()) { isGranted ->
if (isGranted) {
IpLogger.d("Write External permission granted")
loadData()
} else {
IpLogger.e("Permission not granted")
interactionListener.cancel()
}
}

private lateinit var presenter: ImagePickerPresenter
private lateinit var interactionListener: ImagePickerInteractionListener

Expand Down Expand Up @@ -127,10 +139,13 @@ class ImagePickerFragment : Fragment() {
return@observe
}

if (config.isFolderMode) {
setFolderAdapter(state.folders)
} else {
setImageAdapter(state.images)
state.isFolder.fetch {
val isFolderMode = this
if (isFolderMode) {
setFolderAdapter(state.folders)
} else {
setImageAdapter(state.images)
}
}

state.finishPickImage.fetch {
Expand Down Expand Up @@ -249,50 +264,20 @@ class ImagePickerFragment : Fragment() {
*/
private fun requestWriteExternalPermission() {
IpLogger.w("Write External permission is not granted. Requesting permission")
val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (ActivityCompat.shouldShowRequestPermissionRationale(
requireActivity(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
) {
requestPermissions(permissions, RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE)
} else {
val permission = ImagePickerPreferences.PREF_WRITE_EXTERNAL_STORAGE_REQUESTED
if (!preferences.isPermissionRequested(permission)) {
preferences.setPermissionRequested(permission)
requestPermissions(permissions, RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE)
} else {
binding?.efSnackbar?.show(R.string.ef_msg_no_write_external_permission) {
openAppSettings()
}
}
}
}

/**
* Handle permission results
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
RC_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
IpLogger.d("Write External permission granted")
loadData()
return
}
IpLogger.e(
"Permission not granted: results len = " + grantResults.size +
" Result code = " + if (grantResults.isNotEmpty()) grantResults[0] else "(empty)"
)
interactionListener.cancel()
val permission = Manifest.permission.WRITE_EXTERNAL_STORAGE
when {
shouldShowRequestPermissionRationale(permission) -> {
requestPermissionLauncher.launch(permission)
}
else -> {
IpLogger.d("Got unexpected permission result: $requestCode")
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (!preferences.isPermissionRequested()) {
preferences.setPermissionIsRequested()
requestPermissionLauncher.launch(permission)
} else {
binding?.efSnackbar?.show(R.string.ef_msg_no_write_external_permission) {
openAppSettings()
}
}
}
}
}
Expand All @@ -319,7 +304,7 @@ class ImagePickerFragment : Fragment() {
if (resultCode == Activity.RESULT_OK) {
presenter.finishCaptureImage(requireContext(), data, config)
} else if (resultCode == Activity.RESULT_CANCELED) {
presenter.abortCaptureImage()
presenter.abortCaptureImage(requireContext())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ internal class ImagePickerPresenter(

private val cameraModule: CameraModule = ImagePickerComponentsHolder.cameraModule

private val stateObs = LiveDataObservableState(ImagePickerState(isLoading = true), usePostValue = true)
private val stateObs = LiveDataObservableState(
ImagePickerState(isLoading = true),
usePostValue = true
)

private fun setState(newState: ImagePickerState.() -> ImagePickerState) {
stateObs.set(newState(stateObs.get()))
Expand All @@ -43,7 +46,8 @@ internal class ImagePickerPresenter(
ImagePickerState(
images = images,
folders = folders,
isLoading = false
isLoading = false,
isFolder = config.isFolderMode.asSingleEvent()
)
}
}
Expand Down Expand Up @@ -81,7 +85,11 @@ internal class ImagePickerPresenter(
val context = fragment.requireContext().applicationContext
val intent = cameraModule.getCameraIntent(fragment.requireContext(), config)
if (intent == null) {
Toast.makeText(context, context.getString(R.string.ef_error_create_image_file), Toast.LENGTH_LONG).show()
Toast.makeText(
context,
context.getString(R.string.ef_error_create_image_file),
Toast.LENGTH_LONG
).show()
return
}
fragment.startActivityForResult(intent, requestCode)
Expand All @@ -101,7 +109,7 @@ internal class ImagePickerPresenter(
}
}

fun abortCaptureImage() {
cameraModule.removeImage()
fun abortCaptureImage(context: Context) {
cameraModule.removeImage(context)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import android.widget.Toast
import com.esafirm.imagepicker.R

object CameraHelper {
@JvmStatic
fun checkCameraAvailability(context: Context): Boolean {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val isAvailable = intent.resolveActivity(context.packageManager) != null
if (!isAvailable) {
val appContext = context.applicationContext
Toast.makeText(appContext,
appContext.getString(R.string.ef_error_no_camera), Toast.LENGTH_LONG).show()
Toast.makeText(
appContext,
appContext.getString(R.string.ef_error_no_camera), Toast.LENGTH_LONG
).show()
}
return isAvailable
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ typealias OnImageReadyListener = (List<Image>?) -> Unit
interface CameraModule {
fun getCameraIntent(context: Context, config: BaseConfig): Intent?
fun getImage(context: Context, intent: Intent?, imageReadyListener: OnImageReadyListener)
fun removeImage()
fun removeImage(context: Context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,34 @@ class DefaultCameraModule : CameraModule {
put(MediaStore.Images.Media.DISPLAY_NAME, imageFile.name)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
}
val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val collection =
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
return appContext.contentResolver.insert(collection, values)
}
return UriUtils.uriForFile(appContext, imageFile)
}

override fun getImage(context: Context, intent: Intent?, imageReadyListener: OnImageReadyListener) {
override fun getImage(
context: Context,
intent: Intent?,
imageReadyListener: OnImageReadyListener
) {
if (currentImagePath == null) {
IpLogger.w("currentImagePath null. " +
"This happen if you haven't call #getCameraIntent() or the activity is being recreated")
IpLogger.w(
"currentImagePath null. " +
"This happen if you haven't call #getCameraIntent() or the activity is being recreated"
)
imageReadyListener.invoke(null)
return
}

val imageUri = Uri.parse(currentImagePath)
if (imageUri != null) {
MediaScannerConnection.scanFile(context.applicationContext, arrayOf(imageUri.path), null) { path: String?, uri: Uri? ->
MediaScannerConnection.scanFile(
context.applicationContext,
arrayOf(imageUri.path),
null
) { path: String?, uri: Uri? ->
IpLogger.d("File $path was scanned successfully: $uri")

if (path == null) {
Expand All @@ -82,11 +93,20 @@ class DefaultCameraModule : CameraModule {
}
}

override fun removeImage() {
override fun removeImage(context: Context) {
val imagePath = currentImagePath ?: return
val file = File(imagePath)
if (file.exists()) {
file.delete()
}

// Delete in the media store
try {
val uri = currentUri?.let { Uri.parse(it) } ?: return
context.applicationContext.contentResolver.delete(uri, null, null)
} catch (e: Exception) {
IpLogger.e("Can't delete cancelled uri")
e.printStackTrace()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ class RecyclerViewManager(

/* Init folder and image adapter */
val imageLoader = ImagePickerComponentsHolder.imageLoader
imageAdapter = ImagePickerAdapter(context, imageLoader, selectedImages
?: emptyList(), onImageClick)
imageAdapter = ImagePickerAdapter(
context, imageLoader, selectedImages
?: emptyList(), onImageClick
)
folderAdapter = FolderPickerAdapter(context, imageLoader) {
foldersState = recyclerView.layoutManager?.onSaveInstanceState()
onFolderClick(it)
Expand Down Expand Up @@ -135,7 +137,11 @@ class RecyclerViewManager(
return if (config.limit == IpCons.MAX_LIMIT) {
String.format(context.getString(R.string.ef_selected), imageSize)
} else {
String.format(context.getString(R.string.ef_selected_with_limit), imageSize, config.limit)
String.format(
context.getString(R.string.ef_selected_with_limit),
imageSize,
config.limit
)
}
}

Expand All @@ -155,7 +161,10 @@ class RecyclerViewManager(
}
}

/* --------------------------------------------------- */ /* > Images */ /* --------------------------------------------------- */
/* --------------------------------------------------- */
/* > Images */
/* --------------------------------------------------- */

private fun checkAdapterIsInitialized() {
if (!::imageAdapter.isInitialized) {
error("Must call setupAdapters first!")
Expand Down