Skip to content

Commit

Permalink
Merge pull request #18 from Atwa/enhancemnt/add_file_extensions
Browse files Browse the repository at this point in the history
return high quality camera captured image
  • Loading branch information
Atwa authored May 18, 2023
2 parents 9f49288 + 85e0c13 commit c9814e1
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 79 deletions.
8 changes: 5 additions & 3 deletions filepicker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ plugins {
}

android {
compileSdk 32
compileSdk 33

defaultConfig {
minSdk 21
targetSdk 32
targetSdk 33
versionCode 1
versionName "1.0"

Expand All @@ -19,10 +19,12 @@ android {
buildTypes {
release {
minifyEnabled true
debuggable false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
debuggable true
}
}
compileOptions {
Expand All @@ -44,7 +46,7 @@ afterEvaluate {

groupId = 'com.github.atwa'
artifactId = 'filepicker'
version = '1.0.7'
version = '1.0.8'
}
}
repositories {
Expand Down
11 changes: 11 additions & 0 deletions filepicker/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
<application
android:label="@string/app_name"
android:supportsRtl="true">

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.atwa.filepicker.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,42 +1,85 @@
package com.atwa.filepicker.core

import android.graphics.Bitmap
import android.content.Context
import android.net.Uri
import android.os.Environment
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.atwa.filepicker.BuildConfig
import com.atwa.filepicker.decoder.Decoder
import com.atwa.filepicker.decoder.UriDecoder
import com.atwa.filepicker.request.*
import com.atwa.filepicker.result.FileMeta
import com.atwa.filepicker.result.ImageMeta
import com.atwa.filepicker.result.VideoMeta
import java.io.File
import java.io.IOException
import java.lang.ref.WeakReference


internal class ActivityFilePicker(private val activity: WeakReference<AppCompatActivity>) : FilePicker {

private lateinit var pickerRequest: PickerRequest
private lateinit var cameraRequest: ImageCameraRequest
private lateinit var imageCameraRequest: ImageCameraRequest

private val decoder: Decoder by lazy { UriDecoder(activity.get()?.applicationContext) }

private val filePickerLauncher =
activity.get()?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result?.data?.data?.let { processFile(it) }
private val filePickerLauncher = activity.get()
?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result?.data?.data?.let { processFile(it,pickerRequest) }
}

private val cameraCaptureLauncher =
activity.get()?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
(result.data?.extras?.get("data") as? Bitmap)?.let { processBitmap(it) }
private val cameraCaptureLauncher = activity.get()
?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
imageCameraRequest.photoURI?.let { processFile(it, imageCameraRequest) }
}

private fun initializeCameraRequest(context: Context, onImagePicked: (ImageMeta?) -> Unit) {
imageCameraRequest = ImageCameraRequest(decoder, onImagePicked).apply {
try {
createImageFile(context)
} catch (ex: IOException) {
null
}?.also { file ->
photoURI = FileProvider.getUriForFile(
context,
"${BuildConfig.LIBRARY_PACKAGE_NAME}.provider",
file
)
}

}
}

fun observeLifeCycle() {
activity.get()?.lifecycle?.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
activity.get()?.lifecycle?.removeObserver(this)
activity.clear()
}
}
})
}

override fun pickImage(onImagePicked: (ImageMeta?) -> Unit) {
pickerRequest = ImagePickerRequest(decoder, onImagePicked)
initialize()
}

override fun captureCameraImage(onImagePicked: (ImageMeta?) -> Unit) {
cameraRequest = ImageCameraRequest(decoder, onImagePicked)
cameraCaptureLauncher?.launch(cameraRequest.intent)
activity.get()?.applicationContext?.let { context ->
observeLifeCycle()
initializeCameraRequest(context, onImagePicked)
imageCameraRequest.intent.let { intent ->
cameraCaptureLauncher?.launch(intent)
}
}
}

