Skip to content

minsuk-jang/ImagePicker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ImagePicker

API License: MIT

A fully customizable, DSL-based image picker for Jetpack Compose

ImagePicker is a Jetpack Compose library for displaying and selecting media from the device gallery.
ImagePicker uses a declarative DSL structure to define screens within a navigation graph, similar to NavHost and composable in Jetpack Navigation

Features

  • 🧩 DSL-based Navigation Graph: Declare screens inside ImagePickerNavHost like NavHost
  • πŸ“¦ Fully customizable UI for album selector, preview bar, image cells and preview screen
  • πŸ–οΈ Multi-selection with drag gesture support
  • πŸ”’ Visual selection order (e.g., 1st, 2nd...)
  • πŸ“· Camera integration with optional auto-select after capture
  • πŸ–ΌοΈ Full Preview screen for selected images
  • πŸ”„ Pagination for smooth loading of large image sets (via Paging 3)
  • πŸ—‚οΈ Album-based grouping with dynamic filtering

Installation

Step 1. Add it in your root build.gradle at the end of repositories:

dependencyResolutionManagement {
  ...
  repositories {
    maven { url 'https://jitpack.io' }
  }
}

Step 2. Add the dependency

dependencies {
    implementation 'com.github.minsuk-jang:ImagePicker:1.0.15'
}

Permissions

Make sure to include the following in your AndroidManifest.xml

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA" />

πŸš€ Quick Start

Declare your image picker UI using ImagePickerNavHost, just like NavHost in Jetpack Navigation.
Each slot (albumTopBar, previewTopBar, cellContent) and PreviewScreen gives access to its own custom scope to help you build highly flexible UIs.

ImagePickerNavHost(state = state) {
    ImagePickerScreen(
        albumTopBar = { ... },
        previewTopBar = { ... },
        cellContent = { ... }
    )

    PreviewScreen {
        // Full-screen preview UI
    }
}

πŸ“· Example Output:

Slot APIs, Scopes

Each slot in ImagePickerScreen or PreviewScreen is powered by a custom scope. These scopes provide the necessary state and event handlers you need to build fully customized UIs. Below is a breakdown of each slot, its associated scope, and what you can do inside it.

πŸŽ›οΈ albumTopBar β†’ ImagePickerAlbumScope

Use this slot to show album-related UI such as a dropdown or album selector. The ImagePickerAlbumScope gives you access to:

Property / Function Description
albums: List<Album> List of all albums on the device
selectedAlbum: Album? Currently selected album
onClick(album: Album) Select the given album

Example usage:

albumTopBar = {
    var expanded by remember { mutableStateOf(false) }
  
    Box {
        Text(
            text = selectedAlbum?.name ?: "All Albums",
            modifier = Modifier.clickable { expanded = true }
        )
  
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false }
        ) {
            albums.forEach { album ->
                DropdownMenuItem(
                    text = { Text("${album.name} (${album.count})") },
                    onClick = {
                        expanded = false
                        onClick(album) // Select the album
                    }
                )
            }
        }
  }
}

πŸŽ›οΈ previewTopBar β†’ ImagePickerPreviewScope

This slot allows you to preview currently selected media contents in a custom layout. The ImagePickerPreviewScope gives you access to:

Property / Function Description
selectedMediaContents: List<MediaContent> List of selected media content
onDeselect(mediaContent: MediaContent) Deselect the given media content

Example usage:

previewTopBar = {
    Row {
        selectedMediaContents.forEach { media ->
            AsyncImage(
                model = media.uri,
                contentDescription = null,
                modifier = Modifier.clickable {
                    onDeselect(media) // Deselect
                }
            )
        }
    }
}

πŸŽ›οΈ cellContent β†’ ImagePickerCellScope

This slot renders each image cell in the grid. Only the MediaContent is provided - the rest is up to you.
Use this slot to:

  • Display thumbnails
  • Indicate selected state (e.g., with a badge or overlay)
  • Navigate to the preview screen

The ImagePickerCellScope gives you access to:

Property / Function Description
mediaContent: MediaContent The media content represented by this cell
onNavigateToPreviewScreen(mediaContent: MediaContent) Triggers navigation to the Preview Screen

Example usage:

cellContent = {
    Box(modifier = Modifier.clickable {
        onNavigateToPreviewScreen(mediaContent)
    }) {
        AsyncImage(model = mediaContent.uri, contentDescription = null)
        if (mediaContent.selected) {
            Text(
                text = "#${mediaContent.selectedOrder}",
                modifier = Modifier.align(Alignment.TopEnd)
            )
        }
    }
}

πŸ’‘ You can fully control the UI β€” whether it's adding badges, applying blur, or animating selection β€” by customizing this slot.

πŸŽ›οΈ PreviewScreen β†’ PreviewScreenScope

This slot allows you to define the full-screen preview UI for selected media content. The PreviewScreenScope provides:

Property / Function Description
mediaContent: MediaContent The currently visible media content
onBack() Navigate back to the picker screen
onToggleSelection(mediaContent: MediaContent) Selects or deselects the given media content

⚠️ Important
PreviewScreen must be explicitly declared inside ImagePickerNavHost.
If omitted, calling onNavigateToPreviewScreen() from a cell will cause a runtime crash.

πŸ“¦ ImagePickerNavHostState

The ImagePickerNavHostState stores shared selection state and picker configuration across ImagePickerScreen and PreviewScreen.

Parameter Description
max Maximum number of media contents that can be selected
autoSelectAfterCapture Whether to auto-select the image after camera shot

🧷 Properties

Property Type Description
selectedMediaContents List<MediaContent> List of currently selected media contents

You can read this state anywhere in your app to reflect selection results, UI updates, or submission logic:

val selected = state.selectedMediaContents

🧠 Concept

ImagePicker is designed around three key principles:

1. βœ… Declarative Navigation DSL

Screens are declared inside ImagePickerNavHost { ... } just like NavHost and composable in Jetpack Navigation.

ImagePickerNavHost(state) {
    ImagePickerScreen(...)
    PreviewScreen { ... }
}

2. 🧩 Scoped Slot APIs

Each UI slot receives a dedicated Scope that provides data, actions for full composability:

Slot Scope Interface Description
albumTopBar ImagePickerAlbumScope Provides album list & selection
previewTopBar ImagePickerPreviewTopBarScope Shows selected media, toggles select
cellContent ImagePickerCellScope Controls image cell UI & navigate to Preview Screen
PreviewScreen PreviewScreenScope Full-screen preview screen actions

3. πŸ“¦ Shared Picker State

ImagePickerNavHostState gives access to current selections and configuration.

val selectedItems = state.selectedMediaContents
val maxSelectable = state.max

πŸ’‘ Why this matters

Most pickers are monolithic and hard to extend. ImagePicker provides navigation-level flexibility, enabling clean separation between:

  • Image list & preview
  • UI customization & logic
  • App-level state & picker state This structure makes it easy to build an image picker that feels native to your app, not boxed-in.

About

πŸ–ΌοΈ It is an Image Picker library created in the Compose language.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages