From c374669091ab03ff6d0515bc09c3230d01888861 Mon Sep 17 00:00:00 2001 From: Andrea Colombo Date: Sun, 9 Aug 2020 02:27:01 +0200 Subject: [PATCH] Add clear --- app/build.gradle | 4 +- .../acolombo/plukke/example/DemoActivity.kt | 6 +- build.gradle | 5 +- plukke/build.gradle | 2 +- .../eu/acolombo/plukke/ExternalContent.kt | 24 ------ .../java/eu/acolombo/plukke/ImagePicker.kt | 66 -------------- .../main/java/eu/acolombo/plukke/Plukke.kt | 85 +++++++++++++++++++ ...ts.kt => PlukkeActivityResultContracts.kt} | 15 ++-- .../main/java/eu/acolombo/plukke/PlukkeExt.kt | 32 +++++++ 9 files changed, 131 insertions(+), 108 deletions(-) delete mode 100644 plukke/src/main/java/eu/acolombo/plukke/ExternalContent.kt delete mode 100644 plukke/src/main/java/eu/acolombo/plukke/ImagePicker.kt create mode 100644 plukke/src/main/java/eu/acolombo/plukke/Plukke.kt rename plukke/src/main/java/eu/acolombo/plukke/{ActivityResultContracts.kt => PlukkeActivityResultContracts.kt} (81%) create mode 100644 plukke/src/main/java/eu/acolombo/plukke/PlukkeExt.kt diff --git a/app/build.gradle b/app/build.gradle index c53e916..0af8963 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,10 +39,10 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation "androidx.fragment:fragment-ktx:1.3.0-alpha07" + implementation "androidx.fragment:fragment-ktx:$fragment_ktx_version" implementation "androidx.constraintlayout:constraintlayout:2.0.0-rc1" implementation 'com.google.android.material:material:1.2.0' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.github.bumptech.glide:glide:4.11.0' } diff --git a/app/src/main/java/eu/acolombo/plukke/example/DemoActivity.kt b/app/src/main/java/eu/acolombo/plukke/example/DemoActivity.kt index 7c9d213..468cab0 100644 --- a/app/src/main/java/eu/acolombo/plukke/example/DemoActivity.kt +++ b/app/src/main/java/eu/acolombo/plukke/example/DemoActivity.kt @@ -21,14 +21,14 @@ class DemoActivity : AppCompatActivity(R.layout.activity_demo) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // Choose between taking a photo or picking from gallery using Plukke's ActivityResultContracts.Choose + // Choose between taking a photo or picking from gallery using PlukkeResultContracts.Choose buttonPicker.setOnClickListener { pickImage { uri -> viewModel.savePicker(uri) // Do your own thing here instead } } - // Taking a photo using Ktx's ActivityResultContracts.TakePicture without the need of a FileProvider + // Taking a photo using ActivityResultContracts.TakePicture without the need of a FileProvider buttonCamera.setOnClickListener { takePicture { uri -> viewModel.saveCamera(uri) // Do your own thing here instead @@ -43,7 +43,7 @@ class DemoActivity : AppCompatActivity(R.layout.activity_demo) { private fun setupDemo() { // Using a crude implementation of ViewModel just to retain state on configuration change // The resulting uri from the pickers gets saved in the vm and observed below - // On change the uri get loaded in the target image using Glide + // On change the uri gets loaded in the target image using Glide viewModel.pickerResult.observe(this, Observer { uri -> imagePicker.load(uri) imagePicker.alpha = 1f diff --git a/build.gradle b/build.gradle index 10a7392..edad332 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,6 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { ext.kotlin_version = '1.3.72' + ext.fragment_ktx_version = '1.3.0-alpha07' repositories { google() jcenter() @@ -10,8 +9,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files } } diff --git a/plukke/build.gradle b/plukke/build.gradle index 03c0888..7f3a4ba 100644 --- a/plukke/build.gradle +++ b/plukke/build.gradle @@ -25,5 +25,5 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.fragment:fragment-ktx:1.3.0-alpha07" + implementation "androidx.fragment:fragment-ktx:$fragment_ktx_version" } \ No newline at end of file diff --git a/plukke/src/main/java/eu/acolombo/plukke/ExternalContent.kt b/plukke/src/main/java/eu/acolombo/plukke/ExternalContent.kt deleted file mode 100644 index 1319bd1..0000000 --- a/plukke/src/main/java/eu/acolombo/plukke/ExternalContent.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.acolombo.plukke - -import android.content.ContentResolver -import android.content.ContentValues -import android.provider.MediaStore -import androidx.activity.ComponentActivity -import androidx.fragment.app.Fragment -import java.io.Closeable - -class ExternalContent(private val resolver: ContentResolver) : Closeable { - - constructor(activity: ComponentActivity) : this (activity.contentResolver) - - constructor(fragment: Fragment) : this (fragment.requireActivity().contentResolver) - - var uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ContentValues()) - - override fun close() { - uri?.let { resolver.delete(it, null, null) } - } - - fun clear() : Nothing? = close().let { return null } - -} \ No newline at end of file diff --git a/plukke/src/main/java/eu/acolombo/plukke/ImagePicker.kt b/plukke/src/main/java/eu/acolombo/plukke/ImagePicker.kt deleted file mode 100644 index 6ada18c..0000000 --- a/plukke/src/main/java/eu/acolombo/plukke/ImagePicker.kt +++ /dev/null @@ -1,66 +0,0 @@ -package eu.acolombo.plukke - -import android.Manifest.permission.WRITE_EXTERNAL_STORAGE -import android.app.Activity.RESULT_OK -import android.content.pm.PackageManager.PERMISSION_GRANTED -import android.net.Uri -import androidx.activity.ComponentActivity -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.result.contract.ActivityResultContracts.RequestPermission -import androidx.core.content.ContextCompat.checkSelfPermission -import androidx.fragment.app.Fragment -import eu.acolombo.plukke.ActivityResultContracts.Choose -import eu.acolombo.plukke.ActivityResultContracts.PickImage -import eu.acolombo.plukke.ActivityResultContracts.TakePhoto - -fun ComponentActivity.pickImage(onResult: (Uri) -> Unit) = doWithPermission { - val exc = ExternalContent(contentResolver) - - val photo = exc.uri?.let { TakePhoto(it) } - val pick = PickImage() - - registerForActivityResult(Choose()) choose@{ result -> - if (result.resultCode == RESULT_OK) onResult( - photo?.output - ?: exc.clear() - ?: result?.data?.data - ?: return@choose - ) else exc.close() - }.launch(listOfNotNull(photo, pick)) -} - -fun ComponentActivity.takePicture(onResult: (Uri) -> Unit) = doWithPermission { - val exc = ExternalContent(contentResolver) - - exc.uri?.let { uri -> - registerForActivityResult(ActivityResultContracts.TakePicture()) { ok -> - if (ok) onResult(uri) else exc.close() - }.launch(uri) - } -} - -fun Fragment.pickImage(onPick: (Uri) -> Unit) = - activity?.pickImage(onPick) - -fun Fragment.takePicture(onTake: (Uri) -> Unit) = - activity?.takePicture(onTake) - -private fun ComponentActivity.doWithPermission( - permission: String = WRITE_EXTERNAL_STORAGE, - action: () -> Unit -) { - // Some devices don't need WRITE_EXTERNAL_STORAGE permission to be granted, so we'll just try to do what we want - // If we can't (catch) we do it the proper way, requesting permission if needed - // The try & catch is there to improve first time experience on some devices, after the first time the try will succeed anyways - try { - action() - } catch (e: SecurityException) { - if (checkSelfPermission(this, permission) != PERMISSION_GRANTED) { - registerForActivityResult(RequestPermission()) { - if (it) action() - }.launch(permission) - } else { - action() - } - } -} \ No newline at end of file diff --git a/plukke/src/main/java/eu/acolombo/plukke/Plukke.kt b/plukke/src/main/java/eu/acolombo/plukke/Plukke.kt new file mode 100644 index 0000000..eec4866 --- /dev/null +++ b/plukke/src/main/java/eu/acolombo/plukke/Plukke.kt @@ -0,0 +1,85 @@ +package eu.acolombo.plukke + +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.app.Activity +import android.content.ContentResolver +import android.content.ContentValues +import android.net.Uri +import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI +import androidx.activity.ComponentActivity +import androidx.activity.result.contract.ActivityResultContracts.TakePicture +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import eu.acolombo.plukke.PlukkeActivityResultContracts.Choose +import java.io.Closeable + +class Plukke( + private val componentActivity: ComponentActivity, + clearOnDestroy: Boolean = false +) : LifecycleObserver, Closeable { + + private class ExternalContent(private val resolver: ContentResolver) : Closeable { + + var uri = resolver.insert(EXTERNAL_CONTENT_URI, ContentValues()) + + override fun close() { + uri?.let { resolver.delete(it, null, null) } + } + + fun clear(): Nothing? = close().let { return null } + + } + + private val exc = ExternalContent(componentActivity.contentResolver) + + val uri = exc.uri + + fun pickImage(onResult: (Uri) -> Unit) = componentActivity.doWithPermission { + val photo = uri?.let { PlukkeActivityResultContracts.TakePhoto(it) } + val pick = PlukkeActivityResultContracts.PickImage() + + componentActivity.registerForActivityResult(Choose()) { result -> + if (result.resultCode == Activity.RESULT_OK) onResult( + photo?.uri + ?: exc.clear() + ?: result?.data?.data + ?: return@registerForActivityResult + ) else exc.close() + }.launch(listOfNotNull(photo, pick)) + } + + fun takePicture(onResult: (Uri) -> Unit) = componentActivity.doWithPermission { + uri?.let { uri -> + registerForActivityResult(TakePicture()) { ok -> + if (ok) onResult(uri) else exc.close() + }.launch(uri) + } + } + + var autoClear: Boolean = false + set(value) { + field = value + componentActivity.lifecycle.removeObserver(this) + if (value) componentActivity.lifecycle.addObserver(this) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + override fun close() = exc.close() + + init { + autoClear = clearOnDestroy + } + + private fun ComponentActivity.doWithPermission(action: ComponentActivity.() -> Unit) { + // Some devices don't need WRITE_EXTERNAL_STORAGE permission to be granted, so we'll just try to do what we want + // If we can't (catch) we do it the proper way, requesting permission if needed + // The try & catch is here to improve first time experience on some devices + try { + action() + } catch (e: SecurityException) { + doWithPermission(WRITE_EXTERNAL_STORAGE, action) + } + } + +} \ No newline at end of file diff --git a/plukke/src/main/java/eu/acolombo/plukke/ActivityResultContracts.kt b/plukke/src/main/java/eu/acolombo/plukke/PlukkeActivityResultContracts.kt similarity index 81% rename from plukke/src/main/java/eu/acolombo/plukke/ActivityResultContracts.kt rename to plukke/src/main/java/eu/acolombo/plukke/PlukkeActivityResultContracts.kt index c0b8480..abc68b2 100644 --- a/plukke/src/main/java/eu/acolombo/plukke/ActivityResultContracts.kt +++ b/plukke/src/main/java/eu/acolombo/plukke/PlukkeActivityResultContracts.kt @@ -9,7 +9,7 @@ import android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContract -object ActivityResultContracts { +object PlukkeActivityResultContracts { class Choose : ActivityResultContract>, ActivityResult>() { @@ -28,18 +28,16 @@ object ActivityResultContracts { } - // Same as ActivityResultContracts.TakePicture but input and output are fields - class TakePhoto(private val input: Uri) : ActivityResultContract() { - - var output: Uri? = null + // Same as ActivityResultContracts.TakePicture but the input uri is passed to the constructor + class TakePhoto(val uri: Uri) : ActivityResultContract() { override fun createIntent( context: Context, input: Unit? - ) = Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, this.input) + ) = Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, uri) override fun parseResult(resultCode: Int, intent: Intent?): Uri? = - input.takeIf { resultCode == RESULT_OK }.also { output = it } + uri.takeIf { resultCode == RESULT_OK } } @@ -50,7 +48,8 @@ object ActivityResultContracts { input: Unit? ) = Intent(Intent.ACTION_PICK, EXTERNAL_CONTENT_URI) - override fun parseResult(resultCode: Int, intent: Intent?) = intent?.data + override fun parseResult(resultCode: Int, intent: Intent?) : Uri? = + intent?.data } diff --git a/plukke/src/main/java/eu/acolombo/plukke/PlukkeExt.kt b/plukke/src/main/java/eu/acolombo/plukke/PlukkeExt.kt new file mode 100644 index 0000000..8b58f6c --- /dev/null +++ b/plukke/src/main/java/eu/acolombo/plukke/PlukkeExt.kt @@ -0,0 +1,32 @@ +package eu.acolombo.plukke + +import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission +import androidx.core.content.ContextCompat.checkSelfPermission +import androidx.fragment.app.Fragment + +fun ComponentActivity.pickImage(onPick: (Uri) -> Unit) = Plukke(this).also { it.pickImage(onPick) } + +fun ComponentActivity.takePicture(onPick: (Uri) -> Unit) = Plukke(this).also { it.takePicture(onPick) } + +fun Fragment.pickImage(onPick: (Uri) -> Unit) = activity?.pickImage(onPick) + +fun Fragment.takePicture(onTake: (Uri) -> Unit) = activity?.takePicture(onTake) + +fun ComponentActivity.doWithPermission( + permission: String, + action: ComponentActivity.() -> Unit +) = if (checkSelfPermission(this, permission) != PERMISSION_GRANTED) { + registerForActivityResult(RequestPermission()) { + if (it) action() + }.launch(permission) +} else { + action() +} + +fun Fragment.doWithPermission( + permission: String, + action: ComponentActivity.() -> Unit +) = activity?.doWithPermission(permission, action) \ No newline at end of file