├── .gitignore ├── FilePicker ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mahdiasd │ │ └── filepicker │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mahdiasd │ │ │ └── filepicker │ │ │ ├── BaseRecyclerAdapter.java │ │ │ ├── FileModel.kt │ │ │ ├── FilePicker.kt │ │ │ ├── FilePickerAdapter.kt │ │ │ ├── FilePickerFragment.kt │ │ │ ├── FilePickerListener.kt │ │ │ ├── PickerMode.kt │ │ │ ├── SectionAdapter.kt │ │ │ └── SectionModel.kt │ └── res │ │ ├── drawable │ │ ├── mahdiasd_dash_line.xml │ │ ├── mahdiasd_ic_audio.xml │ │ ├── mahdiasd_ic_camera.xml │ │ ├── mahdiasd_ic_checkmark.xml │ │ ├── mahdiasd_ic_document.xml │ │ ├── mahdiasd_ic_done.xml │ │ ├── mahdiasd_ic_file.xml │ │ ├── mahdiasd_ic_image.xml │ │ ├── mahdiasd_ic_play.xml │ │ ├── mahdiasd_ic_search.xml │ │ ├── mahdiasd_ic_send.xml │ │ ├── mahdiasd_ic_video.xml │ │ ├── mahdiasd_shape_circle.xml │ │ ├── mahdiasd_shape_radius_20.xml │ │ ├── mahdiasd_shape_radius_35.xml │ │ └── mahdiasd_shape_square.xml │ │ ├── layout │ │ ├── file_picker_fragment.xml │ │ ├── item_file_picker.xml │ │ ├── item_file_picker_manager.xml │ │ └── item_section.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── com │ └── mahdiasd │ └── filepicker │ └── ExampleUnitTest.kt ├── LICENSE.md ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mahdiasd │ │ └── sample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mahdiasd │ │ │ └── sample │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── com │ └── mahdiasd │ └── sample │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── jitpack.yml ├── pom.xml ├── screenshot ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /FilePicker/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | mavenLocal() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.1.3' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | plugins { 16 | id 'com.android.library' 17 | id 'org.jetbrains.kotlin.android' 18 | id 'kotlin-kapt' 19 | id 'maven-publish' 20 | } 21 | 22 | android { 23 | compileSdk 33 24 | namespace = "com.mahdiasd.filepicker" 25 | 26 | defaultConfig { 27 | aarMetadata { 28 | minCompileSdk = 29 29 | } 30 | minSdk 21 31 | targetSdk 33 32 | multiDexEnabled true 33 | 34 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 35 | consumerProguardFiles "consumer-rules.pro" 36 | } 37 | 38 | buildTypes { 39 | release { 40 | minifyEnabled false 41 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 42 | } 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_11 46 | targetCompatibility JavaVersion.VERSION_11 47 | } 48 | kotlinOptions { 49 | jvmTarget = '11' 50 | } 51 | 52 | buildFeatures { 53 | dataBinding true 54 | } 55 | 56 | publishing { 57 | singleVariant("release") { 58 | withSourcesJar() 59 | withJavadocJar() 60 | } 61 | } 62 | 63 | } 64 | 65 | dependencies { 66 | 67 | implementation 'androidx.core:core-ktx:1.8.0' 68 | implementation 'androidx.appcompat:appcompat:1.5.1' 69 | implementation 'com.google.android.material:material:1.6.1' 70 | 71 | annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2' 72 | implementation "com.github.bumptech.glide:glide:4.13.2" 73 | implementation 'com.github.onimur:handle-path-oz:1.0.7' 74 | 75 | testImplementation 'junit:junit:4.13.2' 76 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 78 | } 79 | 80 | 81 | publishing { 82 | publications { 83 | release(MavenPublication) { 84 | groupId = 'com.mahdiasd.filepicker' 85 | artifactId = 'filepicker' 86 | version = "2.3.6" 87 | 88 | afterEvaluate { 89 | from components.release 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /FilePicker/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdiasd/BottomDialogFilePicker/08ccb3ab8831a171f799d74ed594e3ef82666764/FilePicker/consumer-rules.pro -------------------------------------------------------------------------------- /FilePicker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep public class * implements com.bumptech.glide.module.GlideModule 24 | -keep class * extends com.bumptech.glide.module.AppGlideModule { 25 | (...); 26 | } 27 | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { 28 | **[] $VALUES; 29 | public *; 30 | } 31 | -keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { 32 | *** rewind(); 33 | } 34 | 35 | # Uncomment for DexGuard only 36 | #-keepresourcexmlelements manifest/application/meta-data@value=GlideModule -------------------------------------------------------------------------------- /FilePicker/src/androidTest/java/com/mahdiasd/filepicker/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.mahdiasd.filepicker.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /FilePicker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/BaseRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker; 2 | 3 | 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.databinding.DataBindingUtil; 9 | import androidx.databinding.ViewDataBinding; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Created by : youngkaaa on 2016/10/3. 16 | * Contact me : 645326280@qq.com 17 | */ 18 | 19 | public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter { 20 | private List mLists; 21 | private final Context mContext; 22 | 23 | public BaseRecyclerAdapter(Context context, List list) { 24 | this.mContext = context; 25 | this.mLists = list; 26 | } 27 | 28 | public abstract int getRootLayoutId(); 29 | 30 | public abstract void onBind(BaseViewHolder viewHolder, int position); 31 | 32 | @Override 33 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 34 | onBind((BaseViewHolder) holder, position); 35 | } 36 | 37 | @Override 38 | public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 39 | ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), getRootLayoutId(), parent, false); 40 | return new BaseViewHolder(binding); 41 | } 42 | 43 | public Context getContext() { 44 | return mContext; 45 | } 46 | 47 | @Override 48 | public int getItemCount() { 49 | if (mLists == null) return 0; 50 | return mLists.size(); 51 | } 52 | 53 | public List getList() { 54 | return mLists; 55 | } 56 | 57 | public void setLists(List mLists) { 58 | this.mLists = mLists; 59 | } 60 | 61 | public class BaseViewHolder extends RecyclerView.ViewHolder { 62 | private final ViewDataBinding binding; 63 | 64 | public BaseViewHolder(ViewDataBinding itemView) { 65 | super(itemView.getRoot()); 66 | binding = itemView; 67 | } 68 | 69 | public ViewDataBinding getBinding() { 70 | return binding; 71 | } 72 | 73 | public Object getData(int position) { 74 | if (position >= mLists.size()) 75 | return null; 76 | return mLists.get(position); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/FileModel.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import java.io.File 6 | 7 | data class FileModel( 8 | var path: String 9 | ) : BaseObservable() { 10 | 11 | var file = File(path) 12 | 13 | @Bindable 14 | var selected = false 15 | set(value) { 16 | notifyPropertyChanged(BR.selected) 17 | field = value 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/FilePicker.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import androidx.core.content.ContextCompat 6 | import androidx.databinding.BaseObservable 7 | import androidx.databinding.Bindable 8 | import androidx.fragment.app.FragmentManager 9 | 10 | data class FilePicker( 11 | private val context: Context, 12 | private val fragmentManager: FragmentManager, 13 | var listener: FilePickerListener? = null, 14 | 15 | var mode: List = listOf(PickerMode.Image, PickerMode.Image, PickerMode.Video, PickerMode.Audio), 16 | private var defaultMode: PickerMode? = null, 17 | 18 | var videoText: String = context.getString(R.string.mahdiasd_file_picker_video), 19 | var audioText: String = context.getString(R.string.mahdiasd_file_picker_audio), 20 | var fileManagerText: String = context.getString(R.string.mahdiasd_file_picker_file_manager), 21 | var imageText: String = context.getString(R.string.mahdiasd_file_picker_image), 22 | var cameraText: String = context.getString(R.string.mahdiasd_file_picker_camera), 23 | var openStorageText: String = context.getString(R.string.mahdiasd_file_picker_open_storage), 24 | var maxTotalFileSizeText: String = context.getString(R.string.mahdiasd_file_picker_max_total_size), 25 | var maxEachFileSizeText: String = context.getString(R.string.mahdiasd_file_picker_max_each_size), 26 | 27 | var videoIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_video), 28 | var audioIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_audio), 29 | var documentIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_document), 30 | var fileManagerIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_file), 31 | var imageIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_image), 32 | var searchIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_search), 33 | var doneIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_send), 34 | 35 | var showFileWhenClick: Boolean = false, 36 | var maxSelection: Int = 10, 37 | 38 | var totalFileSize: Int? = null, 39 | var eachFileSize: Int? = null, 40 | 41 | var activeColor: Int = ContextCompat.getColor(context, R.color.colorPrimary), 42 | var deActiveColor: Int = ContextCompat.getColor(context, R.color.gray), 43 | var cardBackgroundColor: Int = ContextCompat.getColor(context, R.color.white), 44 | 45 | 46 | ) : 47 | BaseObservable() { 48 | private val fragmentTag = "mahdiasd_file_picker" 49 | private var filePickerFragment = FilePickerFragment.newInstance() 50 | 51 | @Bindable 52 | var selectedMode: PickerMode = defaultMode() 53 | set(value) { 54 | notifyPropertyChanged(BR.selectedMode) 55 | field = value 56 | } 57 | 58 | 59 | fun setListener(listener: FilePickerListener): FilePicker { 60 | this.listener = listener 61 | return this 62 | } 63 | 64 | fun setIcons( 65 | videoIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_video), 66 | audioIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_audio), 67 | documentIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_document), 68 | fileManagerIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_file), 69 | imageIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_image), 70 | doneIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_send), 71 | searchIcon: Drawable? = ContextCompat.getDrawable(context, R.drawable.mahdiasd_ic_search), 72 | ): FilePicker { 73 | this.searchIcon = searchIcon 74 | this.videoIcon = videoIcon 75 | this.audioIcon = audioIcon 76 | this.documentIcon = documentIcon 77 | this.fileManagerIcon = fileManagerIcon 78 | this.imageIcon = imageIcon 79 | this.doneIcon = doneIcon 80 | 81 | return this 82 | } 83 | 84 | fun setMode(vararg modes: PickerMode): FilePicker { 85 | this.mode = modes.toList() 86 | return this 87 | } 88 | 89 | fun setMaxSelection(value: Int): FilePicker { 90 | this.maxSelection = value 91 | return this 92 | } 93 | 94 | fun setMaxTotalFileSize(value: Int): FilePicker { 95 | this.totalFileSize = value 96 | return this 97 | } 98 | 99 | fun setMaxEachFileSize(value: Int): FilePicker { 100 | this.eachFileSize = value 101 | return this 102 | } 103 | 104 | fun setShowFileWhenClick(value: Boolean): FilePicker { 105 | this.showFileWhenClick = value 106 | return this 107 | } 108 | 109 | fun setActiveColor(value: Int): FilePicker { 110 | if (value > 0) 111 | this.activeColor = ContextCompat.getColor(context, value) 112 | else 113 | this.activeColor = value 114 | return this 115 | } 116 | 117 | fun setDeActiveColor(value: Int): FilePicker { 118 | if (value > 0) 119 | this.deActiveColor = ContextCompat.getColor(context, value) 120 | else 121 | this.deActiveColor = value 122 | return this 123 | } 124 | 125 | fun setCardBackgroundColor(value: Int): FilePicker { 126 | if (value > 0) 127 | this.cardBackgroundColor = ContextCompat.getColor(context, value) 128 | else 129 | this.cardBackgroundColor = value 130 | return this 131 | } 132 | 133 | fun setDefaultMode(defaultMode: PickerMode): FilePicker { 134 | this.defaultMode = defaultMode 135 | return this 136 | } 137 | 138 | fun defaultMode(): PickerMode { 139 | return if (defaultMode == null) { 140 | if (mode.isEmpty()) 141 | PickerMode.Image 142 | else mode.first() 143 | } else { 144 | if (!mode.contains(defaultMode)) mode.first() 145 | else defaultMode!! 146 | } 147 | } 148 | 149 | fun setCustomText( 150 | videoText: String = context.getString(R.string.mahdiasd_file_picker_video), 151 | audioText: String = context.getString(R.string.mahdiasd_file_picker_audio), 152 | fileManagerText: String = context.getString(R.string.mahdiasd_file_picker_file_manager), 153 | imageText: String = context.getString(R.string.mahdiasd_file_picker_image), 154 | cameraText: String = context.getString(R.string.mahdiasd_file_picker_camera), 155 | openStorageText: String = context.getString(R.string.mahdiasd_file_picker_open_storage), 156 | maxTotalFileSizeText: String = context.getString(R.string.mahdiasd_file_picker_max_total_size), 157 | maxEachFileSizeText: String = context.getString(R.string.mahdiasd_file_picker_max_each_size), 158 | ): FilePicker { 159 | this.videoText = videoText 160 | this.audioText = audioText 161 | this.fileManagerText = fileManagerText 162 | this.imageText = imageText 163 | this.cameraText = cameraText 164 | this.openStorageText = openStorageText 165 | this.maxTotalFileSizeText = maxTotalFileSizeText 166 | this.maxEachFileSizeText = maxEachFileSizeText 167 | return this 168 | } 169 | 170 | fun show() { 171 | if (fragmentManager.findFragmentByTag(fragmentTag) != null) return 172 | selectedMode = defaultMode() 173 | filePickerFragment.show(fragmentManager, fragmentTag) 174 | filePickerFragment.setConfig(this) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/FilePickerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.view.View 8 | import android.webkit.MimeTypeMap 9 | import android.widget.ImageView 10 | import android.widget.Toast 11 | import androidx.core.content.FileProvider 12 | import androidx.lifecycle.MutableLiveData 13 | import com.bumptech.glide.Glide 14 | import com.bumptech.glide.load.engine.DiskCacheStrategy 15 | import com.mahdiasd.filepicker.databinding.ItemFilePickerBinding 16 | import com.mahdiasd.filepicker.databinding.ItemFilePickerManagerBinding 17 | import java.io.File 18 | 19 | class FilePickerAdapter( 20 | context: Context?, 21 | list: List?, 22 | private var selectedFiles: MutableList, 23 | private val pickerMode: PickerMode, 24 | private val config: FilePicker, 25 | private val filePickerFragment: FilePickerFragment? = null, 26 | ) : BaseRecyclerAdapter(context, list) { 27 | 28 | private var stack = ArrayDeque?>() 29 | var liveData: MutableLiveData = MutableLiveData() 30 | 31 | init { 32 | stack.add(list) 33 | } 34 | 35 | override fun getRootLayoutId(): Int { 36 | return if (pickerMode == PickerMode.Audio || pickerMode == PickerMode.File) 37 | R.layout.item_file_picker_manager 38 | else 39 | R.layout.item_file_picker 40 | } 41 | 42 | override fun onBind(viewHolder: BaseViewHolder, position: Int) { 43 | val model = viewHolder.getData(position) as FileModel 44 | when (pickerMode) { 45 | PickerMode.Audio, PickerMode.File -> { 46 | val itemBinding = viewHolder.binding as ItemFilePickerManagerBinding 47 | itemBinding.let { 48 | it.item = model 49 | it.presenter = this 50 | it.position = position 51 | it.pickerMode = pickerMode 52 | it.subFolderCount = model.file.listFiles()?.size ?: 0 53 | it.stackListSize = stack.size 54 | it.activeColor = config.activeColor 55 | it.deActiveColor = config.deActiveColor 56 | } 57 | } 58 | else -> { 59 | val itemBinding = viewHolder.binding as ItemFilePickerBinding 60 | itemBinding.let { 61 | it.item = model 62 | glideSdCart(itemBinding.image, model.path) 63 | it.presenter = this 64 | it.containCamera = config.mode.contains(PickerMode.Camera) 65 | it.activeColor = config.activeColor 66 | it.deActiveColor = config.deActiveColor 67 | it.type = config.selectedMode 68 | } 69 | } 70 | } 71 | } 72 | 73 | @SuppressLint("NotifyDataSetChanged") 74 | fun checkBox(view: View?, file: FileModel) { 75 | if (!checkMaxSize(file) && !file.selected) { 76 | notifyDataSetChanged() 77 | return 78 | } 79 | 80 | file.selected = !file.selected 81 | if (selectedFiles.size >= config.maxSelection) { 82 | selectedFiles.last().selected = false 83 | selectedFiles.removeLast() 84 | } 85 | 86 | if (file.selected) 87 | selectedFiles.add(file) 88 | else 89 | selectedFiles.remove(file) 90 | 91 | liveData.postValue(selectedFiles.size) 92 | } 93 | 94 | private fun checkMaxSize(file: FileModel): Boolean { 95 | val fileSize = (file.file.length() / 1024).toInt() 96 | var totalSize = fileSize 97 | 98 | selectedFiles.forEach { 99 | totalSize += (it.file.length() / 1024).toInt() 100 | } 101 | 102 | return when { 103 | config.eachFileSize != null && fileSize > config.eachFileSize!! -> { 104 | Toast.makeText( 105 | context, "${config.maxEachFileSizeText} ${config.eachFileSize}kb", 106 | Toast.LENGTH_LONG 107 | ).show() 108 | false 109 | } 110 | config.totalFileSize != null && totalSize > config.totalFileSize!! -> { 111 | Toast.makeText( 112 | context, "${config.maxTotalFileSizeText} ${config.totalFileSize}kb", 113 | Toast.LENGTH_LONG 114 | ).show() 115 | false 116 | } 117 | else -> true 118 | } 119 | } 120 | 121 | fun onClick(view: View, fileModel: FileModel) { 122 | if (fileModel.path == "Camera") 123 | filePickerFragment?.openCamera() 124 | else if (config.showFileWhenClick) 125 | openFile(fileModel.path) 126 | else 127 | checkBox(null, fileModel) 128 | } 129 | 130 | 131 | @SuppressLint("NotifyDataSetChanged") 132 | fun back(view: View) { 133 | stack.removeLast() 134 | if (stack.isNotEmpty()) { 135 | setLists(stack.last()) 136 | notifyDataSetChanged() 137 | } 138 | } 139 | 140 | @SuppressLint("NotifyDataSetChanged") 141 | fun nextFolder(view: View, file: FileModel) { 142 | if (file.file.isDirectory && file.file.listFiles() != null) { 143 | val temp: MutableList = ArrayList() 144 | if (file.file.listFiles()!!.isEmpty()) return 145 | 146 | file.file.listFiles()?.forEach { 147 | temp.add(FileModel(it.path)) 148 | } 149 | setLists(temp) 150 | stack.add(list) 151 | notifyDataSetChanged() 152 | } 153 | } 154 | 155 | private fun glideSdCart(view: ImageView, imageUrl: String?) { 156 | if (imageUrl == null) return 157 | val file = File(imageUrl) 158 | val imageUri = Uri.fromFile(file) ?: return 159 | Glide.with(view.context) 160 | .load(imageUri) 161 | .placeholder(R.color.gray) 162 | .skipMemoryCache(false) 163 | .diskCacheStrategy(DiskCacheStrategy.ALL) 164 | .into(view) 165 | } 166 | 167 | private fun openFile(fileAddress: String?) { 168 | if (fileAddress == null) return 169 | try { 170 | val file = File(fileAddress) 171 | val map = MimeTypeMap.getSingleton() 172 | val ext = MimeTypeMap.getFileExtensionFromUrl(file.name) 173 | val type = map.getMimeTypeFromExtension(ext) 174 | val intent = Intent(Intent.ACTION_VIEW) 175 | intent.setDataAndType(FileProvider.getUriForFile(context, "${context.packageName}.provider", file), type) 176 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 177 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) 178 | context.startActivity(intent) 179 | } catch (e: java.lang.Exception) { 180 | Toast.makeText( 181 | context, 182 | context.resources.getString(R.string.mahdiasd_file_picker_failed_open_file), 183 | Toast.LENGTH_SHORT 184 | ).show() 185 | } 186 | } 187 | 188 | } -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/FilePickerFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.ContentValues 6 | import android.content.Intent 7 | import android.content.pm.PackageManager 8 | import android.content.res.Configuration 9 | import android.database.Cursor 10 | import android.graphics.Bitmap 11 | import android.net.Uri 12 | import android.os.Build 13 | import android.os.Bundle 14 | import android.os.Environment 15 | import android.provider.MediaStore 16 | import android.util.Log 17 | import android.view.LayoutInflater 18 | import android.view.View 19 | import android.view.ViewGroup 20 | import android.widget.Toast 21 | import androidx.activity.result.contract.ActivityResultContracts 22 | import androidx.core.content.ContextCompat 23 | import androidx.core.content.FileProvider 24 | import androidx.databinding.DataBindingUtil 25 | import androidx.recyclerview.widget.GridLayoutManager 26 | import androidx.recyclerview.widget.LinearLayoutManager 27 | import androidx.recyclerview.widget.RecyclerView 28 | import androidx.recyclerview.widget.SimpleItemAnimator 29 | import br.com.onimur.handlepathoz.HandlePathOz 30 | import br.com.onimur.handlepathoz.HandlePathOzListener 31 | import br.com.onimur.handlepathoz.model.PathOz 32 | import com.google.android.material.bottomsheet.BottomSheetBehavior 33 | import com.google.android.material.bottomsheet.BottomSheetDialog 34 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 35 | import com.mahdiasd.filepicker.databinding.FilePickerFragmentBinding 36 | import kotlinx.coroutines.FlowPreview 37 | import java.io.File 38 | 39 | 40 | class FilePickerFragment : BottomSheetDialogFragment() { 41 | private var handlePathOz: HandlePathOz? = null 42 | 43 | private var myAdapter: FilePickerAdapter? = null 44 | private lateinit var binding: FilePickerFragmentBinding 45 | 46 | private var imageList: MutableList = ArrayList() 47 | private var videoList: MutableList = ArrayList() 48 | private var audioList: MutableList = ArrayList() 49 | 50 | private var selectedFiles: MutableList = ArrayList() 51 | 52 | private lateinit var config: FilePicker 53 | private var storageIsOpen = false 54 | private var cameraImagePath = "" 55 | private var cameraImageName = "" 56 | 57 | companion object { 58 | fun newInstance() = FilePickerFragment() 59 | } 60 | 61 | override fun onCreateView( 62 | inflater: LayoutInflater, 63 | container: ViewGroup?, 64 | savedInstanceState: Bundle? 65 | ): View { 66 | binding = DataBindingUtil.inflate(inflater, R.layout.file_picker_fragment, container, false) 67 | binding.presenter = this 68 | binding.config = config 69 | 70 | if (config.mode.size == 1 && config.selectedMode == PickerMode.File) 71 | openFileManager() 72 | else 73 | initSectionList() 74 | 75 | 76 | return binding.root 77 | } 78 | 79 | private fun checkPermission() { 80 | val list = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 81 | if (isGrant(Manifest.permission.READ_MEDIA_IMAGES) 82 | && isGrant(Manifest.permission.READ_MEDIA_AUDIO) 83 | && isGrant(Manifest.permission.READ_MEDIA_VIDEO) 84 | ) getFiles() 85 | mutableListOf( 86 | Manifest.permission.READ_MEDIA_IMAGES, 87 | Manifest.permission.READ_MEDIA_VIDEO, 88 | Manifest.permission.READ_MEDIA_AUDIO 89 | ) 90 | } else { 91 | if (isGrant(Manifest.permission.READ_EXTERNAL_STORAGE)) getFiles() 92 | mutableListOf( 93 | Manifest.permission.READ_EXTERNAL_STORAGE, 94 | ) 95 | } 96 | 97 | if (config.mode.contains(PickerMode.Camera)) { 98 | list.add(Manifest.permission.CAMERA) 99 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) 100 | list.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) 101 | } 102 | requestMultiplePermissions.launch(list.toTypedArray()) 103 | } 104 | 105 | private fun isGrant(permission: String): Boolean { 106 | return ContextCompat.checkSelfPermission(requireContext(), permission) == PackageManager.PERMISSION_GRANTED 107 | } 108 | 109 | fun setConfig(filePicker: FilePicker) { 110 | this.config = filePicker 111 | } 112 | 113 | override fun onResume() { 114 | super.onResume() 115 | checkPermission() 116 | } 117 | 118 | private fun initSectionList() { 119 | val sectionAdapter = SectionAdapter( 120 | requireContext(), 121 | getSectionList(), 122 | config.selectedMode, 123 | config.activeColor, 124 | config.deActiveColor 125 | ) 126 | 127 | binding.sectionList.let { 128 | it.layoutManager = (LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)) 129 | it.adapter = sectionAdapter 130 | } 131 | 132 | sectionAdapter.changeMode.observe(this) { 133 | config.selectedMode = it 134 | initRecyclerView() 135 | if (it == PickerMode.Camera) { 136 | openCamera() 137 | } 138 | } 139 | 140 | } 141 | 142 | private fun getUriFromFile(file: File?): Uri? { 143 | if (file == null) return null 144 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 145 | FileProvider.getUriForFile(requireContext(), "${requireContext().packageName}.provider", file) 146 | else 147 | Uri.fromFile(file) 148 | } 149 | 150 | 151 | fun openCamera() { 152 | if (!isGrant(Manifest.permission.CAMERA)) 153 | checkPermission() 154 | else { 155 | cameraImageName = "${System.currentTimeMillis()}.png" 156 | cameraImagePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path + File.separator + cameraImageName 157 | val file = File(cameraImagePath) 158 | file.createNewFile() 159 | val outputFileUri = getUriFromFile(file) 160 | val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) 161 | cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri) 162 | cameraLauncher.launch(cameraIntent) 163 | } 164 | } 165 | 166 | private fun saveBitmapToGallery(bitmap: Bitmap) { 167 | val contentValues = ContentValues().apply { 168 | put(MediaStore.MediaColumns.DISPLAY_NAME, cameraImageName) 169 | put(MediaStore.MediaColumns.MIME_TYPE, "image/png") 170 | } 171 | 172 | val uri = requireContext().contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?: return 173 | 174 | requireContext().contentResolver.openOutputStream(uri)?.use { outputStream -> 175 | // Compress and save bitmap to output stream 176 | bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) 177 | outputStream.flush() 178 | outputStream.close() 179 | 180 | } 181 | requireContext().sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)) 182 | } 183 | 184 | private fun openFileManager() { 185 | storageIsOpen = true 186 | val intent = Intent() 187 | .setType("*/*") 188 | .setAction(Intent.ACTION_GET_CONTENT) 189 | .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, config.maxSelection > 1) 190 | storageLauncher.launch(intent) 191 | 192 | handlePathOz = 193 | HandlePathOz(requireContext(), object : HandlePathOzListener.MultipleUri { 194 | override fun onRequestHandlePathOz(listPathOz: List, tr: Throwable?) { 195 | listPathOz.forEach { 196 | val file = File(it.path) 197 | if (file.exists()) { 198 | val fileModel = FileModel(it.path) 199 | if (checkMaxSize(fileModel) && selectedFiles.size < config.maxSelection) { 200 | fileModel.selected = true 201 | selectedFiles.add(fileModel) 202 | } 203 | } else { 204 | Toast.makeText(requireContext(), getString(R.string.mahdiasd_file_picker_cant_find_this_file), Toast.LENGTH_SHORT).show() 205 | } 206 | } 207 | initRecyclerView() 208 | } 209 | }) 210 | } 211 | 212 | private fun checkMaxSize(file: FileModel): Boolean { 213 | val fileSize = (file.file.length() / 1024).toInt() 214 | var totalSize = fileSize 215 | 216 | selectedFiles.forEach { 217 | totalSize += (it.file.length() / 1024).toInt() 218 | } 219 | 220 | return when { 221 | config.eachFileSize != null && fileSize > config.eachFileSize!! -> { 222 | Toast.makeText( 223 | context, "${config.maxEachFileSizeText} ${config.eachFileSize}kb", 224 | Toast.LENGTH_LONG 225 | ).show() 226 | false 227 | } 228 | 229 | config.totalFileSize != null && totalSize > config.totalFileSize!! -> { 230 | Toast.makeText( 231 | context, "${config.maxTotalFileSizeText} ${config.totalFileSize}kb", 232 | Toast.LENGTH_LONG 233 | ).show() 234 | false 235 | } 236 | 237 | else -> true 238 | } 239 | } 240 | 241 | private var cameraLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 242 | if (result.resultCode == Activity.RESULT_OK) { 243 | initRecyclerView() 244 | result.data?.extras?.get("data")?.let { 245 | 246 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU && it is Bitmap) { 247 | saveBitmapToGallery(it) 248 | } 249 | 250 | getImage() 251 | if (File(cameraImagePath).exists()) { 252 | val fileModel = FileModel(cameraImagePath) 253 | if (checkMaxSize(fileModel) && selectedFiles.size < config.maxSelection) { 254 | fileModel.selected = true 255 | selectedFiles.add(fileModel) 256 | initRecyclerView() 257 | } 258 | } else { 259 | Toast.makeText(requireContext(), getString(R.string.mahdiasd_file_picker_cant_find_this_file), Toast.LENGTH_SHORT).show() 260 | } 261 | } 262 | } 263 | } 264 | 265 | 266 | @OptIn(FlowPreview::class) 267 | private var storageLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 268 | storageIsOpen = false 269 | if (result.resultCode == Activity.RESULT_OK) { 270 | storageIsOpen = false 271 | result.data?.clipData?.also { 272 | val a: MutableList = ArrayList() 273 | for (i in 0 until it.itemCount) { 274 | a.add(it.getItemAt(i).uri) 275 | } 276 | handlePathOz?.getListRealPath(a) 277 | } 278 | 279 | result.data?.data?.also { 280 | handlePathOz?.getListRealPath(arrayListOf(it)) 281 | } 282 | } 283 | } 284 | 285 | private fun getSectionList(): MutableList { 286 | val temp: MutableList = ArrayList() 287 | config.mode.forEach { 288 | when (it) { 289 | PickerMode.File -> { 290 | temp.add( 291 | SectionModel( 292 | config.fileManagerText, 293 | config.fileManagerIcon, 294 | PickerMode.File 295 | ) 296 | ) 297 | } 298 | 299 | PickerMode.Audio -> { 300 | temp.add( 301 | SectionModel( 302 | config.audioText, 303 | config.audioIcon, 304 | PickerMode.Audio 305 | ) 306 | ) 307 | } 308 | 309 | PickerMode.Video -> { 310 | temp.add( 311 | SectionModel( 312 | config.videoText, 313 | config.videoIcon, 314 | PickerMode.Video 315 | ) 316 | ) 317 | } 318 | 319 | PickerMode.Image -> { 320 | temp.add( 321 | SectionModel( 322 | config.imageText, 323 | config.imageIcon, 324 | PickerMode.Image 325 | ) 326 | ) 327 | } 328 | 329 | else -> {} 330 | } 331 | } 332 | temp.find { it.mode == config.selectedMode }?.selected = true 333 | return temp 334 | } 335 | 336 | private val requestMultiplePermissions = 337 | registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> 338 | Log.e("TAG", "" + permissions.entries) 339 | if (permissions.entries.all { it.value }) { 340 | getFiles() 341 | } else { 342 | Toast.makeText(requireContext(), R.string.mahdiasd_file_picker_permission_denied_toast, Toast.LENGTH_SHORT).show() 343 | dismiss() 344 | } 345 | } 346 | 347 | 348 | private fun getFiles() { 349 | val temp = config.mode 350 | 351 | if (temp.contains(PickerMode.Video)) 352 | getVideo() 353 | 354 | if (temp.contains(PickerMode.Image)) 355 | getImage() 356 | 357 | if (temp.contains(PickerMode.Audio)) 358 | getAudio() 359 | 360 | initRecyclerView() 361 | } 362 | 363 | private fun getImage() { 364 | imageList.clear() 365 | val columns = arrayOf(MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID) 366 | 367 | val orderBy = MediaStore.Images.Media.DATE_ADDED + " DESC" 368 | 369 | val cursor: Cursor = requireContext().contentResolver.query( 370 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 371 | columns, 372 | null, 373 | null, 374 | orderBy 375 | ) ?: return 376 | 377 | while (cursor.moveToNext()) { 378 | val dataColumnIndex: Int = cursor.getColumnIndex(MediaStore.Images.Media.DATA) 379 | imageList.add(FileModel(cursor.getString(dataColumnIndex))) 380 | } 381 | 382 | if (config.mode.contains(PickerMode.Camera)) { 383 | imageList.takeIf { it.find { it.path == "Camera" } == null }?.let { imageList.add(0, FileModel("Camera")) } 384 | } 385 | 386 | cursor.close() 387 | } 388 | 389 | private fun getVideo() { 390 | val columns = arrayOf( 391 | MediaStore.Video.VideoColumns.DATA, 392 | MediaStore.Video.VideoColumns._ID 393 | ) 394 | 395 | val orderBy = MediaStore.Video.VideoColumns.DATE_ADDED + " DESC" 396 | 397 | val cursor: Cursor = requireContext().contentResolver.query( 398 | MediaStore.Video.Media.EXTERNAL_CONTENT_URI, columns, null, 399 | null, orderBy 400 | ) ?: return 401 | 402 | while (cursor.moveToNext()) { 403 | val dataColumnIndex: Int = 404 | cursor.getColumnIndex(MediaStore.Video.VideoColumns.DATA) 405 | videoList.add(FileModel(cursor.getString(dataColumnIndex))) 406 | } 407 | cursor.close() 408 | } 409 | 410 | private fun getAudio() { 411 | val columns = arrayOf( 412 | MediaStore.Audio.AudioColumns._ID, 413 | MediaStore.Audio.AudioColumns.DATA, 414 | ) 415 | val orderBy = MediaStore.Audio.AudioColumns.DATE_ADDED + " DESC" 416 | 417 | val cursor = requireContext().contentResolver.query( 418 | MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 419 | columns, 420 | null, 421 | null, 422 | orderBy 423 | ) ?: return 424 | 425 | while (cursor.moveToNext()) { 426 | val dataColumnIndex: Int = cursor.getColumnIndex(MediaStore.Audio.Media.DATA) 427 | audioList.add(FileModel(cursor.getString(dataColumnIndex))) 428 | } 429 | cursor.close() 430 | } 431 | 432 | private fun initRecyclerView() { 433 | binding.loading = false 434 | 435 | var temp = getSelectedList() 436 | if (binding.edt.text.toString().isNotEmpty() && config.selectedMode != PickerMode.File) 437 | temp = temp.filter { 438 | it.file.name.contains(binding.edt.text.toString(), false) 439 | } as MutableList 440 | 441 | if (selectedFiles.isEmpty()) 442 | temp.onEach { it.selected = false } 443 | else 444 | selectedFiles.forEach { 445 | temp.find { t -> t.path == it.path }?.let { te -> te.selected = true } 446 | } 447 | 448 | myAdapter = FilePickerAdapter(context, temp, selectedFiles, config.selectedMode, config, this) 449 | 450 | binding.list.let { 451 | it.layoutManager = handleLayoutManager() 452 | it.adapter = myAdapter 453 | it.disableItemAnimator() 454 | } 455 | 456 | binding.listSize = temp.size 457 | 458 | handleBehaviorFragment() 459 | 460 | myAdapter!!.liveData.observe(this) { 461 | binding.countFrame.visibility = if (it == 0) View.GONE else View.VISIBLE 462 | binding.count.text = it.toString() 463 | } 464 | } 465 | 466 | private fun handleLayoutManager(): RecyclerView.LayoutManager { 467 | return when (config.selectedMode) { 468 | PickerMode.Audio, PickerMode.File -> 469 | LinearLayoutManager(context, RecyclerView.VERTICAL, false) 470 | 471 | else -> { 472 | val gridCount = 473 | if (isTablet() && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 5 474 | else if (isTablet() && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) 4 475 | else if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) 4 476 | else 3 477 | GridLayoutManager(context, gridCount) 478 | } 479 | } 480 | } 481 | 482 | private fun getSelectedList(): MutableList { 483 | return when (config.selectedMode) { 484 | PickerMode.Audio -> { 485 | audioList 486 | } 487 | 488 | PickerMode.Video -> { 489 | videoList 490 | } 491 | 492 | PickerMode.Image -> { 493 | imageList 494 | } 495 | 496 | else -> { 497 | selectedFiles 498 | } 499 | } 500 | } 501 | 502 | private fun isTablet(): Boolean { 503 | return requireContext().resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE 504 | } 505 | 506 | private fun handleBehaviorFragment() { 507 | try { 508 | val behavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior 509 | behavior.peekHeight = 1500 510 | 511 | behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { 512 | override fun onStateChanged(bottomSheet: View, newState: Int) { 513 | bottomSheet.setBackgroundResource(android.R.color.transparent) 514 | } 515 | 516 | override fun onSlide(bottomSheet: View, slideOffset: Float) { 517 | bottomSheet.setBackgroundResource(android.R.color.transparent) 518 | binding.footer.y = 519 | ((bottomSheet.parent as View).height - bottomSheet.top - binding.footer.height).toFloat() 520 | binding.done.y = 521 | (((bottomSheet.parent as View).height - bottomSheet.top - binding.done.height).toFloat() - binding.footer.height / 1.2).toFloat() 522 | binding.countFrame.y = 523 | (((bottomSheet.parent as View).height - bottomSheet.top - binding.done.height).toFloat() - binding.footer.height / 1.9).toFloat() 524 | 525 | binding.countFrame.visibility = if (selectedFiles.size == 0) View.GONE else View.VISIBLE 526 | binding.count.text = selectedFiles.size.toString() 527 | } 528 | }.apply { 529 | binding.root.post { onSlide(binding.root.parent as View, 0f) } 530 | }) 531 | } catch (e: Exception) { 532 | } 533 | 534 | } 535 | 536 | fun openStorage(view: View?) { 537 | openFileManager() 538 | } 539 | 540 | fun search(s: CharSequence, start: Int, before: Int, count: Int) { 541 | initRecyclerView() 542 | } 543 | 544 | fun btn(view: View?) { 545 | val uris: MutableList = ArrayList() 546 | selectedFiles.forEach { 547 | getUriFromFile(it.file)?.let { it1 -> uris.add(it1) } 548 | } 549 | config.listener?.selectedFiles(selectedFiles, uris) 550 | selectedFiles.clear() 551 | dismiss() 552 | } 553 | 554 | private fun RecyclerView.disableItemAnimator() { 555 | //disable blink when notifyDataSetChanged 556 | (itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false 557 | } 558 | 559 | } -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/FilePickerListener.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import android.net.Uri 4 | 5 | 6 | interface FilePickerListener { 7 | fun selectedFiles(files: List?, uris: List?) 8 | } -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/PickerMode.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | enum class PickerMode { 4 | Video, 5 | Image, 6 | Audio, 7 | File, 8 | Camera, 9 | } -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/SectionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.MutableLiveData 5 | import com.mahdiasd.filepicker.databinding.ItemSectionBinding 6 | 7 | class SectionAdapter( 8 | context: Context, 9 | list: List, 10 | private var selectedMode: PickerMode, 11 | private val activeColor: Int, 12 | private val deActiveColor: Int, 13 | ) : BaseRecyclerAdapter(context, list) { 14 | 15 | override fun getRootLayoutId(): Int { 16 | return R.layout.item_section 17 | } 18 | 19 | var changeMode: MutableLiveData = MutableLiveData() 20 | 21 | override fun onBind(viewHolder: BaseViewHolder, position: Int) { 22 | val model = viewHolder.getData(position) as SectionModel 23 | val itemBinding = viewHolder.binding as ItemSectionBinding 24 | itemBinding.item = model 25 | itemBinding.activeColor = activeColor 26 | itemBinding.deActiveColor = deActiveColor 27 | 28 | viewHolder.itemView.setOnClickListener { 29 | changeMode.postValue(model.mode) 30 | refreshSelected(model) 31 | } 32 | } 33 | 34 | private fun refreshSelected(model: SectionModel) { 35 | list.forEach { it.selected = false } 36 | model.selected = true 37 | } 38 | } -------------------------------------------------------------------------------- /FilePicker/src/main/java/com/mahdiasd/filepicker/SectionModel.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import android.graphics.drawable.Drawable 4 | import androidx.databinding.BaseObservable 5 | import androidx.databinding.Bindable 6 | 7 | data class SectionModel( 8 | var text: String, 9 | var icon: Drawable?, 10 | var mode: PickerMode, 11 | ) : BaseObservable() { 12 | @Bindable 13 | var selected = false 14 | set(value) { 15 | notifyPropertyChanged(BR.selected) 16 | field = value 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_dash_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_audio.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_camera.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_checkmark.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_document.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_done.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_file.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_image.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_play.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_search.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_send.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_ic_video.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_shape_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_shape_radius_20.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_shape_radius_35.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/drawable/mahdiasd_shape_square.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/layout/file_picker_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 46 | 47 | 57 | 58 | 59 | 73 | 74 | 83 | 84 | 104 | 105 | 106 | 118 | 119 | 120 | 130 | 131 | 136 | 137 | 138 | 139 | 155 | 156 | 169 | 170 | 179 | 180 | 181 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/layout/item_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 34 | 35 | 38 | 39 | 40 | 44 | 45 | 58 | 59 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/layout/item_file_picker_manager.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 50 | 60 | 61 | 72 | 73 | 82 | 83 | 102 | 103 | 110 | 111 | 120 | 121 | 122 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/layout/item_section.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 26 | 27 | 35 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000 4 | #FFF 5 | 6 | #2196F3 7 | #E91E63 8 | #A4A4A4 9 | 10 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 3dp 4 | 8dp 5 | 45dp 6 | 16dp 7 | 18dp 8 | 12dp 9 | 4dp 10 | 350dp 11 | 2dp 12 | 48dp 13 | 10dp 14 | 24dp 15 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Error when open file, please choose another! 3 | Can`t find this file 4 | Video 5 | Audio 6 | File Manager 7 | Image 8 | Camera 9 | Open Storage 10 | Max size for total files is 11 | Max size for each files is 12 | Search… 13 | Permission needed! 14 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /FilePicker/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /FilePicker/src/test/java/com/mahdiasd/filepicker/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.filepicker 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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 2022 Mahdi Asadollahpour BottomDialogFilePicker 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 | # 📢 Deprecated Library Notice 2 | 3 | ⚠️ **This library is deprecated. Please refer to [ComposeFilePicker](https://github.com/mahdiasd/ComposeFilePicker) for the latest updates.** 4 | 5 | --- 6 | 7 | # 📁 Bottom Dialog Android Picker 8 | 9 | [![JitPack](https://jitpack.io/v/mahdiasd/BottomDialogFilePicker.svg)](https://jitpack.io/#mahdiasd/BottomDialogFilePicker) 10 | 11 | A bottom dialog picker inspired by Telegram, compatible with all Android versions (1, 10, 11, 12, 13). 12 | 13 | ## 🚀 Features 14 | 15 | - 📸 **Take Pictures**: Capture photos with the camera and save them to storage. 16 | - 🔍 **File Search**: Easily search through your files. 17 | - 📱 **Wide Compatibility**: Supports Android 10+. 18 | - 📜 **Expandable & Scrollable**: Smooth and intuitive dialog navigation. 19 | - 🎨 **Fully Customizable**: Customize colors, text, minimum and maximum file sizes, and more. 20 | - 🔒 **No Runtime Permissions Needed**: Simplified user experience without requiring additional permissions. 21 | 22 | ## 📸 Screenshots 23 | 24 | ![Demo 1](https://raw.githubusercontent.com/mahdiasd/BottomDialogFilePicker/master/screenshot/1.png) 25 | ![Demo 2](https://raw.githubusercontent.com/mahdiasd/BottomDialogFilePicker/master/screenshot/2.png) 26 | ![Demo 3](https://raw.githubusercontent.com/mahdiasd/BottomDialogFilePicker/master/screenshot/3.png) 27 | ![Demo 4](https://raw.githubusercontent.com/mahdiasd/BottomDialogFilePicker/master/screenshot/4.png) 28 | ![Demo 5](https://raw.githubusercontent.com/mahdiasd/BottomDialogFilePicker/master/screenshot/5.png) 29 | 30 | ## 🛠 Installation 31 | 32 | ### Step 1. Add the JitPack Repository 33 | 34 | Add the JitPack repository to your root `build.gradle` file: 35 | 36 | ```groovy 37 | allprojects { 38 | repositories { 39 | ... 40 | maven { url 'https://jitpack.io' } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | namespace = "com.mahdiasd.sample" 9 | 10 | defaultConfig { 11 | applicationId "com.mahdiasd.sample" 12 | minSdk 21 13 | targetSdk 32 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_11 28 | targetCompatibility JavaVersion.VERSION_11 29 | } 30 | kotlinOptions { 31 | jvmTarget = '11' 32 | } 33 | 34 | buildFeatures { 35 | //noinspection DataBindingWithoutKapt 36 | dataBinding true 37 | } 38 | } 39 | 40 | dependencies { 41 | 42 | implementation 'androidx.core:core-ktx:1.8.0' 43 | implementation 'androidx.appcompat:appcompat:1.5.1' 44 | implementation 'com.google.android.material:material:1.6.1' 45 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 46 | 47 | implementation project(":FilePicker") 48 | 49 | testImplementation 'junit:junit:4.13.2' 50 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 52 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mahdiasd/sample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.sample 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.mahdiasd.sample", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/mahdiasd/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mahdiasd.sample 2 | 3 | import android.graphics.Color 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.view.View 7 | import android.widget.Toast 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.databinding.DataBindingUtil 10 | import com.mahdiasd.filepicker.FileModel 11 | import com.mahdiasd.filepicker.FilePicker 12 | import com.mahdiasd.filepicker.FilePickerListener 13 | import com.mahdiasd.filepicker.PickerMode 14 | import com.mahdiasd.sample.databinding.ActivityMainBinding 15 | 16 | class MainActivity : AppCompatActivity() { 17 | private lateinit var binding: ActivityMainBinding 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | initBinding(R.layout.activity_main) 22 | test(null) 23 | } 24 | 25 | private fun initBinding(layout: Int) { 26 | binding = DataBindingUtil.setContentView(this, layout) 27 | binding.let { 28 | it.lifecycleOwner = this 29 | } 30 | } 31 | 32 | fun btn(view: View) { 33 | val modes = 34 | mutableListOf(PickerMode.File, PickerMode.Audio, PickerMode.Image, PickerMode.Video, PickerMode.Camera) 35 | 36 | if (!binding.Image.isChecked) 37 | modes.remove(PickerMode.Image) 38 | if (!binding.video.isChecked) 39 | modes.remove(PickerMode.Video) 40 | if (!binding.storage.isChecked) 41 | modes.remove(PickerMode.File) 42 | if (!binding.audio.isChecked) 43 | modes.remove(PickerMode.Audio) 44 | 45 | if (modes.isEmpty()) { 46 | Toast.makeText(this, "choose mode", Toast.LENGTH_SHORT).show() 47 | return 48 | } 49 | 50 | val max = 51 | if (binding.maxSelection.text.isEmpty()) 1 else binding.maxSelection.text.toString() 52 | .toInt() 53 | 54 | val activeColor = 55 | if (binding.color.text.isEmpty()) "#2196F3" else binding.color.text.toString() 56 | 57 | 58 | val deActiveColor = 59 | if (binding.deActiveColor.text.isEmpty()) "#A4A4A4" else binding.deActiveColor.text.toString() 60 | 61 | 62 | val cardBackgroundColor = 63 | if (binding.cardViewColor.text.isEmpty()) "#ffffff" else binding.cardViewColor.text.toString() 64 | 65 | 66 | FilePicker(this, supportFragmentManager) 67 | .setMode(*modes.toTypedArray()) 68 | .setDefaultMode(PickerMode.Image) 69 | .setMaxSelection(max) 70 | .setCardBackgroundColor(Color.parseColor(cardBackgroundColor)) 71 | .setCustomText( 72 | videoText = binding.videoTitle.text.toString(), 73 | audioText = binding.audioTitle.text.toString(), 74 | fileManagerText = binding.storage.text.toString(), 75 | imageText = binding.imageTitle.text.toString(), 76 | openStorageText = binding.openStorageTitle.text.toString() 77 | ) 78 | .setShowFileWhenClick(binding.showWhenClick.isChecked) 79 | .setDeActiveColor(Color.parseColor(deActiveColor)) 80 | .setActiveColor(Color.parseColor(activeColor)) 81 | .setListener(object : FilePickerListener { 82 | override fun selectedFiles(files: List?, uris: List?) { 83 | val a = uris 84 | } 85 | }) 86 | .show() 87 | 88 | } 89 | 90 | 91 | fun test(view: View?) { 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 20 | 29 | 30 | 31 | 39 | 40 | 46 | 47 | 54 | 55 | 62 | 63 | 70 | 71 | 78 | 79 | 80 | 85 | 86 | 94 | 95 | 100 | 101 | 106 | 107 | 108 | 116 | 117 | 127 | 128 | 133 | 134 | 142 | 143 | 152 | 153 | 158 | 159 | 167 | 168 | 177 | 178 | 183 | 184 | 192 | 193 | 202 | 203 | 208 | 209 | 217 | 218 | 224 | 225 | 233 | 234 | 242 | 243 | 251 | 252 | 260 | 261 | 262 | 270 | 271 | 272 |