{"id":49154050,"url":"https://github.com/minsuk-jang/imagepicker","last_synced_at":"2026-04-22T08:00:51.152Z","repository":{"id":224018398,"uuid":"762162064","full_name":"minsuk-jang/ImagePicker","owner":"minsuk-jang","description":"🖼️  It is an Image Picker library created in the Compose language. ","archived":false,"fork":false,"pushed_at":"2025-07-10T11:00:03.000Z","size":832,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-10T17:24:57.004Z","etag":null,"topics":["android","android-library","android-ui","coil","compose","imagepicker","kotlin"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/minsuk-jang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-23T07:56:13.000Z","updated_at":"2025-07-10T12:32:36.000Z","dependencies_parsed_at":"2024-03-30T12:21:54.644Z","dependency_job_id":"bac5ab17-f432-42b7-8dc6-df491c018bf9","html_url":"https://github.com/minsuk-jang/ImagePicker","commit_stats":null,"previous_names":["minsuk-jang/galleryselector","minsuk-jang/imagepicker"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/minsuk-jang/ImagePicker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minsuk-jang%2FImagePicker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minsuk-jang%2FImagePicker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minsuk-jang%2FImagePicker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minsuk-jang%2FImagePicker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/minsuk-jang","download_url":"https://codeload.github.com/minsuk-jang/ImagePicker/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minsuk-jang%2FImagePicker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32126709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T07:37:52.372Z","status":"ssl_error","status_checked_at":"2026-04-22T07:37:51.635Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-library","android-ui","coil","compose","imagepicker","kotlin"],"created_at":"2026-04-22T08:00:50.274Z","updated_at":"2026-04-22T08:00:51.141Z","avatar_url":"https://github.com/minsuk-jang.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align = \"center\"\u003eImagePicker\u003c/h1\u003e\n\n\u003cp align = \"center\"\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/3dc78705-e90d-42e4-859c-79e9b28ff8b9\" width=\"200\"/\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/2d6daad9-a499-443a-b7c7-282ad2c69177\" width=\"200\"/\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/9531c4a4-9603-47b7-a716-d74aecf75c8f\" width=\"200\"/\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/64724a9e-669a-4e8a-9bc7-773c440bc755\" width=\"200\"/\u003e\n\u003c/p\u003e\n\n\u003cdiv align = \"center\"\u003e\n\n[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)\n[![](https://jitpack.io/v/minsuk-jang/ImagePicker.svg)](https://jitpack.io/#minsuk-jang/ImagePicker)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n### A fully customizable, DSL-based image picker for Jetpack Compose\n\nImagePicker is a Jetpack Compose library for displaying and selecting media from the device gallery.  \nIt uses a declarative DSL structure to define screens within a navigation graph, similar to `NavHost` in Jetpack Navigation.\n\n\u003c/div\u003e\n\n---\n\n## Features\n\n- **DSL-based Navigation Graph** — Declare screens inside `ImagePickerNavHost` like `NavHost`\n- **Fully customizable UI** — Control album selector, preview bar, image cells, and preview screen independently\n- **Multi-selection with drag gesture** — Long-press and drag to batch-select images\n- **Visual selection order** — Display selection index (1st, 2nd, ...) on each cell\n- **Full preview screen** — Swipeable full-screen preview for selected images\n- **Pagination** — Smooth loading of large galleries via Paging 3\n- **Album filtering** — Dynamic album-based grouping and switching\n\n---\n\n## Installation\n\n**Step 1.** Add JitPack to your root `settings.gradle`:\n```gradle\ndependencyResolutionManagement {\n    repositories {\n        maven { url 'https://jitpack.io' }\n    }\n}\n```\n\n**Step 2.** Add the dependency:\n```gradle\ndependencies {\n    implementation 'com.github.minsuk-jang:ImagePicker:1.0.16'\n}\n```\n\n---\n\n## Permissions\n\nAdd the appropriate permissions to your `AndroidManifest.xml` based on the target API level:\n\n```xml\n\u003c!-- API 32 and below --\u003e\n\u003cuses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" /\u003e\n\n\u003c!-- API 33 and above --\u003e\n\u003cuses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" /\u003e\n```\n\n\u003e You should request these permissions at runtime before launching `ImagePickerNavHost`.\n\n---\n\n## Concept\n\nImagePicker is built around three principles:\n\n### 1. Declarative Navigation DSL\nScreens are declared inside `ImagePickerNavHost { }`, just like Jetpack Navigation's `NavHost`:\n\n```kotlin\nImagePickerNavHost(state = state) {\n    ImagePickerScreen(...)\n    PreviewScreen { ... }\n}\n```\n\n### 2. Scoped Slot APIs\nEach UI slot receives a dedicated scope that exposes only the data and actions relevant to that screen:\n\n| Slot            | Scope                       | Responsibility                              |\n|-----------------|-----------------------------|---------------------------------------------|\n| `albumTopBar`   | `ImagePickerAlbumScope`     | Album list and selection                    |\n| `previewTopBar` | `ImagePickerPreviewScope`   | Selected media preview and deselection      |\n| `cellContent`   | `ImagePickerCellScope`      | Image cell UI and navigation to preview     |\n| `PreviewScreen` | `PreviewScreenScope`        | Full-screen preview actions                 |\n\n### 3. Shared Picker State\n`ImagePickerNavHostState` bridges the picker and your app, exposing the final selection result:\n\n```kotlin\nval state = rememberImagePickerNavHostState(max = 10)\n\n// Read selected results anywhere in your composable\nval selected = state.selectedMediaContents\n```\n\n---\n\n## Quick Start\n\nA complete example from setup to reading results:\n\n```kotlin\n// 1. Create state\nval state = rememberImagePickerNavHostState(max = 10)\n\n// 2. Declare the picker\nImagePickerNavHost(state = state) {\n    ImagePickerScreen(\n        albumTopBar = {\n            // Show album selector using 'albums', 'selectedAlbum', 'onClick'\n        },\n        previewTopBar = {\n            // Show selected thumbnails using 'selectedMediaContents', 'onDeselect'\n        },\n        cellContent = {\n            // Render each cell using 'mediaContent', 'onNavigateToPreviewScreen'\n        }\n    )\n\n    PreviewScreen {\n        // Full-screen preview using 'mediaContent', 'onBack', 'onToggleSelection'\n    }\n}\n\n// 3. Read selected results\nval selected = state.selectedMediaContents\n```\n\n\u003cp\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/2d6daad9-a499-443a-b7c7-282ad2c69177\" width=\"250\"/\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/dcf74aef-64ac-4552-a005-5f63721c65e7\" width=\"250\"/\u003e\n\u003cimg src = \"https://github.com/user-attachments/assets/34a1a634-32b8-42e1-b519-134118118f6f\" width=\"250\"/\u003e\n\u003c/p\u003e\n\n---\n\n## Slot APIs\n\n### `albumTopBar` — `ImagePickerAlbumScope`\n\nRenders the album selector UI. The scope provides:\n\n| Property / Function       | Description                      |\n|---------------------------|----------------------------------|\n| `albums: List\u003cAlbum\u003e`     | All albums available on device   |\n| `selectedAlbum: Album?`   | Currently selected album         |\n| `onClick(album: Album)`   | Switch to the given album        |\n\n```kotlin\nalbumTopBar = {\n    var expanded by remember { mutableStateOf(false) }\n\n    Box {\n        Text(\n            text = selectedAlbum?.name ?: \"All\",\n            modifier = Modifier.clickable { expanded = true }\n        )\n\n        DropdownMenu(\n            expanded = expanded,\n            onDismissRequest = { expanded = false }\n        ) {\n            albums.forEach { album -\u003e\n                DropdownMenuItem(\n                    text = { Text(\"${album.name} (${album.count})\") },\n                    onClick = {\n                        expanded = false\n                        onClick(album)\n                    }\n                )\n            }\n        }\n    }\n}\n```\n\n---\n\n### `previewTopBar` — `ImagePickerPreviewScope`\n\nRenders selected media in a preview bar. The scope provides:\n\n| Property / Function                          | Description                      |\n|----------------------------------------------|----------------------------------|\n| `selectedMediaContents: List\u003cMediaContent\u003e`  | Currently selected media         |\n| `onDeselect(mediaContent: MediaContent)`     | Deselect the given item          |\n\n```kotlin\npreviewTopBar = {\n    Row {\n        selectedMediaContents.forEach { media -\u003e\n            AsyncImage(\n                model = media.uri,\n                contentDescription = null,\n                modifier = Modifier.clickable { onDeselect(media) }\n            )\n        }\n    }\n}\n```\n\n---\n\n### `cellContent` — `ImagePickerCellScope`\n\nRenders each image cell in the grid. The scope provides:\n\n| Property / Function                                        | Description                                    |\n|------------------------------------------------------------|------------------------------------------------|\n| `mediaContent: MediaContent`                               | The media item for this cell                   |\n| `onNavigateToPreviewScreen(mediaContent: MediaContent)`    | Navigate to the full preview screen            |\n\n```kotlin\ncellContent = {\n    Box(modifier = Modifier.clickable {\n        onNavigateToPreviewScreen(mediaContent)\n    }) {\n        AsyncImage(model = mediaContent.uri, contentDescription = null)\n\n        if (mediaContent.selected) {\n            Text(\n                text = \"${mediaContent.selectedOrder + 1}\",\n                modifier = Modifier.align(Alignment.TopEnd)\n            )\n        }\n    }\n}\n```\n\n---\n\n### `PreviewScreen` — `PreviewScreenScope`\n\nRenders the full-screen swipeable preview. The scope provides:\n\n| Property / Function                              | Description                                  |\n|--------------------------------------------------|----------------------------------------------|\n| `mediaContent: MediaContent`                     | The currently visible media item             |\n| `onBack()`                                       | Navigate back to the picker screen           |\n| `onToggleSelection(mediaContent: MediaContent)`  | Select or deselect the current item          |\n\n\u003e **Note:** `PreviewScreen` must be explicitly declared inside `ImagePickerNavHost`.  \n\u003e Omitting it while calling `onNavigateToPreviewScreen()` from a cell will cause a runtime crash.\n\n---\n\n## ImagePickerNavHostState\n\n`ImagePickerNavHostState` holds picker configuration and exposes the selection result to your app.\n\n### Parameters\n\n| Parameter | Description                                           |\n|-----------|-------------------------------------------------------|\n| `max`     | Maximum number of media items that can be selected    |\n\n### Properties\n\n| Property                 | Type                  | Description                                |\n|--------------------------|-----------------------|--------------------------------------------|\n| `selectedMediaContents`  | `List\u003cMediaContent\u003e`  | Currently selected media items             |\n\n```kotlin\nval state = rememberImagePickerNavHostState(max = 10)\n\n// Pass state to the picker\nImagePickerNavHost(state = state) { ... }\n\n// Read results\nval selected = state.selectedMediaContents\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminsuk-jang%2Fimagepicker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fminsuk-jang%2Fimagepicker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminsuk-jang%2Fimagepicker/lists"}