├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── layout
│ │ │ │ ├── list_item_selected_media.xml
│ │ │ │ └── activity_main.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── geeksmediapickerdemo
│ │ │ ├── SelectedMediaAdapter.kt
│ │ │ └── MainActivity.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── geeksmediapickerdemo
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── geeksmediapickerdemo
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .name
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── gradle.xml
├── misc.xml
└── jarRepositories.xml
├── GeeksArts
├── empty.md
├── art1.jpg
├── art2.jpg
├── art3.jpg
├── art4.jpg
├── art5.jpg
├── art6.jpg
└── art7.jpg
├── geeksmediapicker
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── font
│ │ │ │ ├── poppins_bold.otf
│ │ │ │ ├── poppins_light.otf
│ │ │ │ ├── poppins_medium.otf
│ │ │ │ ├── poppins_regular.otf
│ │ │ │ ├── poppins_bold_italic.otf
│ │ │ │ ├── poppins_light_italic.otf
│ │ │ │ └── poppins_medium_italic.otf
│ │ │ ├── drawable
│ │ │ │ ├── check_box.png
│ │ │ │ ├── check_box_tick.png
│ │ │ │ ├── shadow_top_bottom.png
│ │ │ │ ├── ic_arrow_back.xml
│ │ │ │ └── ic_video_white.xml
│ │ │ ├── xml
│ │ │ │ └── provider_paths.xml
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ └── layout
│ │ │ │ ├── activity_camera.xml
│ │ │ │ ├── my_custom_toast.xml
│ │ │ │ ├── list_item_album.xml
│ │ │ │ ├── list_item_media.xml
│ │ │ │ └── activity_picker.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── geeksmediapicker
│ │ │ │ ├── GeeksMediaType.kt
│ │ │ │ ├── utils
│ │ │ │ ├── AppConstants.kt
│ │ │ │ ├── ViewExtensions.kt
│ │ │ │ ├── ActivityExtensions.kt
│ │ │ │ ├── FileUtils.kt
│ │ │ │ └── ImageCompressor.kt
│ │ │ │ ├── interfaces
│ │ │ │ ├── ItemClickListener.kt
│ │ │ │ └── MediaPickerListener.kt
│ │ │ │ ├── models
│ │ │ │ ├── MediaStoreAlbum.kt
│ │ │ │ └── MediaStoreData.kt
│ │ │ │ ├── databinding
│ │ │ │ └── BindingAdapters.kt
│ │ │ │ ├── adapters
│ │ │ │ ├── AlbumAdapter.kt
│ │ │ │ ├── MediaAdapter.kt
│ │ │ │ └── GridSpacingItemDecoration.kt
│ │ │ │ ├── GeeksMediaPicker.kt
│ │ │ │ └── ui
│ │ │ │ ├── CameraActivity.kt
│ │ │ │ ├── PickerActivity.kt
│ │ │ │ └── PickerActivityVM.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── geeksmediapicker
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── geeksmediapicker
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── geeksmediapicker.zip
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── LICENSE
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | GeeksMediaPickerDemo
--------------------------------------------------------------------------------
/GeeksArts/empty.md:
--------------------------------------------------------------------------------
1 | empty file
2 |
--------------------------------------------------------------------------------
/geeksmediapicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/geeksmediapicker/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/GeeksArts/art1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art1.jpg
--------------------------------------------------------------------------------
/GeeksArts/art2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art2.jpg
--------------------------------------------------------------------------------
/GeeksArts/art3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art3.jpg
--------------------------------------------------------------------------------
/GeeksArts/art4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art4.jpg
--------------------------------------------------------------------------------
/GeeksArts/art5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art5.jpg
--------------------------------------------------------------------------------
/GeeksArts/art6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art6.jpg
--------------------------------------------------------------------------------
/GeeksArts/art7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/GeeksArts/art7.jpg
--------------------------------------------------------------------------------
/geeksmediapicker.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker.zip
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GeeksMediaPickerDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_bold.otf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/drawable/check_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/drawable/check_box.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_light.otf
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_medium.otf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_regular.otf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/drawable/check_box_tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/drawable/check_box_tick.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_bold_italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_bold_italic.otf
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/drawable/shadow_top_bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/drawable/shadow_top_bottom.png
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_light_italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_light_italic.otf
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/font/poppins_medium_italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/im-manisharma/GeeksMediaPicker/HEAD/geeksmediapicker/src/main/res/font/poppins_medium_italic.otf
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/GeeksMediaType.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker
2 |
3 | object GeeksMediaType {
4 | const val IMAGE = "IMAGE"
5 | const val VIDEO = "VIDEO"
6 | }
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/utils/AppConstants.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.utils
2 |
3 | internal const val REQ_EXTERNAL_STORAGE = 0x1045
4 | internal const val REQ_CODE_CAPTURE_IMAGE = 0x1046
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/interfaces/ItemClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.interfaces
2 |
3 | interface ItemClickListener {
4 | fun onClick(position: Int, event: Any? = null)
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 12 02:52:52 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/interfaces/MediaPickerListener.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.interfaces
2 |
3 | import com.geeksmediapicker.models.MediaStoreData
4 |
5 | interface MediaPickerListener {
6 | fun onMediaPicked(selectedMediaList: ArrayList = ArrayList(), mediaStoreData: MediaStoreData = MediaStoreData())
7 | }
8 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 | #0071BC
5 | #6200EE
6 | #3700B3
7 | #03DAC5
8 |
9 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/drawable/ic_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | contentDescription
4 | Select Album
5 | Ok
6 | Selected
7 | Please select a media
8 | Compressing Image...
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | jcenter()
13 | mavenCentral()
14 | }
15 | }
16 | rootProject.name='GeeksMediaPickerDemo'
17 | include ':app'
18 | include ':geeksmediapicker'
19 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/com/geeksmediapickerdemo/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapickerdemo
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/test/java/com/geeksmediapicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/layout/activity_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/drawable/ic_video_white.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/models/MediaStoreAlbum.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.models
2 |
3 | import android.net.Uri
4 |
5 | /**
6 | * Simple data class to hold information about an Album included in the device's MediaStore.
7 | */
8 | data class MediaStoreAlbum(
9 | val bucket_id: Long,
10 | val bucket_name: String?,
11 | val imageUri: Uri?,
12 | val medias: List
13 | ){
14 | fun getTotalImages(): String{
15 | return medias.size.toString()
16 | }
17 | }
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/databinding/BindingAdapters.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.databinding
2 |
3 | import android.net.Uri
4 | import android.widget.ImageView
5 | import androidx.databinding.BindingAdapter
6 | import com.bumptech.glide.Glide
7 |
8 |
9 | @BindingAdapter("bind:loadImageUri")
10 | internal fun loadImageUri(imageView: ImageView, uri: Uri?) {
11 | if (uri == null)
12 | return
13 |
14 | Glide.with(imageView.context)
15 | .load(uri)
16 | .thumbnail(0.3f)
17 | .into(imageView)
18 | }
19 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/models/MediaStoreData.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.models
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import kotlinx.parcelize.Parcelize
6 | import java.util.*
7 |
8 | /**
9 | * Simple data class to hold information about an image or a video included in the device's MediaStore.
10 | */
11 |
12 | @Parcelize
13 | data class MediaStoreData(
14 | var media_id: Long = 0,
15 | var bucket_id: Long = 0,
16 | var media_name: String = "",
17 | var media_type: String = "",
18 | var bucket_name: String? = "",
19 | var date_added: Date? = null,
20 | var content_uri: Uri? = null,
21 | var media_path: String = "",
22 | var media_duration: Long = 0,
23 | var isSelected: Boolean = false
24 | ): Parcelable
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/geeksmediapickerdemo/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapickerdemo
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.geeksmediapickerdemo", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/androidTest/java/com/geeksmediapicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.geeksmediapicker.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/geeksmediapicker/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 1.8
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/layout/my_custom_toast.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/utils/ViewExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.utils
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.widget.TextView
7 | import android.widget.Toast
8 | import com.geeksmediapicker.R
9 |
10 |
11 | internal fun View.visible() {
12 | visibility = View.VISIBLE
13 | }
14 |
15 | internal fun View.gone() {
16 | visibility = View.GONE
17 | }
18 |
19 | internal fun View.invisible() {
20 | visibility = View.INVISIBLE
21 | }
22 |
23 | internal fun View.isVisible(): Boolean = visibility == View.VISIBLE
24 |
25 | internal fun Context.showToast(message: String) {
26 | val myInflater = LayoutInflater.from(this)
27 | val view = myInflater.inflate(R.layout.my_custom_toast, null)
28 | val myToast = Toast(this)
29 | myToast.duration = Toast.LENGTH_SHORT
30 | myToast.view = view
31 | view.findViewById(R.id.tvMsg).text = message
32 | myToast.show()
33 | }
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/utils/ActivityExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.utils
2 |
3 | import android.app.Activity
4 | import android.app.AlertDialog
5 | import android.content.Intent
6 | import android.net.Uri
7 | import android.provider.Settings
8 |
9 | internal fun Activity.showPermissionSettingDialog() {
10 | val alertDialogBuilder: AlertDialog.Builder = AlertDialog.Builder(this)
11 | alertDialogBuilder.setTitle("Permission needed")
12 | alertDialogBuilder.setMessage("Location permission needed to access location")
13 | alertDialogBuilder.setPositiveButton("Open Setting") { _, _ ->
14 | startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
15 | data = Uri.fromParts("package", packageName, null)
16 | })
17 | finish()
18 | }
19 | alertDialogBuilder.setNegativeButton("Cancel") { dialogInterface, i ->
20 | dialogInterface.dismiss()
21 | finish()
22 | }
23 | val dialog: AlertDialog = alertDialogBuilder.create()
24 | dialog.show()
25 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Manish Sharma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
23 |
24 |
29 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/adapters/AlbumAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.geeksmediapicker.databinding.ListItemAlbumBinding
7 | import com.geeksmediapicker.interfaces.ItemClickListener
8 | import com.geeksmediapicker.models.MediaStoreAlbum
9 | import java.util.*
10 |
11 | class AlbumAdapter(
12 | private val list: ArrayList,
13 | private val itemClickListener: ItemClickListener
14 | ) : RecyclerView.Adapter() {
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
17 | val inflater = LayoutInflater.from(parent.context)
18 | return MyViewHolder(ListItemAlbumBinding.inflate(inflater))
19 | }
20 |
21 | override fun getItemCount(): Int {
22 | return list.size
23 | }
24 |
25 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) = holder.bind(position)
26 |
27 | inner class MyViewHolder(private val binding: ListItemAlbumBinding) : RecyclerView.ViewHolder(binding.root) {
28 | fun bind(position: Int) {
29 | val model = list[position]
30 | binding.albumData = model
31 | binding.itemPosition = position
32 | binding.clickListener = itemClickListener
33 | binding.executePendingBindings()
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-parcelize'
4 | apply plugin: 'kotlin-kapt'
5 |
6 | android {
7 | compileSdk 33
8 | namespace 'com.geeksmediapickerdemo'
9 | defaultConfig {
10 | applicationId "com.geeksmediapickerdemo"
11 | minSdk 16
12 | targetSdk 33
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | dataBinding {
27 | enabled = true
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_17
32 | targetCompatibility JavaVersion.VERSION_17
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = "${JavaVersion.VERSION_17}"
37 | }
38 |
39 | }
40 |
41 | dependencies {
42 | implementation 'androidx.core:core-ktx:1.10.1'
43 | implementation 'androidx.appcompat:appcompat:1.6.1'
44 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
45 |
46 | implementation project(':geeksmediapicker')
47 |
48 | //RecyclerView
49 | implementation 'androidx.recyclerview:recyclerview:1.3.0'
50 |
51 | //Material Library
52 | implementation 'com.google.android.material:material:1.9.0'
53 |
54 | //Glide Library
55 | implementation 'com.github.bumptech.glide:glide:4.15.1'
56 | }
57 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/adapters/MediaAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.adapters
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.geeksmediapicker.databinding.ListItemMediaBinding
7 | import com.geeksmediapicker.interfaces.ItemClickListener
8 | import com.geeksmediapicker.models.MediaStoreData
9 | import java.util.*
10 | import java.util.concurrent.TimeUnit
11 |
12 | class MediaAdapter(
13 | private val list: ArrayList,
14 | private val itemClickListener: ItemClickListener
15 | ) : RecyclerView.Adapter() {
16 |
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
18 | val inflater = LayoutInflater.from(parent.context)
19 | return MyViewHolder(ListItemMediaBinding.inflate(inflater))
20 | }
21 |
22 | override fun getItemCount(): Int {
23 | return list.size
24 | }
25 |
26 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) = holder.bind(position)
27 |
28 | inner class MyViewHolder(private val binding: ListItemMediaBinding) : RecyclerView.ViewHolder(binding.root) {
29 | fun bind(position: Int) {
30 | val model = list[position]
31 | binding.mediaData = model
32 | binding.itemPosition = position
33 | binding.clickListener = itemClickListener
34 |
35 | val minutes = TimeUnit.MILLISECONDS.toMinutes(model.media_duration)
36 | val seconds = TimeUnit.MILLISECONDS.toSeconds(model.media_duration)
37 | binding.tvMediaDuration.text = String.format("%d:%d s", minutes, seconds)
38 | binding.executePendingBindings()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/geeksmediapickerdemo/SelectedMediaAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapickerdemo
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.bumptech.glide.Glide
8 | import com.geeksmediapicker.models.MediaStoreData
9 | import com.geeksmediapickerdemo.databinding.ListItemSelectedMediaBinding
10 | import java.util.*
11 |
12 | class SelectedMediaAdapter(
13 | private val list: ArrayList
14 | ) : RecyclerView.Adapter() {
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
17 | val inflater = LayoutInflater.from(parent.context)
18 | return MyViewHolder(ListItemSelectedMediaBinding.inflate(inflater))
19 | }
20 |
21 | override fun getItemCount(): Int {
22 | return list.size
23 | }
24 |
25 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) = holder.bind(position)
26 |
27 | inner class MyViewHolder(val binding: ListItemSelectedMediaBinding) : RecyclerView.ViewHolder(binding.root) {
28 | fun bind(position: Int) {
29 | val model = list[position]
30 |
31 | with(itemView){
32 | Glide.with(binding.ivProductImage.context)
33 | .load(model.content_uri)
34 | .thumbnail(0.3f)
35 | .into(binding.ivProductImage)
36 | //ivProductImage.setImageURI(model.content_uri)
37 | binding.tvMediaName.text = "Name: ${model.media_name}"
38 | binding.tvMediaMimeType.text = "Type: ${model.media_type}"
39 | binding.tvMediaUri.text = "Uri: ${model.content_uri}"
40 | binding.tvMediaPath.text = "Path: ${model.media_path}"
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/adapters/GridSpacingItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.adapters
2 |
3 | import android.graphics.Rect
4 | import android.view.View
5 | import androidx.recyclerview.widget.RecyclerView
6 |
7 | class GridSpacingItemDecoration(
8 | private val spanCount: Int,
9 | private val spacing: Int,
10 | private val includeEdge: Boolean = false
11 | ) : RecyclerView.ItemDecoration() {
12 |
13 | override fun getItemOffsets(
14 | outRect: Rect,
15 | view: View,
16 | parent: RecyclerView,
17 | state: RecyclerView.State
18 | ) {
19 | val position = parent.getChildAdapterPosition(view) // item position
20 |
21 | if (position >= 0) {
22 | val column = position % spanCount // item column
23 |
24 | if (includeEdge) {
25 | outRect.left =
26 | spacing - column * spacing / spanCount // spacing - column * ((1f / spanCount) * spacing)
27 | outRect.right =
28 | (column + 1) * spacing / spanCount // (column + 1) * ((1f / spanCount) * spacing)
29 |
30 | if (position < spanCount) { // top edge
31 | outRect.top = spacing
32 | }
33 | outRect.bottom = spacing // item bottom
34 | } else {
35 | outRect.left = column * spacing / spanCount // column * ((1f / spanCount) * spacing)
36 | outRect.right =
37 | spacing - (column + 1) * spacing / spanCount // spacing - (column + 1) * ((1f / spanCount) * spacing)
38 | if (position >= spanCount) {
39 | outRect.top = spacing // item top
40 | }
41 | }
42 | } else {
43 | outRect.left = 0
44 | outRect.right = 0
45 | outRect.top = 0
46 | outRect.bottom = 0
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/geeksmediapicker/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-parcelize'
4 | apply plugin: 'kotlin-kapt'
5 |
6 | android {
7 | compileSdk 33
8 | namespace 'com.geeksmediapicker'
9 | defaultConfig {
10 | minSdk 16
11 | targetSdkVersion 33
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles 'consumer-rules.pro'
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | dataBinding {
27 | enabled = true
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_17
32 | targetCompatibility JavaVersion.VERSION_17
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = "${JavaVersion.VERSION_17}"
37 | }
38 |
39 | }
40 |
41 | dependencies {
42 | implementation 'androidx.core:core-ktx:1.10.1'
43 | implementation 'androidx.appcompat:appcompat:1.6.1'
44 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
45 |
46 | //RecyclerView
47 | implementation 'androidx.recyclerview:recyclerview:1.3.0'
48 |
49 | //Material Library
50 | implementation 'com.google.android.material:material:1.9.0'
51 |
52 | //AndroidX Lifecycle Components with coroutines
53 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
54 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
55 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
56 |
57 | //Glide Library
58 | implementation 'com.github.bumptech.glide:glide:4.15.1'
59 |
60 | // SDP and SSP
61 | implementation 'com.intuit.sdp:sdp-android:1.0.6'
62 | implementation 'com.intuit.ssp:ssp-android:1.0.6'
63 | }
64 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_selected_media.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
16 |
17 |
24 |
25 |
34 |
35 |
41 |
42 |
49 |
50 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/layout/list_item_album.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
19 |
20 |
21 |
25 |
26 |
30 |
31 |
38 |
39 |
44 |
45 |
59 |
60 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/geeksmediapickerdemo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapickerdemo
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.databinding.DataBindingUtil
7 | import com.geeksmediapicker.GeeksMediaPicker
8 | import com.geeksmediapicker.GeeksMediaType
9 | import com.geeksmediapicker.models.MediaStoreData
10 | import com.geeksmediapickerdemo.databinding.ActivityMainBinding
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 |
15 | private lateinit var binding : ActivityMainBinding
16 | private val mediaList = ArrayList()
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
21 | inItAdapter()
22 | inItListener()
23 | }
24 |
25 | private fun inItAdapter(){
26 | binding.rvSelectedMedia.adapter = SelectedMediaAdapter(mediaList)
27 | }
28 |
29 | private fun inItListener() {
30 | binding.startPickerBtn.setOnClickListener {
31 |
32 | val includesFilePath = binding.cbIncludesPath.isChecked
33 | val isCompressionEnable = binding.cbEnableImageCompression.isChecked
34 |
35 | val mediaType = if (binding.rbImage.isChecked) {
36 | GeeksMediaType.IMAGE
37 | } else {
38 | GeeksMediaType.VIDEO
39 | }
40 |
41 | if (binding.rbSingle.isChecked) {
42 | GeeksMediaPicker.with(this)
43 | .setMediaType(mediaType)
44 | .setIncludesFilePath(includesFilePath)
45 | .setEnableCompression(isCompressionEnable)
46 | .startSingle { data ->
47 | mediaList.clear()
48 | mediaList.add(data)
49 | binding.rvSelectedMedia.adapter?.notifyDataSetChanged()
50 | //Log.e("My TAG", "${data}")
51 | }
52 | } else {
53 | GeeksMediaPicker.with(this)
54 | .setMediaType(mediaType)
55 | .setIncludesFilePath(includesFilePath)
56 | .setEnableCompression(isCompressionEnable)
57 | .startMultiple { dataList ->
58 | mediaList.clear()
59 | mediaList.addAll(dataList)
60 | binding.rvSelectedMedia.adapter?.notifyDataSetChanged()
61 | //Log.e("My TAG", "${dataList}")
62 | }
63 | }
64 | }
65 |
66 | binding.startCameraBtn.setOnClickListener {
67 | val isCompressionEnable = binding.cbEnableImageCompression.isChecked
68 | if (binding.rbVideo.isChecked) {
69 | Toast.makeText(this, "Under Development", Toast.LENGTH_SHORT).show()
70 | }else {
71 | GeeksMediaPicker.with(this)
72 | .setEnableCompression(isCompressionEnable)
73 | .startCamera { mediaStoreData ->
74 | mediaList.clear()
75 | mediaList.add(mediaStoreData)
76 | binding.rvSelectedMedia.adapter?.notifyDataSetChanged()
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/layout/list_item_media.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
18 |
19 |
22 |
23 |
24 |
28 |
29 |
33 |
34 |
41 |
42 |
46 |
47 |
57 |
58 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/utils/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.utils
2 |
3 | import android.content.ContentResolver
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import android.graphics.Bitmap
7 | import android.media.MediaMetadataRetriever
8 | import android.net.Uri
9 | import android.os.Build
10 | import android.provider.OpenableColumns
11 | import android.util.Log
12 | import android.util.Size
13 | import java.io.File
14 | import java.io.FileOutputStream
15 |
16 | object FileUtils {
17 |
18 | private const val TAG = "FileUtil"
19 |
20 | fun getFilePath(context: Context, uri: Uri): String {
21 | var filePath = ""
22 | val contentResolver = context.contentResolver
23 | val fileName = getFileName(contentResolver, uri)
24 | //Log.e(TAG, "file name: $fileName")
25 | if (fileName.isNotEmpty()) {
26 | contentResolver.openInputStream(uri)?.use { inputStream ->
27 | val file = File(context.getExternalFilesDir(null), fileName)
28 | Log.e(TAG, "file path: ${file.absolutePath}")
29 | val outputStream = FileOutputStream(file)
30 | inputStream.copyTo(outputStream)
31 | filePath = file.absolutePath
32 | }
33 | }
34 | return filePath
35 | }
36 |
37 | fun getFileName(contentResolver: ContentResolver, uri: Uri): String {
38 | var name = "Image_${System.currentTimeMillis()}.jpeg"
39 | contentResolver.query(uri, null, null, null, null)?.use {
40 | val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
41 | if (it.moveToFirst()) {
42 | name = it.getString(nameIndex)
43 | }
44 | }
45 | return name
46 | }
47 |
48 | fun getVideoThumb(context: Context, videoUri: Uri): String {
49 | val bitmapList = getVideoThumbBitmaps(context, videoUri)
50 | if (bitmapList.isNotEmpty()){
51 | val filePath = File(context.getExternalFilesDir(null), "Thumbnails")
52 |
53 | //Log.e("Check", "cache file path : ${filePath.absolutePath}")
54 |
55 | if (!filePath.exists()) {
56 | filePath.mkdirs()
57 | //Log.e("Check", "File path created")
58 | }
59 |
60 | val outFile = File(filePath, "thumb_${System.currentTimeMillis()}.jpg")
61 | val fileOutputStream = FileOutputStream(outFile)
62 | bitmapList[0].compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream)
63 |
64 | fileOutputStream.flush()
65 | fileOutputStream.close()
66 |
67 | return outFile.absolutePath
68 | }
69 |
70 | return ""
71 | }
72 |
73 | private fun getVideoThumbBitmaps(context: Context, videoUri: Uri): ArrayList {
74 | val thumbnailList = ArrayList()
75 | try {
76 | val mdr = MediaMetadataRetriever()
77 | mdr.setDataSource(context, videoUri)
78 |
79 | // Retrieve media data
80 | val videoLengthInMs = mdr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong()?:0 * 1000
81 |
82 | // Set thumbnail properties (Thumbs are squares)
83 | val thumbWidth = 512
84 | val thumbHeight = 512
85 |
86 | val numThumbs = 10
87 |
88 | val interval = videoLengthInMs / numThumbs
89 |
90 | for (i in 0 until numThumbs) {
91 | try {
92 | var bitmap = mdr.getFrameAtTime(i * interval, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
93 | bitmap = Bitmap.createScaledBitmap(bitmap!!, thumbWidth, thumbHeight, false)
94 | thumbnailList.add(bitmap)
95 | } catch (e: Exception) {
96 | e.printStackTrace()
97 | }
98 | }
99 | mdr.release()
100 | }catch (e: Exception) {
101 | e.printStackTrace()
102 | }
103 | return thumbnailList
104 | }
105 | }
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | xmlns:android
24 |
25 | ^$
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | xmlns:.*
35 |
36 | ^$
37 |
38 |
39 | BY_NAME
40 |
41 |
42 |
43 |
44 |
45 |
46 | .*:id
47 |
48 | http://schemas.android.com/apk/res/android
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | .*:name
58 |
59 | http://schemas.android.com/apk/res/android
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | name
69 |
70 | ^$
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | style
80 |
81 | ^$
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | .*
91 |
92 | ^$
93 |
94 |
95 | BY_NAME
96 |
97 |
98 |
99 |
100 |
101 |
102 | .*
103 |
104 | http://schemas.android.com/apk/res/android
105 |
106 |
107 | ANDROID_ATTRIBUTE_ORDER
108 |
109 |
110 |
111 |
112 |
113 |
114 | .*
115 |
116 | .*
117 |
118 |
119 | BY_NAME
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/res/layout/activity_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
15 |
16 |
21 |
22 |
30 |
31 |
44 |
45 |
59 |
60 |
61 |
67 |
68 |
75 |
76 |
77 |
78 |
87 |
88 |
91 |
92 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/GeeksMediaPicker.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import androidx.fragment.app.Fragment
7 | import com.geeksmediapicker.interfaces.MediaPickerListener
8 | import com.geeksmediapicker.models.MediaStoreData
9 | import com.geeksmediapicker.ui.CameraActivity
10 | import com.geeksmediapicker.ui.PickerActivity
11 | import java.lang.ref.WeakReference
12 | import java.util.*
13 |
14 | class GeeksMediaPicker private constructor(activity: Activity?, fragment: Fragment?) {
15 |
16 | private val activityWeakRef: WeakReference = WeakReference(activity)
17 | private val fragmentWeakRef: WeakReference = WeakReference(fragment)
18 |
19 | companion object {
20 | const val EXTRA_MEDIA_TYPE = "MEDIA_TYPE"
21 | const val EXTRA_MULTI_SELECTION = "MULTI_SELECTION"
22 | const val EXTRA_INCLUDES_FILE_PATH = "INCLUDES_FILE_PATH"
23 | const val EXTRA_ENABLE_COMPRESSION = "ENABLE_COMPRESSION"
24 | const val EXTRA_TOOLBAR_COLOR = "TOOLBAR_COLOR"
25 | const val EXTRA_MAX_COUNT = "MAX_COUNT"
26 |
27 | @JvmStatic
28 | fun with(activity: Activity) = GeeksMediaPicker(activity, null)
29 |
30 | @JvmStatic
31 | fun with(fragment: Fragment) = GeeksMediaPicker(null, fragment)
32 |
33 | @JvmStatic
34 | val listenerDeque: Deque = ArrayDeque()
35 | }
36 |
37 | private var maxCount: Int = -1
38 | private var toolBarColor: Int = -1
39 | private var mediaType: String = GeeksMediaType.IMAGE
40 | private var isMultiSelection: Boolean = false
41 | private var includesFilePath: Boolean = false
42 | private var isCompressionEnable: Boolean = false
43 |
44 | fun setMediaType(mediaType: String): GeeksMediaPicker {
45 | this.mediaType = mediaType
46 | return this
47 | }
48 |
49 | fun setToolbarColor(toolBarColor: Int): GeeksMediaPicker {
50 | this.toolBarColor = toolBarColor
51 | return this
52 | }
53 |
54 | fun setIncludesFilePath(includesFilePath: Boolean): GeeksMediaPicker {
55 | this.includesFilePath = includesFilePath
56 | return this
57 | }
58 |
59 | fun setEnableCompression(isCompressionEnable: Boolean): GeeksMediaPicker {
60 | this.isCompressionEnable = isCompressionEnable
61 | return this
62 | }
63 |
64 | fun startSingle(action: (MediaStoreData) -> Unit) {
65 | listenerDeque.clear()
66 | listenerDeque.push(object : MediaPickerListener {
67 | override fun onMediaPicked(selectedMediaList: ArrayList, mediaStoreData: MediaStoreData) {
68 | action(selectedMediaList[0])
69 | }
70 | })
71 |
72 | isMultiSelection = false
73 | startPicker()
74 | }
75 |
76 | fun startMultiple(maxCount: Int = -1, action: (ArrayList) -> Unit) {
77 | this.maxCount = maxCount
78 | listenerDeque.clear()
79 | listenerDeque.push(object : MediaPickerListener {
80 | override fun onMediaPicked(selectedMediaList: ArrayList, mediaStoreData: MediaStoreData) {
81 | action(selectedMediaList)
82 | }
83 | })
84 |
85 | isMultiSelection = true
86 | startPicker()
87 | }
88 |
89 | fun startCamera(action: (MediaStoreData) -> Unit) {
90 | listenerDeque.clear()
91 | listenerDeque.push(object : MediaPickerListener {
92 | override fun onMediaPicked(selectedMediaList: ArrayList, mediaStoreData: MediaStoreData) {
93 | action(mediaStoreData)
94 | }
95 | })
96 |
97 | val activity = activityWeakRef.get()
98 | val fragment = fragmentWeakRef.get()
99 |
100 | when {
101 | activity != null -> openCamera(activity)
102 | fragment != null -> openCamera(fragment.requireActivity())
103 | else -> {
104 | listenerDeque.clear()
105 | throw NullPointerException("activity or fragment can't be null.")
106 | }
107 | }
108 | }
109 |
110 | private fun openCamera(activity: Activity) {
111 | with(activity) {
112 | startActivity(Intent(this, CameraActivity::class.java).apply {
113 | putExtra(EXTRA_ENABLE_COMPRESSION, isCompressionEnable)
114 | })
115 | }
116 | }
117 |
118 | private fun startPicker() {
119 | val activity = activityWeakRef.get()
120 | val fragment = fragmentWeakRef.get()
121 |
122 | when {
123 | activity != null -> activity.startActivity(getIntentWithData(activity))
124 | fragment != null -> fragment.startActivity(getIntentWithData(fragment.requireContext()))
125 | else -> {
126 | listenerDeque.clear()
127 | throw NullPointerException("activity or fragment can't be null.")
128 | }
129 | }
130 | }
131 |
132 | private fun getIntentWithData(context: Context?): Intent {
133 | return Intent(context, PickerActivity::class.java).apply {
134 | putExtra(EXTRA_MEDIA_TYPE, mediaType)
135 | putExtra(EXTRA_MULTI_SELECTION, isMultiSelection)
136 | putExtra(EXTRA_INCLUDES_FILE_PATH, includesFilePath)
137 | putExtra(EXTRA_ENABLE_COMPRESSION, isCompressionEnable)
138 | putExtra(EXTRA_TOOLBAR_COLOR, toolBarColor)
139 | putExtra(EXTRA_MAX_COUNT, maxCount)
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/utils/ImageCompressor.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.utils
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.graphics.Matrix
7 | import android.net.Uri
8 | import android.util.Log
9 | import androidx.exifinterface.media.ExifInterface
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.withContext
12 | import java.io.File
13 | import java.io.FileOutputStream
14 | import java.io.IOException
15 | import java.io.InputStream
16 |
17 | object ImageCompressor {
18 |
19 | private const val MAX_WIDTH = 612
20 | private const val MAX_HEIGHT = 816
21 | private const val IMAGE_QUALITY = 80
22 |
23 |
24 | suspend fun getCompressedImage(context: Context, uri: Uri?, success: (String) -> Unit) {
25 | if (uri == null) {
26 | throw NullPointerException("uri can't be null.")
27 | }
28 |
29 | try {
30 | val compressedFilePath = compressImageFromUri(context, uri)
31 | success(compressedFilePath)
32 | } catch (e: Exception) {
33 | Log.e("ImageCompressor", "error --> ${e.localizedMessage}")
34 | }
35 | }
36 |
37 | private suspend fun compressImageFromUri(context: Context, uri: Uri): String {
38 | var filePath = ""
39 |
40 | withContext(Dispatchers.IO){
41 | val options = BitmapFactory.Options()
42 | // Here true means that we don’t want to load bitmap into memory.
43 | // We just want to get information(width, height, etc.) about image. So we can calculate scale factor with that information.
44 | options.inJustDecodeBounds = true
45 |
46 |
47 | val fileName = FileUtils.getFileName(context.contentResolver, uri)
48 |
49 |
50 | context.contentResolver.openInputStream(uri)?.use {inputStream1->
51 | BitmapFactory.decodeStream(inputStream1, null, options)
52 |
53 | // Calculate and set inSampleSize
54 | options.inSampleSize = calculateInSampleSize(
55 | options,
56 | MAX_WIDTH,
57 | MAX_HEIGHT
58 | )
59 | // Decode bitmap with inSampleSize set
60 | options.inJustDecodeBounds = false
61 |
62 | context.contentResolver.openInputStream(uri)?.use {inputStream2 ->
63 | BitmapFactory.decodeStream(inputStream2, null, options)?.also {bitmap ->
64 |
65 | //Log.e("Check", "scaled bitmap size : ${bitmap.byteCount}")
66 |
67 | context.contentResolver.openInputStream(uri)?.use {
68 | val scaledBitmap = rotateImageIfRequired(bitmap, it)
69 | val newFile = File(context.getExternalFilesDir(null), "compressed_$fileName")
70 | //Log.e("Check", "cache file path : ${newFile.absolutePath}")
71 |
72 | if (newFile.exists()) {
73 | newFile.delete()
74 | //Log.e("Check", "File deleted")
75 | }
76 |
77 | val fileOutputStream = FileOutputStream(newFile)
78 | scaledBitmap?.compress(Bitmap.CompressFormat.JPEG, IMAGE_QUALITY, fileOutputStream)
79 |
80 | //Log.e("Check", "Is bitmap compressed: $bitmapCompressed")
81 | //Log.e("ImageCompressor", "resize scaled bitmap size : ${scaledBitmap?.byteCount}")
82 |
83 | fileOutputStream.flush()
84 | fileOutputStream.close()
85 |
86 | filePath = newFile.absolutePath
87 | }
88 | }
89 | }
90 |
91 | }
92 |
93 | }
94 | return filePath
95 | }
96 |
97 | private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
98 | // Raw height and width of image
99 | val (height: Int, width: Int) = options.run { outHeight to outWidth }
100 | var inSampleSize = 1
101 |
102 | if (height > reqHeight || width > reqWidth) {
103 |
104 | val halfHeight: Int = height / 2
105 | val halfWidth: Int = width / 2
106 |
107 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both
108 | // height and width larger than the requested height and width.
109 | while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
110 | inSampleSize *= 2
111 | }
112 | }
113 |
114 | return inSampleSize
115 | }
116 |
117 | /**
118 | * Rotate an image if required.
119 | *
120 | * @param bitmap The image bitmap
121 | * @param inputStream Image InputStream
122 | * @return The resulted Bitmap after manipulation
123 | */
124 | @Throws(IOException::class)
125 | private fun rotateImageIfRequired(bitmap: Bitmap, inputStream: InputStream): Bitmap? {
126 | return when (ExifInterface(inputStream).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
127 | ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(bitmap, 90)
128 | ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(bitmap, 180)
129 | ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(bitmap, 270)
130 | else -> bitmap
131 | }
132 | }
133 |
134 | private fun rotateImage(img: Bitmap, degree: Int): Bitmap? {
135 | val matrix = Matrix()
136 | matrix.postRotate(degree.toFloat())
137 | val rotatedImg = Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
138 | img.recycle()
139 | return rotatedImg
140 | }
141 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
19 |
20 |
26 |
27 |
33 |
34 |
40 |
41 |
48 |
49 |
50 |
51 |
58 |
59 |
65 |
66 |
72 |
73 |
79 |
80 |
87 |
88 |
89 |
90 |
98 |
99 |
107 |
108 |
114 |
115 |
122 |
123 |
132 |
133 |
134 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/ui/CameraActivity.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.ui
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.pm.PackageManager
8 | import android.net.Uri
9 | import android.os.Bundle
10 | import android.provider.MediaStore
11 | import androidx.appcompat.app.AppCompatActivity
12 | import androidx.core.app.ActivityCompat
13 | import androidx.core.content.ContextCompat
14 | import androidx.core.content.FileProvider
15 | import androidx.lifecycle.lifecycleScope
16 | import com.geeksmediapicker.GeeksMediaPicker
17 | import com.geeksmediapicker.GeeksMediaType
18 | import com.geeksmediapicker.R
19 | import com.geeksmediapicker.interfaces.MediaPickerListener
20 | import com.geeksmediapicker.models.MediaStoreData
21 | import com.geeksmediapicker.utils.ImageCompressor
22 | import com.geeksmediapicker.utils.REQ_CODE_CAPTURE_IMAGE
23 | import com.geeksmediapicker.utils.showPermissionSettingDialog
24 | import kotlinx.coroutines.Dispatchers
25 | import kotlinx.coroutines.launch
26 | import java.io.File
27 | import java.util.*
28 |
29 | class CameraActivity : AppCompatActivity() {
30 | private var mImageName = ""
31 | private var mCapturedImagePath = ""
32 | private val isCompressionEnable: Boolean
33 | get() = intent.getBooleanExtra(GeeksMediaPicker.EXTRA_ENABLE_COMPRESSION, false)
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContentView(R.layout.activity_camera)
38 | checkPermission()
39 | }
40 |
41 | private fun checkPermission() {
42 | if (isPermissionGranted(this)) {
43 | openCamera()
44 | } else {
45 | requestCameraPermission()
46 | }
47 | }
48 |
49 | private fun requestCameraPermission() {
50 | ActivityCompat.requestPermissions(
51 | this,
52 | arrayOf(Manifest.permission.CAMERA),
53 | REQ_CODE_CAPTURE_IMAGE
54 | )
55 | }
56 |
57 | private fun isPermissionGranted(context: Context) =
58 | ContextCompat.checkSelfPermission(
59 | context,
60 | Manifest.permission.CAMERA
61 | ) == PackageManager.PERMISSION_GRANTED
62 |
63 | private fun openCamera() {
64 | mImageName = ""
65 | mCapturedImagePath = ""
66 |
67 | val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
68 | val directory = File(getExternalFilesDir(null), "Camera")
69 | if (!directory.exists()) {
70 | directory.mkdir()
71 | }
72 | val file = File.createTempFile("Img_", ".jpg", directory)
73 | mImageName = file.name
74 | mCapturedImagePath = file.absolutePath
75 |
76 | val fileUri = FileProvider.getUriForFile(this, "${packageName}.provider", file)
77 |
78 | val resolvedIntentActivities = packageManager.queryIntentActivities(
79 | cameraIntent, PackageManager.MATCH_DEFAULT_ONLY
80 | )
81 | for (resolvedIntentInfo in resolvedIntentActivities) {
82 | val packageName = resolvedIntentInfo.activityInfo.packageName
83 | grantUriPermission(
84 | packageName,
85 | fileUri,
86 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
87 | )
88 | }
89 | cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri)
90 | startActivityForResult(cameraIntent, REQ_CODE_CAPTURE_IMAGE)
91 | }
92 |
93 | override fun onRequestPermissionsResult(
94 | requestCode: Int,
95 | permissions: Array,
96 | grantResults: IntArray
97 | ) {
98 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
99 | if (requestCode == REQ_CODE_CAPTURE_IMAGE && grantResults.isNotEmpty()) {
100 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
101 | openCamera()
102 | } else if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
103 | val shouldShowRequestPermissionRationale =
104 | ActivityCompat.shouldShowRequestPermissionRationale(
105 | this,
106 | Manifest.permission.ACCESS_FINE_LOCATION
107 | )
108 | if (shouldShowRequestPermissionRationale) {
109 | requestCameraPermission()
110 | } else {
111 | showPermissionSettingDialog()
112 | }
113 | }
114 | }
115 | }
116 |
117 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
118 | super.onActivityResult(requestCode, resultCode, data)
119 | if (requestCode == REQ_CODE_CAPTURE_IMAGE && resultCode == Activity.RESULT_OK) {
120 | val mediaPickerListener: MediaPickerListener = GeeksMediaPicker.listenerDeque.pop()
121 | GeeksMediaPicker.listenerDeque.clear()
122 |
123 | val mediaUri = Uri.fromFile(File(mCapturedImagePath))
124 |
125 | if (isCompressionEnable) {
126 | lifecycleScope.launch {
127 | ImageCompressor.getCompressedImage(this@CameraActivity, mediaUri) { filePath ->
128 | launch(Dispatchers.Main) {
129 | val mediaStoreData = MediaStoreData(
130 | media_type = GeeksMediaType.IMAGE,
131 | media_path = filePath,
132 | media_name = File(filePath).name,
133 | content_uri = Uri.fromFile(File(filePath))
134 | )
135 | mediaPickerListener.onMediaPicked(mediaStoreData = mediaStoreData)
136 | finish()
137 | }
138 | }
139 | }
140 | } else {
141 | val mediaStoreData = MediaStoreData(
142 | media_type = GeeksMediaType.IMAGE,
143 | media_path = mCapturedImagePath,
144 | media_name = mImageName,
145 | content_uri = mediaUri
146 | )
147 | mediaPickerListener.onMediaPicked(mediaStoreData = mediaStoreData)
148 | finish()
149 | }
150 | } else {
151 | finish()
152 | }
153 | }
154 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GeeksMediaPicker
2 | GeeksMediaPicker is a **simple media picker** library for Android.
3 |
4 | - Support Image/Video types.
5 | - Support Single/Multiple select.
6 | - Support ImageCompression.
7 | - Competible with Android 10 and above.
8 | - You can get real path of a selected media (it will copy selected file to your app's package storage and returned that path).
9 |
10 |
11 | | Demo App | Select Album | Select Media | Get Result |
12 | |:-----------------------:|:-----------------------:|:-----------------------:|:-----------------------: |
13 | |  |  |  |  |
14 |
15 |
16 |
17 | ## Setup
18 |
19 | ### Gradle
20 |
21 | ```gradle
22 |
23 | - Project level gradle
24 | repositories {
25 | maven { url "https://jitpack.io" }
26 | }
27 |
28 | - App level gradle file
29 | dependencies {
30 | //GeeksMediaPicker
31 | implementation 'com.github.im-manisharma:GeeksMediaPicker:1.1.4'
32 | }
33 |
34 | ```
35 |
36 |
37 |
38 | ## How to use it
39 |
40 | ### 1. Enable databinding
41 |
42 | - GeeksMediaPicker uses databinding
43 | - Set enable databinding in your app `build.gradle`
44 |
45 | ```
46 | dataBinding {
47 | enabled = true
48 | }
49 | ```
50 |
51 |
52 |
53 |
54 | ### 2. Let's Start GeeksMediaPicker
55 |
56 | - GeeksMediaPicker uses Interfaces and Lambda {()->Unit} callback so you don't need to use startActivityForResult to get selected Media files
57 | - You can call GeeksMediaPicker from Activity and Fragment.
58 |
59 |
60 |
61 | #### To Get Single Media File
62 |
63 |
64 | ##### For Image
65 |
66 | ```
67 | GeeksMediaPicker.with(this)
68 | .setMediaType(GeeksMediaType.IMAGE)
69 | .startSingle { mediaStoreData ->
70 | val imageUri = mediaStoreData.content_uri
71 | //Now use this uri to load image
72 | }
73 | ```
74 |
75 | ##### For Video
76 |
77 |
78 | ```
79 | GeeksMediaPicker.with(this)
80 | .setMediaType(GeeksMediaType.VIDEO)
81 | .startSingle { mediaStoreData ->
82 | val videoUri = mediaStoreData.content_uri
83 | //Now use this uri to play video
84 |
85 | //To get video duration (in milliseconds)
86 | val videoDuration = mediaStoreData.media_duration
87 | }
88 | ```
89 |
90 |
91 |
92 |
93 | #### To Get Multiple Media Files
94 |
95 |
96 | ##### For Image
97 |
98 | ```
99 | GeeksMediaPicker.with(this)
100 | .setMediaType(GeeksMediaType.IMAGE)
101 | .startMultiple { mediaStoreDataList ->
102 | //Now use this list to get images uri
103 | }
104 | ```
105 |
106 | ##### For Video
107 |
108 |
109 | ```
110 | GeeksMediaPicker.with(this)
111 | .setMediaType(GeeksMediaType.VIDEO)
112 | .startMultiple { mediaStoreDataList ->
113 | //Now use this list to get video uri and video duration
114 | }
115 | ```
116 |
117 | ##### For Image and Video (with max count)
118 |
119 | ```
120 | GeeksMediaPicker.with(this)
121 | .setMediaType(GeeksMediaType.IMAGE //or GeeksMediaType.VIDEO)
122 | // Pass max count value in startMultiple() method as first parameter
123 | .startMultiple(5//your count value) { mediaStoreDataList ->
124 | //Now use this list to get images uri
125 | }
126 | ```
127 |
128 | #### To Get Media File From Camera (Only for Image. Record video is in underdevelopment)
129 |
130 | ```
131 | GeeksMediaPicker.with(this)
132 | .setEnableCompression(true)
133 | .startCamera { mediaStoreData ->
134 | val filePath = mediaStoreData.mediaPath
135 | }
136 | ```
137 |
138 |
139 |
140 |
141 |
142 | ### 3. GeeksMediaPicker Return Type
143 |
144 | ##### MediaStoreData Class
145 |
146 | - It will return MediaStoreData class object
147 | - You can use this object to get all required information i.e.
148 | - Fields of MediaStoreData class
149 |
150 | ```
151 | media_name: String : Name of the selected media file.
152 | media_type: String : Type of the selected media file.
153 | bucket_name: String? : Album name of the selected media file, null if it is not belongs to any Album.
154 | content_uri: Uri? : Original MediaStore Uri of the selected file
155 | media_path: String : Real Path of the selected file
156 | media_duration: Long : Duration of the selected media for Video file
157 | ```
158 |
159 |
160 |
161 |
162 |
163 | ### 4. GeeksMediaPicker Customization
164 |
165 | - For now it is only support change toolbar color by calling e.g.
166 |
167 | ```
168 | GeeksMediaPicker.with(this)
169 | .setMediaType(GeeksMediaType.IMAGE)
170 | .setToolbarColor(YOUR_COLOR) // It should be an Integer value
171 | .startSingle { mediaStoreData ->
172 | val imageUri = mediaStoreData.content_uri
173 | //Now use this uri to load image
174 | }
175 | ```
176 |
177 |
178 |
179 |
180 | ### 5. To Get Media File Path
181 |
182 | #### For Real Path (Without compression works for all types of MediaType)
183 |
184 | ```
185 | GeeksMediaPicker.with(this)
186 | .setMediaType(GeeksMediaType.IMAGE)
187 | .setIncludesFilePath(true)
188 | .startSingle { mediaStoreData ->
189 | val filePath = mediaStoreData.media_path
190 | }
191 | ```
192 |
193 |
194 | #### For Compressed File Path (Works only for GeeksMediaType.IMAGE)
195 |
196 | ```
197 | GeeksMediaPicker.with(this)
198 | .setMediaType(GeeksMediaType.IMAGE)
199 | .setEnableCompression(true)
200 | .startSingle { mediaStoreData ->
201 | // you will receive compressed path in same key used for real file path
202 | val compressedFilePath = mediaStoreData.media_path
203 | }
204 | ```
205 |
206 |
207 |
208 |
209 |
210 | ### 6. All Public Functions
211 |
212 | * `setMediaType(MediaType)` : (default value)GeeksMediaType.IMAGE / GeeksMediaType.IMAGE
213 | * `setToolbarColor(Color.Blue)` : (default theme color)
214 | * `setIncludesFilePath(Boolean)` : (default false) // if true you can get real file path of selected media file from {mediaStoreData.media_path}.
215 | * `setEnableCompression(Boolean)` : (default false) // if true it will compress all selected Images and return their path in {mediaStoreData.media_path}.
216 |
217 |
218 |
219 | ### 7. Incoming improvements
220 |
221 | - Set Minimum Number Of Selection
222 | - Record Video From Camera Support.
223 | - Add Support for Pdf and Audio file.
224 | - More customization will be added in next release.
225 |
226 |
227 |
228 |
229 |
230 |
231 | ## License
232 |
233 | ````code
234 | MIT License
235 |
236 | Copyright (c) 2020 Manish Sharma
237 |
238 | Permission is hereby granted, free of charge, to any person obtaining a copy
239 | of this software and associated documentation files (the "Software"), to deal
240 | in the Software without restriction, including without limitation the rights
241 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
242 | copies of the Software, and to permit persons to whom the Software is
243 | furnished to do so, subject to the following conditions:
244 |
245 | The above copyright notice and this permission notice shall be included in all
246 | copies or substantial portions of the Software.
247 |
248 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
249 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
250 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
251 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
252 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
253 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
254 | SOFTWARE.
255 | ````
256 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/ui/PickerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.ui
2 |
3 | import android.Manifest
4 | import android.content.Intent
5 | import android.content.pm.PackageManager.PERMISSION_GRANTED
6 | import android.net.Uri
7 | import android.os.Build
8 | import android.os.Bundle
9 | import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
10 | import android.view.View
11 | import androidx.appcompat.app.AppCompatActivity
12 | import androidx.core.app.ActivityCompat
13 | import androidx.core.content.ContextCompat
14 | import androidx.databinding.DataBindingUtil
15 | import androidx.lifecycle.ViewModelProvider
16 | import androidx.lifecycle.lifecycleScope
17 | import com.geeksmediapicker.GeeksMediaPicker
18 | import com.geeksmediapicker.GeeksMediaPicker.Companion.EXTRA_ENABLE_COMPRESSION
19 | import com.geeksmediapicker.GeeksMediaPicker.Companion.EXTRA_INCLUDES_FILE_PATH
20 | import com.geeksmediapicker.GeeksMediaPicker.Companion.EXTRA_MAX_COUNT
21 | import com.geeksmediapicker.GeeksMediaPicker.Companion.EXTRA_MEDIA_TYPE
22 | import com.geeksmediapicker.GeeksMediaPicker.Companion.EXTRA_MULTI_SELECTION
23 | import com.geeksmediapicker.GeeksMediaPicker.Companion.EXTRA_TOOLBAR_COLOR
24 | import com.geeksmediapicker.GeeksMediaType
25 | import com.geeksmediapicker.R
26 | import com.geeksmediapicker.adapters.AlbumAdapter
27 | import com.geeksmediapicker.adapters.GridSpacingItemDecoration
28 | import com.geeksmediapicker.adapters.MediaAdapter
29 | import com.geeksmediapicker.databinding.ActivityPickerBinding
30 | import com.geeksmediapicker.interfaces.ItemClickListener
31 | import com.geeksmediapicker.interfaces.MediaPickerListener
32 | import com.geeksmediapicker.models.MediaStoreAlbum
33 | import com.geeksmediapicker.models.MediaStoreData
34 | import com.geeksmediapicker.utils.*
35 | import kotlinx.coroutines.*
36 |
37 | class PickerActivity : AppCompatActivity(), View.OnClickListener {
38 |
39 | private var selectedCount: Int = 0
40 | private var selectedItemPos: Int = -1
41 | private var selectedAlbumPos: Int = -1
42 | private var mediaList = ArrayList()
43 | private val albumList = ArrayList()
44 | private lateinit var viewModel: PickerActivityVM
45 | private lateinit var binding: ActivityPickerBinding
46 |
47 | private val mediaType: String
48 | get() = intent.getStringExtra(EXTRA_MEDIA_TYPE) ?: GeeksMediaType.IMAGE
49 |
50 | private val isMultiSelection: Boolean
51 | get() = intent.getBooleanExtra(EXTRA_MULTI_SELECTION, false)
52 |
53 | private val includesFilePath: Boolean
54 | get() = intent.getBooleanExtra(EXTRA_INCLUDES_FILE_PATH, false)
55 |
56 | private val isCompressionEnable: Boolean
57 | get() = intent.getBooleanExtra(EXTRA_ENABLE_COMPRESSION, false)
58 |
59 | private val toolbarColor: Int
60 | get() = intent.getIntExtra(EXTRA_TOOLBAR_COLOR, 0)
61 |
62 | private val maxCount: Int
63 | get() = intent.getIntExtra(EXTRA_MAX_COUNT, -1)
64 |
65 | override fun onCreate(savedInstanceState: Bundle?) {
66 | super.onCreate(savedInstanceState)
67 | binding = DataBindingUtil.setContentView(this, R.layout.activity_picker)
68 | viewModel = ViewModelProvider(this).get(PickerActivityVM::class.java)
69 |
70 | inItUI()
71 | inItControl()
72 | inItAdapter()
73 | inItObservable()
74 | startFetchingMedia()
75 | }
76 |
77 | private fun inItUI() {
78 | if (toolbarColor != -1) {
79 | binding.toolbarLayout.setBackgroundColor(toolbarColor)
80 | }
81 | }
82 |
83 | override fun onBackPressed() {
84 | if (binding.rvMedia.isVisible()) {
85 | albumList[selectedAlbumPos].medias.forEach {
86 | it.isSelected = false
87 | }
88 | showAlbumLayout()
89 | } else {
90 | super.onBackPressed()
91 | }
92 | }
93 |
94 | private fun inItControl() {
95 | binding.backBtn.setOnClickListener(this)
96 | binding.okBtn.setOnClickListener(this)
97 | }
98 |
99 | private fun showAlbumLayout() {
100 | selectedItemPos = -1
101 | selectedCount = 0
102 | binding.apply {
103 | rvMedia.gone()
104 | okBtn.invisible()
105 | rvAlbums.visible()
106 | tvTitle.text = getString(R.string.select_album)
107 | }
108 | }
109 |
110 | private fun showMediaLayout() {
111 | with(binding) {
112 | okBtn.visible()
113 | rvAlbums.gone()
114 | rvMedia.visible()
115 | tvTitle.text = if (isMultiSelection) {
116 | String.format("%d %s", selectedCount, getString(R.string.selected))
117 | } else {
118 | getString(R.string.please_select_a_media)
119 | }
120 | }
121 | }
122 |
123 | override fun onClick(v: View) {
124 | when (v.id) {
125 | R.id.backBtn -> onBackPressed()
126 | R.id.okBtn -> if (isMultiSelection && selectedCount == 0) {
127 | showToast("Please select an item")
128 | } else {
129 | val selectedMediaList = ArrayList(mediaList.filter { it.isSelected })
130 | //Log.e("check", "${selectedMediaList.size}")
131 | if (selectedMediaList.isNotEmpty()) {
132 | val mediaPickerListener: MediaPickerListener = GeeksMediaPicker.listenerDeque.pop()
133 | GeeksMediaPicker.listenerDeque.clear()
134 | if (isCompressionEnable && mediaType == GeeksMediaType.IMAGE) {
135 | binding.layoutProgressBar.visible()
136 | lifecycleScope.launch {
137 | selectedMediaList.map {
138 | async {
139 | ImageCompressor.getCompressedImage(
140 | this@PickerActivity,
141 | it.content_uri
142 | ) { filePath ->
143 | //Log.e("PickerActivity", "compressed image path -->> ${filePath}")
144 | it.media_path = filePath
145 | }
146 | }
147 | }.awaitAll()
148 |
149 | launch(Dispatchers.Main) {
150 | binding.layoutProgressBar.gone()
151 | finish()
152 | mediaPickerListener.onMediaPicked(selectedMediaList)
153 | }
154 | }
155 | } else {
156 | if (includesFilePath) {
157 | for (media in selectedMediaList) {
158 | if (media.content_uri != null) {
159 | media.media_path = FileUtils.getFilePath(this, media.content_uri!!)
160 | }
161 | }
162 | }
163 | mediaPickerListener.onMediaPicked(selectedMediaList)
164 | finish()
165 | }
166 | } else {
167 | showToast("Please select an item")
168 | }
169 | }
170 | }
171 | }
172 |
173 | private fun inItAdapter() {
174 | with(binding.rvAlbums) {
175 | adapter = AlbumAdapter(albumList, object : ItemClickListener {
176 | override fun onClick(position: Int, event: Any?) {
177 | selectedAlbumPos = position
178 | mediaList.clear()
179 | mediaList.addAll(albumList[position].medias)
180 | //Log.e("list_size", "${mediaList.size}")
181 | binding.rvMedia.adapter?.notifyDataSetChanged()
182 | showMediaLayout()
183 | }
184 | })
185 | addItemDecoration(GridSpacingItemDecoration(2, 8))
186 | }
187 |
188 | with(binding.rvMedia) {
189 | adapter = MediaAdapter(mediaList, object : ItemClickListener {
190 | override fun onClick(position: Int, event: Any?) {
191 | if (isMultiSelection) {
192 | val isSelected = !mediaList[position].isSelected
193 | if (isSelected) {
194 | selectedCount += 1
195 | } else {
196 | selectedCount -= 1
197 | }
198 | //Log.e("My Check", "$selectedCount")
199 | if (maxCount != -1 && selectedCount >= maxCount + 1 ) {
200 | selectedCount -= 1
201 | showToast("Can't select more than $maxCount media items.")
202 | }else {
203 | binding.tvTitle.text = String.format("%d %s", selectedCount, getString(R.string.selected))
204 | mediaList[position].isSelected = isSelected
205 | binding.rvMedia.adapter?.notifyItemChanged(position)
206 | }
207 | } else {
208 | if (selectedItemPos == -1) {
209 | mediaList[position].isSelected = true
210 | binding.rvMedia.adapter?.notifyItemChanged(position)
211 | } else {
212 | mediaList[selectedItemPos].isSelected = false
213 | binding.rvMedia.adapter?.notifyItemChanged(selectedItemPos)
214 | mediaList[position].isSelected = true
215 | binding.rvMedia.adapter?.notifyItemChanged(position)
216 | }
217 | selectedItemPos = position
218 | }
219 | }
220 | })
221 | addItemDecoration(GridSpacingItemDecoration(3, 8))
222 | }
223 | }
224 |
225 |
226 | private fun inItObservable() {
227 | viewModel.mediaList.observe(this) { images ->
228 | val mAlbumList = images.groupBy { it.bucket_id }.map {
229 | MediaStoreAlbum(
230 | bucket_id = it.key,
231 | bucket_name = it.value[0].bucket_name,
232 | imageUri = it.value[0].content_uri,
233 | medias = it.value
234 | )
235 | }
236 |
237 | albumList.clear()
238 | albumList.addAll(mAlbumList)
239 | binding.rvAlbums.adapter?.notifyDataSetChanged()
240 | }
241 | }
242 |
243 | override fun onRequestPermissionsResult(
244 | requestCode: Int,
245 | permissions: Array,
246 | grantResults: IntArray
247 | ) {
248 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
249 | when (requestCode) {
250 | REQ_EXTERNAL_STORAGE -> {
251 | // If request is cancelled, the result arrays are empty.
252 | if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) {
253 | loadImages()
254 | } else {
255 | // If we weren't granted the permission, check to see if we should show
256 | // rationale for the permission.
257 | val showRationale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
258 | ActivityCompat.shouldShowRequestPermissionRationale(
259 | this,
260 | Manifest.permission.READ_MEDIA_IMAGES
261 | ) && ActivityCompat.shouldShowRequestPermissionRationale(
262 | this,
263 | Manifest.permission.READ_MEDIA_VIDEO
264 | )
265 | } else {
266 | ActivityCompat.shouldShowRequestPermissionRationale(
267 | this,
268 | Manifest.permission.READ_EXTERNAL_STORAGE
269 | )
270 | }
271 | if (showRationale) {
272 | requestPermission()
273 | } else {
274 | showPermissionSettingDialog()
275 | }
276 | }
277 | return
278 | }
279 | }
280 | }
281 |
282 | private fun loadImages() {
283 | viewModel.loadImages(mediaType, this)
284 | }
285 |
286 | private fun startFetchingMedia() {
287 | if (haveReadImagePermission()) {
288 | loadImages()
289 | } else {
290 | requestPermission()
291 | }
292 | }
293 |
294 | private fun goToSettings() {
295 | Intent(ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$packageName")).apply {
296 | addCategory(Intent.CATEGORY_DEFAULT)
297 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
298 | }.also { intent ->
299 | startActivity(intent)
300 | }
301 | }
302 |
303 | /**
304 | * Convenience method to check if [Manifest.permission.READ_EXTERNAL_STORAGE] permission
305 | * has been granted to the app.
306 | */
307 | private fun haveReadImagePermission(): Boolean {
308 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
309 | ContextCompat.checkSelfPermission(
310 | this,
311 | Manifest.permission.READ_MEDIA_IMAGES
312 | ) == PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
313 | this,
314 | Manifest.permission.READ_MEDIA_VIDEO
315 | ) == PERMISSION_GRANTED
316 | } else {
317 | ContextCompat.checkSelfPermission(
318 | this,
319 | Manifest.permission.READ_EXTERNAL_STORAGE
320 | ) == PERMISSION_GRANTED
321 | }
322 | }
323 |
324 | /**
325 | * Convenience method to request [Manifest.permission.READ_EXTERNAL_STORAGE] permission.
326 | */
327 | private fun requestPermission() {
328 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
329 | ActivityCompat.requestPermissions(
330 | this, arrayOf(
331 | Manifest.permission.READ_MEDIA_IMAGES,
332 | Manifest.permission.READ_MEDIA_VIDEO,
333 | ), REQ_EXTERNAL_STORAGE
334 | )
335 | } else {
336 | ActivityCompat.requestPermissions(
337 | this, arrayOf(
338 | Manifest.permission.READ_EXTERNAL_STORAGE,
339 | Manifest.permission.WRITE_EXTERNAL_STORAGE
340 | ), REQ_EXTERNAL_STORAGE
341 | )
342 | }
343 | }
344 | }
345 |
346 |
--------------------------------------------------------------------------------
/geeksmediapicker/src/main/java/com/geeksmediapicker/ui/PickerActivityVM.kt:
--------------------------------------------------------------------------------
1 | package com.geeksmediapicker.ui
2 |
3 | import android.content.ContentProvider
4 | import android.content.ContentResolver
5 | import android.content.ContentUris
6 | import android.content.Context
7 | import android.database.Cursor
8 | import android.provider.MediaStore
9 | import android.util.Log
10 | import androidx.lifecycle.LiveData
11 | import androidx.lifecycle.MutableLiveData
12 | import androidx.lifecycle.ViewModel
13 | import androidx.lifecycle.viewModelScope
14 | import com.geeksmediapicker.GeeksMediaType
15 | import com.geeksmediapicker.models.MediaStoreData
16 | import kotlinx.coroutines.Dispatchers
17 | import kotlinx.coroutines.launch
18 | import kotlinx.coroutines.withContext
19 | import java.util.*
20 | import java.util.concurrent.TimeUnit
21 |
22 | class PickerActivityVM: ViewModel() {
23 | private val _mediaList = MutableLiveData>()
24 | val mediaList: LiveData> get() = _mediaList
25 |
26 | /**
27 | * Performs a one shot load of images from [MediaStore.Images.Media.EXTERNAL_CONTENT_URI] into
28 | * the [_mediaList] [LiveData] above.
29 | */
30 | fun loadImages(mediaType: String, context: Context) {
31 | viewModelScope.launch {
32 | val mediaList = if (mediaType == GeeksMediaType.IMAGE) queryImages(context) else queryVideos(context)
33 | _mediaList.postValue(mediaList)
34 | }
35 | }
36 |
37 | private suspend fun queryImages(context: Context): List {
38 | val images = mutableListOf()
39 |
40 | /**
41 | * Working with [ContentResolver]s can be slow, so we'll do this off the main
42 | * thread inside a coroutine.
43 | */
44 | withContext(Dispatchers.IO) {
45 |
46 | /**
47 | * A key concept when working with Android [ContentProvider]s is something called
48 | * "projections". A projection is the list of columns to request from the provider,
49 | * and can be thought of (quite accurately) as the "SELECT ..." clause of a SQL
50 | * statement.
51 | *
52 | * It's not _required_ to provide a projection. In this case, one could pass `null`
53 | * in place of `projection` in the call to [ContentResolver.query], but requesting
54 | * more data than is required has a performance impact.
55 | *
56 | * For this sample, we only use a few columns of data, and so we'll request just a
57 | * subset of columns.
58 | */
59 |
60 | val mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
61 | val columnMediaId = MediaStore.Images.Media._ID
62 | val columnMediaName = MediaStore.Images.Media.DISPLAY_NAME
63 | val columnBucketName = MediaStore.Images.Media.BUCKET_DISPLAY_NAME
64 | val columnBucketId = MediaStore.Images.Media.BUCKET_ID
65 | val columnMediaDateAdded = MediaStore.Images.Media.DATE_ADDED
66 |
67 |
68 | val projection = arrayOf(
69 | columnMediaId,
70 | columnMediaName,
71 | columnBucketId,
72 | columnBucketName,
73 | columnMediaDateAdded
74 | )
75 |
76 | /**
77 | * The `selection` is the "WHERE ..." clause of a SQL statement. It's also possible
78 | * to omit this by passing `null` in its place, and then all rows will be returned.
79 | * In this case we're using a selection based on the date the image was taken.
80 | *
81 | * Note that we've included a `?` in our selection. This stands in for a variable
82 | * which will be provided by the next variable.
83 | */
84 | val selection = MediaStore.Images.Media.SIZE + " > 0"
85 |
86 | /**
87 | * The `selectionArgs` is a list of values that will be filled in for each `?`
88 | * in the `selection`.
89 | */
90 | /*val selectionArgs = arrayOf(
91 | // Release day of the G1. :)
92 | dateToTimestamp(day = 22, month = 10, year = 2008).toString()
93 | )*/
94 |
95 | /**
96 | * Sort order to use. This can also be null, which will use the default sort
97 | * order. For [MediaStore.Images], the default sort order is ascending by date taken.
98 | */
99 | val sortOrder = "$columnMediaDateAdded DESC"
100 |
101 | context.contentResolver.query(
102 | mediaUri,
103 | projection,
104 | selection,
105 | null,
106 | sortOrder
107 | )?.use { cursor ->
108 |
109 | /**
110 | * In order to retrieve the data from the [Cursor] that's returned, we need to
111 | * find which index matches each column that we're interested in.
112 | *
113 | * There are two ways to do this. The first is to use the method
114 | * [Cursor.getColumnIndex] which returns -1 if the column ID isn't found. This
115 | * is useful if the code is programmatically choosing which columns to request,
116 | * but would like to use a single method to parse them into objects.
117 | *
118 | * In our case, since we know exactly which columns we'd like, and we know
119 | * that they must be included (since they're all supported from API 1), we'll
120 | * use [Cursor.getColumnIndexOrThrow]. This method will throw an
121 | * [IllegalArgumentException] if the column named isn't found.
122 | *
123 | * In either case, while this method isn't slow, we'll want to cache the results
124 | * to avoid having to look them up for each row.
125 | */
126 |
127 | val indexColMediaId = cursor.getColumnIndexOrThrow(columnMediaId)
128 | val indexColMediaDateAdded = cursor.getColumnIndexOrThrow(columnMediaDateAdded)
129 | val indexColMediaName = cursor.getColumnIndexOrThrow(columnMediaName)
130 | val indexColBucketName = cursor.getColumnIndexOrThrow(columnBucketName)
131 | val indexColBucketId = cursor.getColumnIndexOrThrow(columnBucketId)
132 |
133 |
134 | while (cursor.moveToNext()) {
135 |
136 | try{
137 | // Here we'll use the column index that we found above.
138 | val mediaId = cursor.getLong(indexColMediaId)
139 | val dateModified = Date(TimeUnit.SECONDS.toMillis(cursor.getLong(indexColMediaDateAdded)))
140 | val mediaName = cursor.getString(indexColMediaName)
141 | val bucketName = cursor.getString(indexColBucketName)
142 | val bucketId = cursor.getLong(indexColBucketId)
143 |
144 |
145 | //Log.e(TAG, "Album name --> $bucketName Album Id --> $bucketId")
146 |
147 |
148 | /**
149 | * This is one of the trickiest parts:
150 | *
151 | * Since we're accessing images (using
152 | * [MediaStore.Images.Media.EXTERNAL_CONTENT_URI], we'll use that
153 | * as the base URI and append the ID of the image to it.
154 | *
155 | * This is the exact same way to do it when working with [MediaStore.Video] and
156 | * [MediaStore.Audio] as well. Whatever `Media.EXTERNAL_CONTENT_URI` you
157 | * query to get the items is the base, and the ID is the document to
158 | * request there.
159 | */
160 | val contentUri = ContentUris.withAppendedId(mediaUri, mediaId)
161 | val image = MediaStoreData(
162 | media_id = mediaId,
163 | bucket_id = bucketId,
164 | media_name = mediaName,
165 | media_type = GeeksMediaType.IMAGE,
166 | bucket_name = bucketName,
167 | date_added = dateModified,
168 | content_uri = contentUri
169 | )
170 | images.add(image)
171 |
172 | // For debugging, we'll output the image objects we create to logcat.
173 | //Log.v(TAG, "Added image: $image")
174 | }catch (e: Exception){
175 | e.printStackTrace()
176 | Log.e("Exception", "Message -> ${e.localizedMessage}")
177 | }
178 | }
179 | }
180 | }
181 |
182 | //Log.v(TAG, "Found ${images.size} images")
183 | return images
184 | }
185 |
186 | private suspend fun queryVideos(context: Context): List {
187 | val videos = mutableListOf()
188 |
189 | /**
190 | * Working with [ContentResolver]s can be slow, so we'll do this off the main
191 | * thread inside a coroutine.
192 | */
193 | withContext(Dispatchers.IO) {
194 |
195 | /**
196 | * A key concept when working with Android [ContentProvider]s is something called
197 | * "projections". A projection is the list of columns to request from the provider,
198 | * and can be thought of (quite accurately) as the "SELECT ..." clause of a SQL
199 | * statement.
200 | *
201 | * It's not _required_ to provide a projection. In this case, one could pass `null`
202 | * in place of `projection` in the call to [ContentResolver.query], but requesting
203 | * more data than is required has a performance impact.
204 | *
205 | * For this sample, we only use a few columns of data, and so we'll request just a
206 | * subset of columns.
207 | */
208 |
209 | val mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
210 | val columnMediaId = MediaStore.Video.Media._ID
211 | val columnMediaName = MediaStore.Video.Media.DISPLAY_NAME
212 | val columnBucketName = MediaStore.Video.Media.BUCKET_DISPLAY_NAME
213 | val columnBucketId = MediaStore.Video.Media.BUCKET_ID
214 | val columnMediaDateAdded = MediaStore.Video.Media.DATE_ADDED
215 | val columnMediaDuration = MediaStore.Video.Media.DURATION
216 |
217 |
218 | val projection = arrayOf(
219 | columnMediaId,
220 | columnMediaName,
221 | columnBucketId,
222 | columnBucketName,
223 | columnMediaDateAdded,
224 | columnMediaDuration
225 | )
226 |
227 | /**
228 | * The `selection` is the "WHERE ..." clause of a SQL statement. It's also possible
229 | * to omit this by passing `null` in its place, and then all rows will be returned.
230 | * In this case we're using a selection based on the date the image was taken.
231 | *
232 | * Note that we've included a `?` in our selection. This stands in for a variable
233 | * which will be provided by the next variable.
234 | */
235 | val selection = MediaStore.Images.Media.SIZE + " > 0"
236 |
237 | /**
238 | * The `selectionArgs` is a list of values that will be filled in for each `?`
239 | * in the `selection`.
240 | */
241 | /*val selectionArgs = arrayOf(
242 | // Release day of the G1. :)
243 | dateToTimestamp(day = 22, month = 10, year = 2008).toString()
244 | )*/
245 |
246 | /**
247 | * Sort order to use. This can also be null, which will use the default sort
248 | * order. For [MediaStore.Images], the default sort order is ascending by date taken.
249 | */
250 | val sortOrder = "$columnMediaDateAdded DESC"
251 |
252 | context.contentResolver.query(
253 | mediaUri,
254 | projection,
255 | selection,
256 | null,
257 | sortOrder
258 | )?.use { cursor ->
259 |
260 | /**
261 | * In order to retrieve the data from the [Cursor] that's returned, we need to
262 | * find which index matches each column that we're interested in.
263 | *
264 | * There are two ways to do this. The first is to use the method
265 | * [Cursor.getColumnIndex] which returns -1 if the column ID isn't found. This
266 | * is useful if the code is programmatically choosing which columns to request,
267 | * but would like to use a single method to parse them into objects.
268 | *
269 | * In our case, since we know exactly which columns we'd like, and we know
270 | * that they must be included (since they're all supported from API 1), we'll
271 | * use [Cursor.getColumnIndexOrThrow]. This method will throw an
272 | * [IllegalArgumentException] if the column named isn't found.
273 | *
274 | * In either case, while this method isn't slow, we'll want to cache the results
275 | * to avoid having to look them up for each row.
276 | */
277 |
278 | val indexColMediaId = cursor.getColumnIndexOrThrow(columnMediaId)
279 | val indexColMediaDateAdded = cursor.getColumnIndexOrThrow(columnMediaDateAdded)
280 | val indexColMediaName = cursor.getColumnIndexOrThrow(columnMediaName)
281 | val indexColBucketName = cursor.getColumnIndexOrThrow(columnBucketName)
282 | val indexColBucketId = cursor.getColumnIndexOrThrow(columnBucketId)
283 | val indexColDuration = cursor.getColumnIndexOrThrow(columnMediaDuration)
284 |
285 |
286 | while (cursor.moveToNext()) {
287 |
288 | try{
289 | // Here we'll use the column index that we found above.
290 | val mediaId = cursor.getLong(indexColMediaId)
291 | val dateModified = Date(TimeUnit.SECONDS.toMillis(cursor.getLong(indexColMediaDateAdded)))
292 | val mediaName = cursor.getString(indexColMediaName)
293 | val bucketName = cursor.getString(indexColBucketName)
294 | val bucketId = cursor.getLong(indexColBucketId)
295 | val mediaDuration = cursor.getLong(indexColDuration)
296 |
297 |
298 | //Log.e(TAG, "Album name --> $bucketName Album Id --> $bucketId")
299 |
300 |
301 | /**
302 | * This is one of the trickiest parts:
303 | *
304 | * Since we're accessing images (using
305 | * [MediaStore.Images.Media.EXTERNAL_CONTENT_URI], we'll use that
306 | * as the base URI and append the ID of the image to it.
307 | *
308 | * This is the exact same way to do it when working with [MediaStore.Video] and
309 | * [MediaStore.Audio] as well. Whatever `Media.EXTERNAL_CONTENT_URI` you
310 | * query to get the items is the base, and the ID is the document to
311 | * request there.
312 | */
313 | val contentUri = ContentUris.withAppendedId(mediaUri, mediaId)
314 | val video = MediaStoreData(
315 | media_id = mediaId,
316 | bucket_id = bucketId,
317 | media_name = mediaName,
318 | media_type = GeeksMediaType.VIDEO,
319 | bucket_name = bucketName,
320 | date_added = dateModified,
321 | content_uri = contentUri,
322 | media_duration = mediaDuration
323 | )
324 | videos.add(video)
325 |
326 | // For debugging, we'll output the image objects we create to logcat.
327 | //Log.v(TAG, "Added image: $image")
328 | }catch (e: Exception){
329 | e.printStackTrace()
330 | Log.e("Exception", "Message -> ${e.localizedMessage}")
331 | }
332 | }
333 | }
334 | }
335 |
336 | //Log.v(TAG, "Found ${videos.size} Videos")
337 | return videos
338 | }
339 | }
--------------------------------------------------------------------------------