├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── mediapicker
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── crazylegend
│ │ │ └── mediapicker
│ │ │ ├── FragmentResult.kt
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_checked.xml
│ │ ├── ic_close.xml
│ │ ├── ic_launcher_background.xml
│ │ └── ic_unchecked.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
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── crazylegend
│ └── mediapicker
│ └── ExampleUnitTest.kt
├── audiopicker
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
├── screens
│ ├── screen_1.png
│ └── screen_3.png
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── audiopicker
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── crazylegend
│ │ │ └── audiopicker
│ │ │ ├── adapters
│ │ │ ├── AudioDiffUtil.kt
│ │ │ ├── multi
│ │ │ │ ├── AudioMultiSelectAdapter.kt
│ │ │ │ └── AudioMultiSelectViewHolder.kt
│ │ │ └── single
│ │ │ │ ├── AudioSingleAdapter.kt
│ │ │ │ └── AudioSingleViewHolder.kt
│ │ │ ├── audios
│ │ │ ├── AudioModel.kt
│ │ │ └── AudiosVM.kt
│ │ │ ├── consts
│ │ │ └── DialogConsts.kt
│ │ │ ├── contracts
│ │ │ ├── MultiPickerContracts.kt
│ │ │ └── SinglePickerContracts.kt
│ │ │ ├── dialogs
│ │ │ ├── multi
│ │ │ │ └── MultiAudioPickerBottomSheetDialog.kt
│ │ │ └── single
│ │ │ │ └── SingleAudioPickerBottomSheetDialog.kt
│ │ │ ├── listeners
│ │ │ ├── onAudioPicked.kt
│ │ │ ├── onAudiosPicked.kt
│ │ │ └── onShouldRecycleBitmaps.kt
│ │ │ ├── modifiers
│ │ │ ├── MultiAudioPickerModifier.kt
│ │ │ └── SingleAudioPickerModifier.kt
│ │ │ └── pickers
│ │ │ ├── MultiAudioPicker.kt
│ │ │ └── SingleAudioPicker.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_album.xml
│ │ └── ic_album_second.xml
│ │ └── layout
│ │ └── itemview_audio.xml
│ └── test
│ └── java
│ └── com
│ └── crazylegend
│ └── audiopicker
│ └── ExampleUnitTest.kt
├── build.gradle
├── core
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── core
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── crazylegend
│ │ │ └── core
│ │ │ ├── Extensions.kt
│ │ │ ├── abstracts
│ │ │ ├── AbstractAVM.kt
│ │ │ └── AbstractBottomSheetDialogFragment.kt
│ │ │ ├── adapters
│ │ │ ├── BaseViewHolder.kt
│ │ │ ├── multi
│ │ │ │ ├── MultiSelectAdapter.kt
│ │ │ │ └── MultiSelectViewHolder.kt
│ │ │ └── single
│ │ │ │ ├── SingleAdapter.kt
│ │ │ │ ├── SingleDiffUtil.kt
│ │ │ │ └── SingleViewHolder.kt
│ │ │ ├── consts
│ │ │ └── Consts.kt
│ │ │ ├── contracts
│ │ │ ├── BaseContractMultiPick.kt
│ │ │ └── BaseContractSinglePick.kt
│ │ │ ├── dto
│ │ │ ├── BaseCursorModel.kt
│ │ │ └── PickerConfig.kt
│ │ │ ├── modifiers
│ │ │ ├── SizeTextModifier.kt
│ │ │ ├── TitleTextModifier.kt
│ │ │ ├── base
│ │ │ │ ├── BaseMultiPickerModifier.kt
│ │ │ │ └── BaseSinglePickerModifier.kt
│ │ │ ├── multi
│ │ │ │ ├── DoneButtonModifier.kt
│ │ │ │ └── SelectIconModifier.kt
│ │ │ └── single
│ │ │ │ └── ImageModifier.kt
│ │ │ └── sorting
│ │ │ └── SortOrder.kt
│ └── res
│ │ ├── anim
│ │ ├── fragment_fade_enter.xml
│ │ └── fragment_fade_exit.xml
│ │ ├── drawable
│ │ ├── ic_checked_default.xml
│ │ ├── ic_close.xml
│ │ ├── ic_image.xml
│ │ ├── ic_minus.xml
│ │ ├── ic_unchecked_default.xml
│ │ └── rounded_bg_abstract_dialog.xml
│ │ ├── layout
│ │ ├── fragment_images_gallery_layout.xml
│ │ ├── fragment_images_gallery_layout_multi.xml
│ │ └── itemview_image.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── crazylegend
│ └── core
│ └── ExampleUnitTest.kt
├── extensions
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── extensions
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── extensions
│ │ ├── Extensions.kt
│ │ ├── FragmentViewBindingDelegate.kt
│ │ └── ViewBindingExtensions.kt
│ └── test
│ └── java
│ └── com
│ └── crazylegend
│ └── extensions
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── imagepicker
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
├── screens
│ ├── screen_1.png
│ └── screen_3.png
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── imagepicker
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── crazylegend
│ │ └── imagepicker
│ │ ├── consts
│ │ └── DialogConsts.kt
│ │ ├── contracts
│ │ ├── MultiPickerContracts.kt
│ │ └── SinglePickerContracts.kt
│ │ ├── dialogs
│ │ ├── multi
│ │ │ └── MultiImagePickerBottomSheetDialog.kt
│ │ └── single
│ │ │ └── SingleImagePickerBottomSheetDialog.kt
│ │ ├── images
│ │ ├── ImageModel.kt
│ │ └── ImagesVM.kt
│ │ ├── listeners
│ │ ├── onImagePicked.kt
│ │ └── onImagesPicked.kt
│ │ └── pickers
│ │ ├── MultiImagePicker.kt
│ │ └── SingleImagePicker.kt
│ └── test
│ └── java
│ └── com
│ └── crazylegend
│ └── imagepicker
│ └── ExampleUnitTest.kt
├── jitpack.yml
├── settings.gradle
└── videopicker
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
├── screens
├── screen_1.png
└── screen_3.png
└── src
├── androidTest
└── java
│ └── com
│ └── crazylegend
│ └── videopicker
│ └── ExampleInstrumentedTest.kt
├── main
├── AndroidManifest.xml
└── java
│ └── com
│ └── crazylegend
│ └── videopicker
│ ├── consts
│ └── DialogConsts.kt
│ ├── contracts
│ ├── MultiPickerContracts.kt
│ └── SinglePickerContracts.kt
│ ├── dialogs
│ ├── multi
│ │ └── MultiVideoPickerBottomSheetDialog.kt
│ └── single
│ │ └── SingleVideoPickerBottomSheetDialog.kt
│ ├── listeners
│ ├── onVideoPicked.kt
│ └── onVideosPicked.kt
│ ├── pickers
│ ├── MultiVideoPicker.kt
│ └── SingleVideoPicker.kt
│ └── videos
│ ├── VideoModel.kt
│ └── VideosVM.kt
└── test
└── java
└── com
└── crazylegend
└── videopicker
└── ExampleUnitTest.kt
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # MediaPicker
6 | ### Kotlin Android library to pick images, videos and audios
7 |
8 | 
9 |
10 | ## Usage
11 | 1. Add JitPack to your project build.gradle
12 |
13 | ```gradle
14 | allprojects {
15 | repositories {
16 | ...
17 | maven { url 'https://jitpack.io' }
18 | }
19 | }
20 | ```
21 |
22 | #### or
23 |
24 | If your Android studio version is Arctic Fox and above then add it in your settings.gradle:
25 |
26 | ```gradle
27 | dependencyResolutionManagement {
28 | repositories {
29 | ...
30 | maven { url 'https://jitpack.io' }
31 | }
32 | }
33 | ```
34 |
35 | 2. Add the dependency in the application build.gradle
36 |
37 | ```gradle
38 | dependencies {
39 | def pickerVersion = "1.0.0" //look-up the latest one on jitpack
40 |
41 | //images
42 | implementation "com.github.FunkyMuse.MediaPicker:imagepicker:$pickerVersion"
43 | //audios
44 | implementation "com.github.FunkyMuse.MediaPicker:audiopicker:$pickerVersion"
45 | //videos
46 | implementation "com.github.FunkyMuse.MediaPicker:videopicker:$pickerVersion"
47 | }
48 | ```
49 |
50 | 3. To not run into any issues in your application build.gradle add
51 |
52 | ```gradle
53 | compileOptions {
54 | sourceCompatibility = 1.8
55 | targetCompatibility = 1.8
56 | }
57 |
58 | kotlinOptions {
59 | jvmTarget = "1.8"
60 | }
61 |
62 | viewBinding {
63 | enabled = true
64 | }
65 | ```
66 |
67 | 4. How to use and screens
68 |
69 | [Image picker](https://github.com/FunkyMuse/MediaPicker/tree/master/imagepicker)
70 |
71 | [Video picker](https://github.com/FunkyMuse/MediaPicker/tree/master/videopicker)
72 |
73 | [Audio picker](https://github.com/FunkyMuse/MediaPicker/tree/master/audiopicker)
74 |
75 | [Sample app](https://github.com/FunkyMuse/MediaPicker/blob/master/app/src/main/java/com/crazylegend/mediapicker/MainActivity.kt)
76 |
77 | ## Contributing
78 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
79 |
80 | ## License
81 | [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
82 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 |
4 | testImplementation 'junit:junit:4.13.2'
5 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
6 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
7 |
8 | //glide
9 | implementation "com.github.bumptech.glide:glide:$glide"
10 | kapt "com.github.bumptech.glide:compiler:$glide"
11 |
12 | //core
13 | implementation "androidx.activity:activity-ktx:$activity"
14 | implementation "androidx.fragment:fragment-ktx:$fragment"
15 | implementation "androidx.appcompat:appcompat:$appCompat"
16 | implementation "androidx.core:core-ktx:$coreKTX"
17 |
18 | //ui
19 | implementation "androidx.constraintlayout:constraintlayout:$constraint_layout"
20 | implementation "androidx.recyclerview:recyclerview:$recycler"
21 | implementation "com.google.android.material:material:$material"
22 |
23 | //live data
24 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle"
25 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle"
26 | implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle"
27 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle"
28 | }
29 |
30 | android {
31 | namespace 'com.crazylegend.mediapicker'
32 | }
33 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/crazylegend/mediapicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.mediapicker
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.crazylegend.mediapicker", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
13 |
14 |
15 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/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_checked.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/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/drawable/ic_unchecked.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/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/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MediaPicker
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
13 |
14 |
17 |
18 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/test/java/com/crazylegend/mediapicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.mediapicker
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/audiopicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/audiopicker/README.md:
--------------------------------------------------------------------------------
1 | Current jitpack version: [](https://jitpack.io/#FunkyMuse/MediaPicker)
2 |
3 | 1. You must declare the permission in your manifest
4 | ```xml
5 |
6 | ```
7 | 2. If you haven't included the dependencies from the previous screen now's a good time
8 | ```gradle
9 | dependencies {
10 | def pickerVersion = "1.0.0" //look-up the latest one on jitpack
11 |
12 | //the core package is a must
13 | implementation "com.github.FunkyMuse.MediaPicker:core:$pickerVersion"
14 |
15 | //audios
16 | implementation "com.github.FunkyMuse.MediaPicker:audiopicker:$pickerVersion"
17 | }
18 | ```
19 | 3. How to use single picker and check out [how to customize single audio picker](https://github.com/FunkyMuse/MediaPicker/wiki/Single-audio-picker-customization)
20 | ```kotlin
21 | //simple usage without customization
22 | SingleAudioPicker.showPicker(this) {
23 | loadAudio(it)
24 | }
25 |
26 |
27 | //customized
28 | SingleAudioPicker.showPicker(this, {
29 | setupViewHolderTitleText {
30 | textColor = Color.BLACK
31 | textPadding = 10 // use dp or sp this is only for demonstration purposes
32 | }
33 | setupBaseModifier(
34 | loadingIndicatorColor = R.color.minusColor,
35 | titleTextModifications = {
36 | textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
37 | textStyle = TitleTextModifier.TextStyle.ITALIC
38 | textColor = Color.BLACK
39 | marginBottom = 30 // use dp or sp this is only for demonstration purposes
40 | textPadding = 5 // use dp or sp this is only for demonstration purposes
41 | textSize = 30f // use sp this is only for demonstration purposes
42 | textString = "Pick an audio"
43 | },
44 | placeHolderModifications = {
45 | resID = R.drawable.ic_image
46 | }
47 | )
48 | }, ::loadAudio)
49 |
50 | ```
51 |
52 | 4. How to use multi picker and check out [how to customize multi audio picker](https://github.com/FunkyMuse/MediaPicker/wiki/Multi-audio-picker-customization)
53 | ```kotlin
54 | //simple usage without customization
55 | MultiAudioPicker.showPicker(this) {
56 | loadAudios(it)
57 | }
58 |
59 |
60 | //customized
61 | MultiAudioPicker.showPicker(this, {
62 | setupViewHolderTitleText {
63 | textColor = ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryDark)
64 | textStyle = TitleTextModifier.TextStyle.BOLD
65 | textPadding = 10 // use dp or sp this is only for demonstration purposes
66 | }
67 | setupBaseMultiPicker(
68 | tintForLoadingProgressBar = ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryDark),
69 | gravityForSelectAndUnSelectIndicators = BaseMultiPickerModifier.Gravity.BOTTOM_LEFT,
70 | titleModifications = {
71 | textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
72 | textStyle = TitleTextModifier.TextStyle.ITALIC
73 | textColor = Color.BLACK
74 | marginBottom = 30 // use dp or sp this is only for demonstration purposes
75 | textPadding = 5 // use dp or sp this is only for demonstration purposes
76 | textSize = 30f // use sp this is only for demonstration purposes
77 | textString = "Pick multiple songs"
78 | },
79 | selectIconModifications = {
80 | resID = R.drawable.ic_checked
81 | tint = Color.BLACK
82 | },
83 | unSelectIconModifications = {
84 | resID = R.drawable.ic_unchecked
85 | tint = Color.BLACK
86 | },
87 | viewHolderPlaceholderModifications = {
88 | resID = R.drawable.ic_album_second
89 | }
90 | )
91 | }, ::doSomethingWithAudioList)
92 | ```
93 |
94 | If you're using Fragments to call the pickers you can leverage [set fragment result listener](https://developer.android.com/reference/androidx/fragment/app/FragmentManager#setfragmentresultlistener) to get back the result and you don't have to restore the listener nor provide a lambda for the listener, it can be as simple as
95 | ```kotlin
96 | SingleAudioPicker.showPicker(requireContext())
97 | ```
98 | ```kotlin
99 |
100 | setFragmentResultListener(SingleAudioPicker.SINGLE_AUDIO_REQUEST_KEY) { _, bundle ->
101 | val loadedModel = bundle.getParcelable(SingleAudioPicker.ON_SINGLE_AUDIO_PICK_KEY)
102 | loadedModel?.let { loadAudio(it) }
103 | }
104 |
105 | setFragmentResultListener(MultiAudioPicker.MULTI_AUDIO_REQUEST_KEY) { _, bundle ->
106 | val loadedModel = bundle.getParcelableArrayList(MultiAudioPicker.ON_MULTI_AUDIO_PICK_KEY)
107 | loadedModel?.let { doSomethingWithAudioList(it) }
108 | }
109 | ```
110 | ##
111 | If you're still not sure how to use, take a look at the [Sample app](https://github.com/FunkyMuse/MediaPicker/blob/master/app/src/main/java/com/crazylegend/mediapicker/MainActivity.kt)
112 |
113 | ##
114 | If you're still not sure how to use fragment listener, take a look at the [Sample app](https://github.com/FunkyMuse/MediaPicker/blob/master/app/src/main/java/com/crazylegend/mediapicker/FragmentResult.kt#L330)
115 |
116 | ## Screens
117 |
118 | Single picker
119 |
120 |
121 |
122 | Multi picker
123 |
124 |
125 |
--------------------------------------------------------------------------------
/audiopicker/build.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | namespace 'com.crazylegend.audiopicker'
3 | }
--------------------------------------------------------------------------------
/audiopicker/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/audiopicker/consumer-rules.pro
--------------------------------------------------------------------------------
/audiopicker/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 |
--------------------------------------------------------------------------------
/audiopicker/screens/screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/audiopicker/screens/screen_1.png
--------------------------------------------------------------------------------
/audiopicker/screens/screen_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/audiopicker/screens/screen_3.png
--------------------------------------------------------------------------------
/audiopicker/src/androidTest/java/com/crazylegend/audiopicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.crazylegend.audiopicker.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/audiopicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/adapters/AudioDiffUtil.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.adapters
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import com.crazylegend.audiopicker.audios.AudioModel
5 |
6 |
7 | /**
8 | * Created by crazy on 5/11/20 to long live and prosper !
9 | */
10 | internal class AudioDiffUtil : DiffUtil.ItemCallback() {
11 | override fun areItemsTheSame(oldItem: AudioModel, newItem: AudioModel) =
12 | oldItem.id == newItem.id
13 |
14 | override fun areContentsTheSame(oldItem: AudioModel, newItem: AudioModel) =
15 | (oldItem.id == newItem.id) && (oldItem.isSelected == newItem.isSelected)
16 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/adapters/multi/AudioMultiSelectAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.adapters.multi
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.ListAdapter
5 | import com.crazylegend.audiopicker.adapters.AudioDiffUtil
6 | import com.crazylegend.audiopicker.audios.AudioModel
7 | import com.crazylegend.audiopicker.databinding.ItemviewAudioBinding
8 | import com.crazylegend.core.inflater
9 | import com.crazylegend.core.modifiers.TitleTextModifier
10 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
11 | import com.crazylegend.core.modifiers.single.ImageModifier
12 |
13 |
14 | /**
15 | * Created by crazy on 5/8/20 to long live and prosper !
16 | */
17 | internal class AudioMultiSelectAdapter(private val modifier: BaseMultiPickerModifier?,
18 | private val viewHolderPlaceholderModifier: ImageModifier?,
19 | private val viewHolderTitleTextModifier: TitleTextModifier?) :
20 | ListAdapter(AudioDiffUtil()) {
21 |
22 |
23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AudioMultiSelectViewHolder {
24 | val holder = AudioMultiSelectViewHolder(ItemviewAudioBinding.inflate(parent.inflater, parent, false), modifier
25 | , viewHolderPlaceholderModifier, viewHolderTitleTextModifier)
26 | holder.itemView.setOnClickListener {
27 | val item = getItem(holder.adapterPosition)
28 | item.isSelected = !item.isSelected
29 | notifyItemChanged(holder.adapterPosition)
30 | }
31 | return holder
32 | }
33 |
34 | override fun onBindViewHolder(holderAudio: AudioMultiSelectViewHolder, position: Int) {
35 | val item = getItem(position)
36 | holderAudio.bind(item)
37 | holderAudio.itemView.tag = item
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/adapters/multi/AudioMultiSelectViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.adapters.multi
2 |
3 | import com.crazylegend.audiopicker.audios.AudioModel
4 | import com.crazylegend.audiopicker.databinding.ItemviewAudioBinding
5 | import com.crazylegend.core.adapters.BaseViewHolder
6 | import com.crazylegend.core.modifiers.TitleTextModifier
7 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
8 | import com.crazylegend.core.modifiers.single.ImageModifier
9 | import com.crazylegend.extensions.visible
10 |
11 |
12 | /**
13 | * Created by crazy on 5/8/20 to long live and prosper !
14 | */
15 | internal class AudioMultiSelectViewHolder(
16 | private val binding: ItemviewAudioBinding,
17 | private val modifier: BaseMultiPickerModifier?,
18 | private val viewHolderPlaceholderModifier: ImageModifier?,
19 | private val viewHolderTitleTextModifier: TitleTextModifier?
20 |
21 | ) : BaseViewHolder(binding) {
22 |
23 | private val selectIconModifier get() = modifier?.selectIconModifier
24 | private val unSelectedIconModifier get() = modifier?.unSelectedIconModifier
25 |
26 | init {
27 | binding.selection.visible()
28 | modifier?.applyGravityWithBottomConstraint(binding.selection, binding.bottomText)
29 | }
30 |
31 | fun bind(item: AudioModel) {
32 | binding.bottomText.text = item.displayName
33 | if (item.thumbnail == null) {
34 | loadPlaceHolders(viewHolderPlaceholderModifier, binding.image)
35 | } else {
36 | binding.image.setImageBitmap(item.thumbnail)
37 | }
38 |
39 | viewHolderTitleTextModifier?.apply {
40 | applyTextParamsConstraint(binding.bottomText)
41 | }
42 |
43 | if (item.isSelected) {
44 | setupSelectedImage(binding.selection, selectIconModifier)
45 | } else {
46 | setupUnselectedImage(binding.selection, unSelectedIconModifier)
47 | }
48 | }
49 |
50 |
51 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/adapters/single/AudioSingleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.adapters.single
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.ListAdapter
5 | import com.crazylegend.audiopicker.adapters.AudioDiffUtil
6 | import com.crazylegend.audiopicker.audios.AudioModel
7 | import com.crazylegend.audiopicker.databinding.ItemviewAudioBinding
8 | import com.crazylegend.core.inflater
9 | import com.crazylegend.core.modifiers.TitleTextModifier
10 | import com.crazylegend.core.modifiers.single.ImageModifier
11 |
12 |
13 | /**
14 | * Created by crazy on 5/8/20 to long live and prosper !
15 | */
16 |
17 | internal class AudioSingleAdapter(private val viewHolderPlaceholderModifier: ImageModifier?,
18 | private val viewHolderTitleTextModifier: TitleTextModifier?,
19 | private val onClick: (AudioModel) -> Unit) : ListAdapter(AudioDiffUtil()) {
20 | override fun onCreateViewHolder(parent: ViewGroup, type: Int) =
21 | AudioSingleViewHolder(ItemviewAudioBinding.inflate(parent.inflater, parent, false), onClick,
22 | viewHolderPlaceholderModifier, viewHolderTitleTextModifier)
23 |
24 | override fun onBindViewHolder(holderAudio: AudioSingleViewHolder, position: Int) {
25 | val item = getItem(position)
26 | holderAudio.bind(item)
27 | holderAudio.itemView.tag = item
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/adapters/single/AudioSingleViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.adapters.single
2 |
3 | import com.bumptech.glide.Glide
4 | import com.crazylegend.audiopicker.audios.AudioModel
5 | import com.crazylegend.audiopicker.databinding.ItemviewAudioBinding
6 | import com.crazylegend.core.adapters.BaseViewHolder
7 | import com.crazylegend.core.modifiers.TitleTextModifier
8 | import com.crazylegend.core.modifiers.single.ImageModifier
9 | import com.crazylegend.extensions.gone
10 |
11 |
12 | /**
13 | * Created by crazy on 5/8/20 to long live and prosper !
14 | */
15 |
16 | internal class AudioSingleViewHolder(private val binding: ItemviewAudioBinding, onClick: (AudioModel) -> Unit,
17 | private val viewHolderPlaceholderModifier: ImageModifier?,
18 | private val viewHolderTitleTextModifier: TitleTextModifier?) :
19 | BaseViewHolder(binding) {
20 |
21 |
22 | fun bind(item: AudioModel) {
23 | binding.bottomText.text = item.displayName
24 | if (item.thumbnail == null) {
25 | loadPlaceHolders(viewHolderPlaceholderModifier, binding.image)
26 | } else {
27 | Glide
28 | .with(binding.image)
29 | .load(item.thumbnail)
30 | .into(binding.image)
31 | }
32 | viewHolderTitleTextModifier?.apply {
33 | applyTextParamsConstraint(binding.bottomText)
34 | }
35 | }
36 |
37 | private val getModel get() = itemView.tag as? AudioModel?
38 |
39 | init {
40 | itemView.setOnClickListener {
41 | val model = getModel ?: return@setOnClickListener
42 | onClick(model)
43 | }
44 | binding.selection.gone()
45 | }
46 |
47 |
48 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/audios/AudioModel.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.audios
2 |
3 | import android.graphics.Bitmap
4 | import android.net.Uri
5 | import android.os.Parcelable
6 | import com.crazylegend.core.dto.BaseCursorModel
7 | import kotlinx.parcelize.IgnoredOnParcel
8 | import kotlinx.parcelize.Parcelize
9 |
10 |
11 | /**
12 | * Created by crazy on 5/12/20 to long live and prosper !
13 | */
14 |
15 | @Parcelize
16 | data class AudioModel(override val id: Long,
17 | override val displayName: String?,
18 | override val dateAdded: Long?,
19 | override val contentUri: Uri,
20 | override val dateModified: Long?,
21 | override val description: String?,
22 | override val size: Int?,
23 | override val width: Int?,
24 | override val height: Int?,
25 | val album: String?,
26 | val composer: String?,
27 | val artist: String?,
28 | val notification: Boolean,
29 | val alarm: Boolean,
30 | val ringtone: Boolean,
31 | val track: Int?,
32 | val year: Int?
33 | ) :
34 | BaseCursorModel(id, displayName, dateAdded, contentUri, dateModified, description, size, width, height), Parcelable {
35 |
36 | /**
37 | * this variable will always be null and/or freed unless after you pick the model and you assign it yourself
38 | * do remember to free the bitmap to not hog the memory
39 | */
40 | @IgnoredOnParcel
41 | var thumbnail: Bitmap? = null
42 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/consts/DialogConsts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.consts
2 |
3 |
4 | /**
5 | * Created by crazy on 5/8/20 to long live and prosper !
6 | */
7 |
8 |
9 | const val SINGLE_PICKER_BOTTOM_SHEET = "audiosSinglePickerBottomSheetDialog"
10 | const val SINGLE_PICKER_DIALOG = "audiosSinglePickerDialog"
11 |
12 |
13 | const val MULTI_PICKER_BOTTOM_SHEET = "audiosMultiPickerBottomSheetDialog"
14 | const val MULTI_PICKER_DIALOG = "audiosMultiPickerDialog"
15 |
16 |
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/contracts/MultiPickerContracts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.contracts
2 |
3 | import com.crazylegend.audiopicker.audios.AudiosVM
4 | import com.crazylegend.audiopicker.listeners.onAudiosPicked
5 | import com.crazylegend.audiopicker.modifiers.MultiAudioPickerModifier
6 | import com.crazylegend.audiopicker.pickers.MultiAudioPicker
7 | import com.crazylegend.core.adapters.multi.MultiSelectAdapter
8 | import com.crazylegend.core.contracts.BaseContractMultiPick
9 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
10 |
11 |
12 | /**
13 | * Created by crazy on 5/12/20 to long live and prosper !
14 | */
15 |
16 | internal interface MultiPickerContracts : BaseContractMultiPick {
17 | val audiosVM: AudiosVM
18 | var onAudiosPicked: onAudiosPicked?
19 | val errorTag: String get() = MultiAudioPicker::javaClass.name
20 | fun addModifier(modifier: MultiAudioPickerModifier)
21 | override val modifier: MultiAudioPickerModifier?
22 | fun recycleBitmaps()
23 | override val multiSelectAdapter: MultiSelectAdapter?
24 | get() = null
25 |
26 | override fun addModifier(modifier: BaseMultiPickerModifier) {}
27 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/contracts/SinglePickerContracts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.contracts
2 |
3 | import com.crazylegend.audiopicker.audios.AudiosVM
4 | import com.crazylegend.audiopicker.listeners.onAudioPicked
5 | import com.crazylegend.audiopicker.modifiers.SingleAudioPickerModifier
6 | import com.crazylegend.audiopicker.pickers.SingleAudioPicker
7 | import com.crazylegend.core.adapters.single.SingleAdapter
8 | import com.crazylegend.core.contracts.BaseContractSinglePick
9 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
10 |
11 |
12 | /**
13 | * Created by crazy on 5/12/20 to long live and prosper !
14 | */
15 | internal interface SinglePickerContracts : BaseContractSinglePick {
16 | val audiosVM: AudiosVM
17 | var onAudioPicked: onAudioPicked?
18 | val errorTag: String get() = SingleAudioPicker::javaClass.name
19 |
20 | override val modifier: SingleAudioPickerModifier?
21 | override val singleAdapter: SingleAdapter?
22 | get() = null
23 |
24 | override fun addModifier(modifier: BaseSinglePickerModifier) {}
25 | fun recycleBitmaps()
26 |
27 | fun addModifier(modifier: SingleAudioPickerModifier)
28 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/dialogs/multi/MultiAudioPickerBottomSheetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.dialogs.multi
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.View
8 |
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.appcompat.widget.AppCompatTextView
11 | import androidx.core.os.bundleOf
12 | import androidx.fragment.app.setFragmentResult
13 | import androidx.fragment.app.viewModels
14 | import androidx.lifecycle.observe
15 | import com.crazylegend.audiopicker.adapters.multi.AudioMultiSelectAdapter
16 | import com.crazylegend.audiopicker.audios.AudiosVM
17 | import com.crazylegend.audiopicker.contracts.MultiPickerContracts
18 | import com.crazylegend.audiopicker.listeners.onAudiosPicked
19 | import com.crazylegend.audiopicker.listeners.recycleBitmapsDSL
20 | import com.crazylegend.audiopicker.modifiers.MultiAudioPickerModifier
21 | import com.crazylegend.audiopicker.pickers.MultiAudioPicker
22 | import com.crazylegend.core.abstracts.AbstractBottomSheetDialogFragment
23 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutMultiBinding
24 | import com.crazylegend.extensions.viewBinding
25 | import com.google.android.material.button.MaterialButton
26 |
27 |
28 | /**
29 | * Created by crazy on 5/8/20 to long live and prosper !
30 | */
31 | internal class MultiAudioPickerBottomSheetDialog : AbstractBottomSheetDialogFragment(),
32 | MultiPickerContracts {
33 |
34 |
35 | override val layout: Int
36 | get() = super.layout
37 | override var onAudiosPicked: onAudiosPicked? = null
38 | override val binding by viewBinding(FragmentImagesGalleryLayoutMultiBinding::bind)
39 | override val audiosVM by viewModels()
40 | override val modifier: MultiAudioPickerModifier?
41 | get() = arguments?.getParcelable(modifierTag)
42 |
43 | private val audioMultiSelectAdapter by lazy {
44 | AudioMultiSelectAdapter(modifier, modifier?.viewHolderPlaceholderModifier, modifier?.viewHolderTitleTextModifier)
45 | }
46 | override val askForStoragePermission =
47 | registerForActivityResult(ActivityResultContracts.RequestPermission()) {
48 | if (it) {
49 | audiosVM.loadAudios()
50 | } else {
51 | Log.e(errorTag, "PERMISSION DENIED")
52 | dismissAllowingStateLoss()
53 | }
54 | }
55 |
56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
57 | super.onViewCreated(view, savedInstanceState)
58 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU){
59 | askForStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
60 | } else {
61 | askForStoragePermission.launch(Manifest.permission.READ_MEDIA_AUDIO)
62 | }
63 | setupUIForMultiPicker(
64 | binding.gallery, audioMultiSelectAdapter, binding.doneButton, binding.title, binding.loadingIndicator, modifier?.loadingIndicatorTint,
65 | ::applyDoneButtonModifications, ::applyTitleModifications)
66 |
67 | audiosVM.audio.observe(viewLifecycleOwner) {
68 | setupList(audioMultiSelectAdapter, it, binding.noContentText, modifier?.noContentTextModifier)
69 | }
70 |
71 | binding.doneButton.setOnClickListener {
72 | val selectedList = audioMultiSelectAdapter.currentList.filter { it.isSelected }
73 | setFragmentResult(MultiAudioPicker.MULTI_AUDIO_REQUEST_KEY, bundleOf(MultiAudioPicker.ON_MULTI_AUDIO_PICK_KEY to selectedList))
74 | onAudiosPicked?.forAudios(selectedList)
75 | dismissAllowingStateLoss()
76 | }
77 |
78 | handleUIIndicator(audiosVM.loadingIndicator, binding.loadingIndicator)
79 | audiosVM.onShouldRecycleBitmaps = recycleBitmapsDSL {
80 | recycleBitmaps()
81 | }
82 | }
83 |
84 |
85 | override fun onDestroyView() {
86 | super.onDestroyView()
87 | onAudiosPicked = null
88 | }
89 |
90 | override fun addModifier(modifier: MultiAudioPickerModifier) {
91 | arguments = bundleOf(modifierTag to modifier)
92 | }
93 |
94 | override fun applyTitleModifications(appCompatTextView: AppCompatTextView) {
95 | modifier?.titleTextModifier?.applyTextParams(appCompatTextView)
96 | }
97 |
98 | override fun applyDoneButtonModifications(doneButton: MaterialButton) {
99 | modifier?.doneButtonModifier?.applyImageParams(doneButton)
100 | }
101 |
102 | override fun recycleBitmaps() {
103 | audioMultiSelectAdapter.currentList.asSequence().forEach {
104 | it?.thumbnail?.apply {
105 | if (!isRecycled)
106 | recycle()
107 | }
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/dialogs/single/SingleAudioPickerBottomSheetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.dialogs.single
2 |
3 | import android.Manifest
4 | import android.graphics.Bitmap
5 | import android.os.Build
6 | import android.os.Bundle
7 | import android.util.Log
8 | import android.view.View
9 |
10 | import androidx.activity.result.contract.ActivityResultContracts
11 | import androidx.appcompat.widget.AppCompatTextView
12 | import androidx.core.os.bundleOf
13 | import androidx.fragment.app.setFragmentResult
14 | import androidx.fragment.app.viewModels
15 | import androidx.lifecycle.observe
16 | import com.crazylegend.audiopicker.adapters.single.AudioSingleAdapter
17 | import com.crazylegend.audiopicker.audios.AudiosVM
18 | import com.crazylegend.audiopicker.contracts.SinglePickerContracts
19 | import com.crazylegend.audiopicker.listeners.onAudioPicked
20 | import com.crazylegend.audiopicker.listeners.recycleBitmapsDSL
21 | import com.crazylegend.audiopicker.modifiers.SingleAudioPickerModifier
22 | import com.crazylegend.audiopicker.pickers.SingleAudioPicker
23 | import com.crazylegend.core.abstracts.AbstractBottomSheetDialogFragment
24 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutBinding
25 | import com.crazylegend.extensions.viewBinding
26 |
27 |
28 | /**
29 | * Created by crazy on 5/8/20 to long live and prosper !
30 | */
31 | internal class SingleAudioPickerBottomSheetDialog : AbstractBottomSheetDialogFragment(), SinglePickerContracts {
32 |
33 | override val layout: Int
34 | get() = super.layout
35 | override var onAudioPicked: onAudioPicked? = null
36 | override val binding by viewBinding(FragmentImagesGalleryLayoutBinding::bind)
37 | override val audiosVM by viewModels()
38 | override val modifier: SingleAudioPickerModifier? get() = arguments?.getParcelable(modifierTag)
39 |
40 | private val singleAudioAdapter by lazy {
41 | AudioSingleAdapter(modifier?.viewHolderPlaceholderModifier, modifier?.viewHolderTitleTextModifier) {
42 | recycleThubmnail(it.thumbnail)
43 | setFragmentResult(SingleAudioPicker.SINGLE_AUDIO_REQUEST_KEY, bundleOf(SingleAudioPicker.ON_SINGLE_AUDIO_PICK_KEY to it))
44 | onAudioPicked?.forAudio(it)
45 | dismissAllowingStateLoss()
46 | }
47 | }
48 |
49 | override val askForStoragePermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
50 | if (it) {
51 | audiosVM.loadAudios()
52 | } else {
53 | Log.e(errorTag, "PERMISSION DENIED")
54 | dismissAllowingStateLoss()
55 | }
56 | }
57 |
58 |
59 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
60 | super.onViewCreated(view, savedInstanceState)
61 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU){
62 | askForStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
63 | } else {
64 | askForStoragePermission.launch(Manifest.permission.READ_MEDIA_AUDIO)
65 | }
66 | setupUIForSinglePicker(binding.gallery, singleAudioAdapter, binding.title, binding.loadingIndicator, modifier?.loadingIndicatorTint, ::applyTitleModifications)
67 | audiosVM.audio.observe(viewLifecycleOwner) {
68 | setupList(singleAudioAdapter, it, binding.noContentText, modifier?.noContentTextModifier)
69 | }
70 |
71 | handleUIIndicator(audiosVM.loadingIndicator, binding.loadingIndicator)
72 | audiosVM.onShouldRecycleBitmaps = recycleBitmapsDSL {
73 | recycleBitmaps()
74 | }
75 | }
76 |
77 | override fun onDestroyView() {
78 | super.onDestroyView()
79 | onAudioPicked = null
80 | }
81 |
82 |
83 | override fun applyTitleModifications(appCompatTextView: AppCompatTextView) {
84 | modifier?.titleTextModifier?.applyTextParams(appCompatTextView)
85 | }
86 |
87 | override fun addModifier(modifier: SingleAudioPickerModifier) {
88 | arguments = bundleOf(modifierTag to modifier)
89 | }
90 |
91 | override fun recycleBitmaps() {
92 | singleAudioAdapter.currentList.asSequence().forEach {
93 | it?.thumbnail?.apply {
94 | if (!isRecycled)
95 | recycle()
96 | }
97 | }
98 | }
99 |
100 | private fun recycleThubmnail(thumbnail: Bitmap?) {
101 | thumbnail?.apply {
102 | if (!isRecycled) {
103 | recycle()
104 | }
105 | }
106 | }
107 |
108 |
109 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/listeners/onAudioPicked.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.listeners
2 |
3 | import com.crazylegend.audiopicker.audios.AudioModel
4 |
5 |
6 | /**
7 | * Created by crazy on 5/12/20 to long live and prosper !
8 | */
9 | internal fun interface onAudioPicked {
10 | fun forAudio(audio: AudioModel)
11 | }
12 |
13 | internal inline fun onAudioDSL(crossinline callback: (audioModel: AudioModel) -> Unit = {}) = onAudioPicked { audio -> callback(audio) }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/listeners/onAudiosPicked.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.listeners
2 |
3 | import com.crazylegend.audiopicker.audios.AudioModel
4 |
5 |
6 | /**
7 | * Created by crazy on 5/12/20 to long live and prosper !
8 | */
9 | internal fun interface onAudiosPicked {
10 | fun forAudios(audioList: List)
11 | }
12 |
13 | internal inline fun onAudiosDSL(crossinline callback: (audioList: List) -> Unit = {}) = onAudiosPicked { audioList -> callback(audioList) }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/listeners/onShouldRecycleBitmaps.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.listeners
2 |
3 |
4 | /**
5 | * Created by crazy on 5/15/20 to long live and prosper !
6 | */
7 | internal interface onShouldRecycleBitmaps {
8 | fun keepItClean()
9 | }
10 |
11 | internal inline fun recycleBitmapsDSL(crossinline recycle: () -> Unit = {}) = object : onShouldRecycleBitmaps {
12 | override fun keepItClean() {
13 | recycle()
14 | }
15 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/modifiers/MultiAudioPickerModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.modifiers
2 |
3 | import com.crazylegend.core.modifiers.TitleTextModifier
4 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
5 | import com.crazylegend.core.modifiers.multi.DoneButtonModifier
6 | import com.crazylegend.core.modifiers.multi.SelectIconModifier
7 | import com.crazylegend.core.modifiers.single.ImageModifier
8 | import kotlinx.parcelize.Parcelize
9 |
10 |
11 | /**
12 | * Created by crazy on 5/14/20 to long live and prosper !
13 | */
14 |
15 | @Parcelize
16 | data class MultiAudioPickerModifier(
17 | override val doneButtonModifier: DoneButtonModifier = DoneButtonModifier(),
18 | override val titleTextModifier: TitleTextModifier = TitleTextModifier(),
19 | override val selectIconModifier: SelectIconModifier = SelectIconModifier(),
20 | override val unSelectedIconModifier: SelectIconModifier = SelectIconModifier(),
21 | override var indicatorsGravity: Gravity = Gravity.BOTTOM_RIGHT,
22 | override val viewHolderPlaceholderModifier: ImageModifier = ImageModifier(),
23 | override val noContentTextModifier: TitleTextModifier = TitleTextModifier(),
24 | override var loadingIndicatorTint: Int? = null,
25 |
26 | val viewHolderTitleTextModifier: TitleTextModifier = TitleTextModifier()
27 | ) : BaseMultiPickerModifier(doneButtonModifier, titleTextModifier, selectIconModifier, unSelectedIconModifier, indicatorsGravity, viewHolderPlaceholderModifier,
28 | noContentTextModifier, loadingIndicatorTint) {
29 |
30 | inline fun setupViewHolderTitleText(viewHolderPlaceholderModifications: TitleTextModifier.() -> Unit = {}) {
31 | viewHolderTitleTextModifier.viewHolderPlaceholderModifications()
32 | }
33 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/modifiers/SingleAudioPickerModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.modifiers
2 |
3 | import com.crazylegend.core.modifiers.TitleTextModifier
4 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
5 | import com.crazylegend.core.modifiers.single.ImageModifier
6 | import kotlinx.parcelize.Parcelize
7 |
8 |
9 | /**
10 | * Created by crazy on 5/14/20 to long live and prosper !
11 | */
12 |
13 | @Parcelize
14 | data class SingleAudioPickerModifier(
15 | override val viewHolderPlaceholderModifier: ImageModifier = ImageModifier(),
16 | override val titleTextModifier: TitleTextModifier = TitleTextModifier(),
17 | override var loadingIndicatorTint: Int? = null,
18 | override val noContentTextModifier: TitleTextModifier = TitleTextModifier(),
19 | val viewHolderTitleTextModifier: TitleTextModifier = TitleTextModifier()
20 | ) : BaseSinglePickerModifier(titleTextModifier, viewHolderPlaceholderModifier, noContentTextModifier, loadingIndicatorTint) {
21 |
22 | inline fun setupViewHolderTitleText(viewHolderPlaceholderModifications: TitleTextModifier.() -> Unit = {}) {
23 | viewHolderTitleTextModifier.viewHolderPlaceholderModifications()
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/pickers/MultiAudioPicker.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.pickers
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.util.Log
6 | import androidx.annotation.RequiresPermission
7 | import androidx.fragment.app.FragmentManager
8 | import com.crazylegend.audiopicker.audios.AudioModel
9 | import com.crazylegend.audiopicker.consts.MULTI_PICKER_BOTTOM_SHEET
10 | import com.crazylegend.audiopicker.consts.MULTI_PICKER_DIALOG
11 | import com.crazylegend.audiopicker.dialogs.multi.MultiAudioPickerBottomSheetDialog
12 | import com.crazylegend.audiopicker.listeners.onAudiosDSL
13 | import com.crazylegend.audiopicker.modifiers.MultiAudioPickerModifier
14 | import com.crazylegend.extensions.setupManager
15 |
16 |
17 | /**
18 | * Created by crazy on 5/12/20 to long live and prosper !
19 | */
20 | object MultiAudioPicker {
21 |
22 |
23 | const val MULTI_AUDIO_REQUEST_KEY = "multiAudiosRequest"
24 | const val ON_MULTI_AUDIO_PICK_KEY = "onMultiAudiosPicked"
25 |
26 | fun restoreListener(context: Context, audioList: (list: List) -> Unit = {}) {
27 | val manager = context.setupManager()
28 | when (val fragment = manager.findFragmentByTag(MULTI_PICKER_BOTTOM_SHEET)
29 | ?: manager.findFragmentByTag(MULTI_PICKER_DIALOG)) {
30 | is MultiAudioPickerBottomSheetDialog -> {
31 | fragment.onAudiosPicked = onAudiosDSL(audioList)
32 | }
33 | null -> {
34 | Log.e(MultiAudioPicker::class.java.name, "FRAGMENT NOT FOUND")
35 | }
36 | }
37 | }
38 |
39 |
40 | fun showPicker(context: Context, modifier: MultiAudioPickerModifier.() -> Unit = {}, audioList: (list: List) -> Unit = {}) {
41 | val manager = context.setupManager()
42 | val setupModifier = setupModifier(modifier)
43 | with(MultiAudioPickerBottomSheetDialog()) {
44 | addModifier(setupModifier)
45 | onAudiosPicked = onAudiosDSL(audioList)
46 | show(manager, MULTI_PICKER_BOTTOM_SHEET)
47 | }
48 | }
49 |
50 | fun showPicker(fragmentManager: FragmentManager, modifier: MultiAudioPickerModifier.() -> Unit = {}, audioList: (list: List) -> Unit = {}) {
51 | val setupModifier = setupModifier(modifier)
52 | with(MultiAudioPickerBottomSheetDialog()) {
53 | addModifier(setupModifier)
54 | onAudiosPicked = onAudiosDSL(audioList)
55 | show(fragmentManager, MULTI_PICKER_BOTTOM_SHEET)
56 | }
57 | }
58 |
59 | private inline fun setupModifier(modifier: MultiAudioPickerModifier.() -> Unit) =
60 | MultiAudioPickerModifier().also(modifier)
61 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/java/com/crazylegend/audiopicker/pickers/SingleAudioPicker.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker.pickers
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.util.Log
6 | import androidx.annotation.RequiresPermission
7 | import androidx.fragment.app.FragmentManager
8 | import com.crazylegend.audiopicker.audios.AudioModel
9 | import com.crazylegend.audiopicker.consts.SINGLE_PICKER_BOTTOM_SHEET
10 | import com.crazylegend.audiopicker.consts.SINGLE_PICKER_DIALOG
11 | import com.crazylegend.audiopicker.dialogs.single.SingleAudioPickerBottomSheetDialog
12 | import com.crazylegend.audiopicker.listeners.onAudioDSL
13 | import com.crazylegend.audiopicker.modifiers.SingleAudioPickerModifier
14 | import com.crazylegend.extensions.setupManager
15 |
16 |
17 | /**
18 | * Created by crazy on 5/12/20 to long live and prosper !
19 | */
20 | object SingleAudioPicker {
21 |
22 | const val SINGLE_AUDIO_REQUEST_KEY = "singleAudioRequest"
23 | const val ON_SINGLE_AUDIO_PICK_KEY = "onSingleAudioPicked"
24 |
25 | fun restoreListener(context: Context, onPickedAudio: (audio: AudioModel) -> Unit = {}) {
26 | val manager = context.setupManager()
27 | when (val fragment = manager.findFragmentByTag(SINGLE_PICKER_BOTTOM_SHEET)
28 | ?: manager.findFragmentByTag(SINGLE_PICKER_DIALOG)) {
29 | is SingleAudioPickerBottomSheetDialog -> {
30 | fragment.onAudioPicked = onAudioDSL(onPickedAudio)
31 | }
32 | null -> {
33 | Log.e(SingleAudioPicker::class.java.name, "FRAGMENT NOT FOUND")
34 | }
35 | }
36 | }
37 |
38 | fun showPicker(context: Context, pickerModifier: SingleAudioPickerModifier.() -> Unit = {}, onPickedAudio: (audio: AudioModel) -> Unit = {}) {
39 | val modifier = setupModifier(pickerModifier)
40 | val manager = context.setupManager()
41 | with(SingleAudioPickerBottomSheetDialog()) {
42 | addModifier(modifier)
43 | onAudioPicked = onAudioDSL(onPickedAudio)
44 | show(manager, SINGLE_PICKER_BOTTOM_SHEET)
45 | }
46 | }
47 |
48 | fun showPicker(manager: FragmentManager, pickerModifier: SingleAudioPickerModifier.() -> Unit = {}, onPickedAudio: (audio: AudioModel) -> Unit = {}) {
49 | val modifier = setupModifier(pickerModifier)
50 | with(SingleAudioPickerBottomSheetDialog()) {
51 | addModifier(modifier)
52 | onAudioPicked = onAudioDSL(onPickedAudio)
53 | show(manager, SINGLE_PICKER_BOTTOM_SHEET)
54 | }
55 | }
56 |
57 | private inline fun setupModifier(audioPicker: SingleAudioPickerModifier.() -> Unit) = SingleAudioPickerModifier().also(audioPicker)
58 | }
--------------------------------------------------------------------------------
/audiopicker/src/main/res/drawable/ic_album.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/audiopicker/src/main/res/drawable/ic_album_second.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/audiopicker/src/main/res/layout/itemview_audio.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
20 |
21 |
29 |
30 |
40 |
41 |
--------------------------------------------------------------------------------
/audiopicker/src/test/java/com/crazylegend/audiopicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.audiopicker
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | lifecycle = '2.3.0'
3 | glide = "4.13.2"
4 | constraint_layout = '2.1.4'
5 | material = "1.3.0"
6 | fragment = "1.5.0"
7 | activity = "1.2.0"
8 | recycler = "1.2.1"
9 | appCompat = "1.2.0"
10 | coreKTX = "1.5.0"
11 |
12 | //compilation
13 | compileVersion = 33
14 | minVersion = 21
15 | verCode = 1
16 | verName = "1.0.0"
17 | testRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildscript {
21 | ext.kotlin_version = '1.7.10'
22 |
23 | ext.compiledAppVersion = 33
24 |
25 | repositories {
26 | google()
27 | mavenCentral()
28 | }
29 | dependencies {
30 | classpath 'com.android.tools.build:gradle:7.2.1'
31 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
32 | }
33 | }
34 |
35 | allprojects {
36 | group = "io.github.funkymuse"
37 |
38 | repositories {
39 | google()
40 | mavenCentral()
41 | }
42 | }
43 |
44 | task clean(type: Delete) {
45 | delete rootProject.buildDir
46 | }
47 |
48 |
49 |
50 | subprojects {
51 |
52 | switch (it.name){
53 | case 'app':
54 | apply plugin: 'com.android.application'
55 | apply plugin: 'kotlin-android'
56 | apply plugin: 'kotlin-kapt'
57 | apply plugin: 'kotlin-parcelize'
58 |
59 | dependencies {
60 | implementation project(path: ':imagepicker')
61 | implementation project(path: ':videopicker')
62 | implementation project(path: ':audiopicker')
63 | implementation project(path: ':core')
64 | }
65 |
66 | break
67 |
68 | case 'core':
69 |
70 | apply plugin: 'com.android.library'
71 | apply plugin: 'kotlin-android'
72 | apply plugin: 'kotlin-parcelize'
73 | apply plugin: 'kotlin-kapt'
74 | apply plugin: 'maven-publish'
75 |
76 |
77 | dependencies {
78 | testImplementation 'junit:junit:4.13.2'
79 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
80 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
81 |
82 | //glide
83 | api "com.github.bumptech.glide:glide:$glide"
84 | kapt "com.github.bumptech.glide:compiler:$glide"
85 |
86 | //core
87 | api "androidx.activity:activity-ktx:$activity"
88 | api "androidx.fragment:fragment-ktx:$fragment"
89 | api "androidx.appcompat:appcompat:$appCompat"
90 | api "androidx.core:core-ktx:$coreKTX"
91 |
92 | //ui
93 | api "androidx.constraintlayout:constraintlayout:$constraint_layout"
94 | api "androidx.recyclerview:recyclerview:$recycler"
95 | api "com.google.android.material:material:$material"
96 |
97 | //live data
98 | api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle"
99 | api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle"
100 | api "androidx.lifecycle:lifecycle-common-java8:$lifecycle"
101 | api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle"
102 | }
103 |
104 | it.afterEvaluate {
105 | publishing {
106 | publications {
107 | release(MavenPublication) {
108 | from components.release
109 | }
110 | }
111 | }
112 | }
113 |
114 | break
115 |
116 | case 'extensions':
117 |
118 |
119 | apply plugin: 'com.android.library'
120 | apply plugin: 'kotlin-android'
121 | apply plugin: 'kotlin-parcelize'
122 | apply plugin: 'kotlin-kapt'
123 | apply plugin: 'maven-publish'
124 |
125 | dependencies {
126 | implementation project(path: ':core')
127 | }
128 | it.afterEvaluate {
129 | publishing {
130 | publications {
131 | release(MavenPublication) {
132 | from components.release
133 | }
134 | }
135 | }
136 | }
137 |
138 | break
139 |
140 | default:
141 |
142 | apply plugin: 'com.android.library'
143 | apply plugin: 'kotlin-android'
144 | apply plugin: 'kotlin-parcelize'
145 | apply plugin: 'kotlin-kapt'
146 | apply plugin: 'maven-publish'
147 |
148 | dependencies {
149 | api project(path: ':core')
150 | implementation project(path: ':extensions')
151 | }
152 |
153 | it.afterEvaluate {
154 | publishing {
155 | publications {
156 | release(MavenPublication) {
157 | from components.release
158 | }
159 | }
160 | }
161 | }
162 |
163 | break
164 |
165 | }
166 |
167 | android {
168 | compileSdkVersion compileVersion
169 |
170 | defaultConfig {
171 | minSdkVersion minVersion
172 | targetSdkVersion compileVersion
173 | versionCode verCode
174 | versionName verName
175 | testInstrumentationRunner testRunner
176 | }
177 |
178 | compileOptions {
179 | sourceCompatibility = 1.8
180 | targetCompatibility = 1.8
181 | }
182 |
183 | kotlinOptions {
184 | jvmTarget = "1.8"
185 | }
186 |
187 | buildFeatures {
188 | viewBinding = true
189 | aidl = false
190 | renderScript = false
191 | resValues = false
192 | shaders = false
193 | buildConfig = false
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/core/build.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | namespace 'com.crazylegend.core'
3 | }
--------------------------------------------------------------------------------
/core/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/core/consumer-rules.pro
--------------------------------------------------------------------------------
/core/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 |
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/crazylegend/core/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.crazylegend.core.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core
2 |
3 | import android.content.Context
4 | import android.content.res.Resources
5 | import android.graphics.drawable.Drawable
6 | import android.net.Uri
7 | import android.util.TypedValue
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import androidx.appcompat.widget.AppCompatImageView
12 | import androidx.constraintlayout.widget.ConstraintLayout
13 | import androidx.constraintlayout.widget.ConstraintSet
14 | import androidx.lifecycle.AndroidViewModel
15 | import com.bumptech.glide.Glide
16 | import com.bumptech.glide.load.DataSource
17 | import com.bumptech.glide.load.engine.GlideException
18 | import com.bumptech.glide.request.RequestListener
19 | import com.bumptech.glide.request.target.Target
20 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
21 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
22 | import kotlin.math.pow
23 |
24 | /**
25 | * Created by crazy on 5/8/20 to long live and prosper !
26 | */
27 |
28 |
29 | internal inline fun AppCompatImageView.loadImage(uri: Uri, crossinline onLoadFailed: () -> Unit = {}) {
30 | Glide.with(this)
31 | .load(uri)
32 | .thumbnail(0.33f)
33 | .listener(object : RequestListener {
34 | override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean {
35 | onLoadFailed()
36 | return true
37 | }
38 |
39 | override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
40 | return false
41 | }
42 | })
43 | .centerCrop()
44 | .into(this)
45 | }
46 |
47 | internal inline fun AppCompatImageView.loadWholeImage(uri: Uri, crossinline onLoadFailed: () -> Unit = {}) {
48 | Glide.with(this)
49 | .load(uri)
50 | .listener(object : RequestListener {
51 | override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean {
52 | onLoadFailed()
53 | return true
54 | }
55 |
56 | override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
57 | return false
58 | }
59 | })
60 | .centerCrop()
61 | .into(this)
62 | }
63 |
64 | inline fun setupModifier(modifier: BaseMultiPickerModifier.() -> Unit) = BaseMultiPickerModifier().also(modifier)
65 |
66 |
67 | inline fun setupModifier(videoPicker: BaseSinglePickerModifier.() -> Unit) = BaseSinglePickerModifier().also(videoPicker)
68 |
69 |
70 | val ViewGroup.inflater: LayoutInflater get() = LayoutInflater.from(context)
71 |
72 |
73 | internal fun View.visible() {
74 | this.visibility = View.VISIBLE
75 | }
76 |
77 | internal fun View.gone() {
78 | this.visibility = View.GONE
79 | }
80 |
81 | internal val AndroidViewModel.context: Context
82 | get() = getApplication()
83 |
84 |
85 |
86 | internal fun T.top(margin: Int): T {
87 | return position(ConstraintLayout.LayoutParams.TOP, margin)
88 | }
89 |
90 | internal fun T.left(margin: Int): T {
91 | return position(ConstraintLayout.LayoutParams.LEFT, margin)
92 | }
93 |
94 | internal fun T.right(margin: Int): T {
95 | return position(ConstraintLayout.LayoutParams.RIGHT, margin)
96 | }
97 |
98 | internal fun T.bottom(margin: Int): T {
99 | return position(ConstraintLayout.LayoutParams.BOTTOM, margin)
100 | }
101 |
102 | internal fun T.position(position: Int, margin: Int): T {
103 | (parent as? ConstraintLayout)?.let { constraintLayout ->
104 | constraintLayout.addConstraints {
105 | connect(id, position, ConstraintLayout.LayoutParams.PARENT_ID, position, margin.dp)
106 | }
107 | }
108 | return this
109 | }
110 |
111 | // Bottom
112 |
113 | internal fun T.constrainBottomToTopOf(view: View, margin: Int = 0): T {
114 | return constrainBottomToTopOf(view.id, margin)
115 | }
116 |
117 | internal fun T.constrainBottomToTopOf(viewId: Int, margin: Int = 0): T {
118 | (parent as? ConstraintLayout)?.addConstraints {
119 | connect(id, ConstraintLayout.LayoutParams.BOTTOM, viewId, ConstraintLayout.LayoutParams.TOP, margin.dp)
120 | }
121 | return this
122 | }
123 |
124 | internal fun ConstraintLayout.addConstraints(block: ConstraintSet.() -> Unit) {
125 | val cs = ConstraintSet()
126 | cs.clone(this)
127 | block(cs)
128 | cs.applyTo(this)
129 | }
130 |
131 |
132 | internal var Int.dp: Int
133 | get() {
134 | val metrics = Resources.getSystem().displayMetrics
135 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), metrics).toInt()
136 | }
137 | set(_) {}
138 |
139 |
140 | internal var Float.dp: Float
141 | get() {
142 | val metrics = Resources.getSystem().displayMetrics
143 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, metrics)
144 | }
145 | set(_) {}
146 |
147 | internal fun Int.bytesToFormattedString(): String {
148 | return when {
149 | this >= 1024.0.pow(3.0) -> {
150 | val gigabytes = this / 1024.0.pow(3.0)
151 | "%.2f GB".format(gigabytes)
152 | }
153 | else -> {
154 | val megabytes = this / 1024.0.pow(2.0)
155 | "%.2f MB".format(megabytes)
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/abstracts/AbstractAVM.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.abstracts
2 |
3 | import android.app.Application
4 | import android.content.ContentResolver
5 | import android.database.ContentObserver
6 | import android.graphics.Bitmap
7 | import android.graphics.BitmapFactory
8 | import android.net.Uri
9 | import android.os.Build
10 | import android.provider.MediaStore
11 | import android.util.Size
12 | import androidx.lifecycle.AndroidViewModel
13 | import androidx.lifecycle.LiveData
14 | import androidx.lifecycle.MutableLiveData
15 | import androidx.lifecycle.SavedStateHandle
16 | import com.crazylegend.core.context
17 |
18 |
19 | /**
20 | * Created by crazy on 5/14/20 to long live and prosper !
21 | */
22 | abstract class AbstractAVM(application: Application,
23 | private val stateHandle: SavedStateHandle) : AndroidViewModel(application) {
24 |
25 | companion object {
26 | private const val CAN_LOAD_KEY = "canLoad"
27 | }
28 |
29 | protected val contentResolver get() = context.contentResolver
30 | protected var contentObserver: ContentObserver? = null
31 |
32 | protected val loadingIndicatorData = MutableLiveData()
33 | val loadingIndicator: LiveData = loadingIndicatorData
34 |
35 | /**
36 | * Using this instead of event since it serves the same purpose thus it's needed here
37 | */
38 | protected val canLoad get() = stateHandle[CAN_LOAD_KEY] ?: true
39 |
40 | protected fun setCanLoad() {
41 | stateHandle[CAN_LOAD_KEY] = true
42 | }
43 |
44 | protected fun setCanNotLoad() {
45 | stateHandle[CAN_LOAD_KEY] = false
46 | }
47 |
48 | override fun onCleared() {
49 | super.onCleared()
50 | contentObserver?.apply {
51 | contentResolver.unregisterContentObserver(this)
52 | }
53 | }
54 |
55 |
56 | /**
57 | * Or just use Glide with the contentUri
58 | * @param contentResolver ContentResolver
59 | * @param customSize Size
60 | * @param options Options?
61 | * @return Bitmap?
62 | */
63 | @Suppress("DEPRECATION")
64 | fun loadThumbnail(contentResolver: ContentResolver,
65 | contentUri: Uri,
66 | id: Long,
67 | customSize: Size = Size(350, 350),
68 | legacyKind: Int = MediaStore.Video.Thumbnails.MICRO_KIND,
69 | options: BitmapFactory.Options? = null): Bitmap? {
70 | return tryOrNull {
71 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
72 | contentResolver.loadThumbnail(contentUri, customSize, null)
73 | } else {
74 | MediaStore.Video.Thumbnails.getThumbnail(contentResolver, id, legacyKind,
75 | options ?: BitmapFactory.Options())
76 | }
77 | }
78 | }
79 |
80 | private fun tryOrNull(function: () -> Bitmap): Bitmap? {
81 | return try {
82 | function()
83 | } catch (e: Exception) {
84 | null
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/abstracts/AbstractBottomSheetDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.abstracts
2 |
3 | import android.content.res.Configuration
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ProgressBar
9 | import androidx.lifecycle.LiveData
10 | import androidx.recyclerview.widget.GridLayoutManager
11 | import androidx.recyclerview.widget.ListAdapter
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.crazylegend.core.R
14 | import com.crazylegend.core.gone
15 | import com.crazylegend.core.modifiers.TitleTextModifier
16 | import com.crazylegend.core.visible
17 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
18 | import com.google.android.material.button.MaterialButton
19 | import com.google.android.material.textview.MaterialTextView
20 |
21 |
22 | /**
23 | * Created by crazy on 5/9/20 to long live and prosper !
24 | */
25 | abstract class AbstractBottomSheetDialogFragment : BottomSheetDialogFragment() {
26 | abstract val layout: Int
27 |
28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(layout, container, false)
29 |
30 | //work around when using DayNight to change the background color to light/night
31 | override fun onConfigurationChanged(overrideConfiguration: Configuration) {
32 | val uiMode = overrideConfiguration.uiMode
33 | overrideConfiguration.setTo(requireContext().resources.configuration)
34 | overrideConfiguration.uiMode = uiMode
35 | super.onConfigurationChanged(overrideConfiguration)
36 | }
37 |
38 |
39 | fun setupUIForSinglePicker(gallery: RecyclerView, singleAdapter: RecyclerView.Adapter<*>,
40 | title: MaterialTextView, loadingIndicator: ProgressBar, progressBarTint: Int?,
41 | titleModifications: (MaterialTextView) -> Unit) {
42 | gallery.apply {
43 | layoutManager = GridLayoutManager(requireContext(), 3)
44 | adapter = singleAdapter
45 | }
46 | titleModifications(title)
47 | progressBarTint?.let { loadingIndicator.indeterminateDrawable.setTint(it) }
48 | }
49 |
50 | fun setupList(adapter: ListAdapter, list: List, noContentText: MaterialTextView, noContentTextModifier: TitleTextModifier?) {
51 | if (list.isEmpty()) {
52 | adapter.submitList(emptyList())
53 | noContentText.visible()
54 | noContentTextModifier?.apply {
55 | textString = textString ?: getString(R.string.no_content_found)
56 | applyTextParams(noContentText)
57 | }
58 | } else {
59 | noContentText.gone()
60 | adapter.submitList(list)
61 | }
62 | }
63 |
64 | fun setupUIForMultiPicker(gallery: RecyclerView,
65 | multiSelectAdapter: RecyclerView.Adapter<*>, doneButton: MaterialButton,
66 | title: MaterialTextView, loadingIndicator: ProgressBar, progressBarTint: Int?, onDoneButton: (MaterialButton) -> Unit,
67 | onTitleButton: (MaterialTextView) -> Unit) {
68 | gallery.apply {
69 | layoutManager = GridLayoutManager(requireContext(), 3)
70 | adapter = multiSelectAdapter
71 | }
72 | onDoneButton(doneButton)
73 | onTitleButton(title)
74 | progressBarTint?.let { loadingIndicator.indeterminateDrawable.setTint(it) }
75 | }
76 |
77 | fun handleUIIndicator(liveData: LiveData, loadingIndicator: ProgressBar) {
78 | liveData.observe(viewLifecycleOwner) {
79 | if (it) {
80 | loadingIndicator.visible()
81 | } else {
82 | loadingIndicator.gone()
83 | }
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/adapters/BaseViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.adapters
2 |
3 | import android.content.ContentResolver
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.net.Uri
7 | import android.os.Build
8 | import android.provider.MediaStore
9 | import android.util.Size
10 | import androidx.appcompat.widget.AppCompatImageView
11 | import androidx.recyclerview.widget.RecyclerView
12 | import androidx.viewbinding.ViewBinding
13 | import com.crazylegend.core.R
14 | import com.crazylegend.core.loadImage
15 | import com.crazylegend.core.modifiers.multi.SelectIconModifier
16 | import com.crazylegend.core.modifiers.single.ImageModifier
17 |
18 |
19 | /**
20 | * Created by crazy on 5/16/20 to long live and prosper !
21 | */
22 | open class BaseViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) {
23 |
24 | fun loadImage(image: AppCompatImageView, contentUri: Uri, viewHolderPlaceholderModifier: ImageModifier?) {
25 | image.loadImage(contentUri) {
26 | if (viewHolderPlaceholderModifier != null) {
27 | loadPlaceHolders(viewHolderPlaceholderModifier, image)
28 | }
29 | }
30 | }
31 |
32 | /**
33 | * Or just use Glide with the contentUri
34 | * @param contentResolver ContentResolver
35 | * @param customSize Size
36 | * @param options Options?
37 | * @return Bitmap?
38 | */
39 | @Suppress("DEPRECATION")
40 | fun loadThumbnail(contentResolver: ContentResolver,
41 | contentUri: Uri,
42 | id: Long,
43 | customSize: Size = Size(350, 350),
44 | legacyKind: Int = MediaStore.Video.Thumbnails.MICRO_KIND,
45 | options: BitmapFactory.Options? = null): Bitmap? {
46 | return tryOrNull {
47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
48 | contentResolver.loadThumbnail(contentUri, customSize, null)
49 | } else {
50 | MediaStore.Video.Thumbnails.getThumbnail(contentResolver, id, legacyKind,
51 | options ?: BitmapFactory.Options())
52 | }
53 | }
54 | }
55 |
56 | private fun tryOrNull(function: () -> Bitmap): Bitmap? {
57 | return try {
58 | function()
59 | } catch (e: Exception) {
60 | null
61 | }
62 | }
63 |
64 | fun loadPlaceHolders(viewHolderPlaceholderModifier: ImageModifier?, image: AppCompatImageView) {
65 | if (viewHolderPlaceholderModifier != null) {
66 | viewHolderPlaceholderModifier.resID = viewHolderPlaceholderModifier.resID
67 | ?: R.drawable.ic_image
68 | viewHolderPlaceholderModifier.applyImageParamsConstraintLayout(image)
69 | } else {
70 | image.setImageResource(R.drawable.ic_image)
71 | }
72 | }
73 |
74 | fun setupUnselectedImage(selection: AppCompatImageView, unSelectedIconModifier: SelectIconModifier?) {
75 | val resID = unSelectedIconModifier?.resID ?: R.drawable.ic_unchecked_default
76 | unSelectedIconModifier?.applyImageParams(selection, resID)
77 | }
78 |
79 | fun setupSelectedImage(selection: AppCompatImageView, selectIconModifier: SelectIconModifier?) {
80 | val resID = selectIconModifier?.resID ?: R.drawable.ic_checked_default
81 | selectIconModifier?.applyImageParams(selection, resID)
82 | }
83 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/adapters/multi/MultiSelectAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.adapters.multi
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.ListAdapter
5 | import com.crazylegend.core.adapters.single.SingleDiffUtil
6 | import com.crazylegend.core.databinding.ItemviewImageBinding
7 | import com.crazylegend.core.dto.BaseCursorModel
8 | import com.crazylegend.core.inflater
9 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
10 |
11 |
12 | /**
13 | * Created by crazy on 5/8/20 to long live and prosper !
14 | */
15 | class MultiSelectAdapter(private val modifier: BaseMultiPickerModifier?, private val showFileSize: Boolean) :
16 | ListAdapter(SingleDiffUtil()) {
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiSelectViewHolder {
19 | val holder = MultiSelectViewHolder(ItemviewImageBinding.inflate(parent.inflater, parent, false), modifier)
20 | holder.itemView.setOnClickListener {
21 | val item = getItem(holder.bindingAdapterPosition)
22 | item.isSelected = !item.isSelected
23 | notifyItemChanged(holder.bindingAdapterPosition)
24 | }
25 | return holder
26 | }
27 |
28 |
29 | override fun onBindViewHolder(holder: MultiSelectViewHolder, position: Int) {
30 | val item = getItem(position)
31 | holder.bind(item, showFileSize)
32 | holder.itemView.tag = item
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/adapters/multi/MultiSelectViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.adapters.multi
2 |
3 | import androidx.core.view.isVisible
4 | import com.crazylegend.core.adapters.BaseViewHolder
5 | import com.crazylegend.core.bytesToFormattedString
6 | import com.crazylegend.core.databinding.ItemviewImageBinding
7 | import com.crazylegend.core.dto.BaseCursorModel
8 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
9 | import com.crazylegend.core.visible
10 |
11 |
12 | /**
13 | * Created by crazy on 5/8/20 to long live and prosper !
14 | */
15 | class MultiSelectViewHolder(
16 | private val binding: ItemviewImageBinding,
17 | private val modifier: BaseMultiPickerModifier?
18 | ) : BaseViewHolder(binding) {
19 |
20 | private val selectIconModifier get() = modifier?.selectIconModifier
21 | private val unSelectedIconModifier get() = modifier?.unSelectedIconModifier
22 | private val viewHolderPlaceholderModifier get() = modifier?.viewHolderPlaceholderModifier
23 |
24 | init {
25 | binding.selection.visible()
26 | modifier?.applyGravity(binding.selection)
27 | }
28 |
29 | fun bind(cursorModel: BaseCursorModel, showFileSize: Boolean) {
30 | loadImage(binding.image, cursorModel.contentUri, viewHolderPlaceholderModifier)
31 | if (showFileSize) {
32 | modifier?.sizeTextModifier?.applyTextParams(binding.size)
33 | modifier?.sizeTextModifier?.applyTextParamsConstraint(binding.size)
34 | binding.size.isVisible = false
35 | cursorModel.size?.let { size ->
36 | binding.size.isVisible = size > 0
37 | binding.size.text = size.bytesToFormattedString()
38 | }
39 | }
40 | if (cursorModel.isSelected) {
41 | setupSelectedImage(binding.selection, selectIconModifier)
42 | } else {
43 | setupUnselectedImage(binding.selection, unSelectedIconModifier)
44 | }
45 | }
46 |
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/adapters/single/SingleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.adapters.single
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.ListAdapter
5 | import com.crazylegend.core.databinding.ItemviewImageBinding
6 | import com.crazylegend.core.dto.BaseCursorModel
7 | import com.crazylegend.core.inflater
8 | import com.crazylegend.core.modifiers.SizeTextModifier
9 | import com.crazylegend.core.modifiers.single.ImageModifier
10 |
11 |
12 | /**
13 | * Created by crazy on 5/8/20 to long live and prosper !
14 | */
15 |
16 | open class SingleAdapter(private val showFileSize: Boolean,
17 | private val viewHolderPlaceholderModifier: ImageModifier?,
18 | private val sizeTextModifier: SizeTextModifier?,
19 | private val onClick: (BaseCursorModel) -> Unit) : ListAdapter(SingleDiffUtil()) {
20 |
21 | override fun onCreateViewHolder(parent: ViewGroup, viewBaseCursorModelype: Int) =
22 | SingleViewHolder(ItemviewImageBinding.inflate(parent.inflater, parent, false), viewHolderPlaceholderModifier, sizeTextModifier, onClick)
23 |
24 | override fun onBindViewHolder(holder: SingleViewHolder, position: Int) {
25 | val item = getItem(position)
26 | holder.bind(item, showFileSize)
27 | holder.itemView.tag = item
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/adapters/single/SingleDiffUtil.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.adapters.single
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import com.crazylegend.core.dto.BaseCursorModel
5 |
6 |
7 | /**
8 | * Created by crazy on 5/11/20 to long live and prosper !
9 | */
10 | class SingleDiffUtil : DiffUtil.ItemCallback() {
11 | override fun areItemsTheSame(oldItem: BaseCursorModel, newItem: BaseCursorModel) =
12 | oldItem.id == newItem.id
13 |
14 | override fun areContentsTheSame(oldItem: BaseCursorModel, newItem: BaseCursorModel) =
15 | (oldItem.id == newItem.id) && (oldItem.isSelected == newItem.isSelected)
16 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/adapters/single/SingleViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.adapters.single
2 |
3 | import androidx.core.view.isVisible
4 | import com.crazylegend.core.adapters.BaseViewHolder
5 | import com.crazylegend.core.bytesToFormattedString
6 | import com.crazylegend.core.databinding.ItemviewImageBinding
7 | import com.crazylegend.core.dto.BaseCursorModel
8 | import com.crazylegend.core.gone
9 | import com.crazylegend.core.modifiers.SizeTextModifier
10 | import com.crazylegend.core.modifiers.single.ImageModifier
11 |
12 |
13 | /**
14 | * Created by crazy on 5/8/20 to long live and prosper !
15 | */
16 |
17 | class SingleViewHolder(private val binding: ItemviewImageBinding,
18 | private val viewHolderPlaceholderModifier: ImageModifier?, private val sizeTextModifier: SizeTextModifier?, onClick: (BaseCursorModel) -> Unit) :
19 | BaseViewHolder(binding) {
20 |
21 | fun bind(item: BaseCursorModel, showFileSize: Boolean) {
22 | loadImage(binding.image, item.contentUri, viewHolderPlaceholderModifier)
23 | if (showFileSize) {
24 | sizeTextModifier?.applyTextParams(binding.size)
25 | sizeTextModifier?.applyTextParamsConstraint(binding.size)
26 | binding.size.isVisible = false
27 | item.size?.let { size ->
28 | binding.size.isVisible = size > 0
29 | binding.size.text = size.bytesToFormattedString()
30 | }
31 | }
32 | }
33 |
34 |
35 | private val getModel get() = itemView.tag as? BaseCursorModel?
36 |
37 | init {
38 | itemView.setOnClickListener {
39 | val model = getModel ?: return@setOnClickListener
40 | onClick(model)
41 | }
42 | binding.selection.gone()
43 | }
44 |
45 |
46 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/consts/Consts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.consts
2 |
3 |
4 | /**
5 | * Created by crazy on 5/11/20 to long live and prosper !
6 | */
7 |
8 | const val MODIFIER_ARGUMENT_CONST = "modifier"
9 |
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/contracts/BaseContractMultiPick.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.contracts
2 |
3 | import androidx.activity.result.ActivityResultLauncher
4 | import androidx.appcompat.widget.AppCompatTextView
5 | import com.crazylegend.core.R
6 | import com.crazylegend.core.adapters.multi.MultiSelectAdapter
7 | import com.crazylegend.core.consts.MODIFIER_ARGUMENT_CONST
8 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutMultiBinding
9 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
10 | import com.google.android.material.button.MaterialButton
11 |
12 |
13 | /**
14 | * Created by crazy on 5/11/20 to long live and prosper !
15 | */
16 | interface BaseContractMultiPick {
17 | val multiSelectAdapter: MultiSelectAdapter?
18 | fun addModifier(modifier: BaseMultiPickerModifier)
19 | val modifier: BaseMultiPickerModifier?
20 | val binding: FragmentImagesGalleryLayoutMultiBinding
21 | val askForStoragePermission: ActivityResultLauncher
22 | val layout get() = R.layout.fragment_images_gallery_layout_multi
23 | fun applyTitleModifications(appCompatTextView: AppCompatTextView)
24 | fun applyDoneButtonModifications(doneButton: MaterialButton)
25 | val modifierTag get() = MODIFIER_ARGUMENT_CONST
26 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/contracts/BaseContractSinglePick.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.contracts
2 |
3 | import androidx.activity.result.ActivityResultLauncher
4 | import androidx.appcompat.widget.AppCompatTextView
5 | import com.crazylegend.core.R
6 | import com.crazylegend.core.adapters.single.SingleAdapter
7 | import com.crazylegend.core.consts.MODIFIER_ARGUMENT_CONST
8 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutBinding
9 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
10 |
11 |
12 | /**
13 | * Created by crazy on 5/11/20 to long live and prosper !
14 | */
15 | interface BaseContractSinglePick {
16 | fun addModifier(modifier: BaseSinglePickerModifier)
17 | val modifier: BaseSinglePickerModifier?
18 | fun applyTitleModifications(appCompatTextView: AppCompatTextView)
19 | val modifierTag get() = MODIFIER_ARGUMENT_CONST
20 | val binding: FragmentImagesGalleryLayoutBinding
21 | val askForStoragePermission: ActivityResultLauncher
22 | val layout get() = R.layout.fragment_images_gallery_layout
23 | val singleAdapter: SingleAdapter?
24 |
25 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/dto/BaseCursorModel.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.dto
2 |
3 | import android.content.ContentResolver
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.net.Uri
7 | import android.os.Build
8 | import android.os.Parcelable
9 | import android.provider.MediaStore
10 | import android.util.Size
11 | import kotlinx.parcelize.Parcelize
12 | import java.util.*
13 | import java.util.concurrent.TimeUnit
14 |
15 |
16 | /**
17 | * Created by crazy on 5/11/20 to long live and prosper !
18 | */
19 |
20 | @Parcelize
21 | open class BaseCursorModel(
22 | open val id: Long,
23 | open val displayName: String?,
24 | open val dateAdded: Long?,
25 | open val contentUri: Uri,
26 | open val dateModified: Long?,
27 | open val description: String?,
28 | open val size: Int?,
29 | open val width: Int?,
30 | open val height: Int?,
31 | var isSelected: Boolean = false
32 | ) : Parcelable {
33 |
34 |
35 | /**
36 | * Or just use Glide with the contentUri
37 | * @param contentResolver ContentResolver
38 | * @param customSize Size
39 | * @param options Options?
40 | * @return Bitmap?
41 | */
42 | @Suppress("DEPRECATION")
43 | fun loadThumbnail(contentResolver: ContentResolver,
44 | customSize: Size = Size(size ?: 150, size ?: 150),
45 | legacyKind: Int = MediaStore.Video.Thumbnails.MICRO_KIND,
46 | options: BitmapFactory.Options? = null): Bitmap? {
47 |
48 | return tryOrNull {
49 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
50 | contentResolver.loadThumbnail(contentUri, customSize, null)
51 | } else {
52 | MediaStore.Video.Thumbnails.getThumbnail(contentResolver, id, legacyKind,
53 | options ?: BitmapFactory.Options())
54 | }
55 | }
56 | }
57 |
58 | private fun tryOrNull(function: () -> Bitmap): Bitmap? {
59 | return try {
60 | function()
61 | } catch (e: Exception) {
62 | null
63 | }
64 | }
65 |
66 | val extension get() = displayName?.substringAfterLast(".")
67 |
68 | val addedDateAsDate
69 | get() = dateAdded?.let {
70 | Date(TimeUnit.SECONDS.toMillis(it))
71 | }
72 | val dateModifiedAsDate
73 | get() = dateModified?.let {
74 | Date(TimeUnit.SECONDS.toMillis(it))
75 | }
76 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/dto/PickerConfig.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.dto
2 |
3 | data class PickerConfig(val showFileSize: Boolean = false)
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/SizeTextModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers
2 |
3 | import android.content.res.ColorStateList
4 | import android.graphics.Color
5 | import android.graphics.Paint
6 | import android.graphics.Typeface
7 | import android.os.Parcelable
8 | import android.widget.TextView
9 | import androidx.appcompat.widget.AppCompatTextView
10 | import androidx.constraintlayout.widget.ConstraintLayout
11 | import androidx.core.content.ContextCompat
12 | import androidx.core.view.setMargins
13 | import androidx.core.view.setPadding
14 | import androidx.core.view.updateLayoutParams
15 | import kotlinx.parcelize.Parcelize
16 |
17 |
18 | /**
19 | * Created by crazy on 5/11/20 to long live and prosper !
20 | */
21 |
22 |
23 | @Parcelize
24 | data class SizeTextModifier(
25 | var textColor: Int? = null,
26 | var textPadding: Int? = null,
27 | var textSize: Float? = null,
28 | var startMargin: Int? = null,
29 | var endMargin: Int? = null,
30 | var marginTop: Int? = null,
31 | var marginBottom: Int? = null,
32 | var margin: Int? = null,
33 | var backgroundColor: Int? = null,
34 | var backgroundDrawable: Int? = null,
35 | var textStyle: TextStyle = TextStyle.NORMAL,
36 | var textAlignment: Int = TextView.TEXT_ALIGNMENT_VIEW_START
37 | ) : Parcelable {
38 |
39 |
40 | private val allSizeMarginCondition get() = margin != null
41 |
42 | enum class TextStyle {
43 | BOLD, UNDERLINED, ITALIC, BOLD_ITALIC, NORMAL
44 | }
45 |
46 | private fun updateMargins(textView: AppCompatTextView) {
47 | textView.updateLayoutParams {
48 | startMargin?.let { marginStart = it }
49 | endMargin?.let { marginEnd = it }
50 | marginTop?.let { topMargin = it }
51 | marginBottom?.let { bottomMargin = it }
52 | }
53 | }
54 |
55 | private fun updateMarginsConstraint(textView: AppCompatTextView) {
56 | textView.updateLayoutParams {
57 | startMargin?.let { marginStart = it }
58 | endMargin?.let { marginEnd = it }
59 | marginTop?.let { topMargin = it }
60 | marginBottom?.let { bottomMargin = it }
61 | }
62 | }
63 |
64 | private fun updateAllMargins(textView: AppCompatTextView) {
65 | textView.updateLayoutParams {
66 | margin?.let { setMargins(it) }
67 | }
68 | }
69 |
70 | private fun updateAllMarginsConstraint(textView: AppCompatTextView) {
71 | textView.updateLayoutParams {
72 | margin?.let { setMargins(it) }
73 | }
74 | }
75 |
76 | fun applyTextParams(text: AppCompatTextView) {
77 | textSize?.let { text.textSize = it }
78 | textPadding?.let { text.setPadding(it) }
79 | textColor?.let { text.setTextColor(it) }
80 | text.textAlignment = textAlignment
81 | backgroundColor?.let { text.setBackgroundColor(it) }
82 | applyTextStyle(text)
83 | if (allSizeMarginCondition) {
84 | updateAllMargins(text)
85 | } else {
86 | updateMargins(text)
87 | }
88 | }
89 |
90 | fun applyTextParamsConstraint(text: AppCompatTextView) {
91 | textSize?.let { text.textSize = it }
92 | textPadding?.let { text.setPadding(it) }
93 | textColor?.let { text.setTextColor(it) }
94 | text.textAlignment = textAlignment
95 | backgroundDrawable?.let { text.background = ContextCompat.getDrawable(text.context, it) }
96 | ?: run {
97 | text.backgroundTintList = ColorStateList.valueOf(Color.BLACK)
98 | }
99 | applyTextStyle(text)
100 | if (allSizeMarginCondition) {
101 | updateAllMarginsConstraint(text)
102 | } else {
103 | updateMarginsConstraint(text)
104 | }
105 | }
106 |
107 | private fun applyTextStyle(text: AppCompatTextView) {
108 | when (textStyle) {
109 | TextStyle.BOLD -> {
110 | text.setTypeface(text.typeface, Typeface.BOLD)
111 | }
112 |
113 | TextStyle.UNDERLINED -> {
114 | text.paintFlags = text.paintFlags or Paint.UNDERLINE_TEXT_FLAG
115 | }
116 |
117 | TextStyle.ITALIC -> {
118 | text.setTypeface(text.typeface, Typeface.ITALIC)
119 | }
120 |
121 | TextStyle.BOLD_ITALIC -> {
122 | text.setTypeface(text.typeface, Typeface.BOLD_ITALIC)
123 | }
124 |
125 | else -> {
126 |
127 | }
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/TitleTextModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers
2 |
3 | import android.graphics.Paint
4 | import android.graphics.Typeface
5 | import android.os.Parcelable
6 | import android.widget.RelativeLayout
7 | import android.widget.TextView
8 | import androidx.appcompat.widget.AppCompatTextView
9 | import androidx.constraintlayout.widget.ConstraintLayout
10 | import androidx.core.view.setMargins
11 | import androidx.core.view.setPadding
12 | import androidx.core.view.updateLayoutParams
13 | import kotlinx.parcelize.Parcelize
14 |
15 |
16 | /**
17 | * Created by crazy on 5/11/20 to long live and prosper !
18 | */
19 |
20 |
21 | @Parcelize
22 | data class TitleTextModifier(
23 | var textColor: Int? = null,
24 | var textPadding: Int? = null,
25 | var textSize: Float? = null,
26 | var textString: String? = null,
27 | var startMargin: Int? = null,
28 | var endMargin: Int? = null,
29 | var marginTop: Int? = null,
30 | var marginBottom: Int? = null,
31 | var margin: Int? = null,
32 | var textStyle: TextStyle = TextStyle.NORMAL,
33 | var textAlignment: Int = TextView.TEXT_ALIGNMENT_VIEW_START
34 | ) : Parcelable {
35 |
36 |
37 | private val allSizeMarginCondition get() = margin != null
38 |
39 | enum class TextStyle {
40 | BOLD, UNDERLINED, ITALIC, BOLD_ITALIC, NORMAL
41 | }
42 |
43 | private fun updateMargins(textView: AppCompatTextView) {
44 | textView.updateLayoutParams {
45 | startMargin?.let { marginStart = it }
46 | endMargin?.let { marginEnd = it }
47 | marginTop?.let { topMargin = it }
48 | marginBottom?.let { bottomMargin = it }
49 | }
50 | }
51 |
52 | private fun updateMarginsConstraint(textView: AppCompatTextView) {
53 | textView.updateLayoutParams {
54 | startMargin?.let { marginStart = it }
55 | endMargin?.let { marginEnd = it }
56 | marginTop?.let { topMargin = it }
57 | marginBottom?.let { bottomMargin = it }
58 | }
59 | }
60 |
61 | private fun updateAllMargins(textView: AppCompatTextView) {
62 | textView.updateLayoutParams {
63 | margin?.let { setMargins(it) }
64 | }
65 | }
66 |
67 | private fun updateAllMarginsConstraint(textView: AppCompatTextView) {
68 | textView.updateLayoutParams {
69 | margin?.let { setMargins(it) }
70 | }
71 | }
72 |
73 | fun applyTextParams(text: AppCompatTextView) {
74 | textSize?.let { text.textSize = it }
75 | textPadding?.let { text.setPadding(it) }
76 | textColor?.let { text.setTextColor(it) }
77 | text.textAlignment = textAlignment
78 | applyTextStyle(text)
79 | textString?.let { text.text = it }
80 | if (allSizeMarginCondition) {
81 | updateAllMargins(text)
82 | } else {
83 | updateMargins(text)
84 | }
85 | }
86 |
87 | fun applyTextParamsConstraint(text: AppCompatTextView) {
88 | textSize?.let { text.textSize = it }
89 | textPadding?.let { text.setPadding(it) }
90 | textColor?.let { text.setTextColor(it) }
91 | text.textAlignment = textAlignment
92 | applyTextStyle(text)
93 | textString?.let { text.text = it }
94 | if (allSizeMarginCondition) {
95 | updateAllMarginsConstraint(text)
96 | } else {
97 | updateMarginsConstraint(text)
98 | }
99 | }
100 |
101 |
102 | private fun applyTextStyle(text: AppCompatTextView) {
103 | when (textStyle) {
104 | TextStyle.BOLD -> {
105 | text.setTypeface(text.typeface, Typeface.BOLD)
106 | }
107 | TextStyle.UNDERLINED -> {
108 | text.paintFlags = text.paintFlags or Paint.UNDERLINE_TEXT_FLAG
109 | }
110 | TextStyle.ITALIC -> {
111 | text.setTypeface(text.typeface, Typeface.ITALIC)
112 | }
113 | TextStyle.BOLD_ITALIC -> {
114 | text.setTypeface(text.typeface, Typeface.BOLD_ITALIC)
115 | }
116 | else -> {
117 |
118 | }
119 | }
120 | }
121 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/base/BaseMultiPickerModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers.base
2 |
3 | import android.os.Parcelable
4 | import android.view.View
5 | import androidx.appcompat.widget.AppCompatImageView
6 | import androidx.core.view.marginBottom
7 | import androidx.core.view.marginLeft
8 | import androidx.core.view.marginRight
9 | import androidx.core.view.marginTop
10 | import com.crazylegend.core.bottom
11 | import com.crazylegend.core.constrainBottomToTopOf
12 | import com.crazylegend.core.left
13 | import com.crazylegend.core.modifiers.SizeTextModifier
14 | import com.crazylegend.core.modifiers.TitleTextModifier
15 | import com.crazylegend.core.modifiers.multi.DoneButtonModifier
16 | import com.crazylegend.core.modifiers.multi.SelectIconModifier
17 | import com.crazylegend.core.modifiers.single.ImageModifier
18 | import com.crazylegend.core.right
19 | import com.crazylegend.core.top
20 | import kotlinx.parcelize.Parcelize
21 |
22 |
23 | /**
24 | * Created by crazy on 5/16/20 to long live and prosper !
25 | */
26 |
27 | @Parcelize
28 | open class BaseMultiPickerModifier(
29 | open val doneButtonModifier: DoneButtonModifier = DoneButtonModifier(),
30 | open val titleTextModifier: TitleTextModifier = TitleTextModifier(),
31 | open val selectIconModifier: SelectIconModifier = SelectIconModifier(),
32 | open val unSelectedIconModifier: SelectIconModifier = SelectIconModifier(),
33 | open var indicatorsGravity: Gravity = Gravity.BOTTOM_RIGHT,
34 | open val viewHolderPlaceholderModifier: ImageModifier = ImageModifier(),
35 | open val noContentTextModifier: TitleTextModifier = TitleTextModifier(),
36 | open var loadingIndicatorTint: Int? = null,
37 | open val sizeTextModifier: SizeTextModifier = SizeTextModifier(),
38 | ) : Parcelable {
39 |
40 | enum class Gravity {
41 | TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
42 | }
43 |
44 | inline fun setupBaseMultiPicker(
45 | doneButtonModifications: DoneButtonModifier.() -> Unit = {},
46 | titleModifications: TitleTextModifier.() -> Unit = {},
47 | selectIconModifications: SelectIconModifier.() -> Unit = {},
48 | unSelectIconModifications: SelectIconModifier.() -> Unit = {},
49 | viewHolderPlaceholderModifications: ImageModifier.() -> Unit = {},
50 | gravityForSelectAndUnSelectIndicators: Gravity = Gravity.BOTTOM_RIGHT,
51 | tintForLoadingProgressBar: Int? = null,
52 | sizeTextModifications: SizeTextModifier.() -> Unit = {},
53 | ) {
54 | doneButtonModifier.doneButtonModifications()
55 | titleTextModifier.titleModifications()
56 | selectIconModifier.selectIconModifications()
57 | unSelectedIconModifier.unSelectIconModifications()
58 | viewHolderPlaceholderModifier.viewHolderPlaceholderModifications()
59 | indicatorsGravity = gravityForSelectAndUnSelectIndicators
60 | loadingIndicatorTint = tintForLoadingProgressBar
61 | sizeTextModifier.sizeTextModifications()
62 | }
63 |
64 | fun applyGravity(imageView: AppCompatImageView) {
65 | when (indicatorsGravity) {
66 | Gravity.TOP_LEFT -> {
67 | imageView.top(imageView.marginTop)
68 | imageView.left(imageView.marginLeft)
69 | }
70 |
71 | Gravity.TOP_RIGHT -> {
72 | imageView.top(imageView.marginTop)
73 | imageView.right(imageView.marginRight)
74 | }
75 |
76 | Gravity.BOTTOM_LEFT -> {
77 | imageView.bottom(imageView.marginBottom)
78 | imageView.left(imageView.marginLeft)
79 | }
80 |
81 | Gravity.BOTTOM_RIGHT -> {
82 | imageView.bottom(imageView.marginBottom)
83 | imageView.right(imageView.marginRight)
84 | }
85 | }
86 | }
87 |
88 | fun applyGravityWithBottomConstraint(imageView: AppCompatImageView, view: View) {
89 | when (indicatorsGravity) {
90 | Gravity.TOP_LEFT -> {
91 | imageView.top(imageView.marginTop)
92 | imageView.left(imageView.marginLeft)
93 | }
94 |
95 | Gravity.TOP_RIGHT -> {
96 | imageView.top(imageView.marginTop)
97 | imageView.right(imageView.marginRight)
98 | }
99 |
100 | Gravity.BOTTOM_LEFT -> {
101 | imageView.constrainBottomToTopOf(view, imageView.marginBottom)
102 | imageView.left(imageView.marginLeft)
103 | }
104 |
105 | Gravity.BOTTOM_RIGHT -> {
106 | imageView.constrainBottomToTopOf(view, imageView.marginBottom)
107 | imageView.right(imageView.marginRight)
108 | }
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/base/BaseSinglePickerModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers.base
2 |
3 | import android.os.Parcelable
4 | import com.crazylegend.core.modifiers.SizeTextModifier
5 | import com.crazylegend.core.modifiers.TitleTextModifier
6 | import com.crazylegend.core.modifiers.single.ImageModifier
7 | import kotlinx.parcelize.Parcelize
8 |
9 |
10 | /**
11 | * Created by crazy on 5/16/20 to long live and prosper !
12 | */
13 |
14 | @Parcelize
15 | open class BaseSinglePickerModifier(
16 | open val titleTextModifier: TitleTextModifier = TitleTextModifier(),
17 | open val viewHolderPlaceholderModifier: ImageModifier = ImageModifier(),
18 | open val noContentTextModifier: TitleTextModifier = TitleTextModifier(),
19 | open var loadingIndicatorTint: Int? = null,
20 | open val sizeTextModifier: SizeTextModifier = SizeTextModifier(),
21 | ) : Parcelable {
22 |
23 | inline fun setupBaseModifier(
24 | loadingIndicatorColor: Int? = null,
25 | titleTextModifications: TitleTextModifier.() -> Unit = {},
26 | placeHolderModifications: ImageModifier.() -> Unit = {},
27 | noContentTextModifications: TitleTextModifier.() -> Unit = {},
28 | sizeTextModifications: SizeTextModifier.() -> Unit = {},
29 | ) {
30 | titleTextModifier.titleTextModifications()
31 | loadingIndicatorTint = loadingIndicatorColor
32 | viewHolderPlaceholderModifier.placeHolderModifications()
33 | noContentTextModifier.noContentTextModifications()
34 | sizeTextModifier.sizeTextModifications()
35 | }
36 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/multi/DoneButtonModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers.multi
2 |
3 | import android.content.res.ColorStateList
4 | import android.os.Parcelable
5 | import android.widget.RelativeLayout
6 | import androidx.core.view.setMargins
7 | import androidx.core.view.setPadding
8 | import androidx.core.view.updateLayoutParams
9 | import com.google.android.material.button.MaterialButton
10 | import kotlinx.parcelize.Parcelize
11 |
12 |
13 | /**
14 | * Created by crazy on 5/11/20 to long live and prosper !
15 | */
16 |
17 | @Parcelize
18 | data class DoneButtonModifier(
19 | var padding: Int? = null,
20 | var startMargin: Int? = null,
21 | var endMargin: Int? = null,
22 | var marginTop: Int? = null,
23 | var marginBottom: Int? = null,
24 | var margin: Int? = null,
25 | var tint: Int? = null,
26 | var cornerRadius: Int? = null,
27 | var rippleColor: Int? = null,
28 | var icon: Int? = null,
29 | var iconGravity: Int? = null,
30 | var iconTint: Int? = null
31 | ) : Parcelable {
32 |
33 | private val allSizeMarginCondition get() = margin != null
34 |
35 | fun applyImageParams(button: MaterialButton) {
36 | padding?.let { button.setPadding(it) }
37 | if (allSizeMarginCondition) {
38 | updateAllMargins(button)
39 | } else {
40 | updateMargins(button)
41 | }
42 | iconTint?.let { button.iconTint = ColorStateList.valueOf(it) }
43 | iconGravity?.let { button.iconGravity = it }
44 | icon?.let { button.setIconResource(it) }
45 | rippleColor?.let { button.rippleColor = ColorStateList.valueOf(it) }
46 | cornerRadius?.let { button.cornerRadius = it }
47 | tint?.let { button.backgroundTintList = ColorStateList.valueOf(it) }
48 | }
49 |
50 | private fun updateMargins(button: MaterialButton) {
51 | button.updateLayoutParams {
52 | startMargin?.let { marginStart = it }
53 | endMargin?.let { marginEnd = it }
54 | marginTop?.let { topMargin = it }
55 | marginBottom?.let { bottomMargin = it }
56 | }
57 | }
58 |
59 | private fun updateAllMargins(button: MaterialButton) {
60 | button.updateLayoutParams {
61 | margin?.let { setMargins(it) }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/multi/SelectIconModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers.multi
2 |
3 | import android.content.res.ColorStateList
4 | import android.os.Parcelable
5 | import androidx.appcompat.widget.AppCompatImageView
6 | import androidx.constraintlayout.widget.ConstraintLayout
7 | import androidx.core.view.setMargins
8 | import androidx.core.view.setPadding
9 | import androidx.core.view.updateLayoutParams
10 | import androidx.core.widget.ImageViewCompat
11 | import kotlinx.parcelize.Parcelize
12 |
13 |
14 | /**
15 | * Created by crazy on 5/11/20 to long live and prosper !
16 | */
17 |
18 | @Parcelize
19 | data class SelectIconModifier(
20 | var resID: Int? = null,
21 | var padding: Int? = null,
22 | var startMargin: Int? = null,
23 | var endMargin: Int? = null,
24 | var marginTop: Int? = null,
25 | var marginBottom: Int? = null,
26 | var margin: Int? = null,
27 | var tint: Int? = null
28 | ) : Parcelable {
29 |
30 | private val allSizeMarginCondition get() = margin != null
31 |
32 |
33 | fun applyImageParams(imageView: AppCompatImageView, resourceID: Int) {
34 | imageView.setImageResource(resourceID)
35 | padding?.let { imageView.setPadding(it) }
36 | tint?.let { ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(it)) }
37 |
38 | if (allSizeMarginCondition) {
39 | updateAllMargins(imageView)
40 | } else {
41 | updateMargins(imageView)
42 | }
43 | }
44 |
45 | private fun updateMargins(imageView: AppCompatImageView) {
46 | imageView.updateLayoutParams {
47 | startMargin?.let { marginStart = it }
48 | endMargin?.let { marginEnd = it }
49 | marginTop?.let { topMargin = it }
50 | marginBottom?.let { bottomMargin = it }
51 | }
52 | }
53 |
54 | private fun updateAllMargins(imageView: AppCompatImageView) {
55 | imageView.updateLayoutParams {
56 | margin?.let { setMargins(it) }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/modifiers/single/ImageModifier.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.modifiers.single
2 |
3 | import android.os.Parcelable
4 | import androidx.appcompat.widget.AppCompatImageView
5 | import androidx.constraintlayout.widget.ConstraintLayout
6 | import androidx.core.view.setMargins
7 | import androidx.core.view.setPadding
8 | import androidx.core.view.updateLayoutParams
9 | import kotlinx.parcelize.Parcelize
10 |
11 |
12 | /**
13 | * Created by crazy on 5/11/20 to long live and prosper !
14 | */
15 |
16 | /**
17 | * Only works for dialog fragment since there it's visible
18 | */
19 | @Parcelize
20 | data class ImageModifier(
21 | var resID: Int? = null,
22 | var padding: Int? = null,
23 | var startMargin: Int? = null,
24 | var endMargin: Int? = null,
25 | var marginTop: Int? = null,
26 | var marginBottom: Int? = null,
27 | var margin: Int? = null
28 | ) : Parcelable {
29 |
30 | private val allSizeMarginCondition get() = margin != null
31 |
32 | fun applyImageParamsConstraintLayout(imageView: AppCompatImageView) {
33 | resID?.let { imageView.setImageResource(it) }
34 | padding?.let { imageView.setPadding(it) }
35 | if (allSizeMarginCondition) {
36 | updateAllMarginsConstraint(imageView)
37 | } else {
38 | updateMarginsConstraint(imageView)
39 | }
40 | }
41 |
42 |
43 | private fun updateMarginsConstraint(imageView: AppCompatImageView) {
44 | imageView.updateLayoutParams {
45 | startMargin?.let { marginStart = it }
46 | endMargin?.let { marginEnd = it }
47 | marginTop?.let { topMargin = it }
48 | marginBottom?.let { bottomMargin = it }
49 | }
50 | }
51 |
52 | private fun updateAllMarginsConstraint(imageView: AppCompatImageView) {
53 | imageView.updateLayoutParams {
54 | margin?.let { setMargins(it) }
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/crazylegend/core/sorting/SortOrder.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core.sorting
2 |
3 |
4 | /**
5 | * Created by crazy on 5/8/20 to long live and prosper !
6 | */
7 | enum class SortOrder {
8 | DATE_ADDED_DESC,
9 | DATE_ADDED_ASC,
10 | DISPLAY_NAME_DESC,
11 | DISPLAY_NAME_ASC,
12 | DATE_MODIFIED_DESC,
13 | DATE_MODIFIED_ASC,
14 | SIZE_ASC,
15 | SIZE_DESC
16 | }
--------------------------------------------------------------------------------
/core/src/main/res/anim/fragment_fade_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/anim/fragment_fade_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/core/src/main/res/drawable/ic_checked_default.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/main/res/drawable/ic_image.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/main/res/drawable/ic_minus.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/main/res/drawable/ic_unchecked_default.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/main/res/drawable/rounded_bg_abstract_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/src/main/res/layout/fragment_images_gallery_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
33 |
34 |
38 |
39 |
48 |
49 |
50 |
57 |
58 |
64 |
65 |
--------------------------------------------------------------------------------
/core/src/main/res/layout/fragment_images_gallery_layout_multi.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
32 |
33 |
38 |
39 |
51 |
52 |
63 |
64 |
65 |
66 |
73 |
74 |
80 |
--------------------------------------------------------------------------------
/core/src/main/res/layout/itemview_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
25 |
26 |
41 |
42 |
--------------------------------------------------------------------------------
/core/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #BDBDBD
4 | #FFFFFF
5 | #000000
6 |
--------------------------------------------------------------------------------
/core/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Done
4 | ¯\_(ツ)_/¯\n\nNo content has been found on your device
5 |
--------------------------------------------------------------------------------
/core/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/core/src/test/java/com/crazylegend/core/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.core
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/extensions/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/extensions/build.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | namespace 'com.crazylegend.extensions'
3 | }
--------------------------------------------------------------------------------
/extensions/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/extensions/consumer-rules.pro
--------------------------------------------------------------------------------
/extensions/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 |
--------------------------------------------------------------------------------
/extensions/src/androidTest/java/com/crazylegend/extensions/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.extensions
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.crazylegend.extensions.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/extensions/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/src/main/java/com/crazylegend/extensions/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.extensions
2 |
3 | import android.content.ContentResolver
4 | import android.content.Context
5 | import android.database.ContentObserver
6 | import android.database.Cursor
7 | import android.net.Uri
8 | import android.os.Handler
9 | import android.os.Looper
10 | import android.view.View
11 | import androidx.appcompat.app.AppCompatActivity
12 | import androidx.fragment.app.Fragment
13 | import androidx.fragment.app.FragmentManager
14 | import androidx.lifecycle.AndroidViewModel
15 |
16 |
17 | /**
18 | * Created by crazy on 5/11/20 to long live and prosper !
19 | */
20 |
21 | fun View.visible() {
22 | this.visibility = View.VISIBLE
23 | }
24 |
25 | fun View.gone() {
26 | this.visibility = View.GONE
27 | }
28 |
29 | fun Cursor.getSafeColumn(column: String): Int? {
30 | return try {
31 | getColumnIndexOrThrow(column)
32 | } catch (e: Exception) {
33 | null
34 | }
35 | }
36 |
37 | fun Context.setupManager(): FragmentManager {
38 | val manager = when (this) {
39 | is Fragment -> this.childFragmentManager
40 | is AppCompatActivity -> if (isFinishing) null else this.supportFragmentManager
41 | else -> null
42 | }
43 | requireNotNull(manager) {
44 | "Use a Fragment or AppCompat activity"
45 | }
46 | return manager
47 | }
48 |
49 |
50 | val AndroidViewModel.context: Context
51 | get() = getApplication()
52 |
53 |
54 | inline fun ContentResolver.registerObserver(
55 | uri: Uri,
56 | crossinline observer: (change: Boolean) -> Unit
57 | ): ContentObserver {
58 | val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
59 | override fun onChange(selfChange: Boolean) {
60 | observer(selfChange)
61 | }
62 | }
63 | registerContentObserver(uri, true, contentObserver)
64 | return contentObserver
65 | }
66 |
67 | fun Int.bytesToMegabytes(): Double {
68 | return this / (1024.0 * 1024.0)
69 | }
70 |
--------------------------------------------------------------------------------
/extensions/src/main/java/com/crazylegend/extensions/FragmentViewBindingDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.extensions
2 |
3 |
4 | import android.view.View
5 | import androidx.fragment.app.Fragment
6 | import androidx.lifecycle.DefaultLifecycleObserver
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleOwner
9 | import androidx.lifecycle.observe
10 | import androidx.viewbinding.ViewBinding
11 | import kotlin.properties.ReadOnlyProperty
12 | import kotlin.reflect.KProperty
13 |
14 | class FragmentViewBindingDelegate(val fragment: Fragment, val viewBinder: (View) -> T) : ReadOnlyProperty {
15 |
16 | private var fragmentBinding: T? = null
17 |
18 | init {
19 | fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
20 | override fun onCreate(owner: LifecycleOwner) {
21 | fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
22 | viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
23 | override fun onDestroy(owner: LifecycleOwner) {
24 | fragmentBinding = null
25 | }
26 | })
27 | }
28 | }
29 | })
30 | }
31 |
32 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
33 | val binding = fragmentBinding
34 | if (binding != null) {
35 | return binding
36 | }
37 |
38 | val lifecycle = fragment.viewLifecycleOwner.lifecycle
39 | if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
40 | throw IllegalStateException("Fragment views are destroyed.")
41 | }
42 | return viewBinder(thisRef.requireView()).also { fragmentBinding = it }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/extensions/src/main/java/com/crazylegend/extensions/ViewBindingExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.extensions
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.fragment.app.Fragment
7 | import androidx.viewbinding.ViewBinding
8 |
9 |
10 | inline fun AppCompatActivity.viewBinding(crossinline bindingInflater: (LayoutInflater) -> T) =
11 | lazy(LazyThreadSafetyMode.NONE) {
12 | bindingInflater.invoke(layoutInflater)
13 | }
14 |
15 |
16 | fun Fragment.viewBinding(viewBindingFactory: (View) -> T) =
17 | FragmentViewBindingDelegate(this, viewBindingFactory)
18 |
19 |
--------------------------------------------------------------------------------
/extensions/src/test/java/com/crazylegend/extensions/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.extensions
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536m
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | kotlin.code.style=obsolete
5 | kapt.incremental.apt=true
6 | org.gradle.parallel=true
7 | org.gradle.caching=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 02 12:28:58 CET 2020
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-7.3.3-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 |
--------------------------------------------------------------------------------
/imagepicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/imagepicker/build.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | namespace 'com.crazylegend.imagepicker'
3 | }
--------------------------------------------------------------------------------
/imagepicker/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/imagepicker/consumer-rules.pro
--------------------------------------------------------------------------------
/imagepicker/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 |
--------------------------------------------------------------------------------
/imagepicker/screens/screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/imagepicker/screens/screen_1.png
--------------------------------------------------------------------------------
/imagepicker/screens/screen_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/imagepicker/screens/screen_3.png
--------------------------------------------------------------------------------
/imagepicker/src/androidTest/java/com/crazylegend/imagepicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.crazylegend.imagepicker.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/imagepicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/consts/DialogConsts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.consts
2 |
3 |
4 | /**
5 | * Created by crazy on 5/8/20 to long live and prosper !
6 | */
7 |
8 |
9 | const val SINGLE_PICKER_BOTTOM_SHEET = "imagesSinglePickerBottomSheetDialog"
10 | const val SINGLE_PICKER_DIALOG = "imagesSinglePickerDialog"
11 |
12 |
13 | const val MULTI_PICKER_BOTTOM_SHEET = "imagesMultiPickerBottomSheetDialog"
14 | const val MULTI_PICKER_DIALOG = "imagesMultiPickerDialog"
15 |
16 |
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/contracts/MultiPickerContracts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.contracts
2 |
3 | import com.crazylegend.core.contracts.BaseContractMultiPick
4 | import com.crazylegend.imagepicker.images.ImagesVM
5 | import com.crazylegend.imagepicker.listeners.onImagesPicked
6 | import com.crazylegend.imagepicker.pickers.MultiImagePicker
7 |
8 |
9 | /**
10 | * Created by crazy on 5/8/20 to long live and prosper !
11 | */
12 | internal interface MultiPickerContracts : BaseContractMultiPick {
13 | val imagesVM: ImagesVM
14 | var onImagesPicked: onImagesPicked?
15 | val errorTag: String get() = MultiImagePicker::javaClass.name
16 |
17 | }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/contracts/SinglePickerContracts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.contracts
2 |
3 | import com.crazylegend.core.contracts.BaseContractSinglePick
4 | import com.crazylegend.imagepicker.images.ImagesVM
5 | import com.crazylegend.imagepicker.listeners.onImagePicked
6 | import com.crazylegend.imagepicker.pickers.SingleImagePicker
7 |
8 |
9 | /**
10 | * Created by crazy on 5/8/20 to long live and prosper !
11 | */
12 | internal interface SinglePickerContracts : BaseContractSinglePick {
13 | val imagesVM: ImagesVM
14 | var onImagePicked: onImagePicked?
15 | val errorTag get() = SingleImagePicker::javaClass.name
16 | }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/dialogs/multi/MultiImagePickerBottomSheetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.dialogs.multi
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.View
8 |
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.appcompat.widget.AppCompatTextView
11 | import androidx.core.os.bundleOf
12 | import androidx.fragment.app.setFragmentResult
13 | import androidx.fragment.app.viewModels
14 | import androidx.lifecycle.observe
15 | import com.crazylegend.core.abstracts.AbstractBottomSheetDialogFragment
16 | import com.crazylegend.core.adapters.multi.MultiSelectAdapter
17 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutMultiBinding
18 | import com.crazylegend.core.dto.PickerConfig
19 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
20 |
21 | import com.crazylegend.extensions.viewBinding
22 | import com.crazylegend.imagepicker.contracts.MultiPickerContracts
23 | import com.crazylegend.imagepicker.images.ImageModel
24 | import com.crazylegend.imagepicker.images.ImagesVM
25 | import com.crazylegend.imagepicker.listeners.onImagesPicked
26 | import com.crazylegend.imagepicker.pickers.MultiImagePicker
27 | import com.google.android.material.button.MaterialButton
28 |
29 |
30 | /**
31 | * Created by crazy on 5/8/20 to long live and prosper !
32 | */
33 | internal class MultiImagePickerBottomSheetDialog : AbstractBottomSheetDialogFragment(),
34 | MultiPickerContracts {
35 |
36 | var extensions: Array? = arrayOf()
37 | var pickerConfig: PickerConfig = PickerConfig()
38 |
39 | override val layout: Int
40 | get() = super.layout
41 | override var onImagesPicked: onImagesPicked? = null
42 | override val binding by viewBinding(FragmentImagesGalleryLayoutMultiBinding::bind)
43 | override val imagesVM by viewModels()
44 | override val modifier: BaseMultiPickerModifier?
45 | get() = arguments?.getParcelable(modifierTag)
46 |
47 | override val multiSelectAdapter by lazy {
48 | MultiSelectAdapter(modifier, pickerConfig.showFileSize)
49 | }
50 | override val askForStoragePermission =
51 | registerForActivityResult(ActivityResultContracts.RequestPermission()) {
52 | if (it) {
53 | imagesVM.loadImages(extensions = extensions)
54 | } else {
55 | Log.e(errorTag, "PERMISSION DENIED")
56 | dismissAllowingStateLoss()
57 | }
58 | }
59 |
60 | @Suppress("UNCHECKED_CAST")
61 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
62 | super.onViewCreated(view, savedInstanceState)
63 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
64 | askForStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
65 | } else {
66 | askForStoragePermission.launch(Manifest.permission.READ_MEDIA_IMAGES)
67 | }
68 | setupUIForMultiPicker(
69 | binding.gallery,
70 | multiSelectAdapter,
71 | binding.doneButton,
72 | binding.title,
73 | binding.loadingIndicator,
74 | modifier?.loadingIndicatorTint,
75 | ::applyDoneButtonModifications,
76 | ::applyTitleModifications
77 | )
78 |
79 | imagesVM.images.observe(viewLifecycleOwner) {
80 | setupList(
81 | multiSelectAdapter,
82 | it,
83 | binding.noContentText,
84 | modifier?.noContentTextModifier
85 | )
86 | }
87 | handleUIIndicator(imagesVM.loadingIndicator, binding.loadingIndicator)
88 |
89 |
90 | binding.doneButton.setOnClickListener {
91 | val imagesList =
92 | multiSelectAdapter.currentList.filter { it.isSelected } as? List
93 | ?: emptyList()
94 | setFragmentResult(
95 | MultiImagePicker.MULTI_IMAGE_REQUEST_KEY,
96 | bundleOf(MultiImagePicker.ON_MULTI_IMAGE_PICK_KEY to imagesList)
97 | )
98 | onImagesPicked?.onImagesPicked(imagesList)
99 | dismissAllowingStateLoss()
100 | }
101 | }
102 |
103 | override fun onDestroyView() {
104 | super.onDestroyView()
105 | onImagesPicked = null
106 | }
107 |
108 | override fun addModifier(modifier: BaseMultiPickerModifier) {
109 | arguments = bundleOf(modifierTag to modifier)
110 | }
111 |
112 | override fun applyTitleModifications(appCompatTextView: AppCompatTextView) {
113 | modifier?.titleTextModifier?.applyTextParams(appCompatTextView)
114 | }
115 |
116 | override fun applyDoneButtonModifications(doneButton: MaterialButton) {
117 | modifier?.doneButtonModifier?.applyImageParams(doneButton)
118 | }
119 | }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/dialogs/single/SingleImagePickerBottomSheetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.dialogs.single
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.View
8 |
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.appcompat.widget.AppCompatTextView
11 | import androidx.core.os.bundleOf
12 | import androidx.fragment.app.setFragmentResult
13 | import androidx.fragment.app.viewModels
14 | import androidx.lifecycle.observe
15 | import com.crazylegend.core.abstracts.AbstractBottomSheetDialogFragment
16 | import com.crazylegend.core.adapters.single.SingleAdapter
17 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutBinding
18 | import com.crazylegend.core.dto.PickerConfig
19 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
20 | import com.crazylegend.extensions.viewBinding
21 | import com.crazylegend.imagepicker.contracts.SinglePickerContracts
22 | import com.crazylegend.imagepicker.images.ImageModel
23 | import com.crazylegend.imagepicker.images.ImagesVM
24 | import com.crazylegend.imagepicker.listeners.onImagePicked
25 | import com.crazylegend.imagepicker.pickers.SingleImagePicker
26 |
27 |
28 | /**
29 | * Created by crazy on 5/8/20 to long live and prosper !
30 | */
31 | internal class SingleImagePickerBottomSheetDialog : AbstractBottomSheetDialogFragment(),
32 | SinglePickerContracts {
33 |
34 | override val layout: Int
35 | get() = super.layout
36 | override var onImagePicked: onImagePicked? = null
37 | override val binding by viewBinding(FragmentImagesGalleryLayoutBinding::bind)
38 | override val imagesVM by viewModels()
39 | override val modifier: BaseSinglePickerModifier?
40 | get() = arguments?.getParcelable(modifierTag)
41 | var extensions: Array? = arrayOf()
42 | var pickerConfig: PickerConfig = PickerConfig()
43 |
44 | override val singleAdapter by lazy {
45 | SingleAdapter(pickerConfig.showFileSize, modifier?.viewHolderPlaceholderModifier, modifier?.sizeTextModifier) {
46 | val image = it as ImageModel
47 | setFragmentResult(
48 | SingleImagePicker.SINGLE_IMAGE_REQUEST_KEY,
49 | bundleOf(SingleImagePicker.ON_SINGLE_IMAGE_PICK_KEY to image)
50 | )
51 | onImagePicked?.forImage(image)
52 | dismissAllowingStateLoss()
53 | }
54 | }
55 |
56 | override val askForStoragePermission =
57 | registerForActivityResult(ActivityResultContracts.RequestPermission()) {
58 | if (it) {
59 | imagesVM.loadImages(extensions = extensions)
60 | } else {
61 | Log.e(errorTag, "PERMISSION DENIED")
62 | dismissAllowingStateLoss()
63 | }
64 | }
65 |
66 |
67 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
68 | super.onViewCreated(view, savedInstanceState)
69 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
70 | askForStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
71 | } else {
72 | askForStoragePermission.launch(Manifest.permission.READ_MEDIA_IMAGES)
73 | }
74 | setupUIForSinglePicker(
75 | binding.gallery,
76 | singleAdapter,
77 | binding.title,
78 | binding.loadingIndicator,
79 | modifier?.loadingIndicatorTint,
80 | ::applyTitleModifications
81 | )
82 | imagesVM.images.observe(viewLifecycleOwner) {
83 | setupList(singleAdapter, it, binding.noContentText, modifier?.noContentTextModifier)
84 | }
85 | handleUIIndicator(imagesVM.loadingIndicator, binding.loadingIndicator)
86 |
87 | }
88 |
89 | override fun onDestroyView() {
90 | super.onDestroyView()
91 | onImagePicked = null
92 | }
93 |
94 | override fun applyTitleModifications(appCompatTextView: AppCompatTextView) {
95 | modifier?.titleTextModifier?.applyTextParams(appCompatTextView)
96 | }
97 |
98 | override fun addModifier(modifier: BaseSinglePickerModifier) {
99 | arguments = bundleOf(modifierTag to modifier)
100 | }
101 |
102 | }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/images/ImageModel.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.images
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import com.crazylegend.core.dto.BaseCursorModel
6 | import kotlinx.parcelize.Parcelize
7 |
8 | /**
9 | * Simple data class to hold information about an image included in the device's MediaStore.
10 | */
11 |
12 | @Parcelize
13 | data class ImageModel(override val id: Long,
14 | override val displayName: String?,
15 | override val dateAdded: Long?,
16 | override val contentUri: Uri,
17 | override val dateModified: Long?,
18 | override val description: String?,
19 | override val size: Int?,
20 | override val width: Int?,
21 | override val height: Int?) :
22 | BaseCursorModel(id, displayName, dateAdded, contentUri, dateModified, description, size, width, height), Parcelable
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/listeners/onImagePicked.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.listeners
2 |
3 | import com.crazylegend.imagepicker.images.ImageModel
4 |
5 |
6 | /**
7 | * Created by crazy on 5/8/20 to long live and prosper !
8 | */
9 | internal fun interface onImagePicked {
10 | fun forImage(imageModel: ImageModel)
11 | }
12 |
13 | internal inline fun onImageDSL(crossinline imageCallback: (image: ImageModel) -> Unit) = onImagePicked { imageModel -> imageCallback(imageModel) }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/listeners/onImagesPicked.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.listeners
2 |
3 | import com.crazylegend.imagepicker.images.ImageModel
4 |
5 |
6 | /**
7 | * Created by crazy on 5/8/20 to long live and prosper !
8 | */
9 | internal interface onImagesPicked {
10 | fun onImagesPicked(images: List = emptyList())
11 | }
12 |
13 | internal inline fun onImagesDSL(crossinline callback: (list: List) -> Unit) = object : onImagesPicked {
14 | override fun onImagesPicked(images: List) {
15 | callback(images)
16 | }
17 | }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/pickers/MultiImagePicker.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.pickers
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.fragment.app.FragmentManager
6 | import com.crazylegend.core.dto.PickerConfig
7 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
8 | import com.crazylegend.core.setupModifier
9 | import com.crazylegend.extensions.setupManager
10 | import com.crazylegend.imagepicker.consts.MULTI_PICKER_BOTTOM_SHEET
11 | import com.crazylegend.imagepicker.consts.MULTI_PICKER_DIALOG
12 | import com.crazylegend.imagepicker.dialogs.multi.MultiImagePickerBottomSheetDialog
13 | import com.crazylegend.imagepicker.images.ImageModel
14 | import com.crazylegend.imagepicker.listeners.onImagesDSL
15 |
16 |
17 | /**
18 | * Created by crazy on 5/8/20 to long live and prosper !
19 | */
20 | object MultiImagePicker {
21 |
22 |
23 | const val MULTI_IMAGE_REQUEST_KEY = "multiImagesRequest"
24 | const val ON_MULTI_IMAGE_PICK_KEY = "onMultiImagesPicked"
25 |
26 | fun restoreListener(context: Context, imagesList: (list: List) -> Unit = {}) {
27 | val manager = context.setupManager()
28 | when (val fragment = manager.findFragmentByTag(MULTI_PICKER_BOTTOM_SHEET)
29 | ?: manager.findFragmentByTag(MULTI_PICKER_DIALOG)) {
30 | is MultiImagePickerBottomSheetDialog -> {
31 | fragment.onImagesPicked = onImagesDSL(imagesList)
32 | }
33 |
34 | null -> {
35 | Log.e(MultiImagePicker::class.java.name, "FRAGMENT NOT FOUND")
36 | }
37 | }
38 | }
39 |
40 |
41 | fun showPicker(
42 | context: Context, extensions: Array? = arrayOf(),
43 | pickerConfig: PickerConfig = PickerConfig(),
44 | multiImagePickerModifier: BaseMultiPickerModifier.() -> Unit = {},
45 | imagesList: (list: List) -> Unit = {}
46 | ) {
47 | val manager = context.setupManager()
48 | val modifier = setupModifier(multiImagePickerModifier)
49 | with(MultiImagePickerBottomSheetDialog()) {
50 | this.extensions = extensions
51 | this.pickerConfig = pickerConfig
52 | addModifier(modifier)
53 | onImagesPicked = onImagesDSL(imagesList)
54 | show(manager, MULTI_PICKER_BOTTOM_SHEET)
55 | }
56 | }
57 |
58 | fun showPicker(
59 | fragmentManager: FragmentManager,
60 | extensions: Array? = arrayOf(),
61 | pickerConfig: PickerConfig = PickerConfig(),
62 | multiImagePickerModifier: BaseMultiPickerModifier.() -> Unit = {},
63 | imagesList: (list: List) -> Unit = {}
64 | ) {
65 | val modifier = setupModifier(multiImagePickerModifier)
66 | with(MultiImagePickerBottomSheetDialog()) {
67 | this.extensions = extensions
68 | this.pickerConfig = pickerConfig
69 | addModifier(modifier)
70 | onImagesPicked = onImagesDSL(imagesList)
71 | show(fragmentManager, MULTI_PICKER_BOTTOM_SHEET)
72 | }
73 | }
74 |
75 |
76 | }
--------------------------------------------------------------------------------
/imagepicker/src/main/java/com/crazylegend/imagepicker/pickers/SingleImagePicker.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker.pickers
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.fragment.app.FragmentManager
6 | import com.crazylegend.core.dto.PickerConfig
7 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
8 | import com.crazylegend.core.setupModifier
9 | import com.crazylegend.extensions.setupManager
10 | import com.crazylegend.imagepicker.consts.SINGLE_PICKER_BOTTOM_SHEET
11 | import com.crazylegend.imagepicker.consts.SINGLE_PICKER_DIALOG
12 | import com.crazylegend.imagepicker.dialogs.single.SingleImagePickerBottomSheetDialog
13 | import com.crazylegend.imagepicker.images.ImageModel
14 | import com.crazylegend.imagepicker.listeners.onImageDSL
15 |
16 |
17 | /**
18 | * Created by crazy on 5/8/20 to long live and prosper !
19 | */
20 | object SingleImagePicker {
21 |
22 | const val SINGLE_IMAGE_REQUEST_KEY = "singleImageRequest"
23 | const val ON_SINGLE_IMAGE_PICK_KEY = "onSingleImagePicked"
24 |
25 | fun restoreListener(context: Context, onPickedImage: (image: ImageModel) -> Unit = {}) {
26 | val manager = context.setupManager()
27 | when (val fragment = manager.findFragmentByTag(SINGLE_PICKER_BOTTOM_SHEET)
28 | ?: manager.findFragmentByTag(SINGLE_PICKER_DIALOG)) {
29 | is SingleImagePickerBottomSheetDialog -> {
30 | fragment.onImagePicked = onImageDSL(onPickedImage)
31 | }
32 |
33 | null -> {
34 | Log.e(SingleImagePicker::class.java.name, "FRAGMENT NOT FOUND")
35 | }
36 | }
37 | }
38 |
39 | fun showPicker(
40 | context: Context,
41 | extensions: Array? = arrayOf(),
42 | pickerConfig: PickerConfig = PickerConfig(),
43 | pickerModifier: BaseSinglePickerModifier.() -> Unit = {},
44 | onPickedImage: (image: ImageModel) -> Unit = {}
45 | ) {
46 | val modifier = setupModifier(pickerModifier)
47 | val manager = context.setupManager()
48 | with(SingleImagePickerBottomSheetDialog()) {
49 | this.extensions = extensions
50 | this.pickerConfig = pickerConfig
51 | addModifier(modifier)
52 | onImagePicked = onImageDSL(onPickedImage)
53 | show(manager, SINGLE_PICKER_BOTTOM_SHEET)
54 | }
55 | }
56 |
57 | fun showPicker(
58 | fragmentManager: FragmentManager,
59 | extensions: Array? = arrayOf(),
60 | pickerConfig: PickerConfig = PickerConfig(),
61 | pickerModifier: BaseSinglePickerModifier.() -> Unit = {},
62 | onPickedImage: (image: ImageModel) -> Unit = {}
63 | ) {
64 | val modifier = setupModifier(pickerModifier)
65 | with(SingleImagePickerBottomSheetDialog()) {
66 | this.extensions = extensions
67 | this.pickerConfig = pickerConfig
68 | addModifier(modifier)
69 | onImagePicked = onImageDSL(onPickedImage)
70 | show(fragmentManager, SINGLE_PICKER_BOTTOM_SHEET)
71 | }
72 | }
73 |
74 |
75 | }
--------------------------------------------------------------------------------
/imagepicker/src/test/java/com/crazylegend/imagepicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.imagepicker
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
3 | install:
4 | - ./gradlew publishToMavenLocal
5 | - find . -name "*.aar"
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='MediaPicker'
2 | include ':app'
3 | include ':imagepicker'
4 | include ':core'
5 | include ':videopicker'
6 | include ':extensions'
7 | include ':audiopicker'
8 |
--------------------------------------------------------------------------------
/videopicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/videopicker/README.md:
--------------------------------------------------------------------------------
1 |
2 | Current jitpack version: [](https://jitpack.io/#CraZyLegenD/MediaPicker)
3 |
4 | 1. You must declare the permission in your manifest
5 | ```xml
6 |
7 | ```
8 | 2. If you haven't included the dependencies from the previous screen now's a good time
9 | ```gradle
10 | dependencies {
11 | def pickerVersion = "1.0.0" //look-up the latest one on jitpack
12 |
13 | //the core package is a must
14 | implementation "com.github.FunkyMuse.MediaPicker:core:$pickerVersion"
15 |
16 | //videos
17 | implementation "com.github.FunkyMuse.MediaPicker:videopicker:$pickerVersion"
18 | }
19 | ```
20 | 3. How to use single picker and check out [how to customize single video picker](https://github.com/FunkyMuse/MediaPicker/wiki/Single--image-video-picker-customization)
21 | ```kotlin
22 | import java.awt.Color
23 |
24 | //simple usage without customization
25 | SingleVideoPicker.showPicker(context = this, onPickedVideo = ::loadVideo)
26 |
27 | //customized
28 | SingleVideoPicker.showPicker(this, extensions = arrayOf(),config = Config(showFileSize = true),{
29 | setupBaseModifier(
30 | loadingIndicatorColor = R.color.minusColor,
31 | titleTextModifications = {
32 | textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
33 | textStyle = TitleTextModifier.TextStyle.ITALIC
34 | textColor = Color.BLACK
35 | marginBottom = 30 // use dp or sp this is only for demonstration purposes
36 | textPadding = 5 // use dp or sp this is only for demonstration purposes
37 | textSize = 30f // use sp this is only for demonstration purposes
38 | textString = "Pick a video"
39 | },
40 | placeHolderModifications = {
41 | resID = R.drawable.ic_image
42 | }
43 | )
44 | }, ::loadVideo)
45 |
46 | // You can filter files by passing extensions like extensions = arrayOf("mp4","avi")
47 |
48 | ```
49 |
50 | 4. How to use multi picker and check out [how to customize multi video picker](https://github.com/FunkyMuse/MediaPicker/wiki/Multi-image-video-picker-customization)
51 | ```kotlin
52 | //simple usage without customization
53 | MultiVideoPicker.showPicker(this) { loadVideos(it) }
54 |
55 | //customized
56 | MultiVideoPicker.showPicker(this, extensions = arrayOf(),config = Config(showFileSize = false),{
57 | setupBaseMultiPicker(
58 | tintForLoadingProgressBar = ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryDark),
59 | gravityForSelectAndUnSelectIndicators = BaseMultiPickerModifier.Gravity.TOP_RIGHT,
60 | titleModifications = {
61 | textAlignment = TextView.TEXT_ALIGNMENT_CENTER
62 | textStyle = TitleTextModifier.TextStyle.ITALIC
63 | textColor = Color.BLACK
64 | marginBottom = 30 // use dp or sp this is only for demonstration purposes
65 | textPadding = 5 // use dp or sp this is only for demonstration purposes
66 | textSize = 30f // use sp this is only for demonstration purposes
67 | textString = "Pick multiple videos"
68 | },
69 | selectIconModifications = {
70 | resID = R.drawable.ic_checked
71 | tint = Color.BLACK
72 | },
73 | unSelectIconModifications = {
74 | resID = R.drawable.ic_unchecked
75 | tint = Color.BLACK
76 | },
77 | viewHolderPlaceholderModifications = {
78 | resID = R.drawable.ic_close
79 | },
80 | sizeTextModifications = {
81 | textAlignment = TextView.TEXT_ALIGNMENT_VIEW_START
82 | textStyle = SizeTextModifier.TextStyle.NORMAL
83 | margin = 22 // use dp or sp this is only for demonstration purposes
84 | textColor = Color.BLACK
85 | textPadding = 5 // use dp or sp this is only for demonstration purposes
86 | textSize = 12f // use sp this is only for demonstration purposes
87 | backgroundDrawable = R.drawable.rounded_bg_abstract_dialog
88 | }
89 | )
90 | }, ::doSomethingWithVideoList)
91 |
92 | ```
93 |
94 | If you're using Fragments to call the pickers you can leverage [set fragment result listener](https://developer.android.com/reference/androidx/fragment/app/FragmentManager#setfragmentresultlistener) to get back the result and you don't have to restore the listener nor provide a lambda for the listener, it can be as simple as
95 | ```kotlin
96 | SingleVideoPicker.showPicker(requireContext())
97 | ```
98 | ```kotlin
99 |
100 | setFragmentResultListener(SingleVideoPicker.SINGLE_VIDEO_REQUEST_KEY) { _, bundle ->
101 | val loadedModel = bundle.getParcelable(SingleVideoPicker.ON_SINGLE_VIDEO_PICK_KEY)
102 | loadedModel?.let { loadVideo(it) }
103 | }
104 |
105 | setFragmentResultListener(MultiVideoPicker.MULTI_VIDEO_REQUEST_KEY) { _, bundle ->
106 | val loadedModel = bundle.getParcelableArrayList(MultiVideoPicker.ON_MULTI_VIDEO_PICK_KEY)
107 | loadedModel?.let { doSomethingWithVideoList(it) }
108 | }
109 | ```
110 |
111 | ##
112 | If you're still not sure how to use, take a look at the [Sample app](https://github.com/FunkyMuse/MediaPicker/blob/master/app/src/main/java/com/crazylegend/mediapicker/MainActivity.kt)
113 |
114 | ##
115 | If you're still not sure how to use fragment listener, take a look at the [Sample app](https://github.com/FunkyMuse/MediaPicker/blob/master/app/src/main/java/com/crazylegend/mediapicker/FragmentResult.kt#L320)
116 |
117 | ## Screens
118 |
119 | Single picker
120 |
121 |
122 |
123 | Multi pickers
124 |
125 |
126 |
--------------------------------------------------------------------------------
/videopicker/build.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | namespace 'com.crazylegend.videopicker'
3 | }
--------------------------------------------------------------------------------
/videopicker/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/videopicker/consumer-rules.pro
--------------------------------------------------------------------------------
/videopicker/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 |
--------------------------------------------------------------------------------
/videopicker/screens/screen_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/videopicker/screens/screen_1.png
--------------------------------------------------------------------------------
/videopicker/screens/screen_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FunkyMuse/MediaPicker/3310f617c54475b5f847ae486f7da7621d5e3187/videopicker/screens/screen_3.png
--------------------------------------------------------------------------------
/videopicker/src/androidTest/java/com/crazylegend/videopicker/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.*
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.crazylegend.videopicker.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/videopicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/consts/DialogConsts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.consts
2 |
3 |
4 | /**
5 | * Created by crazy on 5/9/20 to long live and prosper !
6 | */
7 |
8 | const val SINGLE_PICKER_BOTTOM_SHEET = "videoSinglePickerBottomSheetDialog"
9 | const val SINGLE_PICKER_DIALOG = "videoSinglePickerDialog"
10 |
11 |
12 | const val MULTI_PICKER_BOTTOM_SHEET = "videoMultiPickerBottomSheetDialog"
13 | const val MULTI_PICKER_DIALOG = "videoMultiPickerDialog"
14 |
15 |
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/contracts/MultiPickerContracts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.contracts
2 |
3 | import com.crazylegend.core.contracts.BaseContractMultiPick
4 | import com.crazylegend.videopicker.listeners.onVideosPicked
5 | import com.crazylegend.videopicker.pickers.MultiVideoPicker
6 | import com.crazylegend.videopicker.videos.VideosVM
7 |
8 |
9 | /**
10 | * Created by crazy on 5/8/20 to long live and prosper !
11 | */
12 | internal interface MultiPickerContracts : BaseContractMultiPick {
13 | val videosVM: VideosVM
14 | var onVideosPicked: onVideosPicked?
15 | val errorTag: String get() = MultiVideoPicker::javaClass.name
16 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/contracts/SinglePickerContracts.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.contracts
2 |
3 | import com.crazylegend.core.contracts.BaseContractSinglePick
4 | import com.crazylegend.videopicker.listeners.onVideoPicked
5 | import com.crazylegend.videopicker.pickers.SingleVideoPicker
6 | import com.crazylegend.videopicker.videos.VideosVM
7 |
8 |
9 | /**
10 | * Created by crazy on 5/8/20 to long live and prosper !
11 | */
12 | internal interface SinglePickerContracts : BaseContractSinglePick {
13 | val videosVM: VideosVM
14 | var onVideoPicked: onVideoPicked?
15 | val errorTag get() = SingleVideoPicker::javaClass.name
16 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/dialogs/multi/MultiVideoPickerBottomSheetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.dialogs.multi
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.View
8 |
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.appcompat.widget.AppCompatTextView
11 | import androidx.core.os.bundleOf
12 | import androidx.fragment.app.setFragmentResult
13 | import androidx.fragment.app.viewModels
14 | import androidx.lifecycle.observe
15 | import com.crazylegend.core.abstracts.AbstractBottomSheetDialogFragment
16 | import com.crazylegend.core.adapters.multi.MultiSelectAdapter
17 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutMultiBinding
18 | import com.crazylegend.core.dto.PickerConfig
19 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
20 | import com.crazylegend.extensions.viewBinding
21 | import com.crazylegend.videopicker.contracts.MultiPickerContracts
22 | import com.crazylegend.videopicker.listeners.onVideosPicked
23 | import com.crazylegend.videopicker.pickers.MultiVideoPicker
24 | import com.crazylegend.videopicker.videos.VideoModel
25 | import com.crazylegend.videopicker.videos.VideosVM
26 | import com.google.android.material.button.MaterialButton
27 |
28 |
29 | /**
30 | * Created by crazy on 5/8/20 to long live and prosper !
31 | */
32 | internal class MultiVideoPickerBottomSheetDialog : AbstractBottomSheetDialogFragment(),
33 | MultiPickerContracts {
34 |
35 |
36 | override val layout: Int
37 | get() = super.layout
38 | override var onVideosPicked: onVideosPicked? = null
39 | override val binding by viewBinding(FragmentImagesGalleryLayoutMultiBinding::bind)
40 | override val videosVM by viewModels()
41 | override val modifier: BaseMultiPickerModifier?
42 | get() = arguments?.getParcelable(modifierTag)
43 | override val multiSelectAdapter by lazy {
44 | MultiSelectAdapter(modifier, pickerConfig.showFileSize)
45 | }
46 | var extensions: Array? = arrayOf()
47 | var pickerConfig: PickerConfig = PickerConfig()
48 |
49 | override val askForStoragePermission =
50 | registerForActivityResult(ActivityResultContracts.RequestPermission()) {
51 | if (it) {
52 | videosVM.loadVideos(extensions = extensions)
53 | } else {
54 | Log.e(errorTag, "PERMISSION DENIED")
55 | dismissAllowingStateLoss()
56 | }
57 | }
58 |
59 | @Suppress("UNCHECKED_CAST")
60 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
61 | super.onViewCreated(view, savedInstanceState)
62 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
63 | askForStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
64 | } else {
65 | askForStoragePermission.launch(Manifest.permission.READ_MEDIA_VIDEO)
66 | }
67 | setupUIForMultiPicker(
68 | binding.gallery,
69 | multiSelectAdapter,
70 | binding.doneButton,
71 | binding.title,
72 | binding.loadingIndicator,
73 | modifier?.loadingIndicatorTint,
74 | ::applyDoneButtonModifications,
75 | ::applyTitleModifications
76 | )
77 |
78 | videosVM.videos.observe(viewLifecycleOwner) {
79 | setupList(
80 | multiSelectAdapter,
81 | it,
82 | binding.noContentText,
83 | modifier?.noContentTextModifier
84 | )
85 | }
86 | handleUIIndicator(videosVM.loadingIndicator, binding.loadingIndicator)
87 |
88 |
89 | binding.doneButton.setOnClickListener {
90 | val videoList =
91 | multiSelectAdapter.currentList.filter { it.isSelected } as? List
92 | ?: emptyList()
93 | setFragmentResult(
94 | MultiVideoPicker.MULTI_VIDEO_REQUEST_KEY,
95 | bundleOf(MultiVideoPicker.ON_MULTI_VIDEO_PICK_KEY to videoList)
96 | )
97 | onVideosPicked?.onVideosPicked(videoList)
98 | dismissAllowingStateLoss()
99 | }
100 | }
101 |
102 | override fun onDestroyView() {
103 | super.onDestroyView()
104 | onVideosPicked = null
105 | }
106 |
107 | override fun addModifier(modifier: BaseMultiPickerModifier) {
108 | arguments = bundleOf(modifierTag to modifier)
109 | }
110 |
111 | override fun applyTitleModifications(appCompatTextView: AppCompatTextView) {
112 | modifier?.titleTextModifier?.applyTextParams(appCompatTextView)
113 | }
114 |
115 | override fun applyDoneButtonModifications(doneButton: MaterialButton) {
116 | modifier?.doneButtonModifier?.applyImageParams(doneButton)
117 | }
118 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/dialogs/single/SingleVideoPickerBottomSheetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.dialogs.single
2 |
3 | import android.Manifest
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.View
8 |
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.appcompat.widget.AppCompatTextView
11 | import androidx.core.os.bundleOf
12 | import androidx.fragment.app.setFragmentResult
13 | import androidx.fragment.app.viewModels
14 | import androidx.lifecycle.observe
15 | import com.crazylegend.core.abstracts.AbstractBottomSheetDialogFragment
16 | import com.crazylegend.core.adapters.single.SingleAdapter
17 | import com.crazylegend.core.databinding.FragmentImagesGalleryLayoutBinding
18 | import com.crazylegend.core.dto.PickerConfig
19 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
20 | import com.crazylegend.extensions.viewBinding
21 | import com.crazylegend.videopicker.contracts.SinglePickerContracts
22 | import com.crazylegend.videopicker.listeners.onVideoPicked
23 | import com.crazylegend.videopicker.pickers.SingleVideoPicker
24 | import com.crazylegend.videopicker.videos.VideoModel
25 | import com.crazylegend.videopicker.videos.VideosVM
26 |
27 |
28 | /**
29 | * Created by crazy on 5/8/20 to long live and prosper !
30 | */
31 | internal class SingleVideoPickerBottomSheetDialog : AbstractBottomSheetDialogFragment(),
32 | SinglePickerContracts {
33 |
34 | override val layout: Int
35 | get() = super.layout
36 | override var onVideoPicked: onVideoPicked? = null
37 | override val binding by viewBinding(FragmentImagesGalleryLayoutBinding::bind)
38 | override val videosVM by viewModels()
39 | var extensions: Array? = arrayOf()
40 | var pickerConfig: PickerConfig = PickerConfig()
41 |
42 | override val modifier: BaseSinglePickerModifier? get() = arguments?.getParcelable(modifierTag)
43 |
44 | override val singleAdapter by lazy {
45 | SingleAdapter(pickerConfig.showFileSize, modifier?.viewHolderPlaceholderModifier, modifier?.sizeTextModifier) {
46 | setFragmentResult(
47 | SingleVideoPicker.SINGLE_VIDEO_REQUEST_KEY,
48 | bundleOf(SingleVideoPicker.ON_SINGLE_VIDEO_PICK_KEY to it as VideoModel)
49 | )
50 | onVideoPicked?.forVideo(it)
51 | dismissAllowingStateLoss()
52 | }
53 | }
54 |
55 | override val askForStoragePermission =
56 | registerForActivityResult(ActivityResultContracts.RequestPermission()) {
57 | if (it) {
58 | videosVM.loadVideos(extensions = extensions)
59 | } else {
60 | Log.e(errorTag, "PERMISSION DENIED")
61 | dismissAllowingStateLoss()
62 | }
63 | }
64 |
65 |
66 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
67 | super.onViewCreated(view, savedInstanceState)
68 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
69 | askForStoragePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
70 | } else {
71 | askForStoragePermission.launch(Manifest.permission.READ_MEDIA_VIDEO)
72 | }
73 | setupUIForSinglePicker(
74 | binding.gallery,
75 | singleAdapter,
76 | binding.title,
77 | binding.loadingIndicator,
78 | modifier?.loadingIndicatorTint,
79 | ::applyTitleModifications
80 | )
81 | videosVM.videos.observe(viewLifecycleOwner) {
82 | setupList(singleAdapter, it, binding.noContentText, modifier?.noContentTextModifier)
83 | }
84 | handleUIIndicator(videosVM.loadingIndicator, binding.loadingIndicator)
85 |
86 | }
87 |
88 | override fun onDestroyView() {
89 | super.onDestroyView()
90 | onVideoPicked = null
91 | }
92 |
93 | override fun applyTitleModifications(appCompatTextView: AppCompatTextView) {
94 | modifier?.titleTextModifier?.applyTextParams(appCompatTextView)
95 | }
96 |
97 | override fun addModifier(modifier: BaseSinglePickerModifier) {
98 | arguments = bundleOf(modifierTag to modifier)
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/listeners/onVideoPicked.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.listeners
2 |
3 | import com.crazylegend.videopicker.videos.VideoModel
4 |
5 |
6 | /**
7 | * Created by crazy on 5/8/20 to long live and prosper !
8 | */
9 | internal interface onVideoPicked {
10 | fun forVideo(videoModel: VideoModel)
11 | }
12 |
13 | internal inline fun onVideoDSL(crossinline videoCallback: (video: VideoModel) -> Unit) = object : onVideoPicked {
14 | override fun forVideo(videoModel: VideoModel) {
15 | videoCallback(videoModel)
16 | }
17 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/listeners/onVideosPicked.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.listeners
2 |
3 | import com.crazylegend.videopicker.videos.VideoModel
4 |
5 |
6 | /**
7 | * Created by crazy on 5/8/20 to long live and prosper !
8 | */
9 | internal interface onVideosPicked {
10 | fun onVideosPicked(videos: List = emptyList())
11 | }
12 |
13 | internal inline fun onVideosDSL(crossinline callback: (list: List) -> Unit) = object : onVideosPicked {
14 | override fun onVideosPicked(videos: List) {
15 | callback(videos)
16 | }
17 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/pickers/MultiVideoPicker.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.pickers
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.fragment.app.FragmentManager
6 | import com.crazylegend.core.dto.PickerConfig
7 | import com.crazylegend.core.modifiers.base.BaseMultiPickerModifier
8 | import com.crazylegend.core.setupModifier
9 | import com.crazylegend.extensions.setupManager
10 | import com.crazylegend.videopicker.consts.MULTI_PICKER_BOTTOM_SHEET
11 | import com.crazylegend.videopicker.consts.MULTI_PICKER_DIALOG
12 | import com.crazylegend.videopicker.dialogs.multi.MultiVideoPickerBottomSheetDialog
13 | import com.crazylegend.videopicker.listeners.onVideosDSL
14 | import com.crazylegend.videopicker.videos.VideoModel
15 |
16 |
17 | /**
18 | * Created by crazy on 5/9/20 to long live and prosper !
19 | */
20 |
21 | object MultiVideoPicker {
22 |
23 | const val MULTI_VIDEO_REQUEST_KEY = "multiVideosRequest"
24 | const val ON_MULTI_VIDEO_PICK_KEY = "onMultiVideosPicked"
25 |
26 | fun restoreListener(context: Context, videoList: (list: List) -> Unit = {}) {
27 | val manager = context.setupManager()
28 | when (val fragment = manager.findFragmentByTag(MULTI_PICKER_BOTTOM_SHEET)
29 | ?: manager.findFragmentByTag(MULTI_PICKER_DIALOG)) {
30 | is MultiVideoPickerBottomSheetDialog -> {
31 | fragment.onVideosPicked = onVideosDSL(videoList)
32 | }
33 |
34 | null -> {
35 | Log.e(MultiVideoPicker::class.java.name, "FRAGMENT NOT FOUND")
36 | }
37 | }
38 | }
39 |
40 | fun showPicker(
41 | context: Context,
42 | extensions: Array? = arrayOf(),
43 | pickerConfig: PickerConfig = PickerConfig(),
44 | modifier: BaseMultiPickerModifier.() -> Unit = {},
45 | videoList: (list: List) -> Unit = {},
46 | ) {
47 | val manager = context.setupManager()
48 | val setupModifier = setupModifier(modifier)
49 | with(MultiVideoPickerBottomSheetDialog()) {
50 | this.extensions = extensions
51 | this.pickerConfig = pickerConfig
52 | addModifier(setupModifier)
53 | onVideosPicked = onVideosDSL(videoList)
54 | show(manager, MULTI_PICKER_BOTTOM_SHEET)
55 | }
56 | }
57 |
58 | fun showPicker(
59 | fragmentManager: FragmentManager,
60 | extensions: Array? = arrayOf(),
61 | pickerConfig: PickerConfig = PickerConfig(),
62 | modifier: BaseMultiPickerModifier.() -> Unit = {},
63 | videoList: (list: List) -> Unit = {}
64 | ) {
65 | val setupModifier = setupModifier(modifier)
66 | with(MultiVideoPickerBottomSheetDialog()) {
67 | this.extensions = extensions
68 | this.pickerConfig = pickerConfig
69 | addModifier(setupModifier)
70 | onVideosPicked = onVideosDSL(videoList)
71 | show(fragmentManager, MULTI_PICKER_BOTTOM_SHEET)
72 | }
73 | }
74 |
75 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/pickers/SingleVideoPicker.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.pickers
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.fragment.app.FragmentManager
6 | import com.crazylegend.core.dto.PickerConfig
7 | import com.crazylegend.core.modifiers.base.BaseSinglePickerModifier
8 | import com.crazylegend.core.setupModifier
9 | import com.crazylegend.extensions.setupManager
10 | import com.crazylegend.videopicker.consts.SINGLE_PICKER_BOTTOM_SHEET
11 | import com.crazylegend.videopicker.consts.SINGLE_PICKER_DIALOG
12 | import com.crazylegend.videopicker.dialogs.single.SingleVideoPickerBottomSheetDialog
13 | import com.crazylegend.videopicker.listeners.onVideoDSL
14 | import com.crazylegend.videopicker.videos.VideoModel
15 |
16 |
17 | /**
18 | * Created by crazy on 5/9/20 to long live and prosper !
19 | */
20 | object SingleVideoPicker {
21 |
22 | const val SINGLE_VIDEO_REQUEST_KEY = "singleVideoRequest"
23 | const val ON_SINGLE_VIDEO_PICK_KEY = "onSingleVideoPicked"
24 |
25 | fun restoreListener(context: Context, onPickedVideo: (video: VideoModel) -> Unit = {}) {
26 | val manager = context.setupManager()
27 | when (val fragment = manager.findFragmentByTag(SINGLE_PICKER_BOTTOM_SHEET)
28 | ?: manager.findFragmentByTag(SINGLE_PICKER_DIALOG)) {
29 | is SingleVideoPickerBottomSheetDialog -> {
30 | fragment.onVideoPicked = onVideoDSL(onPickedVideo)
31 | }
32 |
33 | null -> {
34 | Log.e(SingleVideoPicker::class.java.name, "FRAGMENT NOT FOUND")
35 | }
36 | }
37 | }
38 |
39 | fun showPicker(
40 | context: Context,
41 | extensions: Array? = arrayOf(),
42 | pickerConfig: PickerConfig = PickerConfig(),
43 | pickerModifier: BaseSinglePickerModifier.() -> Unit = {},
44 | onPickedVideo: (video: VideoModel) -> Unit = {}
45 | ) {
46 | val modifier = setupModifier(pickerModifier)
47 | val manager = context.setupManager()
48 | with(SingleVideoPickerBottomSheetDialog()) {
49 | this.extensions = extensions
50 | this.pickerConfig = pickerConfig
51 | addModifier(modifier)
52 | onVideoPicked = onVideoDSL(onPickedVideo)
53 | show(manager, SINGLE_PICKER_BOTTOM_SHEET)
54 | }
55 | }
56 |
57 | fun showPicker(
58 | fragmentManager: FragmentManager,
59 | extensions: Array? = arrayOf(),
60 | pickerConfig: PickerConfig = PickerConfig(),
61 | pickerModifier: BaseSinglePickerModifier.() -> Unit = {},
62 | onPickedVideo: (video: VideoModel) -> Unit = {}
63 | ) {
64 | val modifier = setupModifier(pickerModifier)
65 | with(SingleVideoPickerBottomSheetDialog()) {
66 | this.extensions = extensions
67 | this.pickerConfig = pickerConfig
68 | addModifier(modifier)
69 | onVideoPicked = onVideoDSL(onPickedVideo)
70 | show(fragmentManager, SINGLE_PICKER_BOTTOM_SHEET)
71 | }
72 | }
73 |
74 |
75 | }
--------------------------------------------------------------------------------
/videopicker/src/main/java/com/crazylegend/videopicker/videos/VideoModel.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker.videos
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import com.crazylegend.core.dto.BaseCursorModel
6 | import kotlinx.parcelize.Parcelize
7 |
8 |
9 | /**
10 | * Simple data class to hold information about an image included in the device's MediaStore.
11 | */
12 |
13 | @Parcelize
14 | data class VideoModel(
15 | override val id: Long,
16 | override val displayName: String?,
17 | override val dateAdded: Long?,
18 | override val contentUri: Uri,
19 | override val dateModified: Long?,
20 | override val description: String?,
21 | override val size: Int?,
22 | override val width: Int?,
23 | override val height: Int?,
24 | val resolution: String?,
25 | val private: Int?,
26 | val tags: String?
27 | ) : BaseCursorModel(id, displayName, dateAdded, contentUri, dateModified, description, size, width, height), Parcelable {
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/videopicker/src/test/java/com/crazylegend/videopicker/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crazylegend.videopicker
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------