override fun pickPdf(onPdfPicked: (FileMeta?) -> Unit) {
Expand All @@ -60,18 +103,13 @@ internal class ActivityFilePicker(private val activity: WeakReference<AppCompatA
}

private fun initialize() {
observeLifeCycle()
filePickerLauncher?.launch(pickerRequest.intent)
}

private fun processFile(uri: Uri) {
activity.get()?.lifecycleScope?.launchWhenResumed {
pickerRequest.invokeCallback(uri)
}
}

private fun processBitmap(bitmap: Bitmap) {
private fun processFile(uri: Uri, request: PickerRequest) {
activity.get()?.lifecycleScope?.launchWhenResumed {
cameraRequest.invokeCameraCallback(bitmap)
request.invokeCallback(uri)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,83 @@
package com.atwa.filepicker.core

import android.graphics.Bitmap
import android.content.Context
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.atwa.filepicker.BuildConfig
import com.atwa.filepicker.decoder.Decoder
import com.atwa.filepicker.decoder.UriDecoder
import com.atwa.filepicker.request.*
import com.atwa.filepicker.result.FileMeta
import com.atwa.filepicker.result.ImageMeta
import com.atwa.filepicker.result.VideoMeta
import java.io.IOException
import java.lang.ref.WeakReference

internal class FragmentFilePicker(private val fragment: WeakReference<Fragment>) : FilePicker {

private lateinit var pickerRequest: PickerRequest
private lateinit var cameraRequest: ImageCameraRequest
private lateinit var imageCameraRequest: ImageCameraRequest
private val decoder: Decoder by lazy {
UriDecoder(
fragment.get()?.requireActivity()?.applicationContext
)
UriDecoder(fragment.get()?.requireActivity()?.applicationContext)
}

private val filePickerLauncher =
fragment.get()
?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result?.data?.data?.let { processFile(it) }
private val filePickerLauncher = fragment.get()
?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result?.data?.data?.let { processFile(it,pickerRequest) }
}

private val cameraCaptureLauncher = fragment.get()
?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
imageCameraRequest.photoURI?.let { processFile(it, imageCameraRequest) }
}

private fun initializeCameraRequest(context: Context, onImagePicked: (ImageMeta?) -> Unit) {
imageCameraRequest = ImageCameraRequest(decoder, onImagePicked).apply {
try {
createImageFile(context)
} catch (ex: IOException) {
null
}?.also { file ->
photoURI = FileProvider.getUriForFile(
context,
"${BuildConfig.LIBRARY_PACKAGE_NAME}.provider",
file
)
}

private val cameraCaptureLauncher =
fragment.get()
?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
(result.data?.extras?.get("data") as? Bitmap)?.let { processBitmap(it) }
}
}

private fun observeLifeCycle() {
fragment.get()?.viewLifecycleOwner?.lifecycle?.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
fragment.get()?.viewLifecycleOwner?.lifecycle?.removeObserver(this)
fragment.clear()
}
}
})
}

override fun pickImage(onImagePicked: (ImageMeta?) -> Unit) {
pickerRequest = ImagePickerRequest(decoder, onImagePicked)
initialize()
}

override fun captureCameraImage(onImagePicked: (ImageMeta?) -> Unit) {
cameraRequest = ImageCameraRequest(decoder, onImagePicked)
cameraCaptureLauncher?.launch(cameraRequest.intent)
fragment.get()?.requireActivity()?.applicationContext?.let { context ->
observeLifeCycle()
initializeCameraRequest(context, onImagePicked)
imageCameraRequest.intent.let { intent ->
cameraCaptureLauncher?.launch(intent)
}
}
}

override fun pickPdf(onPdfPicked: (FileMeta?) -> Unit) {
Expand All @@ -66,18 +101,13 @@ internal class FragmentFilePicker(private val fragment: WeakReference<Fragment>)
}

private fun initialize() {
observeLifeCycle()
filePickerLauncher?.launch(pickerRequest.intent)
}

private fun processFile(uri: Uri) {
fragment.get()?.lifecycleScope?.launchWhenResumed {
pickerRequest.invokeCallback(uri)
}
}

private fun processBitmap(bitmap: Bitmap) {
private fun processFile(uri: Uri, request: PickerRequest) {
fragment.get()?.lifecycleScope?.launchWhenResumed {
cameraRequest.invokeCameraCallback(bitmap)
request.invokeCallback(uri)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.atwa.filepicker.decoder

import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
import java.io.IOException
import java.io.InputStream

class BitmapRotation(private val contentResolver: ContentResolver?, val uri: Uri) {

private fun getRotationAngle(): Float {
return try {
val exifInterface = getExifInterface() ?: return 0f
val orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED
)
when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> 90f
ExifInterface.ORIENTATION_ROTATE_180 -> 180f
ExifInterface.ORIENTATION_ROTATE_270 -> 270f
ExifInterface.ORIENTATION_NORMAL -> 0f
ExifInterface.ORIENTATION_UNDEFINED -> -1f
else -> -1f
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
0f
}
}

private fun getExifInterface(): ExifInterface? {
var inputStream: InputStream? = null
try {
val path = uri.toString()
if (path.startsWith("file://")) {
return ExifInterface(path)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (path.startsWith("content://")) {
inputStream = contentResolver?.openInputStream(uri)
return inputStream?.let { ExifInterface(it) }
}
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
inputStream?.close()
}
return null
}

fun rotateAccordingToOrientation(bitmap: Bitmap?): Bitmap? {
return bitmap?.let {
val matrix = Matrix()
matrix.postRotate(getRotationAngle())
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.width, bitmap.height, true)
Bitmap.createBitmap(
scaledBitmap,
0,
0,
scaledBitmap.width,
scaledBitmap.height,
matrix,
true
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ internal interface Decoder {
fun getStorageImage(imageUri: Uri?): Flow<ImageMeta?>
fun getStoragePDF(pdfUri: Uri?): Flow<FileMeta?>
fun getStorageFile(pdfUri: Uri?): Flow<FileMeta?>
fun saveStorageImage(bitmap: Bitmap): Flow<ImageMeta?>
fun getStorageVideo(videoUri: Uri?): Flow<VideoMeta?>
fun getCameraImage(imageUri: Uri?): Flow<ImageMeta?>
}
Loading

0 comments on commit c9814e1

Please sign in to comment.