├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── app │ │ └── image │ │ └── selection │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── app │ │ │ └── image │ │ │ └── selection │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── app │ └── image │ └── selection │ └── ExampleUnitTest.kt ├── assets ├── preview-ui-2.jpeg ├── preview-ui.jpeg ├── preview1.jpg └── preview2.jpg ├── build.gradle ├── customimageselection ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── app │ │ └── image │ │ └── custom │ │ └── image │ │ └── selection │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── app │ │ │ └── image │ │ │ └── custom │ │ │ └── image │ │ │ └── selection │ │ │ ├── OpenGallery.kt │ │ │ ├── activites │ │ │ └── ActAlbum.kt │ │ │ ├── adapters │ │ │ ├── AdaptFragPager.kt │ │ │ ├── AdapterFolder.kt │ │ │ └── AdapterFolderImages.kt │ │ │ ├── fragments │ │ │ └── FragFolder.kt │ │ │ └── util │ │ │ ├── Extensions.kt │ │ │ ├── ImagesHelper.kt │ │ │ └── ModelImages.kt │ └── res │ │ ├── drawable │ │ ├── ic_check.xml │ │ ├── ic_done.xml │ │ └── ic_play.xml │ │ ├── layout │ │ ├── act_album.xml │ │ ├── cell_album_pic.xml │ │ ├── cell_folder.xml │ │ └── frag_folder.xml │ │ └── values │ │ └── colors.xml │ └── test │ └── java │ └── com │ └── app │ └── image │ └── custom │ └── image │ └── selection │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Custom Image Selection -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 140 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # custom-image-selection 2 | 3 | 4 | [![](https://jitpack.io/v/sz32/custom-image-selection.svg)](https://jitpack.io/#sz32/custom-image-selection) 5 | 6 | Download APK from Release and give it a try 7 | 8 | 9 | How to 10 | To get a Git project into your build: 11 | 12 | 13 | Step 1. Add the JitPack repository to your build file 14 | 15 | Add it in your root build.gradle at the end of repositories: 16 | 17 | ``` 18 | allprojects { 19 | repositories { 20 | maven { url 'https://jitpack.io' } 21 | } 22 | } 23 | ``` 24 | 25 | Step 2. Add the dependency 26 | 27 | ``` 28 | dependencies { 29 | implementation 'com.github.sz32:custom-image-selection:Tag' 30 | } 31 | ``` 32 | 33 | How to use: 34 | 35 | 1. In your activity 36 | ``` 37 | OpenGallery 38 | .init(this) 39 | .isSelectMultiple(true) 40 | .build() 41 | ``` 42 | 2. Get result : 43 | 44 | ``` 45 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 46 | super.onActivityResult(requestCode, resultCode, data) 47 | if (data?.extras != null) { 48 | toast((data.extras?.getSerializable("DATA") as ArrayList).size.toString()) 49 | } 50 | } 51 | ``` 52 | ![](assets/preview-ui.jpeg) 53 | ![](assets/preview-ui-2.jpeg) 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 30 8 | buildToolsVersion "30.0.2" 9 | 10 | defaultConfig { 11 | applicationId "com.app.image.selection" 12 | minSdkVersion 21 13 | targetSdkVersion 30 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 38 | implementation 'androidx.core:core-ktx:1.3.2' 39 | implementation 'androidx.appcompat:appcompat:1.2.0' 40 | implementation 'com.google.android.material:material:1.2.1' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 42 | implementation project(path: ':customimageselection') 43 | testImplementation 'junit:junit:4.+' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 46 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/app/image/selection/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.selection 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.app.image.selection", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/image/selection/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.selection 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.app.image.custom.image.selection.util.ImagePath 7 | import com.app.image.custom.image.selection.util.toast 8 | 9 | class MainActivity : AppCompatActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_main) 13 | 14 | 15 | com.app.image.custom.image.selection.OpenGallery 16 | .init(this) 17 | .isSelectMultiple(true) 18 | /*.setListener(object : OnImageSelectedResultListener { 19 | override fun onImageSelectedResultSuccess() { 20 | 21 | } 22 | 23 | override fun onImageSelectedResultFail(s: String) { 24 | 25 | } 26 | })*/.build() 27 | 28 | } 29 | 30 | 31 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 32 | super.onActivityResult(requestCode, resultCode, data) 33 | if (data?.extras != null) { 34 | toast((data.extras?.getSerializable("DATA") as ArrayList).size.toString()) 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Custom Image Selection 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/test/java/com/app/image/selection/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.selection 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 | } -------------------------------------------------------------------------------- /assets/preview-ui-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/assets/preview-ui-2.jpeg -------------------------------------------------------------------------------- /assets/preview-ui.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/assets/preview-ui.jpeg -------------------------------------------------------------------------------- /assets/preview1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/assets/preview1.jpg -------------------------------------------------------------------------------- /assets/preview2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/assets/preview2.jpg -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = "1.4.21" 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath "com.android.tools.build:gradle:4.1.2" 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } -------------------------------------------------------------------------------- /customimageselection/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /customimageselection/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | } 6 | 7 | android { 8 | compileSdkVersion 30 9 | buildToolsVersion "30.0.2" 10 | 11 | defaultConfig { 12 | minSdkVersion 21 13 | targetSdkVersion 30 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | consumerProguardFiles "consumer-rules.pro" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 39 | implementation 'androidx.core:core-ktx:1.3.2' 40 | implementation 'androidx.appcompat:appcompat:1.2.0' 41 | implementation 'com.google.android.material:material:1.2.1' 42 | testImplementation 'junit:junit:4.+' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 45 | 46 | implementation 'com.github.bumptech.glide:glide:4.11.0' 47 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' 48 | 49 | implementation 'com.intuit.sdp:sdp-android:1.0.6' 50 | implementation 'com.intuit.ssp:ssp-android:1.0.6' 51 | 52 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2' 53 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' 54 | 55 | implementation 'com.nabinbhandari.android:permissions:3.8' 56 | 57 | implementation 'com.eftimoff:android-viewpager-transformers:1.0.1@aar' 58 | } -------------------------------------------------------------------------------- /customimageselection/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/customimageselection/consumer-rules.pro -------------------------------------------------------------------------------- /customimageselection/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 -------------------------------------------------------------------------------- /customimageselection/src/androidTest/java/com/app/image/custom/image/selection/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection 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.app.image.custom.image.selection.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /customimageselection/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/OpenGallery.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import com.app.image.custom.image.selection.activites.ActAlbum 6 | 7 | object OpenGallery { 8 | 9 | private var boolean: Boolean = false 10 | private var context: Activity? = null 11 | private var onImageSelectedResultListener: OnImageSelectedResultListener? = null 12 | 13 | fun init(context: Activity?): OpenGallery { 14 | this.context = context 15 | return this 16 | } 17 | 18 | 19 | fun isSelectMultiple(boolean: Boolean): OpenGallery { 20 | this.boolean = boolean 21 | return this 22 | } 23 | 24 | /*fun setListener(onImageSelectedResultListener: OnImageSelectedResultListener): OpenGallery { 25 | this.onImageSelectedResultListener = onImageSelectedResultListener 26 | return this 27 | }*/ 28 | 29 | fun build() { 30 | context?.startActivityForResult( 31 | Intent(context, ActAlbum::class.java) 32 | .putExtra("isMultiple", boolean), 0 33 | ) 34 | } 35 | 36 | 37 | } 38 | 39 | interface OnImageSelectedResultListener { 40 | fun onImageSelectedResultSuccess() 41 | fun onImageSelectedResultFail(s: String) 42 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/activites/ActAlbum.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.activites 2 | 3 | import android.Manifest.* 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Bundle 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.fragment.app.Fragment 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import androidx.viewpager.widget.ViewPager 12 | import com.app.image.custom.image.selection.R 13 | import com.app.image.custom.image.selection.adapters.AdaptFragPager 14 | import com.app.image.custom.image.selection.adapters.AdapterFolder 15 | import com.app.image.custom.image.selection.fragments.FragFolder 16 | import com.app.image.custom.image.selection.fragments.OnFragmentImageSelectedListener 17 | import com.app.image.custom.image.selection.util.ImagePath 18 | import com.app.image.custom.image.selection.util.ImagesHelper 19 | import com.app.image.custom.image.selection.util.ModelImages 20 | import com.eftimoff.viewpagertransformers.ForegroundToBackgroundTransformer 21 | import com.nabinbhandari.android.permissions.PermissionHandler 22 | import com.nabinbhandari.android.permissions.Permissions 23 | import kotlinx.android.synthetic.main.act_album.* 24 | import kotlinx.android.synthetic.main.act_album.view.* 25 | import kotlinx.android.synthetic.main.frag_folder.view.* 26 | import java.util.* 27 | 28 | 29 | class ActAlbum : AppCompatActivity(), OnFragmentImageSelectedListener { 30 | 31 | 32 | private var allImages: ArrayList? = arrayListOf() 33 | private var adaptFolder: AdapterFolder? = null 34 | private var curPos: Int = 0 35 | private var adaptFragPager: AdaptFragPager? = null 36 | private var selectedImageList: ArrayList = arrayListOf() 37 | 38 | companion object { 39 | var isMultiple = false 40 | } 41 | 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | setContentView(R.layout.act_album) 45 | 46 | isMultiple = intent.getBooleanExtra("isMultiple", false) 47 | 48 | Permissions.check(this, arrayOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE), null, null, object : PermissionHandler() { 49 | @SuppressLint("MissingPermission") 50 | override fun onGranted() { 51 | val imagesHelper = ImagesHelper(this@ActAlbum) 52 | allImages = imagesHelper.getAllImages() 53 | initFragPager() 54 | initRecyFolder() 55 | } 56 | 57 | override fun onDenied(context: Context?, deniedPermissions: ArrayList?) { 58 | finish() 59 | } 60 | }) 61 | 62 | fabDone.setOnClickListener { 63 | val intent = Intent() 64 | intent.putExtra("DATA", selectedImageList) 65 | setResult(0, intent) 66 | finish() 67 | } 68 | 69 | } 70 | 71 | private fun initRecyFolder() { 72 | allImages?.getOrNull(0)?.isSelected = true 73 | adaptFolder = AdapterFolder(this@ActAlbum, allImages ?: arrayListOf()) { index, modelImages -> 74 | pager.setCurrentItem(index, true) 75 | if (modelImages?.isSelected == true) { 76 | pager?.recyImages?.smoothScrollToPosition(0) 77 | } 78 | } 79 | recyFolder?.apply { 80 | hasFixedSize() 81 | layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) 82 | adapter = adaptFolder 83 | } 84 | } 85 | 86 | private fun initFragPager() { 87 | val fragList = arrayListOf() 88 | 89 | allImages?.forEach { it -> 90 | fragList.add(FragFolder.newInstance().setModel(it).setListener(this)) 91 | } 92 | 93 | adaptFragPager = AdaptFragPager(supportFragmentManager, fragList) 94 | pager?.adapter = adaptFragPager 95 | pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { 96 | 97 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { 98 | 99 | } 100 | 101 | override fun onPageSelected(position: Int) { 102 | allImages?.get(curPos)?.isSelected = false 103 | curPos = position 104 | allImages?.get(curPos)?.isSelected = true 105 | recyFolder.smoothScrollToPosition(curPos) 106 | adaptFolder?.notifyDataSetChanged() 107 | } 108 | 109 | override fun onPageScrollStateChanged(state: Int) { 110 | } 111 | }) 112 | pager.setPageTransformer(true, ForegroundToBackgroundTransformer()) 113 | updateUI() 114 | } 115 | 116 | private fun updateUI() { 117 | if (selectedImageList.isEmpty()) { 118 | fabDone.hide() 119 | } else { 120 | fabDone.show() 121 | } 122 | } 123 | 124 | override fun onImageSelected(imagePath: ImagePath?) { 125 | selectedImageList.add(imagePath) 126 | updateUI() 127 | } 128 | 129 | override fun onImageDeSelected(imagePath: ImagePath?) { 130 | selectedImageList.remove(imagePath) 131 | updateUI() 132 | } 133 | 134 | override fun onSingleImageSelected(imagePath: ImagePath?) { 135 | selectedImageList.add(imagePath) 136 | fabDone.performClick() 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/adapters/AdaptFragPager.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.adapters 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentPagerAdapter 6 | import androidx.fragment.app.FragmentStatePagerAdapter 7 | import androidx.viewpager2.adapter.FragmentStateAdapter 8 | 9 | class AdaptFragPager(supportFragmentManager: FragmentManager, private var fragmentList:ArrayList ) : FragmentPagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { 10 | 11 | 12 | override fun getCount() = fragmentList.size 13 | 14 | override fun getItem(position: Int): Fragment { 15 | return fragmentList[position] 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/adapters/AdapterFolder.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.adapters 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.graphics.Typeface 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.app.image.custom.image.selection.R 12 | import com.app.image.custom.image.selection.util.ModelImages 13 | import kotlinx.android.synthetic.main.cell_folder.view.* 14 | 15 | class AdapterFolder(private var context: Context?, var imagePathList: ArrayList, var listener: (Int, ModelImages?) -> Unit) : RecyclerView.Adapter() { 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 18 | return FolderHolder(LayoutInflater.from(parent.context).inflate(R.layout.cell_folder, parent, false)) 19 | } 20 | 21 | override fun getItemCount(): Int { 22 | return imagePathList.size 23 | } 24 | 25 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 26 | (holder as FolderHolder).bindItems(imagePathList[position]) 27 | } 28 | 29 | inner class FolderHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 30 | 31 | @SuppressLint("SetTextI18n") 32 | fun bindItems(modelImages: ModelImages?) = with(itemView) { 33 | 34 | tvFolderName.text = modelImages?.strFolder 35 | if (modelImages?.isSelected == true) { 36 | tvFolderName?.apply { 37 | this.setTypeface(null, Typeface.BOLD) 38 | this.setTextColor(Color.BLACK) 39 | } 40 | } else { 41 | tvFolderName?.apply { 42 | this.setTypeface(null, Typeface.NORMAL) 43 | this.setTextColor(Color.LTGRAY) 44 | } 45 | } 46 | 47 | itemView.setOnClickListener { 48 | listener(adapterPosition, modelImages) 49 | } 50 | 51 | } 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/adapters/AdapterFolderImages.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.adapters 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.app.image.custom.image.selection.R 10 | import com.app.image.custom.image.selection.activites.ActAlbum 11 | import com.app.image.custom.image.selection.util.ImagePath 12 | import com.app.image.custom.image.selection.util.gone 13 | import com.app.image.custom.image.selection.util.isVideoFile 14 | import com.app.image.custom.image.selection.util.visible 15 | import com.bumptech.glide.Glide 16 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions 17 | import kotlinx.android.synthetic.main.cell_album_pic.view.* 18 | 19 | class AdapterFolderImages(private var context: Context?, var imagePathList: ArrayList?, val listener: (Int, ImagePath?,Boolean) -> Unit) : RecyclerView.Adapter() { 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 22 | return FolderHolder( 23 | LayoutInflater.from(parent.context).inflate(R.layout.cell_album_pic, parent, false) 24 | ) 25 | } 26 | 27 | override fun getItemCount(): Int { 28 | return imagePathList?.size ?: 0 29 | } 30 | 31 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 32 | (holder as FolderHolder).bindItems(imagePathList?.get(position)) 33 | } 34 | 35 | inner class FolderHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 36 | 37 | @SuppressLint("SetTextI18n") 38 | fun bindItems(imagePath: ImagePath?) = with(itemView) { 39 | 40 | if (isVideoFile("file://${imagePath?.imagePath}")) { 41 | ivPlayIcon?.visible() 42 | } else { 43 | ivPlayIcon?.gone() 44 | } 45 | 46 | if (ActAlbum.isMultiple) { 47 | if (imagePath?.isSelected == true) { 48 | ivCheck.visible() 49 | } else { 50 | ivCheck.gone() 51 | } 52 | itemView.setOnClickListener { 53 | listener(adapterPosition, imagePath,true) 54 | } 55 | }else{ 56 | itemView.setOnClickListener { 57 | listener(adapterPosition, imagePath,false) 58 | } 59 | } 60 | 61 | Glide.with(context) 62 | .load("file://" + imagePath?.imagePath) 63 | .override(500, 500) 64 | .thumbnail(0.3f) 65 | .transition(DrawableTransitionOptions.withCrossFade()) 66 | .into(ivAlbumImage) 67 | 68 | } 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/fragments/FragFolder.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.recyclerview.widget.GridLayoutManager 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import com.app.image.custom.image.selection.R 11 | import com.app.image.custom.image.selection.adapters.AdapterFolderImages 12 | import com.app.image.custom.image.selection.util.ImagePath 13 | import com.app.image.custom.image.selection.util.ModelImages 14 | import kotlinx.android.synthetic.main.frag_folder.* 15 | 16 | class FragFolder : Fragment() { 17 | 18 | 19 | private var onFragmentImageSelectedListener: OnFragmentImageSelectedListener? = null 20 | private var adapterAlbum: AdapterFolderImages? = null 21 | private var modelImages: ModelImages? = null 22 | 23 | companion object { 24 | fun newInstance(): FragFolder { 25 | return FragFolder() 26 | } 27 | } 28 | 29 | fun setModel(modelImages: ModelImages): FragFolder { 30 | this.modelImages = modelImages 31 | return this 32 | } 33 | 34 | fun setListener(onFragmentImageSelectedListener: OnFragmentImageSelectedListener): FragFolder { 35 | this.onFragmentImageSelectedListener = onFragmentImageSelectedListener 36 | return this 37 | } 38 | 39 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 40 | super.onCreateView(inflater, container, savedInstanceState) 41 | return inflater.inflate(R.layout.frag_folder, container, false) 42 | } 43 | 44 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 45 | super.onViewCreated(view, savedInstanceState) 46 | setupView() 47 | } 48 | 49 | private fun setupView() { 50 | initRecy() 51 | } 52 | 53 | private fun initRecy() { 54 | adapterAlbum = AdapterFolderImages(context, modelImages?.allImagePath) { index, it, isMultiple -> 55 | 56 | if (!isMultiple) { 57 | onFragmentImageSelectedListener?.onSingleImageSelected(it) 58 | return@AdapterFolderImages 59 | } 60 | 61 | val modelImage = modelImages?.allImagePath?.get(index) 62 | modelImage?.isSelected = !modelImage?.isSelected!! 63 | if (modelImage.isSelected == true) { 64 | onFragmentImageSelectedListener?.onImageSelected(it) 65 | } else { 66 | onFragmentImageSelectedListener?.onImageDeSelected(it) 67 | } 68 | adapterAlbum?.notifyDataSetChanged() 69 | } 70 | recyImages?.apply { 71 | hasFixedSize() 72 | layoutManager = GridLayoutManager(context, 2, LinearLayoutManager.VERTICAL, false) 73 | adapter = adapterAlbum 74 | } 75 | } 76 | 77 | } 78 | 79 | interface OnFragmentImageSelectedListener { 80 | fun onImageSelected(imagePath: ImagePath?) 81 | fun onSingleImageSelected(imagePath: ImagePath?) 82 | fun onImageDeSelected(imagePath: ImagePath?) 83 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.util 2 | 3 | import android.app.Activity 4 | import android.view.View 5 | import android.widget.Toast 6 | import java.net.URLConnection 7 | 8 | private var toast: Toast? = null 9 | 10 | fun isImageFile(path: String?): Boolean { 11 | val mimeType: String? = URLConnection.guessContentTypeFromName(path) 12 | return mimeType?.startsWith("image") ?: false 13 | } 14 | 15 | fun isVideoFile(path: String?): Boolean { 16 | val mimeType: String? = URLConnection.guessContentTypeFromName(path) 17 | return mimeType?.startsWith("video") ?: false 18 | } 19 | 20 | fun View.visible() { 21 | this.visibility = View.VISIBLE 22 | } 23 | 24 | fun View.gone() { 25 | this.visibility = View.GONE 26 | } 27 | 28 | fun View.invisible() { 29 | this.visibility = View.INVISIBLE 30 | } 31 | 32 | fun Activity.toast(s: String) { 33 | if (toast != null) { 34 | toast?.cancel() 35 | } 36 | toast = Toast.makeText(this, s, Toast.LENGTH_LONG) 37 | toast?.show() 38 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/util/ImagesHelper.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.util 2 | 3 | import android.Manifest.permission.READ_EXTERNAL_STORAGE 4 | import android.Manifest.permission.WRITE_EXTERNAL_STORAGE 5 | import android.app.Activity 6 | import android.database.Cursor 7 | import android.net.Uri 8 | import android.provider.MediaStore 9 | import androidx.annotation.RequiresPermission 10 | 11 | class ImagesHelper(private val context: Activity) { 12 | 13 | private var allImages: ArrayList = arrayListOf() 14 | private var booleanFolder = false 15 | 16 | init { 17 | getAllImagesPath() 18 | } 19 | 20 | @RequiresPermission(allOf = [READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE]) 21 | fun getAllImages(): ArrayList { 22 | return allImages 23 | } 24 | 25 | private fun getAllImagesPath(): ArrayList? { 26 | allImages = arrayListOf() 27 | allImages.clear() 28 | var intPosition = 0 29 | val cursor: Cursor? 30 | val columnIndexData: Int 31 | val columnIndexFolderName: Int 32 | var absolutePathOfImage: String? 33 | val uri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI 34 | val projection = arrayOf(MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME) 35 | val orderBy: String = MediaStore.Images.Media.DATE_TAKEN 36 | cursor = context.contentResolver.query(uri, projection, null, null, "$orderBy DESC") 37 | columnIndexData = cursor?.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)!! 38 | columnIndexFolderName = 39 | cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME) 40 | while (cursor.moveToNext()) { 41 | if (!cursor.getString(columnIndexData).contains("0/Android/")) { 42 | absolutePathOfImage = cursor.getString(columnIndexData) 43 | for (i in 0 until allImages.size) { 44 | if (allImages[i].strFolder == cursor.getString(columnIndexFolderName)) { 45 | booleanFolder = true 46 | intPosition = i 47 | break 48 | } else { 49 | booleanFolder = false 50 | } 51 | } 52 | if (booleanFolder) { 53 | val allPath: ArrayList = ArrayList() 54 | allPath.addAll(allImages[intPosition].allImagePath!!) 55 | allPath.add(ImagePath(absolutePathOfImage, false)) 56 | allImages[intPosition].allImagePath = allPath 57 | } else { 58 | val allPath: ArrayList = ArrayList() 59 | allPath.add(ImagePath(absolutePathOfImage, false)) 60 | val modelImages = ModelImages(cursor.getString(columnIndexFolderName), allPath) 61 | allImages.add(modelImages) 62 | } 63 | } 64 | 65 | } 66 | allImages.add(0, ModelImages("Recent", getRecentImages())) 67 | allImages.add((allImages.size), ModelImages("Video", getAllVideos())) 68 | return allImages 69 | } 70 | 71 | private fun getRecentImages(): ArrayList { 72 | val recentImageUrls: ArrayList = ArrayList() 73 | val columns = arrayOf( 74 | MediaStore.Images.Media.DATA, 75 | MediaStore.Images.Media._ID 76 | ) //get all columns of type images 77 | val orderBy = MediaStore.Images.Media.DATE_TAKEN //order data by date 78 | val imagecursor: Cursor = context.managedQuery(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, "$orderBy DESC") 79 | for (i in 0 until imagecursor.count) { 80 | imagecursor.moveToPosition(i) 81 | val dataColumnIndex: Int = imagecursor.getColumnIndex(MediaStore.Images.Media.DATA) //get column index 82 | if (!imagecursor.getString(dataColumnIndex).toString().contains("0/Android/")) { 83 | recentImageUrls.add(ImagePath(imagecursor.getString(dataColumnIndex), false)) //get Image from column index 84 | } 85 | } 86 | return recentImageUrls 87 | } 88 | 89 | private fun getAllVideos(): ArrayList { 90 | val recentImageUrls: ArrayList = ArrayList() 91 | val columns = arrayOf(MediaStore.Video.Media.DATA, MediaStore.Video.Media._ID) //get all columns of type images 92 | val orderBy = MediaStore.Video.Media.DATE_TAKEN //order data by date 93 | val imageCursor: Cursor = context.managedQuery(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, columns, null, null, "$orderBy DESC") 94 | for (i in 0 until imageCursor.count) { 95 | imageCursor.moveToPosition(i) 96 | val dataColumnIndex: Int = imageCursor.getColumnIndex(MediaStore.Video.Media.DATA) //get column index 97 | recentImageUrls.add(ImagePath(imageCursor.getString(dataColumnIndex), false)) //get Image from column index 98 | } 99 | return recentImageUrls 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /customimageselection/src/main/java/com/app/image/custom/image/selection/util/ModelImages.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection.util 2 | 3 | import java.io.Serializable 4 | 5 | data class ModelImages( 6 | var strFolder: String? = null, 7 | var allImagePath: ArrayList? = null, 8 | var isSelected: Boolean = false 9 | ):Serializable 10 | 11 | data class ImagePath( 12 | val imagePath: String? = "", 13 | var isSelected: Boolean? = false 14 | ):Serializable -------------------------------------------------------------------------------- /customimageselection/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/layout/act_album.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 21 | 22 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/layout/cell_album_pic.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 | 26 | 27 | 35 | 36 | 46 | 47 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/layout/cell_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/layout/frag_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /customimageselection/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /customimageselection/src/test/java/com/app/image/custom/image/selection/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.app.image.custom.image.selection 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 | } -------------------------------------------------------------------------------- /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=-Xmx2048m -Dfile.encoding=UTF-8 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 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sz32/custom-image-selection/90b0d1750ea18ff6f1beb50e75f2236b688781ac/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 26 23:07:20 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-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':customimageselection' 2 | include ':app' 3 | rootProject.name = "Custom Image Selection" --------------------------------------------------------------------------------