├── .circleci └── config.yml ├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── jarRepositories.xml └── misc.xml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── UnicornFilePicker ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── abhishekti7 │ │ └── unicorn │ │ └── filepicker │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── abhishekti7 │ │ │ └── unicorn │ │ │ └── filepicker │ │ │ ├── ConfigBuilder.java │ │ │ ├── UnicornFilePicker.java │ │ │ ├── adapters │ │ │ ├── DirectoryAdapter.java │ │ │ └── DirectoryStackAdapter.java │ │ │ ├── models │ │ │ ├── Config.java │ │ │ └── DirectoryModel.java │ │ │ ├── ui │ │ │ └── FilePickerActivity.java │ │ │ └── utils │ │ │ ├── Constants.java │ │ │ ├── UnicornSimpleItemDecoration.java │ │ │ └── Utils.java │ └── res │ │ ├── drawable │ │ ├── drawable │ │ │ ├── unicorn_ic_arrow_right.xml │ │ │ ├── unicorn_ic_arrow_solid_right.xml │ │ │ ├── unicorn_ic_done.xml │ │ │ ├── unicorn_ic_folder.xml │ │ │ └── unicorn_ic_search.xml │ │ ├── unicorn_ic_file.png │ │ ├── unicorn_ic_images.png │ │ ├── unicorn_ic_pdf.png │ │ └── unicorn_item_layout_divider.xml │ │ ├── layout │ │ ├── unicorn_activity_file_picker.xml │ │ ├── unicorn_item_layout_directory.xml │ │ ├── unicorn_item_layout_directory_stack.xml │ │ └── unicorn_item_layout_files.xml │ │ ├── menu │ │ └── unicorn_menu_file_picker.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── style.xml │ └── test │ └── java │ └── abhishekti7 │ └── unicorn │ └── filepicker │ └── ExampleUnitTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── abhishekti7 │ │ └── filepickerexample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── abhishekti7 │ │ │ └── filepickerexample │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_arrow.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── dev │ └── abhishekti7 │ └── filepickerexample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image ├── banner.png ├── screenshot_custom.jpeg ├── screenshot_dracula.jpeg └── screenshot_light.jpeg └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/code 5 | docker: 6 | - image: circleci/android:api-28 7 | environment: 8 | JVM_OPTS: -Xmx3200m 9 | steps: 10 | - checkout 11 | - restore_cache: 12 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 13 | - run: 14 | name: Chmod permissions #if permission for Gradlew Dependencies fail, use this. 15 | command: sudo chmod +x ./gradlew 16 | - run: 17 | name: Download Dependencies 18 | command: ./gradlew androidDependencies 19 | - save_cache: 20 | paths: 21 | - ~/.gradle 22 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 23 | - run: 24 | name: Run Tests 25 | command: ./gradlew lint test 26 | - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ 27 | path: app/build/reports 28 | destination: reports 29 | - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ 30 | path: app/build/test-results 31 | # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # UnicornFilePicker is an Open Source Project 2 | 3 | ## You Should Know 4 | - There is a long list of features coming so I welcome even the smallest contribution. 5 | - To contribute with a small fix, simply create a pull request. 6 | - If you intend to work on something BIG, it would be better to open an issue to discuss with the developer and the community . 7 | - It would be better to use English to open all issues and pull requests. 8 | 9 | ## Code Style 10 | 11 | Please follow [Code Style for Contributors](https://source.android.com/source/code-style) of AOSP. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Unicorn File Picker 4 | 5 | [![CircleCI](https://circleci.com/gh/abhishekti7/UnicornFilePicker.svg?style=svg)](https://circleci.com/gh/abhishekti7/UnicornFilePicker/tree/master) ![Download](https://img.shields.io/jitpack/v/github/abhishekti7/UnicornFilePicker?color=%23ffa502&label=version&style=flat-square) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-UnicornFilePicker-orange.svg?style=flat-square)](https://android-arsenal.com/details/1/8215) 6 | 7 | Unicorn File Picker is a library designed to package a powerful file selector for android. You can: 8 | - Start the file picker activity from any activity or fragment 9 | - Select single or multiple files 10 | - Use as a Directory Picker 11 | - Filter out files you want to display 12 | - Add custom theme or use default theme options (Light & Dracula) 13 | - And much more which you have to try out for yourself 14 | 15 | 16 | | Unicorn Dracula | Unicorn Default | Custom Theme | 17 | |:----------------------------------:|:-----------------------------------:|:--------------------------------:| 18 | |![](image/screenshot_dracula.jpeg) | ![](image/screenshot_light.jpeg) | ![](image/screenshot_custom.jpeg)| 19 | 20 | 21 | ## Download 22 | Gradle: 23 | 24 | ```groovy 25 | repositories { 26 | maven { url ‘https://jitpack.io’ } 27 | } 28 | 29 | dependencies { 30 | implementation 'com.github.abhishekti7:UnicornFilePicker:$latest_version' 31 | } 32 | ``` 33 | 34 | ## How do I use Unicorn File Picker? 35 | #### Permission 36 | The library requires two permissions: 37 | - `android.permission.READ_EXTERNAL_STORAGE` 38 | - `android.permission.WRITE_EXTERNAL_STORAGE` 39 | 40 | If you are targeting Android 6.0+, you need to handle runtime permission request before next step. 41 | 42 | Also, don't forget to add ``` requestlegacyexternalstorage=true ``` for Android 10. 43 | 44 | 45 | #### Simple usage snippet 46 | ------ 47 | Start `UnicornFilePicker` from current `Activity` or `Fragment`: 48 | 49 | ```java 50 | UnicornFilePicker.from(MainActivity.this) 51 | .addConfigBuilder() 52 | .selectMultipleFiles(false) 53 | .showOnlyDirectory(true) 54 | .setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath()) 55 | .showHiddenFiles(false) 56 | .setFilters(new String[]{"pdf", "png", "jpg", "jpeg"}) 57 | .addItemDivider(true) 58 | .theme(R.style.UnicornFilePicker_Dracula) 59 | .build() 60 | .forResult(Constants.REQ_UNICORN_FILE); 61 | ``` 62 | 63 | 64 | #### Themes 65 | There are two built-in themes you can use to start `UnicornFilePicker`: 66 | - `R.style.UnicornFilePicker_Default` (light mode) 67 | - `R.style.UnicornFilePicker_Dracula` (dark mode) 68 | 69 | You can also define your own custom theme. 70 | 71 | 72 | #### Receive Result 73 | In `onActivityResult()` callback of the starting `Activity` or `Fragment`: 74 | 75 | ```java 76 | List mSelected_files; 77 | 78 | @Override 79 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 80 | super.onActivityResult(requestCode, resultCode, data); 81 | if (requestCode == REQUEST_CODE_CHOOSE && resultCode == RESULT_OK) { 82 | ArrayList files = data.getStringArrayListExtra("filePaths"); 83 | for(String file : files){ 84 | Log.e(TAG, file); 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | --- 91 | 92 | ## **PLEASE NOTE:** 93 | ### API >= 21 && API <=28 94 | The file picker works great on legacy android versions and there's is no need for any additional configuration. 95 | 96 | ### API 29 97 | You need to add the following line to your manifest file if you want to use this picker on android 10 for atleast a few months. 98 | ``` 99 | android:requestLegacyExternalStorage="true" 100 | ``` 101 | 102 | This line indicates to the operating system that we want to opt out of the new Scoped Storage system. 103 | 104 | ### API > 29 (WIP) 105 | Unfortunately this library has no stable support for file access on Android 11+. 106 | Behaviour noticed on Android 11 -> While the file picker works, the only files that are displayed are the media files which include images, videos, audio, etc. 107 | This is because of the introduction of the Google's infamous new storage model called Scoped Storage which aims to improve file security. You can read all about this here: [Scoped Storage](https://developer.android.com/about/versions/11/privacy/storage) 108 | 109 | This feature is currently a work-in-progress along with many other awesome things on the roadmap. 110 | If you feel you can add value to this feature then please see the section on Contributing. 111 | 112 | --- 113 | 114 | 115 | 116 | ## Contributing 117 | [UnicornFilePicker is an Open Source Project](https://github.com/abhishekti7/UnicornFilePicker/blob/master/CONTRIBUTING.md) 118 | 119 | 120 | ## LICENSE 121 | ``` 122 | Copyright [2021] [Abhishek Tiwari] 123 | 124 | Licensed under the Apache License, Version 2.0 (the "License"); 125 | you may not use this file except in compliance with the License. 126 | You may obtain a copy of the License at 127 | 128 | http://www.apache.org/licenses/LICENSE-2.0 129 | 130 | Unless required by applicable law or agreed to in writing, software 131 | distributed under the License is distributed on an "AS IS" BASIS, 132 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133 | See the License for the specific language governing permissions and 134 | limitations under the License. 135 | ``` 136 | -------------------------------------------------------------------------------- /UnicornFilePicker/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /UnicornFilePicker/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.2" 8 | viewBinding{ 9 | enabled true 10 | } 11 | 12 | resourcePrefix "unicorn_" 13 | 14 | defaultConfig { 15 | minSdkVersion 21 16 | targetSdkVersion 30 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | consumerProguardFiles "consumer-rules.pro" 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | sourceSets { 35 | main { 36 | res { 37 | srcDirs 'src\\main\\res', 'src\\main\\res\\menu', 'src\\main\\res\\drawable' 38 | } 39 | } 40 | } 41 | } 42 | 43 | dependencies { 44 | 45 | implementation 'androidx.appcompat:appcompat:1.2.0' 46 | implementation 'com.google.android.material:material:1.2.1' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 48 | testImplementation 'junit:junit:4.+' 49 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 51 | } -------------------------------------------------------------------------------- /UnicornFilePicker/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekti7/UnicornFilePicker/c523d6274d8cef169569ea1f455996ad37b1d767/UnicornFilePicker/consumer-rules.pro -------------------------------------------------------------------------------- /UnicornFilePicker/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 -------------------------------------------------------------------------------- /UnicornFilePicker/src/androidTest/java/abhishekti7/unicorn/filepicker/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("zeus0789.unicorn.filepicker.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/ConfigBuilder.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | 6 | import androidx.annotation.StyleRes; 7 | import androidx.fragment.app.Fragment; 8 | 9 | import java.lang.reflect.Array; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | 13 | import abhishekti7.unicorn.filepicker.models.Config; 14 | import abhishekti7.unicorn.filepicker.ui.FilePickerActivity; 15 | 16 | /** 17 | * Created by Abhishek Tiwari on 09-01-2021. 18 | */ 19 | public final class ConfigBuilder { 20 | private String rootDir; 21 | private boolean showHidden = false; 22 | private boolean selectMultiple = false; 23 | private boolean addDivider = false; 24 | private boolean showOnlyDir = false; 25 | 26 | @StyleRes 27 | private int themeId = R.style.UnicornFilePicker_Default; 28 | 29 | private final UnicornFilePicker unicornFilePicker; 30 | private ArrayList extensionFilters; 31 | private Config config; 32 | 33 | public ConfigBuilder(UnicornFilePicker unicornFilePicker) { 34 | this.unicornFilePicker = unicornFilePicker; 35 | this.config = Config.getCleanInstance(); 36 | } 37 | 38 | public ConfigBuilder setRootDirectory(String dirPath){ 39 | this.rootDir = dirPath; 40 | return this; 41 | } 42 | 43 | public ConfigBuilder showHiddenFiles(boolean value){ 44 | this.showHidden = value; 45 | return this; 46 | } 47 | 48 | public ConfigBuilder selectMultipleFiles(boolean value){ 49 | this.selectMultiple = value; 50 | return this; 51 | } 52 | 53 | public ConfigBuilder setFilters(String[] filters){ 54 | this.extensionFilters = new ArrayList<>(Arrays.asList(filters)); 55 | return this; 56 | } 57 | 58 | public ConfigBuilder addItemDivider(boolean value){ 59 | this.addDivider = value; 60 | return this; 61 | } 62 | 63 | public ConfigBuilder theme(@StyleRes int theme){ 64 | this.themeId = theme; 65 | return this; 66 | } 67 | 68 | public ConfigBuilder showOnlyDirectory(boolean value){ 69 | this.showOnlyDir = value; 70 | return this; 71 | } 72 | 73 | public UnicornFilePicker build(){ 74 | config.setRootDir(this.rootDir); 75 | config.setSelectMultiple(this.selectMultiple); 76 | config.setShowHidden(this.showHidden); 77 | config.setExtensionFilters(this.extensionFilters); 78 | config.setAddItemDivider(this.addDivider); 79 | config.setThemeId(this.themeId); 80 | config.setShowOnlyDirectory(this.showOnlyDir); 81 | return unicornFilePicker; 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/UnicornFilePicker.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.fragment.app.Fragment; 8 | 9 | import java.lang.ref.WeakReference; 10 | 11 | import abhishekti7.unicorn.filepicker.models.Config; 12 | import abhishekti7.unicorn.filepicker.ui.FilePickerActivity; 13 | 14 | /** 15 | * Created by Abhishek Tiwari on 07-01-2021. 16 | */ 17 | 18 | /* This is the configuration class for File Picker */ 19 | public final class UnicornFilePicker { 20 | 21 | private final WeakReference mActivity; 22 | private final WeakReference mContext; 23 | 24 | private UnicornFilePicker(Activity activity){ 25 | this(activity, null); 26 | } 27 | 28 | private UnicornFilePicker(Fragment fragment){ 29 | this(fragment.getActivity(), fragment); 30 | } 31 | 32 | public UnicornFilePicker(Activity activity, Fragment fragment) { 33 | this.mActivity = new WeakReference<>(activity); 34 | this.mContext = new WeakReference<>(fragment); 35 | } 36 | 37 | /** 38 | * Start UnicornFilePicker from an activity 39 | * 40 | * @param activity Activity instance 41 | * @return UnicornFilePicker instance 42 | */ 43 | public static UnicornFilePicker from(Activity activity){ 44 | return new UnicornFilePicker(activity); 45 | } 46 | 47 | /** 48 | * Start UnicornFilePicker from a fragment 49 | * 50 | * @param fragment Fragment instance 51 | * @return UnicornFilePicker instance 52 | */ 53 | public static UnicornFilePicker from(Fragment fragment){ 54 | return new UnicornFilePicker(fragment); 55 | } 56 | 57 | /** 58 | * Start FilePicker activity and wait for result 59 | * @param requestCode Integer identity for Activity or Fragment request 60 | */ 61 | public void forResult(int requestCode){ 62 | Config.getInstance().setReqCode(requestCode); 63 | 64 | Activity activity = getActivity(); 65 | if(activity==null){ 66 | return; 67 | } 68 | 69 | Intent intent = new Intent(activity, FilePickerActivity.class); 70 | 71 | Fragment fragment = getFragment(); 72 | if(fragment==null){ 73 | activity.startActivityForResult(intent, requestCode); 74 | }else{ 75 | fragment.startActivityForResult(intent, requestCode); 76 | } 77 | } 78 | 79 | public ConfigBuilder addConfigBuilder(){ 80 | return new ConfigBuilder(this); 81 | } 82 | 83 | 84 | @Nullable 85 | Activity getActivity(){ 86 | return mActivity.get(); 87 | } 88 | 89 | @Nullable 90 | Fragment getFragment(){ 91 | return mContext.get(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/adapters/DirectoryAdapter.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.adapters; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.TypedValue; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Filter; 10 | import android.widget.Filterable; 11 | import android.widget.ImageView; 12 | import android.widget.RadioButton; 13 | import android.widget.RelativeLayout; 14 | import android.widget.TextView; 15 | 16 | import androidx.annotation.ColorInt; 17 | import androidx.annotation.NonNull; 18 | import androidx.recyclerview.widget.RecyclerView; 19 | 20 | import java.util.ArrayList; 21 | 22 | import abhishekti7.unicorn.filepicker.R; 23 | import abhishekti7.unicorn.filepicker.models.Config; 24 | import abhishekti7.unicorn.filepicker.models.DirectoryModel; 25 | import abhishekti7.unicorn.filepicker.utils.Utils; 26 | 27 | 28 | /** 29 | * Created by Abhishek Tiwari on 05-01-2021. 30 | */ 31 | public class DirectoryAdapter extends RecyclerView.Adapter implements Filterable { 32 | 33 | private Context context; 34 | private ArrayList filesList; 35 | private ArrayList filesListFiltered; 36 | private onFilesClickListener onFilesClickListener; 37 | private ArrayList selected; 38 | private Config config; 39 | private TypedValue typedValue; 40 | 41 | @ColorInt 42 | private int selectionTint; 43 | @ColorInt 44 | private int backgroundTint; 45 | 46 | 47 | @Override 48 | public Filter getFilter() { 49 | return tempFilter; 50 | } 51 | 52 | private Filter tempFilter = new Filter() { 53 | @Override 54 | protected FilterResults performFiltering(CharSequence constraint) { 55 | ArrayList filteredList = new ArrayList<>(); 56 | if (constraint == null || constraint.length() == 0) { 57 | filteredList = filesList; 58 | } else { 59 | String filteredPattern = constraint.toString().toLowerCase().trim(); 60 | for (DirectoryModel model : filesList) { 61 | if (model.getName().toLowerCase().contains(filteredPattern)) { 62 | filteredList.add(model); 63 | } 64 | } 65 | } 66 | FilterResults results = new FilterResults(); 67 | results.values = filteredList; 68 | return results; 69 | } 70 | 71 | @Override 72 | protected void publishResults(CharSequence constraint, FilterResults results) { 73 | filesListFiltered = (ArrayList) results.values; 74 | notifyDataSetChanged(); 75 | } 76 | }; 77 | 78 | 79 | public interface onFilesClickListener { 80 | void onClicked(DirectoryModel model); 81 | void onFileSelected(DirectoryModel fileModel); 82 | } 83 | 84 | public DirectoryAdapter(Context context, ArrayList list, boolean selectMultiple, onFilesClickListener onFilesClickListener) { 85 | this.context = context; 86 | this.filesList = list; 87 | this.filesListFiltered = list; 88 | this.onFilesClickListener = onFilesClickListener; 89 | this.selected = new ArrayList<>(); 90 | this.config = Config.getInstance(); 91 | 92 | // set color values from theme 93 | this.typedValue = new TypedValue(); 94 | Resources.Theme theme = context.getTheme(); 95 | theme.resolveAttribute(R.attr.unicorn_file_selectionTint, typedValue, true); 96 | this.selectionTint = typedValue.data; 97 | theme.resolveAttribute(R.attr.unicorn_background, typedValue, true); 98 | this.backgroundTint = typedValue.data; 99 | } 100 | 101 | @NonNull 102 | @Override 103 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 104 | View view; 105 | if (viewType == 1) { 106 | view = LayoutInflater.from(context).inflate(R.layout.unicorn_item_layout_directory, parent, false); 107 | } else { 108 | view = LayoutInflater.from(context).inflate(R.layout.unicorn_item_layout_files, parent, false); 109 | } 110 | return new ViewHolder(view); 111 | } 112 | 113 | @Override 114 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 115 | if (filesListFiltered.get(position).isDirectory()) { 116 | holder.tv_folder_name.setText(filesListFiltered.get(position).getName()); 117 | holder.tv_num_files.setText(filesListFiltered.get(position).getNum_files() + " files"); 118 | } else { 119 | holder.tv_file_name.setText(filesListFiltered.get(position).getName()); 120 | } 121 | 122 | if (!filesListFiltered.get(position).isDirectory()) { 123 | changeFileIcon(holder, filesListFiltered.get(position).getName()); 124 | if (selected.contains(String.valueOf(position))) { 125 | holder.rl_file_root.setBackgroundColor(this.selectionTint); 126 | holder.rg_selected.setVisibility(View.VISIBLE); 127 | } else { 128 | holder.rl_file_root.setBackgroundColor(this.backgroundTint); 129 | holder.rg_selected.setVisibility(View.GONE); 130 | } 131 | } 132 | 133 | holder.tv_date.setText(Utils.longToReadableDate(filesListFiltered.get(position).getLast_modif_time())); 134 | holder.itemView.setOnClickListener((v) -> { 135 | if (filesListFiltered.get(position).isDirectory()) { 136 | onFilesClickListener.onClicked(filesListFiltered.get(position)); 137 | } else { 138 | if (config.isSelectMultiple()) { 139 | if (selected.contains(String.valueOf(position))) { 140 | selected.remove(String.valueOf(position)); 141 | holder.rg_selected.setVisibility(View.GONE); 142 | holder.rl_file_root.setBackgroundColor(this.backgroundTint); 143 | } else { 144 | selected.add(String.valueOf(position)); 145 | holder.rg_selected.setVisibility(View.VISIBLE); 146 | holder.rl_file_root.setBackgroundColor(this.selectionTint); 147 | } 148 | } else { 149 | /* if selection is empty, add the current item */ 150 | if (selected.size()==0) { 151 | selected.add(0, String.valueOf(position)); 152 | } 153 | /* if item already selected then remove it */ 154 | else if (selected.get(0).equals(String.valueOf(position))) { 155 | selected.remove(0); 156 | } 157 | /* if another item selected, then remove and then add current item */ 158 | else{ 159 | selected.remove(0); 160 | selected.add(0, String.valueOf(position)); 161 | } 162 | } 163 | notifyDataSetChanged(); 164 | onFilesClickListener.onFileSelected(filesListFiltered.get(position)); 165 | } 166 | }); 167 | } 168 | 169 | private void changeFileIcon(ViewHolder holder, String fileName) { 170 | try{ 171 | String extension = fileName.substring(fileName.lastIndexOf(".")); 172 | if(extension.toLowerCase().contains("pdf")){ 173 | holder.item_icon.setImageResource(R.drawable.unicorn_ic_pdf); 174 | }else if( 175 | extension.toLowerCase().contains("png") || 176 | extension.toLowerCase().contains("jpg") || 177 | extension.toLowerCase().contains("jpeg")){ 178 | holder.item_icon.setImageResource(R.drawable.unicorn_ic_images); 179 | } 180 | }catch (Exception e){ 181 | holder.item_icon.setImageResource(R.drawable.unicorn_ic_file); 182 | // e.printStackTrace(); 183 | } 184 | } 185 | 186 | /** 187 | * resets the value of selected so that UI gets updated 188 | */ 189 | public void resetSelection() { 190 | this.selected = new ArrayList<>(); 191 | } 192 | 193 | @Override 194 | public int getItemViewType(int position) { 195 | if (filesListFiltered.get(position).isDirectory()) { 196 | return 1; 197 | } else { 198 | return 2; 199 | } 200 | } 201 | 202 | @Override 203 | public int getItemCount() { 204 | return filesListFiltered.size(); 205 | } 206 | 207 | public class ViewHolder extends RecyclerView.ViewHolder { 208 | 209 | private TextView tv_folder_name; 210 | private TextView tv_file_name; 211 | private TextView tv_date; 212 | private TextView tv_num_files; 213 | private RadioButton rg_selected; 214 | private RelativeLayout rl_file_root; 215 | private ImageView item_icon; 216 | 217 | 218 | public ViewHolder(@NonNull View itemView) { 219 | super(itemView); 220 | tv_file_name = itemView.findViewById(R.id.tv_file_name); 221 | tv_folder_name = itemView.findViewById(R.id.tv_folder_name); 222 | tv_date = itemView.findViewById(R.id.tv_date); 223 | tv_num_files = itemView.findViewById(R.id.tv_num_files); 224 | rg_selected = itemView.findViewById(R.id.rg_selected); 225 | rl_file_root = itemView.findViewById(R.id.rl_file_root); 226 | item_icon = itemView.findViewById(R.id.item_icon); 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/adapters/DirectoryStackAdapter.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.adapters; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.TypedValue; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.ColorInt; 12 | import androidx.annotation.NonNull; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | 15 | import java.util.ArrayList; 16 | 17 | import abhishekti7.unicorn.filepicker.R; 18 | import abhishekti7.unicorn.filepicker.models.DirectoryModel; 19 | 20 | 21 | /** 22 | * Created by Abhishek Tiwari on 05-01-2021. 23 | */ 24 | public class DirectoryStackAdapter extends RecyclerView.Adapter { 25 | 26 | private Context context; 27 | private ArrayList directoryList; 28 | private onDirectoryStackListener onDirectoryStackListener; 29 | private TypedValue typedValue; 30 | 31 | @ColorInt 32 | private int textColor; 33 | @ColorInt 34 | private int selectedTextColor; 35 | 36 | 37 | public interface onDirectoryStackListener{ 38 | void onDirClicked(DirectoryModel model); 39 | } 40 | 41 | public DirectoryStackAdapter(Context context, ArrayList directoryList, onDirectoryStackListener listener) { 42 | this.context = context; 43 | this.directoryList = directoryList; 44 | this.onDirectoryStackListener = listener; 45 | 46 | this.typedValue = new TypedValue(); 47 | Resources.Theme theme = context.getTheme(); 48 | theme.resolveAttribute(R.attr.unicorn_primaryTextColor, typedValue, true); 49 | this.textColor = typedValue.data; 50 | theme.resolveAttribute(R.attr.unicorn_colorAccent, typedValue, true); 51 | this.selectedTextColor = typedValue.data; 52 | } 53 | 54 | @NonNull 55 | @Override 56 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 57 | View view = LayoutInflater.from(context).inflate(R.layout.unicorn_item_layout_directory_stack, parent, false); 58 | return new ViewHolder(view); 59 | } 60 | 61 | @Override 62 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 63 | holder.tv_dir_name.setText(directoryList.get(position).getName()); 64 | 65 | if(position == getItemCount()-1){ 66 | holder.tv_dir_name.setTextColor(this.selectedTextColor); 67 | }else{ 68 | holder.tv_dir_name.setTextColor(this.textColor); 69 | } 70 | 71 | holder.itemView.setOnClickListener((v)->{ 72 | onDirectoryStackListener.onDirClicked(directoryList.get(position)); 73 | }); 74 | } 75 | 76 | @Override 77 | public int getItemCount() { 78 | return directoryList.size(); 79 | } 80 | 81 | public class ViewHolder extends RecyclerView.ViewHolder { 82 | 83 | private TextView tv_dir_name; 84 | public ViewHolder(@NonNull View itemView) { 85 | super(itemView); 86 | tv_dir_name = itemView.findViewById(R.id.tv_dir_name); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/models/Config.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.models; 2 | 3 | import androidx.annotation.IdRes; 4 | import androidx.annotation.StyleRes; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * Created by Abhishek Tiwari on 07-01-2021. 10 | */ 11 | public final class Config { 12 | 13 | /* Model to save configurations for the file picker */ 14 | 15 | private boolean selectMultiple; 16 | private String rootDir; 17 | private boolean showHidden; 18 | private ArrayList extensionFilters; 19 | private int reqCode; 20 | private boolean addItemDivider; 21 | private boolean showOnlyDirectory; 22 | 23 | @StyleRes 24 | private int themeId; 25 | 26 | @IdRes 27 | private int ic_folder; 28 | 29 | @IdRes 30 | private int ic_arrow; 31 | 32 | public static Config getInstance(){ 33 | return InstanceHolder.INSTANCE; 34 | } 35 | 36 | public static Config getCleanInstance(){ 37 | Config config = getInstance(); 38 | config.reset(); 39 | return config; 40 | } 41 | 42 | 43 | private Config() { 44 | } 45 | 46 | private void reset(){ 47 | selectMultiple = false; 48 | rootDir = null; 49 | showHidden = false; 50 | extensionFilters = null; 51 | addItemDivider = false; 52 | } 53 | 54 | public ArrayList getExtensionFilters() { 55 | return extensionFilters; 56 | } 57 | 58 | public void setExtensionFilters(ArrayList extensionFilters) { 59 | this.extensionFilters = extensionFilters; 60 | } 61 | 62 | public boolean showOnlyDirectory() { 63 | return showOnlyDirectory; 64 | } 65 | 66 | public void setShowOnlyDirectory(boolean showOnlyDirectory) { 67 | this.showOnlyDirectory = showOnlyDirectory; 68 | } 69 | 70 | public boolean addItemDivider() { 71 | return addItemDivider; 72 | } 73 | 74 | public void setAddItemDivider(boolean addItemDivider) { 75 | this.addItemDivider = addItemDivider; 76 | } 77 | 78 | public int getThemeId() { 79 | return themeId; 80 | } 81 | 82 | public void setThemeId(@StyleRes int themeId) { 83 | this.themeId = themeId; 84 | } 85 | 86 | public int getReqCode() { 87 | return reqCode; 88 | } 89 | 90 | public void setReqCode(int reqCode) { 91 | this.reqCode = reqCode; 92 | } 93 | 94 | public boolean showHidden() { 95 | return showHidden; 96 | } 97 | 98 | public void setShowHidden(boolean showHidden) { 99 | this.showHidden = showHidden; 100 | } 101 | 102 | public boolean isSelectMultiple() { 103 | return selectMultiple; 104 | } 105 | 106 | public void setSelectMultiple(boolean selectMultiple) { 107 | this.selectMultiple = selectMultiple; 108 | } 109 | 110 | public String getRootDir() { 111 | return rootDir; 112 | } 113 | 114 | public void setRootDir(String rootDir) { 115 | this.rootDir = rootDir; 116 | } 117 | 118 | public void setFolderIcon(int folderRes){ 119 | this.ic_folder = folderRes; 120 | } 121 | 122 | public int getFolderIcon(){ 123 | return this.ic_folder; 124 | } 125 | 126 | 127 | public void setArrowIcon(int arrowRes){ 128 | this.ic_arrow = arrowRes; 129 | } 130 | 131 | public int getArrowIcon(){ 132 | return this.ic_arrow; 133 | } 134 | 135 | private static final class InstanceHolder{ 136 | private static final Config INSTANCE = new Config(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/models/DirectoryModel.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.models; 2 | 3 | /** 4 | * Created by Abhishek Tiwari on 07-01-2021. 5 | */ 6 | public class DirectoryModel { 7 | 8 | private boolean isDirectory; 9 | private String path; 10 | private String name; 11 | private long last_modif_time; 12 | private int num_files; 13 | 14 | public DirectoryModel(){ 15 | 16 | } 17 | 18 | public DirectoryModel(boolean isDirectory, String path, String name, long last_modif_time, int num_files) { 19 | this.isDirectory = isDirectory; 20 | this.path = path; 21 | this.name = name; 22 | this.last_modif_time = last_modif_time; 23 | this.num_files = num_files; 24 | } 25 | 26 | public boolean isDirectory() { 27 | return isDirectory; 28 | } 29 | 30 | public void setDirectory(boolean directory) { 31 | isDirectory = directory; 32 | } 33 | 34 | public String getPath() { 35 | return path; 36 | } 37 | 38 | public void setPath(String path) { 39 | this.path = path; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public long getLast_modif_time() { 51 | return last_modif_time; 52 | } 53 | 54 | public void setLast_modif_time(long last_modif_time) { 55 | this.last_modif_time = last_modif_time; 56 | } 57 | 58 | public int getNum_files() { 59 | return num_files; 60 | } 61 | 62 | public void setNum_files(int num_files) { 63 | this.num_files = num_files; 64 | } 65 | 66 | 67 | @Override 68 | public String toString() { 69 | return "DirectoryModel{" + 70 | "isDirectory=" + isDirectory + 71 | ", path='" + path + '\'' + 72 | ", name='" + name + '\'' + 73 | ", last_modif_time=" + last_modif_time + 74 | ", num_files=" + num_files + 75 | '}'; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/ui/FilePickerActivity.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import androidx.appcompat.widget.SearchView; 6 | import androidx.core.content.ContextCompat; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import android.content.Intent; 11 | import android.content.pm.PackageManager; 12 | import android.content.res.ColorStateList; 13 | import android.content.res.Resources; 14 | import android.os.Bundle; 15 | import android.os.Environment; 16 | import android.util.Log; 17 | import android.util.TypedValue; 18 | import android.view.Menu; 19 | import android.view.MenuInflater; 20 | import android.view.MenuItem; 21 | import android.view.View; 22 | import android.view.inputmethod.EditorInfo; 23 | 24 | import java.io.File; 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.Comparator; 28 | 29 | import abhishekti7.unicorn.filepicker.R; 30 | import abhishekti7.unicorn.filepicker.adapters.DirectoryAdapter; 31 | import abhishekti7.unicorn.filepicker.adapters.DirectoryStackAdapter; 32 | import abhishekti7.unicorn.filepicker.databinding.UnicornActivityFilePickerBinding; 33 | import abhishekti7.unicorn.filepicker.models.Config; 34 | import abhishekti7.unicorn.filepicker.models.DirectoryModel; 35 | import abhishekti7.unicorn.filepicker.utils.UnicornSimpleItemDecoration; 36 | 37 | /** 38 | * Created by Abhishek Tiwari on 06-01-2021. 39 | */ 40 | 41 | public class FilePickerActivity extends AppCompatActivity { 42 | 43 | private static final String TAG = "FilePickerActivity"; 44 | private UnicornActivityFilePickerBinding filePickerBinding; 45 | 46 | private File root_dir; 47 | private ArrayList selected_files; 48 | private ArrayList arr_dir_stack; 49 | private ArrayList arr_files; 50 | 51 | private DirectoryStackAdapter stackAdapter; 52 | private DirectoryAdapter directoryAdapter; 53 | 54 | private final String[] REQUIRED_PERMISSIONS = new String[]{ 55 | "android.permission.WRITE_EXTERNAL_STORAGE", 56 | "android.permission.READ_EXTERNAL_STORAGE", 57 | }; 58 | 59 | private Config config; 60 | private ArrayList filters; 61 | 62 | 63 | @Override 64 | protected void onCreate(Bundle savedInstanceState) { 65 | super.onCreate(savedInstanceState); 66 | config = Config.getInstance(); 67 | setTheme(config.getThemeId()); 68 | filePickerBinding = UnicornActivityFilePickerBinding.inflate(getLayoutInflater()); 69 | View view = filePickerBinding.getRoot(); 70 | setContentView(view); 71 | 72 | initConfig(); 73 | } 74 | 75 | private void initConfig() { 76 | filters = config.getExtensionFilters(); 77 | 78 | 79 | setSupportActionBar(filePickerBinding.toolbar); 80 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 81 | getSupportActionBar().setDisplayShowTitleEnabled(false); 82 | 83 | if (config.getRootDir() != null) { 84 | root_dir = new File(config.getRootDir()); 85 | } else { 86 | root_dir = Environment.getExternalStorageDirectory(); 87 | } 88 | selected_files = new ArrayList<>(); 89 | arr_dir_stack = new ArrayList<>(); 90 | arr_files = new ArrayList<>(); 91 | 92 | setUpDirectoryStackView(); 93 | setUpFilesView(); 94 | 95 | if (allPermissionsGranted()) { 96 | fetchDirectory(new DirectoryModel( 97 | true, 98 | root_dir.getAbsolutePath(), 99 | root_dir.getName(), 100 | root_dir.lastModified(), 101 | root_dir.listFiles() == null ? 0 : root_dir.listFiles().length 102 | )); 103 | } else { 104 | Log.e(TAG, "Storage permissions not granted. You have to implement it before starting the file picker"); 105 | finish(); 106 | } 107 | 108 | filePickerBinding.fabSelect.setOnClickListener((v)->{ 109 | Intent intent = new Intent(); 110 | if(config.showOnlyDirectory()){ 111 | selected_files.clear(); 112 | selected_files.add(arr_dir_stack.get(arr_dir_stack.size()-1).getPath()); 113 | } 114 | intent.putStringArrayListExtra("filePaths", selected_files); 115 | setResult(config.getReqCode(), intent); 116 | setResult(RESULT_OK, intent); 117 | finish(); 118 | }); 119 | 120 | TypedValue typedValue = new TypedValue(); 121 | Resources.Theme theme = getTheme(); 122 | theme.resolveAttribute(R.attr.unicorn_fabColor, typedValue, true); 123 | if(typedValue.data!=0){ 124 | filePickerBinding.fabSelect.setBackgroundTintList(ColorStateList.valueOf(typedValue.data)); 125 | }else{ 126 | filePickerBinding.fabSelect.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.unicorn_colorAccent))); 127 | } 128 | 129 | } 130 | 131 | private void setUpFilesView() { 132 | LinearLayoutManager layoutManager = new LinearLayoutManager(FilePickerActivity.this); 133 | filePickerBinding.rvFiles.setLayoutManager(layoutManager); 134 | directoryAdapter = new DirectoryAdapter(FilePickerActivity.this, arr_files, false, new DirectoryAdapter.onFilesClickListener() { 135 | @Override 136 | public void onClicked(DirectoryModel model) { 137 | fetchDirectory(model); 138 | } 139 | 140 | @Override 141 | public void onFileSelected(DirectoryModel fileModel) { 142 | if(config.isSelectMultiple()){ 143 | if(selected_files.contains(fileModel.getPath())){ 144 | selected_files.remove(fileModel.getPath()); 145 | }else{ 146 | selected_files.add(fileModel.getPath()); 147 | } 148 | }else{ 149 | selected_files.clear(); 150 | selected_files.add(fileModel.getPath()); 151 | } 152 | } 153 | }); 154 | filePickerBinding.rvFiles.setAdapter(directoryAdapter); 155 | directoryAdapter.notifyDataSetChanged(); 156 | if(config.addItemDivider()){ 157 | filePickerBinding.rvFiles.addItemDecoration(new UnicornSimpleItemDecoration(FilePickerActivity.this)); 158 | } 159 | } 160 | 161 | private void setUpDirectoryStackView() { 162 | LinearLayoutManager layoutManager = new LinearLayoutManager(FilePickerActivity.this, RecyclerView.HORIZONTAL, false); 163 | filePickerBinding.rvDirPath.setLayoutManager(layoutManager); 164 | stackAdapter = new DirectoryStackAdapter(FilePickerActivity.this, arr_dir_stack, model -> { 165 | Log.e(TAG, model.toString()); 166 | arr_dir_stack = new ArrayList<>(arr_dir_stack.subList(0, arr_dir_stack.indexOf(model) + 1)); 167 | setUpDirectoryStackView(); 168 | fetchDirectory(arr_dir_stack.remove(arr_dir_stack.size() - 1)); 169 | }); 170 | 171 | filePickerBinding.rvDirPath.setAdapter(stackAdapter); 172 | stackAdapter.notifyDataSetChanged(); 173 | } 174 | 175 | /** 176 | * Fetches list of files in a folder and filters files if filter present 177 | */ 178 | private void fetchDirectory(DirectoryModel model) { 179 | filePickerBinding.rlProgress.setVisibility(View.VISIBLE); 180 | selected_files.clear(); 181 | 182 | arr_files.clear(); 183 | File dir = new File(model.getPath()); 184 | File[] files_list = dir.listFiles(); 185 | if (files_list != null) { 186 | for (File file : files_list) { 187 | DirectoryModel directoryModel = new DirectoryModel(); 188 | directoryModel.setDirectory(file.isDirectory()); 189 | directoryModel.setName(file.getName()); 190 | directoryModel.setPath(file.getAbsolutePath()); 191 | directoryModel.setLast_modif_time(file.lastModified()); 192 | 193 | if (config.showHidden() || (!config.showHidden() && !file.isHidden())) { 194 | if (file.isDirectory()) { 195 | if (file.listFiles() != null) 196 | directoryModel.setNum_files(file.listFiles().length); 197 | arr_files.add(directoryModel); 198 | } else { 199 | if(!config.showOnlyDirectory()){ 200 | // Filter out files if filters specified 201 | if(filters!=null){ 202 | try { 203 | // Extract the file extension 204 | String fileName = file.getName(); 205 | String extension = fileName.substring(fileName.lastIndexOf(".")); 206 | for (String filter : filters) { 207 | if (extension.toLowerCase().contains(filter)) { 208 | arr_files.add(directoryModel); 209 | } 210 | } 211 | } catch (Exception e) { 212 | // Log.e(TAG, "Encountered a file without an extension: ", e); 213 | } 214 | }else{ 215 | arr_files.add(directoryModel); 216 | } 217 | } 218 | } 219 | } 220 | 221 | } 222 | Collections.sort(arr_files, new CustomFileComparator()); 223 | 224 | arr_dir_stack.add(model); 225 | filePickerBinding.rvDirPath.scrollToPosition(arr_dir_stack.size() - 1); 226 | filePickerBinding.toolbar.setTitle(model.getName()); 227 | } 228 | if (arr_files.size() == 0) { 229 | filePickerBinding.rlNoFiles.setVisibility(View.VISIBLE); 230 | } else { 231 | filePickerBinding.rlNoFiles.setVisibility(View.GONE); 232 | } 233 | filePickerBinding.rlProgress.setVisibility(View.GONE); 234 | stackAdapter.notifyDataSetChanged(); 235 | directoryAdapter.notifyDataSetChanged(); 236 | } 237 | 238 | // Custom Comparator to sort the list of files in lexicographical order 239 | public static class CustomFileComparator implements Comparator { 240 | @Override 241 | public int compare(DirectoryModel o1, DirectoryModel o2) { 242 | if (o1.isDirectory() && o2.isDirectory()) { 243 | return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); 244 | } else if (o1.isDirectory() && !o2.isDirectory()) { 245 | return -1; 246 | } else if (!o1.isDirectory() && o2.isDirectory()) { 247 | return 1; 248 | } else { 249 | return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); 250 | } 251 | } 252 | } 253 | 254 | 255 | @Override 256 | public boolean onCreateOptionsMenu(Menu menu) { 257 | MenuInflater menuInflater = getMenuInflater(); 258 | menuInflater.inflate(R.menu.unicorn_menu_file_picker, menu); 259 | 260 | MenuItem item_search = menu.findItem(R.id.action_search); 261 | 262 | SearchView searchView = (SearchView) item_search.getActionView(); 263 | searchView.setImeOptions(EditorInfo.IME_ACTION_DONE); 264 | 265 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 266 | @Override 267 | public boolean onQueryTextSubmit(String query) { 268 | return false; 269 | } 270 | 271 | @Override 272 | public boolean onQueryTextChange(String newText) { 273 | directoryAdapter.getFilter().filter(newText); 274 | return false; 275 | } 276 | }); 277 | return true; 278 | } 279 | 280 | /** 281 | * This method checks whether STORAGE permissions are granted or not 282 | */ 283 | private boolean allPermissionsGranted() { 284 | for (String permission : REQUIRED_PERMISSIONS) { 285 | if (ContextCompat.checkSelfPermission(FilePickerActivity.this, permission) != PackageManager.PERMISSION_GRANTED) { 286 | return false; 287 | } 288 | } 289 | return true; 290 | } 291 | 292 | @Override 293 | public void onBackPressed() { 294 | if (arr_dir_stack.size() > 1) { 295 | // pop off top value and display 296 | arr_dir_stack.remove(arr_dir_stack.size() - 1); 297 | DirectoryModel model = arr_dir_stack.remove(arr_dir_stack.size() - 1); 298 | fetchDirectory(model); 299 | } else { 300 | // Nothing left in stack so exit 301 | Intent intent = new Intent(); 302 | setResult(config.getReqCode(), intent); 303 | setResult(RESULT_CANCELED, intent); 304 | finish(); 305 | } 306 | } 307 | 308 | @Override 309 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 310 | int id = item.getItemId(); 311 | if (id == android.R.id.home) { 312 | onBackPressed(); 313 | } 314 | return true; 315 | } 316 | } -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.utils; 2 | 3 | /** 4 | * Created by Abhishek Tiwari on 07-01-2021. 5 | */ 6 | public class Constants { 7 | 8 | public static final int REQ_UNICORN_FILE = 9999; 9 | } 10 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/java/abhishekti7/unicorn/filepicker/utils/UnicornSimpleItemDecoration.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.drawable.Drawable; 6 | import android.view.View; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.core.content.ContextCompat; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import abhishekti7.unicorn.filepicker.R; 13 | 14 | /** 15 | * Created by Abhishek Tiwari on 09-01-2021. 16 | */ 17 | public class UnicornSimpleItemDecoration extends RecyclerView.ItemDecoration { 18 | 19 | private Drawable mDivider; 20 | 21 | public UnicornSimpleItemDecoration(Context context) { 22 | this.mDivider = ContextCompat.getDrawable(context, R.drawable.unicorn_item_layout_divider); 23 | } 24 | 25 | @Override 26 | public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 27 | 28 | int left = parent.getPaddingLeft(); 29 | int right = parent.getWidth() - parent.getPaddingRight(); 30 | 31 | int childCount = parent.getChildCount(); 32 | for(int i = 0; i mapOfMonths = new HashMap() {{ 12 | put(1, "Jan"); 13 | put(2, "Feb"); 14 | put(3, "Mar"); 15 | put(4, "Apr"); 16 | put(5, "May"); 17 | put(6, "Jun"); 18 | put(7, "Jul"); 19 | put(8, "Aug"); 20 | put(9, "Sep"); 21 | put(10, "Oct"); 22 | put(11, "Nov"); 23 | put(12, "Dec"); 24 | }}; 25 | public static String longToReadableDate(long time) { 26 | Calendar calendar = Calendar.getInstance(); 27 | calendar.setTimeInMillis(time); 28 | 29 | return mapOfMonths.get(calendar.get(Calendar.MONTH) + 1) + " " + 30 | calendar.get(Calendar.DAY_OF_MONTH) + ", " + calendar.get(Calendar.YEAR); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/drawable/unicorn_ic_arrow_right.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/drawable/unicorn_ic_arrow_solid_right.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/drawable/unicorn_ic_done.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/drawable/unicorn_ic_folder.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/drawable/unicorn_ic_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/unicorn_ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekti7/UnicornFilePicker/c523d6274d8cef169569ea1f455996ad37b1d767/UnicornFilePicker/src/main/res/drawable/unicorn_ic_file.png -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/unicorn_ic_images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekti7/UnicornFilePicker/c523d6274d8cef169569ea1f455996ad37b1d767/UnicornFilePicker/src/main/res/drawable/unicorn_ic_images.png -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/unicorn_ic_pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhishekti7/UnicornFilePicker/c523d6274d8cef169569ea1f455996ad37b1d767/UnicornFilePicker/src/main/res/drawable/unicorn_ic_pdf.png -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/drawable/unicorn_item_layout_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/layout/unicorn_activity_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 21 | 22 | 23 | 24 | 30 | 31 | 39 | 40 | 48 | 49 | 54 | 55 | 56 | 66 | 67 | 74 | 75 | 83 | 84 | 85 | 86 | 95 | 96 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/layout/unicorn_item_layout_directory.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 28 | 36 | 37 | 44 | 53 | 54 | 55 | 64 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/layout/unicorn_item_layout_directory_stack.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 23 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/layout/unicorn_item_layout_files.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 30 | 38 | 39 | 46 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/menu/unicorn_menu_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #ffa502 5 | #000 6 | #000 7 | 8 | #fff 9 | #000 10 | #B3000000 11 | #B3FFFFFF 12 | #353535 13 | #E5E7E9 14 | 15 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Search 3 | No files 4 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/main/res/values/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 | 31 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /UnicornFilePicker/src/test/java/abhishekti7/unicorn/filepicker/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package abhishekti7.unicorn.filepicker; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.2" 8 | 9 | viewBinding{ 10 | enabled true 11 | } 12 | 13 | defaultConfig { 14 | applicationId "dev.abhishekti7.filepickerexample" 15 | minSdkVersion 21 16 | targetSdkVersion 30 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | } 34 | 35 | dependencies { 36 | 37 | implementation 'androidx.appcompat:appcompat:1.2.0' 38 | implementation 'com.google.android.material:material:1.2.1' 39 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 40 | implementation project(path: ':UnicornFilePicker') 41 | testImplementation 'junit:junit:4.+' 42 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 43 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 44 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/dev/abhishekti7/filepickerexample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package dev.abhishekti7.filepickerexample; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("dev.zeus0789.filepickerexample", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/dev/abhishekti7/filepickerexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package dev.abhishekti7.filepickerexample; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import androidx.core.app.ActivityCompat; 7 | import androidx.core.content.ContextCompat; 8 | 9 | import android.content.Intent; 10 | import android.content.pm.PackageManager; 11 | import android.os.Bundle; 12 | import android.os.Environment; 13 | import android.util.Log; 14 | import android.view.View; 15 | import android.widget.Toast; 16 | 17 | import java.util.ArrayList; 18 | 19 | import abhishekti7.unicorn.filepicker.UnicornFilePicker; 20 | import abhishekti7.unicorn.filepicker.utils.Constants; 21 | import dev.abhishekti7.filepickerexample.databinding.ActivityMainBinding; 22 | 23 | public class MainActivity extends AppCompatActivity { 24 | private static final String TAG = "MainActivity"; 25 | 26 | private ActivityMainBinding mainBinding; 27 | private final int REQUEST_CODE_PERMISSIONS = 101; 28 | private final String[] REQUIRED_PERMISSIONS = new String[]{ 29 | "android.permission.WRITE_EXTERNAL_STORAGE", 30 | "android.permission.READ_EXTERNAL_STORAGE", 31 | }; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | mainBinding = ActivityMainBinding.inflate(getLayoutInflater()); 37 | View view = mainBinding.getRoot(); 38 | setContentView(view); 39 | 40 | if (allPermissionsGranted()) { 41 | Toast.makeText(MainActivity.this, "Permissions granted by the user.", Toast.LENGTH_SHORT).show(); 42 | } else { 43 | ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS); 44 | } 45 | 46 | 47 | mainBinding.btnFilesDark.setOnClickListener((v)->{ 48 | UnicornFilePicker.from(MainActivity.this) 49 | .addConfigBuilder() 50 | .selectMultipleFiles(true) 51 | .showOnlyDirectory(true) 52 | .setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath()) 53 | .showHiddenFiles(false) 54 | .setFilters(new String[]{"pdf", "png", "jpg", "jpeg"}) 55 | .addItemDivider(true) 56 | .theme(R.style.UnicornFilePicker_Dracula) 57 | .build() 58 | .forResult(Constants.REQ_UNICORN_FILE); 59 | }); 60 | 61 | mainBinding.btnFilesLight.setOnClickListener((v)->{ 62 | UnicornFilePicker.from(MainActivity.this) 63 | .addConfigBuilder() 64 | .selectMultipleFiles(true) 65 | .setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath()) 66 | .showHiddenFiles(false) 67 | .setFilters(new String[]{"pdf", "png", "jpg", "jpeg"}) 68 | .addItemDivider(true) 69 | .theme(R.style.UnicornFilePicker_Default) 70 | .build() 71 | .forResult(Constants.REQ_UNICORN_FILE); 72 | }); 73 | 74 | mainBinding.btnFilesCustom.setOnClickListener((v)->{ 75 | UnicornFilePicker.from(MainActivity.this) 76 | .addConfigBuilder() 77 | .selectMultipleFiles(true) 78 | .setRootDirectory(Environment.getExternalStorageDirectory().getAbsolutePath()) 79 | .showHiddenFiles(false) 80 | .setFilters(new String[]{"pdf", "png", "jpg", "jpeg"}) 81 | .addItemDivider(true) 82 | .theme(R.style.Theme_CustomUnicorn) 83 | .build() 84 | .forResult(Constants.REQ_UNICORN_FILE); 85 | }); 86 | } 87 | 88 | 89 | @Override 90 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 91 | super.onActivityResult(requestCode, resultCode, data); 92 | if(requestCode == Constants.REQ_UNICORN_FILE && resultCode == RESULT_OK){ 93 | if(data!=null){ 94 | ArrayList files = data.getStringArrayListExtra("filePaths"); 95 | for(String file : files){ 96 | Log.e(TAG, file); 97 | } 98 | } 99 | } 100 | } 101 | 102 | private boolean allPermissionsGranted() { 103 | for (String permission : REQUIRED_PERMISSIONS) { 104 | if (ContextCompat.checkSelfPermission(MainActivity.this, permission) != PackageManager.PERMISSION_GRANTED) { 105 | return false; 106 | } 107 | } 108 | return true; 109 | } 110 | 111 | @Override 112 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 113 | if (requestCode == REQUEST_CODE_PERMISSIONS) { 114 | if (allPermissionsGranted()) { 115 | Toast.makeText(MainActivity.this, "Permissions granted by the user.", Toast.LENGTH_SHORT).show(); 116 | } else { 117 | Toast.makeText(MainActivity.this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show(); 118 | } 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /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_arrow.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |