Skip to content
Original file line number Diff line number Diff line change
@@ -1,78 +1,89 @@
package com.akshayashokcode.imagepicker.builder

import android.content.Context
import android.net.Uri
import androidx.activity.result.ActivityResultCaller
import com.akshayashokcode.imagepicker.coordinator.ImagePickerCoordinator
import com.akshayashokcode.imagepicker.model.ImagePickerException
import com.akshayashokcode.imagepicker.model.ImagePickerResult
import com.akshayashokcode.imagepicker.model.MediaSource

class ImagePickerBuilder(
/**
* Fluent builder used to configure and launch the MediaKit image picker flow.
*
* Consumers should create instances through [with] to keep the public API
* consistent and future-proof.
*/
class ImagePickerBuilder private constructor(
private val context: Context,
private val caller: ActivityResultCaller
) {
private var source: MediaSource = MediaSource.Gallery
private var crop: Boolean = false // Reserved for future integration

// Reserved for future cropper module integration.
private var crop: Boolean = false

private var onResult: ((ImagePickerResult) -> Unit)? = null
private var onError: ((ImagePickerException) -> Unit)? = null

companion object {
/**
* Entry point to start building an image picker flow.
*
* @param context Application or activity context
* @param caller ActivityResultCaller (Activity or Fragment)
* @param context Application or activity context.
* @param caller ActivityResultCaller (Activity or Fragment).
*/
fun with(context: Context, caller: ActivityResultCaller): ImagePickerBuilder {
fun with(
context: Context,
caller: ActivityResultCaller
): ImagePickerBuilder {
return ImagePickerBuilder(context, caller)
}
}

/**
* Set the image source (GALLERY, CAMERA, or BOTH).
* Configure the media source.
*/
fun source(source: MediaSource): ImagePickerBuilder = apply {
this.source = source
}

/**
* Enable cropping after selection (optional feature, not yet active).
* Enables optional cropping support.
*
* Cropper integration is planned for a future MediaKit release.
*/
fun crop(enable: Boolean): ImagePickerBuilder = apply {
this.crop = enable
}

/**
* Callback to receive the picker result.
* Callback invoked with picker results.
*/
fun onResult(callback: (ImagePickerResult) -> Unit): ImagePickerBuilder = apply {
this.onResult = callback
}

/**
* Optional error callback to receive specific error cases.
* Optional callback invoked for picker failures.
*/
fun onError(callback: (ImagePickerException) -> Unit): ImagePickerBuilder = apply {
this.onError = callback
}

/**
* Launch the image picker with the current configuration.
* Launches the picker flow with the current configuration.
*/
fun launch() {
requireNotNull(onResult) {
"You must provide a result callback using onResult()"
}

val coordinator = ImagePickerCoordinator(
ImagePickerCoordinator(
context = context,
caller = caller,
source = source,
onResult = onResult!!,
onError = onError
)

coordinator.launch()
).launch()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,48 @@ import com.akshayashokcode.imagepicker.model.MediaSource
import com.akshayashokcode.imagepicker.picker.CameraImagePicker
import com.akshayashokcode.imagepicker.picker.GalleryImagePicker

/**
* Coordinates the image picker flow and delegates execution to the appropriate
* picker implementation.
*/
internal class ImagePickerCoordinator(
private val context: Context,
private val caller: ActivityResultCaller,
private val source: MediaSource,
private val onResult: (ImagePickerResult) -> Unit,
private val onError: ((ImagePickerException) -> Unit)? = null
) {
private val galleryPicker = GalleryImagePicker(context, caller, onResult, onError)
private val cameraPicker = CameraImagePicker(context, caller, onResult, onError)

private val galleryPicker by lazy {
GalleryImagePicker(
context = context,
caller = caller,
callback = onResult,
onError = onError
)
}

private val cameraPicker by lazy {
CameraImagePicker(
context = context,
caller = caller,
callback = onResult,
onError = onError
)
}

/**
* Starts the configured picker flow.
*/
fun launch() {
when (source) {
is MediaSource.Gallery -> galleryPicker.launch()
is MediaSource.Camera -> cameraPicker.launch()
is MediaSource.Both -> {
// 🚧 Future enhancement: show source selection UI
// Future enhancement:
// Provide a source chooser bottom sheet/dialog.
galleryPicker.launch()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import android.content.Context
import androidx.activity.result.ActivityResultCaller
import com.akshayashokcode.imagepicker.builder.ImagePickerBuilder

/**
* Public entry point for launching MediaKit image picker flows.
*/
object ImagePicker {
fun with(context: Context, caller: ActivityResultCaller): ImagePickerBuilder {
return ImagePickerBuilder(context, caller)

fun with(
context: Context,
caller: ActivityResultCaller
): ImagePickerBuilder {
return ImagePickerBuilder.with(context, caller)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,76 @@
package com.akshayashokcode.imagepicker.model

/**
* Represents various error states that can occur during image picking.
* Represents strongly-typed error states that can occur during the MediaKit
* image picker flow.
*/
sealed class ImagePickerException(message: String) : Exception(message) {

/** Required permission was denied by the user */
object PermissionDenied : ImagePickerException("Required permission was denied.")
/**
* Required runtime permission was denied.
*/
data object PermissionDenied : ImagePickerException(
"Required permission was denied."
)

/** No app available to handle the requested action (e.g., no gallery/camera app) */
object AppNotFound : ImagePickerException("Required app (camera or gallery) is not available.")
/**
* No compatible application is available to handle the requested action.
*/
data object AppNotFound : ImagePickerException(
"Required app (camera or gallery) is not available."
)

/** Could not create temporary file for captured image */
object FileCreationFailed : ImagePickerException("Could not create temporary file.")
/**
* Temporary file creation failed before launching camera capture.
*/
data object FileCreationFailed : ImagePickerException(
"Could not create temporary file."
)

/** The image URI returned was null or invalid */
object InvalidUri : ImagePickerException("Invalid or null URI received.")
/**
* The returned image Uri was invalid or null.
*/
data object InvalidUri : ImagePickerException(
"Invalid or null URI received."
)

/** Failed to decode or rotate the image properly */
object RotationFailed : ImagePickerException("Failed to decode or rotate the image.")
/**
* Bitmap decoding or orientation correction failed.
*/
data object RotationFailed : ImagePickerException(
"Failed to decode or rotate the image."
)

/** Error deleting temporary file after cancel or failure */
object FileDeletionFailed : ImagePickerException("Failed to delete temporary image file.")
/**
* Cleanup of temporary files failed.
*/
data object FileDeletionFailed : ImagePickerException(
"Failed to delete temporary image file."
)

/** Raised when decoding the image or applying orientation corrections fails. */
object DecodingFailed : ImagePickerException("Failed to decode or rotate image.")
/**
* Image decoding pipeline failed.
*/
data object DecodingFailed : ImagePickerException(
"Failed to decode or rotate image."
)

object IntentFailed : ImagePickerException("Failed to launch intent for image capture or selection.")
/**
* Intent launch failed unexpectedly.
*/
data object IntentFailed : ImagePickerException(
"Failed to launch intent for image capture or selection."
)


/** Generic catch-all error */
class Unknown(message: String, cause: Throwable? = null) : ImagePickerException(message) {
/**
* Generic fallback error.
*/
class Unknown(
message: String,
cause: Throwable? = null
) : ImagePickerException(message) {
init {
cause?.let { initCause(it) }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,35 @@ package com.akshayashokcode.imagepicker.model
import android.graphics.Bitmap
import android.net.Uri

/**
* Represents the final state returned from the MediaKit image picker flow.
*/
sealed class ImagePickerResult {

/**
* Successful image selection with resulting Uri.
*/
data class Success(val uri: Uri) : ImagePickerResult()
data class SuccessWithBitmap(val uri: Uri, val bitmap: Bitmap) : ImagePickerResult()

/**
* Successful image selection with decoded bitmap.
*
* Intended for advanced consumers that require immediate bitmap access.
*/
data class SuccessWithBitmap(
val uri: Uri,
val bitmap: Bitmap
) : ImagePickerResult()

/**
* User cancelled the picker flow.
*/
data object Cancelled : ImagePickerResult()

/**
* Generic picker error.
*
* This will later migrate to a sealed error model for stronger typing.
*/
data class Error(val message: String) : ImagePickerResult()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@ import com.akshayashokcode.imagepicker.launcher.CameraImageLauncher
import com.akshayashokcode.imagepicker.model.ImagePickerException
import com.akshayashokcode.imagepicker.model.ImagePickerResult

/**
* Camera-backed picker implementation.
*/
internal class CameraImagePicker(
private val context: Context,
private val caller: ActivityResultCaller,
private val callback: (ImagePickerResult) -> Unit,
private val onError: ((ImagePickerException) -> Unit)? = null
) {
private val launcher = CameraImageLauncher(context, caller, callback, onError)

private val launcher by lazy {
CameraImageLauncher(
context = context,
caller = caller,
callback = callback,
onError = onError
)
}

fun launch() {
launcher.launch()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@ import com.akshayashokcode.imagepicker.launcher.GalleryImageLauncher
import com.akshayashokcode.imagepicker.model.ImagePickerException
import com.akshayashokcode.imagepicker.model.ImagePickerResult

/**
* Gallery-backed picker implementation.
*/
internal class GalleryImagePicker(
private val context: Context,
private val caller: ActivityResultCaller,
private val callback: (ImagePickerResult) -> Unit,
private val onError: ((ImagePickerException) -> Unit)? = null
) {
private val launcher = GalleryImageLauncher(context, caller, callback, onError)

private val launcher by lazy {
GalleryImageLauncher(
context = context,
caller = caller,
callback = callback,
onError = onError
)
}

fun launch() {
launcher.launch()
}
}
}
5 changes: 3 additions & 2 deletions sample-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(project(":ImageCropper"))

implementation(project(":imagecropper"))
implementation(project(":imagepicker"))
}
}
Loading
Loading