├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── assets
├── device-2018-01-08-142511.mp4
├── screenshot001.png
├── screenshot002.png
└── screenshot003.png
├── build.gradle
├── deploy.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── gradle.properties
├── libs
│ └── bottomsheetpickerlib.jar
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── quanturium
│ │ └── android
│ │ └── library
│ │ ├── bottomsheetpicker
│ │ ├── BottomSheetPickerFragment.java
│ │ ├── BottomSheetPickerRecyclerAdapter.java
│ │ ├── CursorRecyclerAdapter.java
│ │ ├── FileUtils.java
│ │ └── IntentUtils.java
│ │ ├── common
│ │ └── util
│ │ │ └── ViewUtils.java
│ │ └── multi_select
│ │ ├── MultiSelectBaseSelector.java
│ │ ├── MultiSelectComponent.java
│ │ ├── MultiSelectManager.java
│ │ ├── MultiSelectSelector.java
│ │ └── MultiSelectWrapper.java
│ └── res
│ ├── anim
│ ├── bottomsheetpicker_selected_icon_grow.xml
│ └── bottomsheetpicker_selected_icon_shrink.xml
│ ├── drawable
│ ├── bg_bottomsheetpicker_bottom_view.xml
│ ├── ic_bottomsheetpicker_camera.xml
│ ├── ic_bottomsheetpicker_close.xml
│ ├── ic_bottomsheetpicker_video_icon.xml
│ ├── ic_multiselect_checked.xml
│ ├── ic_multiselect_checked_transparent.xml
│ └── ic_multiselect_unchecked.xml
│ ├── layout
│ ├── bottomsheetpicker_base.xml
│ └── bottomsheetpicker_tile.xml
│ ├── menu
│ └── bottomsheetpicker_camera_menu.xml
│ ├── values
│ ├── strings.xml
│ └── style.xml
│ └── xml
│ └── file_paths.xml
├── sample
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── quanturium
│ │ └── android
│ │ └── example
│ │ └── bottomsheetpicker
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_landscape_white_48dp.png
│ ├── drawable-mdpi
│ └── ic_landscape_white_48dp.png
│ ├── drawable-xhdpi
│ └── ic_landscape_white_48dp.png
│ ├── drawable-xxhdpi
│ └── ic_landscape_white_48dp.png
│ ├── drawable-xxxhdpi
│ └── ic_landscape_white_48dp.png
│ ├── drawable
│ └── thumbnail_loading.xml
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── file_paths.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | .DS_Store
4 | /build
5 | /captures
6 | *.iml
7 | /.idea
8 | bintray.properties
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | sudo: false
4 | os: linux
5 | android:
6 | components:
7 | - tools
8 | - platform-tools
9 | - build-tools-26.0.2
10 | - build-tools-27.0.3
11 | - android-27
12 | - extra-google-m2repository
13 | - extra-android-m2repository
14 | - extra-android-support
15 | licenses:
16 | - android-sdk-preview-license-.+
17 | - android-sdk-license-.+
18 | - google-gdk-license-.+
19 | script:
20 | - ./gradlew clean build ci
21 | before_install:
22 | - yes | sdkmanager "platforms;android-27"
23 | env:
24 | global:
25 | - BINTRAY_USER="quanturium"
26 | - secure: G3NZ3BsJMwm8EvPS42LY7LIModh22dwbTksHkw+y/Cuxbj6xqr3uQqFXhFj53MmFYFRIL2c9o1dArF0Ub2zql26vTh3LZPszYs/CfFWF64gLppu9St0pFyIQQRWyWJfrAM5rHE4VjWMyVyXJGUoftdPw0sl6DIZts6Cc7R/b8KWJ7BkuzQS9TIpkuCZCfrIBVctZswblbU7p92BA75NeUuwS9nORu+bNoK2v10Q95WV5tJqm78SyXQZE33INr3+Pg3J3BX1FOhvbDgq5EWGlXG6G18ZmZw4ix1HZcIdiEoa59CNOfDqb7l6UvZqyoCDCdtHxyVX27z3pDj42g/6qFPUT8XyxiCpMuBCgrOb2VayxRcVrLpjs09cWgW8g9WRygh4NSh9PglJS0NbwjApQ8g6O0y12R9jlMfG6gd/+X8o5ZVSBUmYgqtUp4Z5ZfFu6Ru92+nyWo9BFTH/gHbhDsmHZ6X+U3aKLKRn/ud1gYonuXAOtSsIwp/ogj0NyF4zBWkWYydKkLtPne1+gBhpVSbLP4oqFTu5+5G55F0ZXaXpQO0PpQghe0HnD0wGBJWwEakOsBKLdSkayNc+QUFWkfVBf9gBZsKfldzzz8ueHk3CNrrTJxaYjxcSY4msQlw5GW09LPTLgnJ2Q8HTIHoYvrBzXfv6as8AU818MlKu+xnA=
27 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | Version 2.0.1
5 | ----------------------------
6 | * Fix: NPE when selecting zero files
7 |
8 | Version 2.0.0
9 | ----------------------------
10 | * New: First release
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [2014] Wouter Dullaert
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BottomSheetPicker
2 |
3 | [ ](https://bintray.com/quanturium/maven/bottomsheetpicker/_latestVersion)
4 | [](https://travis-ci.org/quanturium/BottomSheetPicker)
5 | [](https://github.com/quanturium/BottomSheetPicker/blob/master/LICENSE.txt)
6 |
7 | Picker | Taking photo / video | Multi-select of items
8 | --- | --- | ---
9 |  |  | 
10 |
11 | ## Table of Contents
12 | 1. [Demo](#demo)
13 | 1. [Setup](#setup)
14 | 2. [Getting Started](#getting-started)
15 | 3. [License](#license)
16 |
17 | ## Demo
18 |
19 | [](https://www.youtube.com/watch?v=KVohfD_5FFk)
20 |
21 | ## Setup
22 |
23 | The Gradle dependency is available via jcenter.
24 |
25 | The minimum API level supported by this library is API 19.
26 |
27 | The easiest way to add the Material DateTime Picker library to your project is by adding it as a dependency to your build.gradle
28 |
29 | ```groovy
30 | dependencies {
31 | compile 'com.quanturium.android:bottomsheetpicker:2.0.1'
32 | }
33 | ```
34 |
35 | ## Getting Started
36 |
37 | ### Configuration
38 |
39 | BottomSheetPicker requires some additional configurations made to your app's string and xml resources. Bottomsheetpicker uses a [FileProvider](https://developer.android.com/reference/android/support/v4/content/FileProvider.html) which makes additional configuration necessary.
40 |
41 | * In your manifest.xml, add the following:
42 | ```
43 |
48 |
51 |
52 | ```
53 |
54 | * In your app's strings.xml file, add a string called 'file_provider_authority' and specify a value
55 | ```
56 | FILE_PROVIDER_STRING_HERE
57 | ```
58 |
59 | * In your app's xml directory, add a new file called 'file_paths.xml' (If the xml directory does not exist, create it under the app's res/ directory). The contents of this file should look like:
60 | ```
61 |
62 |
63 |
64 |
65 | ```
66 |
67 | ### Usage
68 |
69 | See sample app for an example of using BottomSheetPicker with required runtime permissions.
70 |
71 | ## License
72 | Copyright (c) 2018 Arnaud Frugier & Steven Hong
73 |
74 | Licensed under the Apache License, Version 2.0 (the "License");
75 | you may not use this file except in compliance with the License.
76 | You may obtain a copy of the License at
77 |
78 | http://www.apache.org/licenses/LICENSE-2.0
79 |
80 | Unless required by applicable law or agreed to in writing, software
81 | distributed under the License is distributed on an "AS IS" BASIS,
82 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
83 | See the License for the specific language governing permissions and
84 | limitations under the License.
85 |
--------------------------------------------------------------------------------
/assets/device-2018-01-08-142511.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quanturium/BottomSheetPicker/32656734e67d2e66d652bbe341bc9c20e450170c/assets/device-2018-01-08-142511.mp4
--------------------------------------------------------------------------------
/assets/screenshot001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quanturium/BottomSheetPicker/32656734e67d2e66d652bbe341bc9c20e450170c/assets/screenshot001.png
--------------------------------------------------------------------------------
/assets/screenshot002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quanturium/BottomSheetPicker/32656734e67d2e66d652bbe341bc9c20e450170c/assets/screenshot002.png
--------------------------------------------------------------------------------
/assets/screenshot003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quanturium/BottomSheetPicker/32656734e67d2e66d652bbe341bc9c20e450170c/assets/screenshot003.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath "com.android.tools.build:gradle:$androidBuildToolsVersion"
9 | classpath "com.novoda:bintray-release:$novodaBintrayVersion"
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | jcenter()
17 | }
18 | }
19 |
20 | task wrapper(type: Wrapper) {
21 | gradleVersion = project.gradleVersion
22 | }
--------------------------------------------------------------------------------
/deploy.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.novoda.bintray-release'
2 |
3 | ext {
4 |
5 | travisRepoSlug = System.getenv('TRAVIS_REPO_SLUG')
6 | travisPullRequest = System.getenv('TRAVIS_PULL_REQUEST')
7 | travisTag = System.getenv('TRAVIS_TAG')
8 |
9 | bintrayUser = System.getenv('BINTRAY_USER')
10 | bintrayKey = System.getenv('BINTRAY_KEY')
11 | }
12 |
13 | project.version = travisTag ?: project.version
14 |
15 | task ci {
16 |
17 | if (travisRepoSlug != project.repoSlog) {
18 | doLast {
19 | println("Skipping snapshot deployment: wrong repository. Expected '$project.repoSlog' but was '$travisRepoSlug'.")
20 | }
21 | } else if (travisPullRequest != "false") {
22 | doLast {
23 | println("Skipping snapshot deployment: was pull request.")
24 | }
25 | } else if (travisTag == null || travisTag == '') {
26 | doLast {
27 | println("Skipping snapshot deployment: tag not set as x.x.x but was '$travisTag'.")
28 | }
29 | } else if (bintrayUser == null || bintrayKey == null) {
30 | doLast {
31 | println("Skipping snapshot deployment: bintray credentials not set.")
32 | }
33 | } else {
34 | dependsOn 'bintrayUpload'
35 | }
36 | }
37 |
38 | publish {
39 | userOrg = project.user
40 | groupId = project.group
41 | artifactId = project.artifactId
42 | publishVersion = project.version
43 | desc = ''
44 | website = project.website
45 | bintrayUser = bintrayUser
46 | bintrayKey = bintrayKey
47 | dryRun = false
48 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Project Properties
2 | user=quanturium
3 | repoSlog=quanturium/BottomSheetPicker
4 | group=com.quanturium.android
5 | version=0.0.1
6 | website=https://github.com/quanturium/BottomSheetPicker
7 |
8 | #Configuration
9 | androidBuildToolsVersion=3.1.3
10 | compileSdkVersion=27
11 | minSdkVersion=19
12 | targetSdkVersion=27
13 | novodaBintrayVersion=0.8.0
14 |
15 | #Dependencies
16 | supportLibraryVersion=27.1.1
17 |
18 | #Test dependencies
19 |
20 | #Gradle Properties
21 | gradleVersion=4.2.1
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quanturium/BottomSheetPicker/32656734e67d2e66d652bbe341bc9c20e450170c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Mar 31 19:24:11 PDT 2018
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-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 |
5 | compileSdkVersion Integer.parseInt(project.compileSdkVersion)
6 |
7 | defaultConfig {
8 | minSdkVersion project.minSdkVersion.toInteger()
9 | targetSdkVersion project.targetSdkVersion.toInteger()
10 | }
11 |
12 | compileOptions {
13 | sourceCompatibility JavaVersion.VERSION_1_8
14 | targetCompatibility JavaVersion.VERSION_1_8
15 | }
16 | }
17 |
18 | dependencies {
19 | implementation "com.android.support:recyclerview-v7:$supportLibraryVersion"
20 | implementation "com.android.support:design:$supportLibraryVersion"
21 | }
22 |
23 | task sourcesJar(type: Jar) {
24 | from android.sourceSets.main.java.srcDirs
25 | classifier = 'sources'
26 | }
27 | //
28 | task javadoc(type: Javadoc) {
29 | source = android.sourceSets.main.java.srcDirs
30 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
31 | }
32 |
33 | task javadocJar(type: Jar, dependsOn: javadoc) {
34 | classifier = 'javadoc'
35 | from javadoc.destinationDir
36 | }
37 |
38 | artifacts {
39 | // archives javadocJar
40 | archives sourcesJar
41 | }
42 |
43 | apply from: rootProject.file('deploy.gradle')
--------------------------------------------------------------------------------
/library/gradle.properties:
--------------------------------------------------------------------------------
1 | #Project Properties
2 | artifactId=bottomsheetpicker
3 | artifactName=BottomSheetPicker
--------------------------------------------------------------------------------
/library/libs/bottomsheetpickerlib.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quanturium/BottomSheetPicker/32656734e67d2e66d652bbe341bc9c20e450170c/library/libs/bottomsheetpickerlib.jar
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/afrugier/Workspace/tools-android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/library/src/main/java/com/quanturium/android/library/bottomsheetpicker/BottomSheetPickerFragment.java:
--------------------------------------------------------------------------------
1 | package com.quanturium.android.library.bottomsheetpicker;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.annotation.SuppressLint;
6 | import android.app.Activity;
7 | import android.app.Dialog;
8 | import android.content.ClipData;
9 | import android.content.Intent;
10 | import android.database.Cursor;
11 | import android.net.Uri;
12 | import android.os.Bundle;
13 | import android.os.Parcelable;
14 | import android.provider.MediaStore;
15 | import android.support.annotation.DrawableRes;
16 | import android.support.annotation.Nullable;
17 | import android.support.design.widget.BottomSheetBehavior;
18 | import android.support.design.widget.BottomSheetDialog;
19 | import android.support.design.widget.BottomSheetDialogFragment;
20 | import android.support.v4.app.LoaderManager;
21 | import android.support.v4.content.CursorLoader;
22 | import android.support.v4.content.Loader;
23 | import android.support.v4.view.animation.FastOutSlowInInterpolator;
24 | import android.support.v7.widget.LinearLayoutManager;
25 | import android.support.v7.widget.PopupMenu;
26 | import android.support.v7.widget.RecyclerView;
27 | import android.view.LayoutInflater;
28 | import android.view.View;
29 | import android.view.ViewTreeObserver;
30 | import android.widget.Button;
31 | import android.widget.FrameLayout;
32 | import android.widget.ImageView;
33 | import android.widget.LinearLayout;
34 | import android.widget.TextView;
35 |
36 | import com.quanturium.android.library.multi_select.MultiSelectManager;
37 |
38 | import java.util.ArrayList;
39 | import java.util.HashMap;
40 | import java.util.List;
41 | import java.util.Map;
42 |
43 | public class BottomSheetPickerFragment extends BottomSheetDialogFragment implements LoaderManager.LoaderCallbacks, BottomSheetPickerRecyclerAdapter.OnRecyclerViewEventListener, View.OnClickListener, MultiSelectManager.MultiSelectListener {
44 | private static final String CAMERA_BUTTON_ENABLED = "camera_button_enabled";
45 | private static final String FILE_BROWSER_BUTTON_ENABLED = "file_browser_button_enabled";
46 | private static final String BROWSE_MORE_BUTTON_ENABLED = "browse_more_button_enabled";
47 | private static final String SELECTION_MODE = "selection_mode";
48 | private static final String MAX_ITEMS = "max_items";
49 | private static final String CAMERA_ICON_RES_ID = "camera_icon_res_id";
50 |
51 | private static final int DEFAULT_MAX_ITEMS = 25;
52 | private static final int DEFAULT_SELECTION_MODE = SelectionMode.IMAGES.value;
53 | private static final int DEFAULT_CAMERA_ICON_RESOURCE = R.drawable.ic_bottomsheetpicker_camera;
54 |
55 | private static final int REQUEST_IMAGE_CAPTURE = 1;
56 | private static final int REQUEST_VIDEO_CAPTURE = 2;
57 | private static final int REQUEST_FILE_PICKER = 3;
58 |
59 | private static final String SIS_LAYOUT_MANAGER = "savedInstanceStateLayoutManager";
60 | private static final String SIS_SELECTED_POSITIONS = "savedInstanceStateSelectedPositions";
61 |
62 | private BottomSheetPickerListener listener;
63 | private boolean isCameraButtonEnabled;
64 | private boolean isFileBrowserButtonEnabled;
65 | private boolean isBrowseMoreButtonEnabled;
66 | private SelectionMode selectionMode;
67 | private int maxItems;
68 | private int cameraIconResId;
69 |
70 | private FrameLayout bottomLayout;
71 | private RecyclerView recyclerView;
72 | private ImageView cameraButton;
73 | private Button fileBrowserButton;
74 | private LinearLayout multiselectLayout;
75 | private ImageView multiselectCloseImageView;
76 | private TextView multiselectCountTextView;
77 | private Button multiselectInsertButton;
78 |
79 | private MultiSelectManager multiSelectManager;
80 | private BottomSheetPickerRecyclerAdapter cursorAdapter;
81 | private RecyclerView.LayoutManager layoutManager;
82 |
83 | private Uri cameraUri = null;
84 |
85 | public enum SelectionMode {
86 | IMAGES(1), VIDEOS(2), IMAGES_AND_VIDEOS(3);
87 |
88 | public final int value;
89 |
90 | SelectionMode(int value) {
91 | this.value = value;
92 | }
93 |
94 | private static Map map = new HashMap<>();
95 |
96 | static {
97 | for (SelectionMode s : SelectionMode.values()) {
98 | map.put(s.value, s);
99 | }
100 | }
101 |
102 | public static SelectionMode valueOf(int selectionMode) {
103 | return map.get(selectionMode);
104 | }
105 | }
106 |
107 | public interface BottomSheetPickerListener {
108 | void onFileLoad(ImageView imageView, Uri uri);
109 |
110 | void onFilesSelected(List uriList);
111 | }
112 |
113 | public static class Builder {
114 | private boolean isCameraButtonEnabled = true;
115 | private boolean isFileBrowserButtonEnabled = true;
116 | private boolean isBrowseMoreButtonEnabled = true;
117 | private SelectionMode selectionMode;
118 | private int maxItems = 0;
119 | private int cameraIconResource = 0;
120 |
121 | public Builder setSelectionMode(SelectionMode selectionMode) {
122 | this.selectionMode = selectionMode;
123 | return this;
124 | }
125 |
126 | public Builder setCameraButtonEnabled(boolean enabled) {
127 | this.isCameraButtonEnabled = enabled;
128 | return this;
129 | }
130 |
131 | public Builder setCameraIcon(@DrawableRes int resId) {
132 | this.cameraIconResource = resId;
133 | return this;
134 | }
135 |
136 | public Builder setFileBrowserButtonEnabled(boolean enabled) {
137 | this.isFileBrowserButtonEnabled = enabled;
138 | return this;
139 | }
140 |
141 | public Builder setMaxItems(int maxItems) {
142 | this.maxItems = maxItems;
143 | return this;
144 | }
145 |
146 | public Builder setBrowseMoreButtonEnabled(boolean enabled) {
147 | this.isBrowseMoreButtonEnabled = enabled;
148 | return this;
149 | }
150 |
151 | public BottomSheetPickerFragment build() {
152 | BottomSheetPickerFragment fragment = new BottomSheetPickerFragment();
153 | Bundle args = new Bundle();
154 |
155 | args.putBoolean(CAMERA_BUTTON_ENABLED, isCameraButtonEnabled);
156 | args.putBoolean(BROWSE_MORE_BUTTON_ENABLED, isBrowseMoreButtonEnabled);
157 | args.putBoolean(FILE_BROWSER_BUTTON_ENABLED, isFileBrowserButtonEnabled);
158 | if (selectionMode != null)
159 | args.putInt(SELECTION_MODE, selectionMode.value);
160 | if (maxItems > 0)
161 | args.putInt(MAX_ITEMS, maxItems);
162 | if (cameraIconResource != 0)
163 | args.putInt(CAMERA_ICON_RES_ID, cameraIconResource);
164 |
165 | fragment.setArguments(args);
166 | return fragment;
167 | }
168 | }
169 |
170 | @Override
171 | public void onCreate(@Nullable Bundle savedInstanceState) {
172 | super.onCreate(savedInstanceState);
173 |
174 | Bundle args = getArguments();
175 | if (args != null) {
176 | isCameraButtonEnabled = args.getBoolean(CAMERA_BUTTON_ENABLED, true);
177 | isBrowseMoreButtonEnabled = args.getBoolean(BROWSE_MORE_BUTTON_ENABLED, true);
178 | isFileBrowserButtonEnabled = args.getBoolean(FILE_BROWSER_BUTTON_ENABLED, true);
179 | selectionMode = SelectionMode.valueOf(args.getInt(SELECTION_MODE, DEFAULT_SELECTION_MODE));
180 | maxItems = args.getInt(MAX_ITEMS, DEFAULT_MAX_ITEMS);
181 | cameraIconResId = args.getInt(CAMERA_ICON_RES_ID, DEFAULT_CAMERA_ICON_RESOURCE);
182 | }
183 | }
184 |
185 | @Override
186 | public void onDestroy() {
187 | super.onDestroy();
188 | listener = null;
189 | }
190 |
191 | @Override
192 | public Dialog onCreateDialog(Bundle savedInstanceState) {
193 | return new BottomSheetDialog(getContext(), getTheme());
194 | }
195 |
196 | @Override
197 | @SuppressLint({"InflateParams", "RestrictedApi"})
198 | public void setupDialog(Dialog dialog, int style) {
199 | super.setupDialog(dialog, style);
200 | final View contentView = LayoutInflater.from(getContext()).inflate(R.layout.bottomsheetpicker_base, null);
201 |
202 | recyclerView = (RecyclerView) contentView.findViewById(R.id.bottomsheetpicker_recycler_view);
203 | fileBrowserButton = (Button) contentView.findViewById(R.id.bottomsheetpicker_file_browser_button);
204 | cameraButton = (ImageView) contentView.findViewById(R.id.bottomsheetpicker_camera_button);
205 | bottomLayout = (FrameLayout) contentView.findViewById(R.id.bottomsheetpicker_bottom_layout);
206 | multiselectLayout = (LinearLayout) contentView.findViewById(R.id.bottomsheetpicker_bottom_multiselect_layout);
207 | multiselectCloseImageView = (ImageView) contentView.findViewById(R.id.bottomsheetpicker_bottom_multiselect_close_image_view);
208 | multiselectCountTextView = (TextView) contentView.findViewById(R.id.bottomsheetpicker_bottom_multiselect_count_text_view);
209 | multiselectInsertButton = (Button) contentView.findViewById(R.id.bottomsheetpicker_bottom_multiselect_insert_button);
210 |
211 | if (isFileBrowserButtonEnabled) {
212 | fileBrowserButton.setVisibility(View.VISIBLE);
213 | fileBrowserButton.setOnClickListener(this);
214 | }
215 |
216 | if (isCameraButtonEnabled) {
217 | cameraButton.setVisibility(View.VISIBLE);
218 | cameraButton.setOnClickListener(this);
219 | cameraButton.setImageResource(cameraIconResId);
220 | }
221 |
222 | multiselectCloseImageView.setOnClickListener(this);
223 | multiselectInsertButton.setOnClickListener(this);
224 |
225 | dialog.setContentView(contentView);
226 |
227 | contentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
228 | @Override
229 | public void onGlobalLayout() {
230 |
231 | contentView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
232 | BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(((View) contentView.getParent()));
233 | bottomSheetBehavior.setPeekHeight(contentView.getHeight());
234 | }
235 | });
236 | }
237 |
238 | @Override
239 | public void onActivityCreated(Bundle savedInstanceState) {
240 | super.onActivityCreated(savedInstanceState);
241 |
242 | cursorAdapter = new BottomSheetPickerRecyclerAdapter(recyclerView.getContext(), this, isBrowseMoreButtonEnabled, maxItems);
243 | multiSelectManager = new MultiSelectManager();
244 | multiSelectManager.setMultiSelectComponent(cursorAdapter);
245 | multiSelectManager.setListener(this);
246 | layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
247 | this.recyclerView.setLayoutManager(layoutManager);
248 | this.recyclerView.setAdapter(cursorAdapter);
249 | this.recyclerView.setHasFixedSize(true);
250 | this.recyclerView.setItemAnimator(null);
251 |
252 | if (savedInstanceState != null) {
253 |
254 | // Restore the scrolling of the RecyclerView
255 | Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(SIS_LAYOUT_MANAGER);
256 | recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
257 |
258 | // Restore the selected items
259 | Bundle savedSelectedIdsBundle = savedInstanceState.getBundle(SIS_SELECTED_POSITIONS);
260 | multiSelectManager.restoreMultiSelectStates(savedSelectedIdsBundle);
261 | onMultiSelectSelectionChanged(false, multiSelectManager.isSelectable(), multiSelectManager.getSelectedCount());
262 | }
263 |
264 | getLoaderManager().initLoader(0, null, this);
265 | }
266 |
267 | @Override
268 | public void onSaveInstanceState(Bundle outState) {
269 | super.onSaveInstanceState(outState);
270 | outState.putParcelable(SIS_LAYOUT_MANAGER, recyclerView.getLayoutManager().onSaveInstanceState());
271 | outState.putBundle(SIS_SELECTED_POSITIONS, multiSelectManager.saveMultiSelectStates());
272 | }
273 |
274 | @Override
275 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
276 | if (resultCode == Activity.RESULT_OK) {
277 | dismiss();
278 | List selectedFiles = new ArrayList<>();
279 | if (requestCode == REQUEST_FILE_PICKER && data != null) {
280 | ClipData clipData;
281 |
282 | clipData = data.getClipData();
283 |
284 | if (clipData != null) { // We are dealing with multiple Uris
285 | for (int i = 0; i < clipData.getItemCount(); i++)
286 | selectedFiles.add(clipData.getItemAt(i).getUri());
287 | } else { // Not clipData. Either the app did not send back any data or it is only one file that can be accessed through getData()
288 | Uri selectedFile = data.getData();
289 | if (selectedFile != null)
290 | selectedFiles.add(selectedFile);
291 | }
292 | } else if (requestCode == REQUEST_IMAGE_CAPTURE || requestCode == REQUEST_VIDEO_CAPTURE) {
293 | if (cameraUri != null) {
294 | selectedFiles.add(cameraUri);
295 | }
296 | }
297 |
298 | if (selectedFiles.size() > 0 && listener != null)
299 | listener.onFilesSelected(selectedFiles);
300 | }
301 | }
302 |
303 | public void setListener(BottomSheetPickerListener listener) {
304 | this.listener = listener;
305 | }
306 |
307 | @Override
308 | public Loader onCreateLoader(int id, Bundle args) {
309 | // Add local images, in descending order of date taken
310 | String[] projection = new String[]{
311 | MediaStore.Files.FileColumns._ID,
312 | MediaStore.Files.FileColumns.DATA,
313 | MediaStore.Files.FileColumns.MEDIA_TYPE
314 | };
315 |
316 | String selection = null;
317 |
318 | switch (selectionMode) {
319 | case IMAGES:
320 | selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
321 | + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
322 | break;
323 |
324 | case VIDEOS:
325 | selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
326 | + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
327 | break;
328 |
329 | case IMAGES_AND_VIDEOS:
330 | selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
331 | + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
332 | + " OR "
333 | + MediaStore.Files.FileColumns.MEDIA_TYPE + "="
334 | + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
335 | break;
336 | }
337 |
338 | return new CursorLoader(this.getActivity(), MediaStore.Files.getContentUri("external"), projection, selection,
339 | null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");
340 | }
341 |
342 | @Override
343 | public void onLoadFinished(Loader loader, Cursor cursor) {
344 | if (cursor != null && cursor.getCount() > 0) {
345 | if (cursorAdapter != null)
346 | cursorAdapter.swapCursor(cursor);
347 | }
348 | }
349 |
350 | @Override
351 | public void onLoaderReset(Loader loader) {
352 | if (cursorAdapter != null)
353 | cursorAdapter.swapCursor(null);
354 | }
355 |
356 | @Override
357 | public void onClick(View v) {
358 | int i = v.getId();
359 | if (i == R.id.bottomsheetpicker_camera_button) {
360 | switch (selectionMode) {
361 | case IMAGES:
362 |
363 | cameraUri = IntentUtils.sendCameraImageIntent(BottomSheetPickerFragment.this, REQUEST_IMAGE_CAPTURE);
364 |
365 | break;
366 |
367 | case VIDEOS:
368 |
369 | cameraUri = IntentUtils.sendCameraVideoIntent(BottomSheetPickerFragment.this, REQUEST_VIDEO_CAPTURE);
370 |
371 | break;
372 |
373 | case IMAGES_AND_VIDEOS:
374 |
375 | PopupMenu popupMenu = new PopupMenu(getActivity(), cameraButton);
376 | popupMenu.inflate(R.menu.bottomsheetpicker_camera_menu);
377 | popupMenu.setOnMenuItemClickListener(item -> {
378 | int id = item.getItemId();
379 | if (id == R.id.menu_package_compose_tile_camera_photo) {
380 | cameraUri = IntentUtils.sendCameraImageIntent(BottomSheetPickerFragment.this, REQUEST_IMAGE_CAPTURE);
381 | } else if (id == R.id.menu_package_compose_tile_camera_video) {
382 | cameraUri = IntentUtils.sendCameraVideoIntent(BottomSheetPickerFragment.this, REQUEST_VIDEO_CAPTURE);
383 | }
384 |
385 | return true;
386 | });
387 | popupMenu.show();
388 |
389 | break;
390 | }
391 | } else if (i == R.id.bottomsheetpicker_file_browser_button) {
392 |
393 | IntentUtils.sendFileBrowserIntent(BottomSheetPickerFragment.this, REQUEST_FILE_PICKER);
394 |
395 | } else if (i == R.id.bottomsheetpicker_bottom_multiselect_close_image_view) {
396 | multiSelectManager.clearSelectedPositions();
397 | } else if (i == R.id.bottomsheetpicker_bottom_multiselect_insert_button) {
398 | if (listener != null) {
399 | listener.onFilesSelected(cursorAdapter.getItems(multiSelectManager.getSelectedPositions()));
400 | }
401 | }
402 | }
403 |
404 | @Override
405 | public void onTileLoad(ImageView imageView, Uri uri) {
406 | if (listener != null)
407 | listener.onFileLoad(imageView, uri);
408 | }
409 |
410 | @Override
411 | public void onTileSelected(Uri uri) {
412 | List selectedFiles = new ArrayList<>();
413 | selectedFiles.add(uri);
414 | if (listener != null)
415 | listener.onFilesSelected(selectedFiles);
416 | }
417 |
418 | @Override
419 | public void onMultiSelectSelectionChanged(boolean stateChanged, boolean isSelectable, int selectedCount) {
420 | if (stateChanged) {
421 | if (isSelectable) {
422 | multiselectLayout.setVisibility(View.VISIBLE);
423 | multiselectLayout.setAlpha(0);
424 | multiselectLayout.animate()
425 | .alpha(1)
426 | .setInterpolator(new FastOutSlowInInterpolator())
427 | .setDuration(200)
428 | .setListener(null)
429 | .start();
430 | } else {
431 | multiselectLayout.animate()
432 | .alpha(0)
433 | .setInterpolator(new FastOutSlowInInterpolator())
434 | .setDuration(200)
435 | .setListener(new AnimatorListenerAdapter() {
436 | @Override
437 | public void onAnimationEnd(Animator animation) {
438 | multiselectLayout.setVisibility(View.GONE);
439 | }
440 | })
441 | .start();
442 | }
443 |
444 | } else {
445 | if (isSelectable) {
446 | multiselectLayout.setVisibility(View.VISIBLE);
447 | } else {
448 | multiselectLayout.setVisibility(View.GONE);
449 | }
450 | }
451 |
452 | multiselectCountTextView.setText(getString(R.string.bottomsheetpicker_selected_count, selectedCount));
453 | }
454 | }
455 |
--------------------------------------------------------------------------------
/library/src/main/java/com/quanturium/android/library/bottomsheetpicker/BottomSheetPickerRecyclerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.quanturium.android.library.bottomsheetpicker;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.net.Uri;
6 | import android.provider.MediaStore;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.ImageView;
13 |
14 | import com.quanturium.android.library.common.util.ViewUtils;
15 | import com.quanturium.android.library.multi_select.MultiSelectComponent;
16 | import com.quanturium.android.library.multi_select.MultiSelectManager;
17 | import com.quanturium.android.library.multi_select.MultiSelectWrapper;
18 |
19 | import java.io.File;
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | public class BottomSheetPickerRecyclerAdapter extends CursorRecyclerAdapter implements MultiSelectComponent {
24 | private final LayoutInflater layoutInflater;
25 | private final OnRecyclerViewEventListener listener;
26 | private final boolean isBrowseMoreEnabled;
27 | private final int maxItems;
28 | private MultiSelectManager.MultiSelectCallback multiSelectCallback;
29 | private int selectionViewPadding = ViewUtils.dpToPx(2);
30 |
31 | public BottomSheetPickerRecyclerAdapter(Context context, OnRecyclerViewEventListener listener, boolean browseMoreEnabled, int maxItems) {
32 | super(null);
33 |
34 | this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
35 | this.listener = listener;
36 | this.isBrowseMoreEnabled = browseMoreEnabled;
37 | this.maxItems = maxItems;
38 | }
39 |
40 | @Override
41 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
42 | View v = layoutInflater.inflate(R.layout.bottomsheetpicker_tile, parent, false);
43 | MultiSelectWrapper multiSelectWrapper = MultiSelectWrapper.wrap(v);
44 | multiSelectWrapper.setDrawableOffset(selectionViewPadding, selectionViewPadding);
45 | return new ViewHolder(multiSelectWrapper);
46 | }
47 |
48 | @Override
49 | public void onBindViewHolder(ViewHolder holder, int position, List