├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── vi
│ │ └── filepicker
│ │ ├── ApplicationTest.java
│ │ ├── MainActivityTest.java
│ │ └── RecyclerViewItemCountAssertion.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── vi
│ │ │ └── filepicker
│ │ │ ├── AppDelegate.java
│ │ │ ├── CallerFragment.java
│ │ │ ├── FragmentActivity.java
│ │ │ ├── ImageAdapter.java
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-xxhdpi
│ │ ├── custom_camera.png
│ │ └── pdf_blue.png
│ │ ├── layout
│ │ ├── activity_fragment.xml
│ │ ├── activity_main.xml
│ │ ├── fragment_caller.xml
│ │ └── item_layout.xml
│ │ ├── menu
│ │ └── main_menu.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-it
│ │ └── strings.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── vi
│ └── filepicker
│ └── ExampleUnitTest.java
├── build.gradle
├── filepicker
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── droidninja
│ │ └── filepicker
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── droidninja
│ │ │ └── filepicker
│ │ │ ├── BaseFilePickerActivity.kt
│ │ │ ├── FilePickerActivity.kt
│ │ │ ├── FilePickerBuilder.kt
│ │ │ ├── FilePickerConst.kt
│ │ │ ├── MediaDetailsActivity.kt
│ │ │ ├── PickerManager.kt
│ │ │ ├── adapters
│ │ │ ├── FileAdapterListener.kt
│ │ │ ├── FileListAdapter.kt
│ │ │ ├── FolderGridAdapter.kt
│ │ │ ├── PhotoGridAdapter.kt
│ │ │ ├── SectionsPagerAdapter.kt
│ │ │ ├── Selectable.kt
│ │ │ └── SelectableAdapter.kt
│ │ │ ├── fragments
│ │ │ ├── BaseFragment.kt
│ │ │ ├── DocFragment.kt
│ │ │ ├── DocPickerFragment.kt
│ │ │ ├── MediaDetailPickerFragment.kt
│ │ │ ├── MediaFolderPickerFragment.kt
│ │ │ ├── MediaPickerFragment.kt
│ │ │ └── PhotoPickerFragmentListener.kt
│ │ │ ├── models
│ │ │ ├── BaseFile.kt
│ │ │ ├── Document.kt
│ │ │ ├── FileType.kt
│ │ │ ├── Media.kt
│ │ │ ├── PhotoDirectory.kt
│ │ │ └── sort
│ │ │ │ ├── NameComparator.kt
│ │ │ │ └── SortingTypes.kt
│ │ │ ├── utils
│ │ │ ├── AndroidLifecycleUtils.kt
│ │ │ ├── ContentUriUtils.kt
│ │ │ ├── FilePickerProvider.kt
│ │ │ ├── FilePickerUtils.kt
│ │ │ ├── FileUtils.kt
│ │ │ ├── FragmentUtil.kt
│ │ │ ├── GridSpacingItemDecoration.kt
│ │ │ ├── ImageCaptureManager.kt
│ │ │ └── TabLayoutHelper.java
│ │ │ ├── viewmodels
│ │ │ ├── BaseViewModel.kt
│ │ │ ├── VMDocPicker.kt
│ │ │ └── VMMediaPicker.kt
│ │ │ └── views
│ │ │ ├── SmoothCheckBox.kt
│ │ │ └── SquareRelativeLayout.kt
│ └── res
│ │ ├── anim
│ │ ├── slide_left_in.xml
│ │ └── slide_left_out.xml
│ │ ├── color
│ │ ├── selector_tab_text_color.xml
│ │ └── selector_tab_text_color_dark.xml
│ │ ├── drawable-xxhdpi
│ │ ├── gallery_album_overlay.9.png
│ │ ├── ic_camera.png
│ │ ├── ic_deselect_all.png
│ │ ├── ic_play_icon.png
│ │ ├── ic_select_all.png
│ │ ├── icon_file_doc.png
│ │ ├── icon_file_pdf.png
│ │ ├── icon_file_ppt.png
│ │ ├── icon_file_unknown.png
│ │ ├── icon_file_xls.png
│ │ └── image_placeholder.png
│ │ ├── drawable
│ │ └── ic_search.xml
│ │ ├── layout
│ │ ├── activity_file_picker.xml
│ │ ├── activity_media_details.xml
│ │ ├── fragment_doc_picker.xml
│ │ ├── fragment_media_folder_picker.xml
│ │ ├── fragment_media_picker.xml
│ │ ├── fragment_photo_picker.xml
│ │ ├── item_doc_layout.xml
│ │ ├── item_folder_layout.xml
│ │ └── item_photo_layout.xml
│ │ ├── menu
│ │ ├── doc_picker_menu.xml
│ │ ├── media_detail_menu.xml
│ │ ├── picker_menu.xml
│ │ └── select_menu.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-fa
│ │ └── strings.xml
│ │ ├── values-hi
│ │ └── strings.xml
│ │ ├── values-pt-rBR
│ │ └── strings.xml
│ │ ├── values-zh
│ │ └── strings.xml
│ │ ├── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── provider_paths.xml
│ └── test
│ └── java
│ └── droidninja
│ └── filepicker
│ └── ExampleUnitTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | /.idea
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion '30.0.2'
8 |
9 | defaultConfig {
10 | applicationId "vi.filepicker"
11 | minSdkVersion 17
12 | targetSdkVersion 30
13 | versionCode 1
14 | versionName "1.0"
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | implementation fileTree(include: ['*.jar'], dir: 'libs')
27 | testImplementation 'junit:junit:4.12'
28 | implementation project(':filepicker')
29 | implementation 'androidx.appcompat:appcompat:1.2.0'
30 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
31 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
32 | implementation "com.github.xinyuez:easypermissions:2.0.1"
33 | implementation 'com.google.android.material:material:1.2.1'
34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
35 | // Required for instrumented tests
36 | androidTestImplementation 'androidx.annotation:annotation:1.1.0'
37 | androidTestImplementation 'androidx.test:runner:1.3.0'
38 | androidTestImplementation 'androidx.test:rules:1.3.0'
39 | // Optional -- UI testing with Espresso
40 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-beta02', {
41 | exclude group: 'com.android.support', module: 'support-annotations'
42 | })
43 | androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
44 | androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
45 |
46 | // compile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
47 | }
48 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/droidNinja/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/vi/filepicker/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/vi/filepicker/MainActivityTest.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.app.Instrumentation;
4 | import android.os.Build;
5 | import androidx.test.espresso.contrib.RecyclerViewActions;
6 | import androidx.test.rule.ActivityTestRule;
7 | import androidx.test.runner.AndroidJUnit4;
8 | import droidninja.filepicker.FilePickerActivity;
9 | import org.junit.After;
10 | import org.junit.Before;
11 | import org.junit.Rule;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 |
15 | import static androidx.test.InstrumentationRegistry.getInstrumentation;
16 | import static androidx.test.InstrumentationRegistry.getTargetContext;
17 | import static androidx.test.espresso.Espresso.onView;
18 | import static androidx.test.espresso.action.ViewActions.click;
19 | import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
20 | import static androidx.test.espresso.matcher.ViewMatchers.withId;
21 | import static org.hamcrest.Matchers.allOf;
22 | import static org.junit.Assert.assertNotNull;
23 | import static vi.filepicker.RecyclerViewItemCountAssertion.withItemCount;
24 |
25 | /**
26 | * Created by droidNinja on 23/02/18.
27 | */
28 | @RunWith(AndroidJUnit4.class)
29 | public class MainActivityTest {
30 |
31 | @Rule public ActivityTestRule activityTestRule = new ActivityTestRule(MainActivity.class);
32 |
33 | private MainActivity mainActivity = null;
34 |
35 | public Instrumentation.ActivityMonitor monitor = getInstrumentation().addMonitor(FilePickerActivity.class.getName(), null,false);
36 |
37 | @Before
38 | public void setUp() throws Exception {
39 | mainActivity = activityTestRule.getActivity();
40 |
41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
42 | getInstrumentation().getUiAutomation().executeShellCommand(
43 | "pm grant " + getTargetContext().getPackageName()
44 | + " android.permission.WRITE_EXTERNAL_STORAGE");
45 | }
46 | }
47 |
48 | @Test
49 | public void testFilePicker(){
50 | assertNotNull(mainActivity.findViewById(R.id.pick_photo));
51 |
52 | onView(withId(R.id.pick_photo)).perform(click());
53 |
54 | FilePickerActivity filePickerActivity =
55 | (FilePickerActivity) getInstrumentation().waitForMonitorWithTimeout(monitor, 5000);
56 |
57 | assertNotNull(filePickerActivity);
58 |
59 | onView(allOf(isDisplayed(), withId(R.id.recyclerview))).perform(
60 | RecyclerViewActions.actionOnItemAtPosition(1, click()));
61 |
62 | onView(allOf(isDisplayed(), withId(R.id.recyclerview))).perform(
63 | RecyclerViewActions.actionOnItemAtPosition(2, click()));
64 |
65 | onView(withId(R.id.action_done)).perform(click());
66 |
67 | onView(allOf(isDisplayed(), withId(R.id.recyclerview))).check(withItemCount(2));
68 |
69 | }
70 |
71 | @After
72 | public void tearDown(){
73 | mainActivity = null;
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/vi/filepicker/RecyclerViewItemCountAssertion.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import androidx.test.espresso.NoMatchingViewException;
4 | import androidx.test.espresso.ViewAssertion;
5 | import androidx.recyclerview.widget.RecyclerView;
6 | import android.view.View;
7 | import org.hamcrest.Matcher;
8 |
9 | import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
10 | import static org.hamcrest.Matchers.is;
11 |
12 | public class RecyclerViewItemCountAssertion implements ViewAssertion {
13 | private final Matcher matcher;
14 |
15 | public static RecyclerViewItemCountAssertion withItemCount(int expectedCount) {
16 | return withItemCount(is(expectedCount));
17 | }
18 |
19 | public static RecyclerViewItemCountAssertion withItemCount(Matcher matcher) {
20 | return new RecyclerViewItemCountAssertion(matcher);
21 | }
22 |
23 | private RecyclerViewItemCountAssertion(Matcher matcher) {
24 | this.matcher = matcher;
25 | }
26 |
27 | @Override
28 | public void check(View view, NoMatchingViewException noViewFoundException) {
29 | if (noViewFoundException != null) {
30 | throw noViewFoundException;
31 | }
32 |
33 | RecyclerView recyclerView = (RecyclerView) view;
34 | RecyclerView.Adapter adapter = recyclerView.getAdapter();
35 | assertThat(adapter.getItemCount(), matcher);
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/vi/filepicker/AppDelegate.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Created by droidNinja on 03/06/17.
7 | */
8 |
9 | public class AppDelegate extends Application {
10 |
11 | @Override
12 | public void onCreate() {
13 | super.onCreate();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/vi/filepicker/CallerFragment.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import androidx.annotation.NonNull;
8 | import androidx.fragment.app.Fragment;
9 | import androidx.recyclerview.widget.DefaultItemAnimator;
10 | import androidx.recyclerview.widget.OrientationHelper;
11 | import androidx.recyclerview.widget.RecyclerView;
12 | import androidx.recyclerview.widget.StaggeredGridLayoutManager;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.Button;
17 | import android.widget.Toast;
18 |
19 | import droidninja.filepicker.FilePickerBuilder;
20 | import droidninja.filepicker.FilePickerConst;
21 | import droidninja.filepicker.fragments.BaseFragment;
22 |
23 | import java.net.URISyntaxException;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | import droidninja.filepicker.utils.ContentUriUtils;
28 | import pub.devrel.easypermissions.AfterPermissionGranted;
29 | import pub.devrel.easypermissions.AppSettingsDialog;
30 | import pub.devrel.easypermissions.EasyPermissions;
31 |
32 | import static vi.filepicker.MainActivity.RC_FILE_PICKER_PERM;
33 | import static vi.filepicker.MainActivity.RC_PHOTO_PICKER_PERM;
34 |
35 | /**
36 | * A simple {@link Fragment} subclass.
37 | */
38 | public class CallerFragment extends BaseFragment implements EasyPermissions.PermissionCallbacks {
39 | private int MAX_ATTACHMENT_COUNT = 10;
40 | private ArrayList photoPaths = new ArrayList<>();
41 | private ArrayList docPaths = new ArrayList<>();
42 |
43 | public CallerFragment() {
44 | // Required empty public constructor
45 | }
46 |
47 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
48 | Bundle savedInstanceState) {
49 | // Inflate the layout for this fragment
50 | View view = inflater.inflate(R.layout.activity_main, container, false);
51 | Button openFragmentBtn = view.findViewById(R.id.open_fragment);
52 | openFragmentBtn.setVisibility(View.GONE);
53 | view.findViewById(R.id.pick_photo).setOnClickListener(new View.OnClickListener() {
54 | @Override
55 | public void onClick(View view) {
56 | pickPhoto();
57 | }
58 | });
59 | view.findViewById(R.id.pick_doc).setOnClickListener(new View.OnClickListener() {
60 | @Override
61 | public void onClick(View view) {
62 | pickDoc();
63 | }
64 | });
65 | return view;
66 | }
67 |
68 | @AfterPermissionGranted(RC_PHOTO_PICKER_PERM)
69 | public void pickPhoto() {
70 | if (EasyPermissions.hasPermissions(getContext(), FilePickerConst.PERMISSIONS_FILE_PICKER)) {
71 | onPickPhoto();
72 | } else {
73 | // Ask for one permission
74 | EasyPermissions.requestPermissions(
75 | this,
76 | getString(R.string.rationale_photo_picker),
77 | RC_PHOTO_PICKER_PERM,
78 | FilePickerConst.PERMISSIONS_FILE_PICKER);
79 | }
80 | }
81 |
82 | @AfterPermissionGranted(RC_FILE_PICKER_PERM)
83 | public void pickDoc() {
84 | if (EasyPermissions.hasPermissions(getContext(), FilePickerConst.PERMISSIONS_FILE_PICKER)) {
85 | onPickDoc();
86 | } else {
87 | // Ask for one permission
88 | EasyPermissions.requestPermissions(
89 | this,
90 | getString(R.string.rationale_doc_picker),
91 | RC_FILE_PICKER_PERM,
92 | FilePickerConst.PERMISSIONS_FILE_PICKER);
93 | }
94 | }
95 |
96 | @Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
97 | switch (requestCode) {
98 | case FilePickerConst.REQUEST_CODE_PHOTO:
99 | if (resultCode == Activity.RESULT_OK && data != null) {
100 | ArrayList dataList = data.getParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_MEDIA);
101 | if(dataList != null) {
102 | photoPaths = new ArrayList();
103 | photoPaths.addAll(dataList);
104 | }
105 | }
106 | break;
107 |
108 | case FilePickerConst.REQUEST_CODE_DOC:
109 | if (resultCode == Activity.RESULT_OK && data != null) {
110 | ArrayList dataList = data.getParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_MEDIA);
111 | if(dataList != null) {
112 | docPaths = new ArrayList<>();
113 | docPaths.addAll(dataList);
114 | }
115 | }
116 | break;
117 | }
118 |
119 | addThemToView(photoPaths, docPaths);
120 | }
121 |
122 | private void addThemToView(ArrayList imagePaths, ArrayList docPaths) {
123 | ArrayList filePaths = new ArrayList<>();
124 | if (imagePaths != null) filePaths.addAll(imagePaths);
125 |
126 | if (docPaths != null) filePaths.addAll(docPaths);
127 |
128 | final RecyclerView recyclerView = (RecyclerView) getView().findViewById(R.id.recyclerview);
129 | if (recyclerView != null) {
130 | StaggeredGridLayoutManager layoutManager =
131 | new StaggeredGridLayoutManager(3, OrientationHelper.VERTICAL);
132 | layoutManager.setGapStrategy(
133 | StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
134 | recyclerView.setLayoutManager(layoutManager);
135 |
136 | ImageAdapter imageAdapter = new ImageAdapter(getActivity(), filePaths, new ImageAdapter.ImageAdapterListener() {
137 | @Override
138 | public void onItemClick(Uri uri) {
139 | try {
140 | //make sure to use this getFilePath method from worker thread
141 | String path = ContentUriUtils.INSTANCE.getFilePath(recyclerView.getContext(), uri);
142 | if (path != null) {
143 | Toast.makeText(recyclerView.getContext(), path, Toast.LENGTH_SHORT).show();
144 | }
145 | } catch (URISyntaxException e) {
146 | e.printStackTrace();
147 | }
148 | }
149 | });
150 |
151 | recyclerView.setAdapter(imageAdapter);
152 | recyclerView.setItemAnimator(new DefaultItemAnimator());
153 | }
154 |
155 | Toast.makeText(getActivity(), "Num of files selected: " + filePaths.size(), Toast.LENGTH_SHORT)
156 | .show();
157 | }
158 |
159 | public void onPickPhoto() {
160 | int maxCount = MAX_ATTACHMENT_COUNT - docPaths.size();
161 | if ((docPaths.size() + photoPaths.size()) == MAX_ATTACHMENT_COUNT) {
162 | Toast.makeText(getActivity(), "Cannot select more than " + MAX_ATTACHMENT_COUNT + " items",
163 | Toast.LENGTH_SHORT).show();
164 | } else {
165 | FilePickerBuilder.Companion.getInstance()
166 | .setMaxCount(maxCount)
167 | .setSelectedFiles(photoPaths)
168 | .setActivityTheme(R.style.FilePickerTheme)
169 | .pickPhoto(this);
170 | }
171 | }
172 |
173 | public void onPickDoc() {
174 | int maxCount = MAX_ATTACHMENT_COUNT - photoPaths.size();
175 | if ((docPaths.size() + photoPaths.size()) == MAX_ATTACHMENT_COUNT) {
176 | Toast.makeText(getActivity(), "Cannot select more than " + MAX_ATTACHMENT_COUNT + " items",
177 | Toast.LENGTH_SHORT).show();
178 | } else {
179 | FilePickerBuilder.Companion.getInstance()
180 | .setMaxCount(maxCount)
181 | .setSelectedFiles(docPaths)
182 | .enableDocSupport(true)
183 | .setActivityTheme(R.style.FilePickerTheme)
184 | .pickFile(this);
185 | }
186 | }
187 |
188 | @Override public void onPermissionsGranted(int requestCode, @NonNull List perms) {
189 | }
190 |
191 | @Override public void onPermissionsDenied(int requestCode, @NonNull List perms) {
192 |
193 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
194 | new AppSettingsDialog.Builder(this).build().show();
195 | }
196 | }
197 |
198 | @Override
199 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
200 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
201 |
202 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/app/src/main/java/vi/filepicker/FragmentActivity.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.os.Bundle;
4 | import androidx.appcompat.app.AppCompatActivity;
5 | import droidninja.filepicker.utils.FragmentUtil;
6 |
7 | public class FragmentActivity extends AppCompatActivity {
8 |
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_fragment);
13 |
14 | initView();
15 | }
16 |
17 | private void initView() {
18 | CallerFragment callerFragment = new CallerFragment();
19 | FragmentUtil.INSTANCE.addFragment(this, R.id.container,callerFragment);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/vi/filepicker/ImageAdapter.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.appcompat.widget.AppCompatImageView;
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | import android.net.Uri;
9 | import android.util.DisplayMetrics;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.view.WindowManager;
14 |
15 | import com.bumptech.glide.Glide;
16 | import com.bumptech.glide.request.RequestOptions;
17 |
18 | import java.io.File;
19 | import java.util.ArrayList;
20 |
21 | /**
22 | * Created by droidNinja on 29/07/16.
23 | */
24 | public class ImageAdapter extends RecyclerView.Adapter {
25 |
26 | private final ImageAdapterListener imageAdapterListener;
27 |
28 | public interface ImageAdapterListener {
29 | void onItemClick(Uri uri);
30 | }
31 |
32 | private final ArrayList paths;
33 | private final Context context;
34 | private int imageSize;
35 |
36 | public ImageAdapter(Context context, ArrayList paths, ImageAdapterListener imageAdapterListener) {
37 | this.context = context;
38 | this.paths = paths;
39 | this.imageAdapterListener = imageAdapterListener;
40 | setColumnNumber(context, 3);
41 | }
42 |
43 | private void setColumnNumber(Context context, int columnNum) {
44 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
45 | DisplayMetrics metrics = new DisplayMetrics();
46 | wm.getDefaultDisplay().getMetrics(metrics);
47 | int widthPixels = metrics.widthPixels;
48 | imageSize = widthPixels / columnNum;
49 | }
50 |
51 | @Override
52 | public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
53 | View itemView =
54 | LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
55 |
56 | return new FileViewHolder(itemView);
57 | }
58 |
59 | @Override
60 | public void onBindViewHolder(FileViewHolder holder, int position) {
61 | final Uri path = paths.get(position);
62 | Glide.with(context)
63 | .load(path)
64 | .apply(RequestOptions.centerCropTransform()
65 | .dontAnimate()
66 | .override(imageSize, imageSize)
67 | .placeholder(droidninja.filepicker.R.drawable.image_placeholder))
68 | .thumbnail(0.5f)
69 | .into(holder.imageView);
70 | holder.itemView.setOnClickListener(new View.OnClickListener() {
71 |
72 | @Override
73 | public void onClick(View v) {
74 | imageAdapterListener.onItemClick(path);
75 | }
76 | });
77 | }
78 |
79 | @Override
80 | public int getItemCount() {
81 | return paths.size();
82 | }
83 |
84 | public static class FileViewHolder extends RecyclerView.ViewHolder {
85 |
86 | AppCompatImageView imageView;
87 |
88 | public FileViewHolder(View itemView) {
89 | super(itemView);
90 | imageView = itemView.findViewById(R.id.iv_photo);
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/vi/filepicker/MainActivity.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.pm.ActivityInfo;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.widget.Toast;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.appcompat.app.AppCompatActivity;
13 | import androidx.recyclerview.widget.DefaultItemAnimator;
14 | import androidx.recyclerview.widget.OrientationHelper;
15 | import androidx.recyclerview.widget.RecyclerView;
16 | import androidx.recyclerview.widget.StaggeredGridLayoutManager;
17 |
18 | import java.net.URISyntaxException;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | import droidninja.filepicker.FilePickerBuilder;
23 | import droidninja.filepicker.FilePickerConst;
24 | import droidninja.filepicker.models.sort.SortingTypes;
25 | import droidninja.filepicker.utils.ContentUriUtils;
26 | import pub.devrel.easypermissions.AfterPermissionGranted;
27 | import pub.devrel.easypermissions.AppSettingsDialog;
28 | import pub.devrel.easypermissions.EasyPermissions;
29 |
30 | public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
31 |
32 | public static final int RC_PHOTO_PICKER_PERM = 123;
33 | public static final int RC_FILE_PICKER_PERM = 321;
34 | private static final int CUSTOM_REQUEST_CODE = 532;
35 | private int MAX_ATTACHMENT_COUNT = 10;
36 | private ArrayList photoPaths = new ArrayList<>();
37 | private ArrayList docPaths = new ArrayList<>();
38 |
39 | @Override
40 | protected void onCreate(Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | setContentView(R.layout.activity_main);
43 | findViewById(R.id.pick_photo).setOnClickListener(new View.OnClickListener() {
44 | @Override
45 | public void onClick(View view) {
46 | pickPhotoClicked();
47 | }
48 | });
49 | findViewById(R.id.pick_doc).setOnClickListener(new View.OnClickListener() {
50 | @Override
51 | public void onClick(View view) {
52 | pickDocClicked();
53 | }
54 | });
55 | }
56 |
57 | @AfterPermissionGranted(RC_PHOTO_PICKER_PERM)
58 | public void pickPhotoClicked() {
59 | if (EasyPermissions.hasPermissions(this, FilePickerConst.PERMISSIONS_FILE_PICKER)) {
60 | onPickPhoto();
61 | } else {
62 | // Ask for one permission
63 | EasyPermissions.requestPermissions(this, getString(R.string.rationale_photo_picker),
64 | RC_PHOTO_PICKER_PERM, FilePickerConst.PERMISSIONS_FILE_PICKER);
65 | }
66 | }
67 |
68 | @AfterPermissionGranted(RC_FILE_PICKER_PERM)
69 | public void pickDocClicked() {
70 | if (EasyPermissions.hasPermissions(this, FilePickerConst.PERMISSIONS_FILE_PICKER)) {
71 | onPickDoc();
72 | } else {
73 | // Ask for one permission
74 | EasyPermissions.requestPermissions(this, getString(R.string.rationale_doc_picker),
75 | RC_FILE_PICKER_PERM, FilePickerConst.PERMISSIONS_FILE_PICKER);
76 | }
77 | }
78 |
79 | @Override
80 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
81 | super.onActivityResult(requestCode, resultCode, data);
82 | switch (requestCode) {
83 | case CUSTOM_REQUEST_CODE:
84 | if (resultCode == Activity.RESULT_OK && data != null) {
85 | ArrayList dataList = data.getParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_MEDIA);
86 | if (dataList != null) {
87 | photoPaths = new ArrayList();
88 | photoPaths.addAll(dataList);
89 | }
90 | }
91 | break;
92 |
93 | case FilePickerConst.REQUEST_CODE_DOC:
94 | if (resultCode == Activity.RESULT_OK && data != null) {
95 | ArrayList dataList = data.getParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_DOCS);
96 | if (dataList != null) {
97 | docPaths = new ArrayList<>();
98 | docPaths.addAll(dataList);
99 | }
100 | }
101 | break;
102 | }
103 |
104 | addThemToView(photoPaths, docPaths);
105 | }
106 |
107 | private void addThemToView(ArrayList imagePaths, ArrayList docPaths) {
108 | ArrayList filePaths = new ArrayList<>();
109 | if (imagePaths != null) filePaths.addAll(imagePaths);
110 |
111 | if (docPaths != null) filePaths.addAll(docPaths);
112 |
113 | final RecyclerView recyclerView = findViewById(R.id.recyclerview);
114 | if (recyclerView != null) {
115 | StaggeredGridLayoutManager layoutManager =
116 | new StaggeredGridLayoutManager(3, OrientationHelper.VERTICAL);
117 | layoutManager.setGapStrategy(
118 | StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
119 | recyclerView.setLayoutManager(layoutManager);
120 |
121 | ImageAdapter imageAdapter = new ImageAdapter(this, filePaths, new ImageAdapter.ImageAdapterListener() {
122 | @Override
123 | public void onItemClick(Uri uri) {
124 | try {
125 | //make sure to use this getFilePath method from worker thread
126 | String path = ContentUriUtils.INSTANCE.getFilePath(recyclerView.getContext(), uri);
127 | if (path != null) {
128 | Toast.makeText(recyclerView.getContext(), path, Toast.LENGTH_SHORT).show();
129 | }
130 | } catch (URISyntaxException e) {
131 | e.printStackTrace();
132 | }
133 | }
134 | });
135 |
136 | recyclerView.setAdapter(imageAdapter);
137 | recyclerView.setItemAnimator(new DefaultItemAnimator());
138 | }
139 |
140 | Toast.makeText(this, "Num of files selected: " + filePaths.size(), Toast.LENGTH_SHORT).show();
141 | }
142 |
143 | public void onPickPhoto() {
144 | int maxCount = MAX_ATTACHMENT_COUNT - docPaths.size();
145 | if ((docPaths.size() + photoPaths.size()) == MAX_ATTACHMENT_COUNT) {
146 | Toast.makeText(this, "Cannot select more than " + MAX_ATTACHMENT_COUNT + " items",
147 | Toast.LENGTH_SHORT).show();
148 | } else {
149 | FilePickerBuilder.getInstance()
150 | .setMaxCount(10)
151 | .setSelectedFiles(photoPaths) //this is optional
152 | .setActivityTheme(R.style.FilePickerTheme)
153 | .setActivityTitle("Please select media")
154 | .setImageSizeLimit(5)
155 | .setVideoSizeLimit(10)
156 | .setSpan(FilePickerConst.SPAN_TYPE.FOLDER_SPAN, 3)
157 | .setSpan(FilePickerConst.SPAN_TYPE.DETAIL_SPAN, 4)
158 | .enableVideoPicker(true)
159 | .enableCameraSupport(true)
160 | .showGifs(true)
161 | .showFolderView(false)
162 | .enableSelectAll(true)
163 | .enableImagePicker(true)
164 | .setCameraPlaceholder(R.drawable.custom_camera)
165 | .withOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
166 | .pickPhoto(this, CUSTOM_REQUEST_CODE);
167 | }
168 | }
169 |
170 | public void onPickDoc() {
171 | String[] zips = {"zip", "rar"};
172 | String[] pdfs = {"aac"};
173 | int maxCount = MAX_ATTACHMENT_COUNT - photoPaths.size();
174 | if ((docPaths.size() + photoPaths.size()) == MAX_ATTACHMENT_COUNT) {
175 | Toast.makeText(this, "Cannot select more than " + MAX_ATTACHMENT_COUNT + " items",
176 | Toast.LENGTH_SHORT).show();
177 | } else {
178 | FilePickerBuilder.getInstance()
179 | .setMaxCount(1)
180 | .setSelectedFiles(docPaths)
181 | .setActivityTheme(R.style.FilePickerTheme)
182 | .setActivityTitle("Please select doc")
183 | .setImageSizeLimit(5) //Provide Size in MB
184 | .setVideoSizeLimit(20)
185 | // .addFileSupport("ZIP", zips)
186 | // .addFileSupport("AAC", pdfs, R.drawable.pdf_blue)
187 | .enableDocSupport(true)
188 | .enableSelectAll(true)
189 | .sortDocumentsBy(SortingTypes.NAME)
190 | .withOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
191 | .pickFile(this);
192 | }
193 | }
194 |
195 | @Override
196 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
197 | @NonNull int[] grantResults) {
198 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
199 |
200 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
201 | }
202 |
203 | public void onOpenFragmentClicked(View view) {
204 | Intent intent = new Intent(this, FragmentActivity.class);
205 | startActivity(intent);
206 | }
207 |
208 | @Override
209 | public void onPermissionsGranted(int requestCode, @NonNull List perms) {
210 | }
211 |
212 | @Override
213 | public void onPermissionsDenied(int requestCode, @NonNull List perms) {
214 |
215 | if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
216 | new AppSettingsDialog.Builder(this).build().show();
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/custom_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/drawable-xxhdpi/custom_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/pdf_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/drawable-xxhdpi/pdf_blue.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
25 |
26 |
32 |
33 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_caller.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | L\'app ha bisogno di questo permesso per accedere ai documenti del dispositivo.
4 | L\'app ha bisogno di questo permesso per selezionare foto dalla galleria e dalla camera.
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #fafafa
5 | #9e9e9e
6 | @android:color/black
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android FilePicker
3 |
4 | Hello blank fragment
5 | We require this permission to select photo from gallery and camera.
6 | We need this permission to read documents from device.
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/test/java/vi/filepicker/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package vi.filepicker;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.4.10'
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:4.1.1'
11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 | maven { url 'https://maven.google.com' }
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
31 | subprojects {
32 | tasks.withType(Javadoc).all { enabled = false }
33 | }
34 |
--------------------------------------------------------------------------------
/filepicker/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/filepicker/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'com.github.dcendents.android-maven'
5 | apply plugin: "com.jfrog.bintray"
6 |
7 | version = "2.2.5"
8 |
9 | android {
10 | compileSdkVersion 30
11 | buildToolsVersion '30.0.2'
12 |
13 | defaultConfig {
14 | minSdkVersion 17
15 | targetSdkVersion 30
16 | versionCode 1
17 | versionName version
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | implementation 'androidx.appcompat:appcompat:1.2.0'
30 | implementation 'com.google.android.material:material:1.2.1'
31 | implementation 'androidx.recyclerview:recyclerview:1.1.0'
32 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
33 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
34 | api 'com.github.bumptech.glide:glide:4.11.0'
35 | annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
36 | testImplementation 'junit:junit:4.12'
37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
38 | }
39 |
40 | def siteUrl = 'https://github.com/DroidNinja/Android-FilePicker' // Homepage URL of the library
41 | def gitUrl = 'https://github.com/DroidNinja/Android-FilePicker.git' // Git repository URL
42 | group = "com.droidninja" // Maven Group ID for the artifact
43 |
44 | install {
45 | repositories.mavenInstaller {
46 | // This generates POM.xml with proper parameters
47 | pom {
48 | project {
49 | packaging 'aar'
50 |
51 | // Add your description here
52 | name 'com.droidninja.filepicker'
53 | description = 'Media and document picker'
54 | url siteUrl
55 |
56 | // Set your license
57 | licenses {
58 | license {
59 | name 'The Apache Software License, Version 2.0'
60 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
61 | }
62 | }
63 | developers {
64 | developer {
65 | id 'droidninja'
66 | name 'Arun Sharma'
67 | email 'arun2007ind@gmail.com'
68 | }
69 | }
70 | scm {
71 | connection gitUrl
72 | developerConnection gitUrl
73 | url siteUrl
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
80 | task sourcesJar(type: Jar) {
81 | from android.sourceSets.main.java.srcDirs
82 | classifier = 'sources'
83 | }
84 |
85 | task javadoc(type: Javadoc) {
86 | excludes = ['**/*.kt']
87 | options.addStringOption('Xdoclint:none', '-quiet')
88 | options.addStringOption('encoding', 'UTF-8')
89 | options.addStringOption('charSet', 'UTF-8')
90 | source = android.sourceSets.main.java.srcDirs
91 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
92 | }
93 |
94 | task javadocJar(type: Jar, dependsOn: javadoc) {
95 | classifier = 'javadoc'
96 | from javadoc.destinationDir
97 | }
98 |
99 | artifacts {
100 | archives javadocJar
101 | archives sourcesJar
102 | }
103 |
104 | Properties properties = new Properties()
105 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
106 |
107 | // https://github.com/bintray/gradle-bintray-plugin
108 | bintray {
109 | user = properties.getProperty("bintray.user")
110 | key = properties.getProperty("bintray.apikey")
111 |
112 | configurations = ['archives']
113 | pkg {
114 | repo = "maven"
115 | // it is the name that appears in bintray when logged
116 | name = "com.droidninja.filepicker" // TODO
117 | websiteUrl = siteUrl
118 | vcsUrl = gitUrl
119 | licenses = ["Apache-2.0"]
120 | publish = true
121 | version {
122 | gpg {
123 | sign = true //Determines whether to GPG sign the files. The default is false
124 | passphrase = properties.getProperty("bintray.gpg.password")
125 | //Optional. The passphrase for GPG signing'
126 | }
127 | }
128 | }
129 | }
130 | repositories {
131 | mavenCentral()
132 | }
--------------------------------------------------------------------------------
/filepicker/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/droidNinja/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/filepicker/src/androidTest/java/droidninja/filepicker/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/filepicker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
14 |
16 |
17 |
22 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/BaseFilePickerActivity.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker
2 |
3 | import android.os.Bundle
4 | import androidx.annotation.LayoutRes
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.google.android.material.appbar.MaterialToolbar
7 |
8 | /**
9 | * Created by droidNinja on 22/07/17.
10 | */
11 |
12 | abstract class BaseFilePickerActivity : AppCompatActivity() {
13 |
14 | protected fun onCreate(savedInstanceState: Bundle?, @LayoutRes layout: Int) {
15 | super.onCreate(savedInstanceState)
16 | setTheme(PickerManager.theme)
17 | setContentView(layout)
18 |
19 | val toolbar = findViewById(R.id.toolbar)
20 | setSupportActionBar(toolbar)
21 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
22 |
23 | //set orientation
24 | requestedOrientation = PickerManager.orientation
25 | initView()
26 | }
27 |
28 | protected abstract fun initView()
29 | }
30 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/FilePickerActivity.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.content.Intent
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import android.text.TextUtils
9 | import android.view.Menu
10 | import android.view.MenuItem
11 | import droidninja.filepicker.fragments.DocFragment
12 | import droidninja.filepicker.fragments.DocPickerFragment
13 | import droidninja.filepicker.fragments.MediaPickerFragment
14 | import droidninja.filepicker.fragments.PhotoPickerFragmentListener
15 | import droidninja.filepicker.utils.FragmentUtil
16 | import java.util.*
17 |
18 | class FilePickerActivity : BaseFilePickerActivity(), PhotoPickerFragmentListener, DocFragment.DocFragmentListener, DocPickerFragment.DocPickerFragmentListener, MediaPickerFragment.MediaPickerFragmentListener {
19 | private var type: Int = 0
20 |
21 | @SuppressLint("MissingSuperCall")
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState, R.layout.activity_file_picker)
24 | }
25 |
26 | override fun initView() {
27 | val intent = intent
28 | if (intent != null) {
29 | val selectedPaths: ArrayList? = intent.getParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_MEDIA)
30 | type = intent.getIntExtra(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.MEDIA_PICKER)
31 |
32 | if (selectedPaths != null) {
33 |
34 | if (PickerManager.getMaxCount() == 1) {
35 | selectedPaths.clear()
36 | }
37 |
38 | PickerManager.clearSelections()
39 | if (type == FilePickerConst.MEDIA_PICKER) {
40 | PickerManager.add(selectedPaths, FilePickerConst.FILE_TYPE_MEDIA)
41 | } else {
42 | PickerManager.add(selectedPaths, FilePickerConst.FILE_TYPE_DOCUMENT)
43 | }
44 | }
45 |
46 | setToolbarTitle(PickerManager.currentCount)
47 | openSpecificFragment(type)
48 | }
49 | }
50 |
51 | override fun setToolbarTitle(count: Int) {
52 | val actionBar = supportActionBar
53 | if (actionBar != null) {
54 | val maxCount = PickerManager.getMaxCount()
55 | if (maxCount == -1 && count > 0) {
56 | actionBar.title = String.format(getString(R.string.attachments_num), count)
57 | } else if (maxCount > 0 && count > 0) {
58 | actionBar.title = String.format(getString(R.string.attachments_title_text), count, maxCount)
59 | } else if (!TextUtils.isEmpty(PickerManager.title)) {
60 | actionBar.title = PickerManager.title
61 | } else {
62 | if (type == FilePickerConst.MEDIA_PICKER) {
63 | actionBar.setTitle(R.string.select_photo_text)
64 | } else {
65 | actionBar.setTitle(R.string.select_doc_text)
66 | }
67 | }
68 | }
69 | }
70 |
71 | private fun openSpecificFragment(type: Int) {
72 | if (type == FilePickerConst.MEDIA_PICKER) {
73 | val photoFragment = MediaPickerFragment.newInstance()
74 | FragmentUtil.replaceFragment(this, R.id.container, photoFragment)
75 | } else {
76 | if (PickerManager.isDocSupport) PickerManager.addDocTypes()
77 |
78 | val photoFragment = DocPickerFragment.newInstance()
79 | FragmentUtil.replaceFragment(this, R.id.container, photoFragment)
80 | }
81 | }
82 |
83 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
84 | menuInflater.inflate(R.menu.picker_menu, menu)
85 | val menuItem = menu.findItem(R.id.action_done)
86 | if (menuItem != null) {
87 | menuItem.isVisible = PickerManager.getMaxCount() != 1
88 | }
89 | return super.onCreateOptionsMenu(menu)
90 | }
91 |
92 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
93 | val i = item.itemId
94 | if (i == R.id.action_done) {
95 | if (type == FilePickerConst.MEDIA_PICKER) {
96 | returnData(PickerManager.selectedPhotos)
97 | } else {
98 | returnData(PickerManager.selectedFiles)
99 | }
100 |
101 | return true
102 | } else if (i == android.R.id.home) {
103 | onBackPressed()
104 | return true
105 | }
106 | return super.onOptionsItemSelected(item)
107 | }
108 |
109 | override fun onBackPressed() {
110 | super.onBackPressed()
111 | setResult(Activity.RESULT_CANCELED)
112 | finish()
113 | }
114 |
115 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
116 | super.onActivityResult(requestCode, resultCode, data)
117 | when (requestCode) {
118 | FilePickerConst.REQUEST_CODE_MEDIA_DETAIL -> if (resultCode == Activity.RESULT_OK) {
119 | if (type == FilePickerConst.MEDIA_PICKER) {
120 | returnData(PickerManager.selectedPhotos)
121 | } else {
122 | returnData(PickerManager.selectedFiles)
123 | }
124 | } else {
125 | setToolbarTitle(PickerManager.currentCount)
126 | }
127 | }
128 | }
129 |
130 | private fun returnData(paths: ArrayList) {
131 | val intent = Intent()
132 | if (type == FilePickerConst.MEDIA_PICKER) {
133 | intent.putParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_MEDIA, paths)
134 | } else {
135 | intent.putParcelableArrayListExtra(FilePickerConst.KEY_SELECTED_DOCS, paths)
136 | }
137 |
138 | setResult(Activity.RESULT_OK, intent)
139 | finish()
140 | }
141 |
142 | override fun onDestroy() {
143 | PickerManager.reset()
144 | super.onDestroy()
145 | }
146 |
147 | override fun onItemSelected() {
148 | val currentCount = PickerManager.currentCount
149 | setToolbarTitle(currentCount)
150 |
151 | if (PickerManager.getMaxCount() == 1 && currentCount == 1) {
152 | returnData(
153 | if (type == FilePickerConst.MEDIA_PICKER)
154 | PickerManager.selectedPhotos
155 | else
156 | PickerManager.selectedFiles)
157 | }
158 | }
159 |
160 | companion object {
161 |
162 | private val TAG = FilePickerActivity::class.java.simpleName
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/FilePickerBuilder.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.content.pm.PackageManager
6 | import android.net.Uri
7 | import android.os.Build
8 | import android.os.Bundle
9 | import androidx.annotation.DrawableRes
10 | import androidx.fragment.app.Fragment
11 | import androidx.core.content.ContextCompat
12 | import android.widget.Toast
13 | import androidx.annotation.IntegerRes
14 | import droidninja.filepicker.models.FileType
15 | import droidninja.filepicker.models.sort.SortingTypes
16 | import java.util.ArrayList
17 |
18 | /**
19 | * Created by droidNinja on 29/07/16.
20 | */
21 | class FilePickerBuilder {
22 |
23 | private val mPickerOptionsBundle: Bundle = Bundle()
24 |
25 | fun setImageSizeLimit(fileSize: Int): FilePickerBuilder {
26 | PickerManager.imageFileSize = fileSize
27 | return this
28 | }
29 |
30 | fun setVideoSizeLimit(fileSize: Int) : FilePickerBuilder{
31 | PickerManager.videoFileSize = fileSize
32 | return this
33 | }
34 |
35 | fun setMaxCount(maxCount: Int): FilePickerBuilder {
36 | PickerManager.setMaxCount(maxCount)
37 | return this
38 | }
39 |
40 | fun setActivityTheme(theme: Int): FilePickerBuilder {
41 | PickerManager.theme = theme
42 | return this
43 | }
44 |
45 | fun setActivityTitle(title: String): FilePickerBuilder {
46 | PickerManager.title = title
47 | return this
48 | }
49 |
50 | /**
51 | * @param spanType it could be [FilePickerConst.SPAN_TYPE.FOLDER_SPAN] (for folder screen)
52 | * or [FilePickerConst.SPAN_TYPE.DETAIL_SPAN] (for details screen)
53 | * @param count span count in integer, defaults for Folder is 2 and Details is 3
54 | */
55 | fun setSpan(spanType: FilePickerConst.SPAN_TYPE, count: Int): FilePickerBuilder {
56 | PickerManager.spanTypes[spanType] = count
57 | return this
58 | }
59 |
60 | fun setSelectedFiles(selectedPhotos: ArrayList): FilePickerBuilder {
61 | mPickerOptionsBundle.putParcelableArrayList(FilePickerConst.KEY_SELECTED_MEDIA, selectedPhotos)
62 | return this
63 | }
64 |
65 | fun enableVideoPicker(status: Boolean): FilePickerBuilder {
66 | PickerManager.setShowVideos(status)
67 | return this
68 | }
69 |
70 | fun enableImagePicker(status: Boolean): FilePickerBuilder {
71 | PickerManager.setShowImages(status)
72 | return this
73 | }
74 |
75 | fun enableSelectAll(status: Boolean): FilePickerBuilder {
76 | PickerManager.enableSelectAll(status)
77 | return this
78 | }
79 |
80 | fun setCameraPlaceholder(@DrawableRes drawable: Int): FilePickerBuilder {
81 | PickerManager.cameraDrawable = drawable
82 | return this
83 | }
84 |
85 | fun showGifs(status: Boolean): FilePickerBuilder {
86 | PickerManager.isShowGif = status
87 | return this
88 | }
89 |
90 | fun showFolderView(status: Boolean): FilePickerBuilder {
91 | PickerManager.isShowFolderView = status
92 | return this
93 | }
94 |
95 | fun enableDocSupport(status: Boolean): FilePickerBuilder {
96 | PickerManager.isDocSupport = status
97 | return this
98 | }
99 |
100 | fun enableCameraSupport(status: Boolean): FilePickerBuilder {
101 | PickerManager.isEnableCamera = status
102 | return this
103 | }
104 |
105 |
106 | fun withOrientation(@IntegerRes orientation: Int): FilePickerBuilder {
107 | PickerManager.orientation = orientation
108 | return this
109 | }
110 |
111 | @JvmOverloads
112 | fun addFileSupport(title: String, extensions: Array,
113 | @DrawableRes drawable: Int = R.drawable.icon_file_unknown): FilePickerBuilder {
114 | PickerManager.addFileType(FileType(title, extensions, drawable))
115 | return this
116 | }
117 |
118 | fun sortDocumentsBy(type: SortingTypes): FilePickerBuilder {
119 | PickerManager.sortingType = type
120 | return this
121 | }
122 |
123 | fun pickPhoto(context: Activity) {
124 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.MEDIA_PICKER)
125 | start(context, FilePickerConst.REQUEST_CODE_PHOTO)
126 | }
127 |
128 | fun pickPhoto(context: Fragment) {
129 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.MEDIA_PICKER)
130 | start(context, FilePickerConst.REQUEST_CODE_PHOTO)
131 | }
132 |
133 | fun pickFile(context: Activity) {
134 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.DOC_PICKER)
135 | start(context, FilePickerConst.REQUEST_CODE_DOC)
136 | }
137 |
138 | fun pickFile(context: Fragment) {
139 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.DOC_PICKER)
140 | start(context, FilePickerConst.REQUEST_CODE_DOC)
141 | }
142 |
143 | fun pickPhoto(context: Activity, requestCode: Int) {
144 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.MEDIA_PICKER)
145 | start(context, requestCode)
146 | }
147 |
148 | fun pickPhoto(context: Fragment, requestCode: Int) {
149 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.MEDIA_PICKER)
150 | start(context, requestCode)
151 | }
152 |
153 | fun pickFile(context: Activity, requestCode: Int) {
154 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.DOC_PICKER)
155 | start(context, requestCode)
156 | }
157 |
158 | fun pickFile(context: Fragment, requestCode: Int) {
159 | mPickerOptionsBundle.putInt(FilePickerConst.EXTRA_PICKER_TYPE, FilePickerConst.DOC_PICKER)
160 | start(context, requestCode)
161 | }
162 |
163 | private fun start(context: Activity, requestCode: Int) {
164 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
165 | if (ContextCompat.checkSelfPermission(context, FilePickerConst.PERMISSIONS_FILE_PICKER) != PackageManager.PERMISSION_GRANTED) {
166 | Toast.makeText(context,
167 | context.resources.getString(R.string.permission_filepicker_rationale),
168 | Toast.LENGTH_SHORT).show()
169 | return
170 | }
171 | }
172 |
173 | val intent = Intent(context, FilePickerActivity::class.java)
174 | intent.putExtras(mPickerOptionsBundle)
175 |
176 | context.startActivityForResult(intent, requestCode)
177 | }
178 |
179 | private fun start(fragment: Fragment, requestCode: Int) {
180 | fragment.context?.let {
181 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
182 | if (ContextCompat.checkSelfPermission(it,
183 | FilePickerConst.PERMISSIONS_FILE_PICKER) != PackageManager.PERMISSION_GRANTED) {
184 | Toast.makeText(fragment.context, it
185 | .resources
186 | .getString(R.string.permission_filepicker_rationale), Toast.LENGTH_SHORT).show()
187 | return
188 | }
189 | }
190 |
191 | val intent = Intent(fragment.activity, FilePickerActivity::class.java)
192 | intent.putExtras(mPickerOptionsBundle)
193 |
194 | fragment.startActivityForResult(intent, requestCode)
195 | }
196 | }
197 |
198 | companion object {
199 | @JvmStatic
200 | val instance: FilePickerBuilder
201 | get() = FilePickerBuilder()
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/FilePickerConst.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker
2 |
3 | import android.Manifest
4 |
5 | /**
6 | * Created by droidNinja on 28/07/16.
7 | */
8 | object FilePickerConst {
9 | const val REQUEST_CODE_PHOTO = 233
10 | const val REQUEST_CODE_DOC = 234
11 |
12 | const val REQUEST_CODE_MEDIA_DETAIL = 235
13 | const val REQUEST_CODE_PERMISSION = 988
14 |
15 | const val DEFAULT_MAX_COUNT = -1
16 | const val DEFAULT_COLUMN_NUMBER = 3
17 | const val DEFAULT_FILE_SIZE = Int.MAX_VALUE
18 |
19 | const val MEDIA_PICKER = 0x11
20 | const val DOC_PICKER = 0x12
21 |
22 | const val KEY_SELECTED_MEDIA = "SELECTED_PHOTOS"
23 | const val KEY_SELECTED_DOCS = "SELECTED_DOCS"
24 |
25 | const val EXTRA_IMAGE_FILE_SIZE = "EXTRA_IMAGE_FILE_SIZE"
26 | const val EXTRA_VIDEO_FILE_SIZE = "EXTRA__VIDEO_FILE_SIZE"
27 | const val EXTRA_DOC_FILE_SIZE = "EXTRA_DOC_FILE_SIZE"
28 | const val EXTRA_PICKER_TYPE = "EXTRA_PICKER_TYPE"
29 | const val EXTRA_SHOW_GIF = "SHOW_GIF"
30 | const val EXTRA_FILE_TYPE = "EXTRA_FILE_TYPE"
31 | const val EXTRA_BUCKET_ID = "EXTRA_BUCKET_ID"
32 | const val ALL_PHOTOS_BUCKET_ID = "ALL_PHOTOS_BUCKET_ID"
33 | const val PPT_MIME_TYPE = "application/mspowerpoint"
34 |
35 | const val FILE_TYPE_MEDIA = 1
36 | const val FILE_TYPE_DOCUMENT = 2
37 |
38 | const val MEDIA_TYPE_IMAGE = 1
39 | const val MEDIA_TYPE_VIDEO = 3
40 |
41 | const val PERMISSIONS_FILE_PICKER = Manifest.permission.READ_EXTERNAL_STORAGE
42 |
43 | val docExtensions = arrayOf("ppt", "pptx", "xls", "xlsx", "doc", "docx", "dot", "dotx")
44 |
45 | const val PDF = "PDF"
46 | const val PPT = "PPT"
47 | const val DOC = "DOC"
48 | const val XLS = "XLS"
49 | const val TXT = "TXT"
50 |
51 | enum class FILE_TYPE {
52 | PDF, WORD, EXCEL, PPT, TXT, UNKNOWN
53 | }
54 |
55 | enum class SPAN_TYPE{
56 | FOLDER_SPAN, DETAIL_SPAN
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/MediaDetailsActivity.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.os.Bundle
6 | import androidx.recyclerview.widget.DefaultItemAnimator
7 | import androidx.recyclerview.widget.OrientationHelper
8 | import androidx.recyclerview.widget.RecyclerView
9 | import androidx.recyclerview.widget.StaggeredGridLayoutManager
10 | import android.view.Menu
11 | import android.view.MenuItem
12 | import android.view.View
13 | import android.widget.TextView
14 | import androidx.lifecycle.Observer
15 | import androidx.lifecycle.ViewModelProvider
16 | import com.bumptech.glide.Glide
17 | import com.bumptech.glide.RequestManager
18 | import droidninja.filepicker.adapters.FileAdapterListener
19 | import droidninja.filepicker.adapters.PhotoGridAdapter
20 | import droidninja.filepicker.models.Media
21 | import droidninja.filepicker.models.PhotoDirectory
22 | import droidninja.filepicker.utils.AndroidLifecycleUtils
23 | import droidninja.filepicker.viewmodels.VMMediaPicker
24 | import java.util.ArrayList
25 | import java.util.Comparator
26 |
27 | class MediaDetailsActivity : BaseFilePickerActivity(), FileAdapterListener {
28 | private var recyclerView: RecyclerView? = null
29 | private var emptyView: TextView? = null
30 | private lateinit var mGlideRequestManager: RequestManager
31 | private var photoGridAdapter: PhotoGridAdapter? = null
32 | private var fileType: Int = 0
33 | private var imageFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE
34 | private var videoFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE
35 | private var selectAllItem: MenuItem? = null
36 | private var photoDirectory: PhotoDirectory? = null
37 | lateinit var viewModel: VMMediaPicker
38 |
39 | @SuppressLint("MissingSuperCall")
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState, R.layout.activity_media_details)
42 | }
43 |
44 | override fun initView() {
45 | viewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application)).get(VMMediaPicker::class.java)
46 | mGlideRequestManager = Glide.with(this)
47 | val intent = intent
48 | if (intent != null) {
49 |
50 | fileType = intent.getIntExtra(FilePickerConst.EXTRA_FILE_TYPE, FilePickerConst.MEDIA_TYPE_IMAGE)
51 | imageFileSize = intent.getIntExtra(FilePickerConst.EXTRA_IMAGE_FILE_SIZE, FilePickerConst.DEFAULT_FILE_SIZE)
52 | videoFileSize = intent.getIntExtra(FilePickerConst.EXTRA_VIDEO_FILE_SIZE, FilePickerConst.DEFAULT_FILE_SIZE)
53 | photoDirectory = intent.getParcelableExtra(PhotoDirectory::class.java.simpleName)
54 | if (photoDirectory != null) {
55 | setUpView()
56 | setTitle(0)
57 | }
58 | }
59 | }
60 |
61 | override fun setTitle(count: Int) {
62 | val actionBar = supportActionBar
63 | if (actionBar != null) {
64 | actionBar.setDisplayHomeAsUpEnabled(true)
65 | val maxCount = PickerManager.getMaxCount()
66 | if (maxCount == -1 && count > 0) {
67 | actionBar.title = String.format(getString(R.string.attachments_num), count)
68 | } else if (maxCount > 0 && count > 0) {
69 | actionBar.title = String.format(getString(R.string.attachments_title_text), count, maxCount)
70 | } else {
71 | actionBar.title = photoDirectory?.name
72 | }
73 | }
74 | }
75 |
76 | private fun setUpView() {
77 | recyclerView = findViewById(R.id.recyclerview)
78 | emptyView = findViewById(R.id.empty_view)
79 |
80 | val spanCount = PickerManager.spanTypes[FilePickerConst.SPAN_TYPE.DETAIL_SPAN] ?: 3
81 | val layoutManager = StaggeredGridLayoutManager(spanCount, OrientationHelper.VERTICAL)
82 | layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
83 | recyclerView?.layoutManager = layoutManager
84 | recyclerView?.itemAnimator = DefaultItemAnimator()
85 |
86 | recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
87 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
88 | super.onScrolled(recyclerView, dx, dy)
89 | // Log.d(">>> Picker >>>", "dy = " + dy);
90 | if (Math.abs(dy) > SCROLL_THRESHOLD) {
91 | mGlideRequestManager.pauseRequests()
92 | } else {
93 | resumeRequestsIfNotDestroyed()
94 | }
95 | }
96 |
97 | override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
98 | if (newState == RecyclerView.SCROLL_STATE_IDLE) {
99 | resumeRequestsIfNotDestroyed()
100 | }
101 | }
102 | })
103 |
104 | viewModel.lvMediaData.observe(this, Observer { data ->
105 | updateList(data)
106 | })
107 | viewModel.getMedia(bucketId = photoDirectory?.bucketId, mediaType = fileType, imageFileSize = imageFileSize, videoFileSize = videoFileSize)
108 | }
109 |
110 | private fun updateList(medias: List) {
111 | if (medias.isNotEmpty()) {
112 | emptyView?.visibility = View.GONE
113 | recyclerView?.visibility = View.VISIBLE
114 | } else {
115 | emptyView?.visibility = View.VISIBLE
116 | recyclerView?.visibility = View.GONE
117 | return
118 | }
119 |
120 | if (photoGridAdapter != null) {
121 | photoGridAdapter?.setData(medias, PickerManager.selectedPhotos)
122 | } else {
123 | photoGridAdapter = PhotoGridAdapter(this, mGlideRequestManager, medias,
124 | PickerManager.selectedPhotos, false, this)
125 | recyclerView?.adapter = photoGridAdapter
126 | }
127 |
128 | if (PickerManager.getMaxCount() == -1) {
129 | if (photoGridAdapter != null && selectAllItem != null) {
130 | if (photoGridAdapter?.itemCount == photoGridAdapter?.selectedItemCount) {
131 | selectAllItem?.setIcon(R.drawable.ic_select_all)
132 | selectAllItem?.isChecked = true
133 | }
134 | }
135 | setTitle(PickerManager.currentCount)
136 | }
137 | }
138 |
139 | private fun resumeRequestsIfNotDestroyed() {
140 | if (!AndroidLifecycleUtils.canLoadImage(this)) {
141 | return
142 | }
143 |
144 | mGlideRequestManager.resumeRequests()
145 | }
146 |
147 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
148 | menuInflater.inflate(R.menu.media_detail_menu, menu)
149 | selectAllItem = menu.findItem(R.id.action_select)
150 | selectAllItem?.isVisible = PickerManager.hasSelectAll()
151 | menu.findItem(R.id.action_done)?.isVisible = PickerManager.getMaxCount() > 1
152 |
153 | return super.onCreateOptionsMenu(menu)
154 | }
155 |
156 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
157 | val itemId = item.itemId
158 | if (itemId == R.id.action_done) {
159 | setResult(Activity.RESULT_OK, null)
160 | finish()
161 |
162 | return true
163 | } else if (itemId == R.id.action_select) {
164 | selectAllItem?.let {
165 | photoGridAdapter?.let { adapter ->
166 | if (it.isChecked) {
167 | PickerManager.deleteMedia(adapter.selectedPaths)
168 | adapter.clearSelection()
169 |
170 | it.setIcon(R.drawable.ic_deselect_all)
171 | } else {
172 | adapter.selectAll()
173 | PickerManager.add(adapter.selectedPaths, FilePickerConst.FILE_TYPE_MEDIA)
174 | it.setIcon(R.drawable.ic_select_all)
175 | }
176 | it.isChecked = !it.isChecked
177 | setTitle(PickerManager.currentCount)
178 | }
179 | }
180 | return true
181 | } else if (itemId == android.R.id.home) {
182 | onBackPressed()
183 | return true
184 | }
185 | return super.onOptionsItemSelected(item)
186 | }
187 |
188 | override fun onItemSelected() {
189 | val maxCount = PickerManager.getMaxCount()
190 | if (maxCount == 1) {
191 | setResult(Activity.RESULT_OK, null)
192 | finish()
193 | }
194 | setTitle(PickerManager.currentCount)
195 | }
196 |
197 | override fun onBackPressed() {
198 | setResult(Activity.RESULT_CANCELED, null)
199 | finish()
200 | }
201 |
202 | companion object {
203 |
204 | private val SCROLL_THRESHOLD = 30
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/PickerManager.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker
2 |
3 | import android.content.pm.ActivityInfo
4 | import android.net.Uri
5 | import java.util.ArrayList
6 |
7 | import droidninja.filepicker.models.BaseFile
8 | import droidninja.filepicker.models.FileType
9 | import droidninja.filepicker.models.sort.SortingTypes
10 | import java.util.LinkedHashSet
11 |
12 | /**
13 | * Created by droidNinja on 29/07/16.
14 | */
15 | object PickerManager {
16 | private var maxCount = FilePickerConst.DEFAULT_MAX_COUNT
17 | private var showImages = true
18 | var cameraDrawable = R.drawable.ic_camera
19 | var sortingType = SortingTypes.NONE
20 |
21 | val selectedPhotos: ArrayList = ArrayList()
22 | val selectedFiles: ArrayList = ArrayList()
23 |
24 | private val fileTypes: LinkedHashSet = LinkedHashSet()
25 |
26 | var theme: Int = R.style.LibAppTheme
27 |
28 | var title: String? = null
29 |
30 | private var showVideos: Boolean = false
31 |
32 | var isShowGif: Boolean = false
33 |
34 | private var showSelectAll = false
35 |
36 | var imageFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE
37 | var videoFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE
38 |
39 | var isDocSupport = true
40 | get() = field
41 |
42 | var isEnableCamera = true
43 |
44 | /**
45 | * Recyclerview span count for both folder and detail screen
46 | * Default Folder span is 2
47 | * Default Detail Span is 3
48 | */
49 | var spanTypes = mutableMapOf(
50 | FilePickerConst.SPAN_TYPE.FOLDER_SPAN to 2,
51 | FilePickerConst.SPAN_TYPE.DETAIL_SPAN to 3
52 | )
53 |
54 | /**
55 | * The preferred screen orientation this activity would like to run in.
56 | * From the {@link android.R.attr#screenOrientation} attribute, one of
57 | * {@link #SCREEN_ORIENTATION_UNSPECIFIED},
58 | * {@link #SCREEN_ORIENTATION_LANDSCAPE},
59 | * {@link #SCREEN_ORIENTATION_PORTRAIT},
60 | * {@link #SCREEN_ORIENTATION_USER},
61 | * {@link #SCREEN_ORIENTATION_BEHIND},
62 | * {@link #SCREEN_ORIENTATION_SENSOR},
63 | * {@link #SCREEN_ORIENTATION_NOSENSOR},
64 | * {@link #SCREEN_ORIENTATION_SENSOR_LANDSCAPE},
65 | * {@link #SCREEN_ORIENTATION_SENSOR_PORTRAIT},
66 | * {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
67 | * {@link #SCREEN_ORIENTATION_REVERSE_PORTRAIT},
68 | * {@link #SCREEN_ORIENTATION_FULL_SENSOR},
69 | * {@link #SCREEN_ORIENTATION_USER_LANDSCAPE},
70 | * {@link #SCREEN_ORIENTATION_USER_PORTRAIT},
71 | * {@link #SCREEN_ORIENTATION_FULL_USER},
72 | * {@link #SCREEN_ORIENTATION_LOCKED},
73 | */
74 | var orientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
75 | get() = field
76 |
77 | var isShowFolderView = true
78 |
79 | val currentCount: Int
80 | get() = selectedPhotos.size + selectedFiles.size
81 |
82 | fun setMaxCount(count: Int) {
83 | reset()
84 | this.maxCount = count
85 | }
86 |
87 | fun getMaxCount(): Int {
88 | return maxCount
89 | }
90 |
91 | fun add(path: Uri?, type: Int) {
92 | if (path != null && shouldAdd()) {
93 | if (!selectedPhotos.contains(path) && type == FilePickerConst.FILE_TYPE_MEDIA) {
94 | selectedPhotos.add(path)
95 | } else if (!selectedFiles.contains(path) && type == FilePickerConst.FILE_TYPE_DOCUMENT) {
96 | selectedFiles.add(path)
97 | } else {
98 | return
99 | }
100 | }
101 | }
102 |
103 | fun add(paths: List, type: Int) {
104 | for (index in paths.indices) {
105 | add(paths[index], type)
106 | }
107 | }
108 |
109 | fun remove(path: Uri?, type: Int) {
110 | if (type == FilePickerConst.FILE_TYPE_MEDIA && selectedPhotos.contains(path)) {
111 | selectedPhotos.remove(path)
112 | } else if (type == FilePickerConst.FILE_TYPE_DOCUMENT) {
113 | selectedFiles.remove(path)
114 | }
115 | }
116 |
117 | fun shouldAdd(): Boolean {
118 | return if (maxCount == -1) true else currentCount < maxCount
119 | }
120 |
121 | fun getSelectedFilePaths(files: ArrayList): ArrayList {
122 | val paths = ArrayList()
123 | for (index in files.indices) {
124 | paths.add(files[index].path)
125 | }
126 | return paths
127 | }
128 |
129 | fun reset() {
130 | selectedFiles.clear()
131 | selectedPhotos.clear()
132 | fileTypes.clear()
133 | maxCount = -1
134 | }
135 |
136 | fun clearSelections() {
137 | selectedPhotos.clear()
138 | selectedFiles.clear()
139 | }
140 |
141 | fun deleteMedia(paths: List) {
142 | selectedPhotos.removeAll(paths)
143 | }
144 |
145 | fun showVideo(): Boolean {
146 | return showVideos
147 | }
148 |
149 | fun setShowVideos(showVideos: Boolean) {
150 | this.showVideos = showVideos
151 | }
152 |
153 | fun showImages(): Boolean {
154 | return showImages
155 | }
156 |
157 | fun setShowImages(showImages: Boolean) {
158 | this.showImages = showImages
159 | }
160 |
161 | fun addFileType(fileType: FileType) {
162 | fileTypes.add(fileType)
163 | }
164 |
165 | fun addDocTypes() {
166 | val pdfs = arrayOf("pdf")
167 | fileTypes.add(FileType(FilePickerConst.PDF, pdfs, R.drawable.icon_file_pdf))
168 |
169 | val docs = arrayOf("doc", "docx", "dot", "dotx")
170 | fileTypes.add(FileType(FilePickerConst.DOC, docs, R.drawable.icon_file_doc))
171 |
172 | val ppts = arrayOf("ppt", "pptx")
173 | fileTypes.add(FileType(FilePickerConst.PPT, ppts, R.drawable.icon_file_ppt))
174 |
175 | val xlss = arrayOf("xls", "xlsx")
176 | fileTypes.add(FileType(FilePickerConst.XLS, xlss, R.drawable.icon_file_xls))
177 |
178 | val txts = arrayOf("txt")
179 | fileTypes.add(FileType(FilePickerConst.TXT, txts, R.drawable.icon_file_unknown))
180 | }
181 |
182 | fun getFileTypes(): ArrayList {
183 | return ArrayList(fileTypes)
184 | }
185 |
186 | fun hasSelectAll(): Boolean {
187 | return maxCount == -1 && showSelectAll
188 | }
189 |
190 | fun enableSelectAll(showSelectAll: Boolean) {
191 | this.showSelectAll = showSelectAll
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/FileAdapterListener.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | interface FileAdapterListener {
4 | fun onItemSelected()
5 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/FileListAdapter.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import androidx.recyclerview.widget.RecyclerView
6 | import android.text.format.Formatter
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.widget.Filter
11 | import android.widget.Filterable
12 | import android.widget.ImageView
13 | import android.widget.TextView
14 |
15 | import java.util.ArrayList
16 |
17 | import droidninja.filepicker.FilePickerConst
18 | import droidninja.filepicker.PickerManager
19 | import droidninja.filepicker.R
20 | import droidninja.filepicker.models.Document
21 | import droidninja.filepicker.views.SmoothCheckBox
22 |
23 | /**
24 | * Created by droidNinja on 29/07/16.
25 | */
26 | class FileListAdapter(private val context: Context, private var mFilteredList: List, selectedPaths: MutableList,
27 | private val mListener: FileAdapterListener?) : SelectableAdapter(mFilteredList, selectedPaths), Filterable {
28 |
29 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileViewHolder {
30 | val itemView = LayoutInflater.from(context).inflate(R.layout.item_doc_layout, parent, false)
31 |
32 | return FileViewHolder(itemView)
33 | }
34 |
35 | override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
36 | val document = mFilteredList[position]
37 |
38 | val drawable = document.fileType?.drawable ?: R.drawable.icon_file_unknown
39 | holder.imageView.setImageResource(drawable)
40 | if (drawable == R.drawable.icon_file_unknown || drawable == R.drawable.icon_file_pdf) {
41 | holder.fileTypeTv.visibility = View.VISIBLE
42 | holder.fileTypeTv.text = document.fileType?.title
43 | } else {
44 | holder.fileTypeTv.visibility = View.GONE
45 | }
46 |
47 | holder.fileNameTextView.text = document.name
48 | holder.fileSizeTextView.text = Formatter.formatShortFileSize(context, java.lang.Long.parseLong(document.size
49 | ?: "0"))
50 |
51 | holder.itemView.setOnClickListener { onItemClicked(document, holder) }
52 |
53 | //in some cases, it will prevent unwanted situations
54 | holder.checkBox.setOnCheckedChangeListener(null)
55 | holder.checkBox.setOnClickListener { onItemClicked(document, holder) }
56 |
57 | //if true, your checkbox will be selected, else unselected
58 | holder.checkBox.isChecked = isSelected(document)
59 |
60 | holder.itemView.setBackgroundResource(
61 | if (isSelected(document)) R.color.bg_gray else android.R.color.white)
62 | holder.checkBox.visibility = if (isSelected(document)) View.VISIBLE else View.GONE
63 |
64 | holder.checkBox.setOnCheckedChangeListener(object : SmoothCheckBox.OnCheckedChangeListener {
65 | override fun onCheckedChanged(checkBox: SmoothCheckBox, isChecked: Boolean) {
66 | toggleSelection(document)
67 | if (isChecked) {
68 | PickerManager.add(document.path, FilePickerConst.FILE_TYPE_DOCUMENT)
69 | } else {
70 | PickerManager.remove(document.path, FilePickerConst.FILE_TYPE_DOCUMENT)
71 | }
72 | holder.itemView.setBackgroundResource(if (isChecked) R.color.bg_gray else android.R.color.white)
73 | }
74 | })
75 | }
76 |
77 | private fun onItemClicked(document: Document, holder: FileViewHolder) {
78 | if (PickerManager.getMaxCount() == 1) {
79 | PickerManager.add(document.path, FilePickerConst.FILE_TYPE_DOCUMENT)
80 | } else {
81 | if (holder.checkBox.isChecked) {
82 | holder.checkBox.setChecked(!holder.checkBox.isChecked, true)
83 | holder.checkBox.visibility = View.GONE
84 | } else if (PickerManager.shouldAdd()) {
85 | holder.checkBox.setChecked(!holder.checkBox.isChecked, true)
86 | holder.checkBox.visibility = View.VISIBLE
87 | }
88 | }
89 |
90 | mListener?.onItemSelected()
91 | }
92 |
93 | override fun getItemCount(): Int {
94 | return mFilteredList.size
95 | }
96 |
97 | override fun getFilter(): Filter {
98 | return object : Filter() {
99 | override fun performFiltering(charSequence: CharSequence): Filter.FilterResults {
100 |
101 | val charString = charSequence.toString()
102 |
103 | if (charString.isEmpty()) {
104 |
105 | mFilteredList = items
106 | } else {
107 |
108 | val filteredList = ArrayList()
109 |
110 | for (document in items) {
111 |
112 | if (document.name.toLowerCase().contains(charString)) {
113 |
114 | filteredList.add(document)
115 | }
116 | }
117 |
118 | mFilteredList = filteredList
119 | }
120 |
121 | val filterResults = Filter.FilterResults()
122 | filterResults.values = mFilteredList
123 | return filterResults
124 | }
125 |
126 | @Suppress("UNCHECKED_CAST")
127 | override fun publishResults(charSequence: CharSequence, filterResults: Filter.FilterResults) {
128 | mFilteredList = filterResults.values as List
129 | notifyDataSetChanged()
130 | }
131 | }
132 | }
133 |
134 | class FileViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
135 | internal var fileTypeTv: TextView
136 |
137 | internal var checkBox: SmoothCheckBox
138 |
139 | internal var imageView: ImageView
140 |
141 | internal var fileNameTextView: TextView
142 |
143 | internal var fileSizeTextView: TextView
144 |
145 | init {
146 | checkBox = itemView.findViewById(R.id.checkbox)
147 | imageView = itemView.findViewById(R.id.file_iv)
148 | fileNameTextView = itemView.findViewById(R.id.file_name_tv)
149 | fileTypeTv = itemView.findViewById(R.id.file_type_tv)
150 | fileSizeTextView = itemView.findViewById(R.id.file_size_tv)
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/FolderGridAdapter.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | import android.content.Context
4 | import androidx.recyclerview.widget.RecyclerView
5 | import android.util.DisplayMetrics
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.view.WindowManager
10 | import android.widget.ImageView
11 | import android.widget.TextView
12 |
13 | import com.bumptech.glide.RequestManager
14 | import com.bumptech.glide.request.RequestOptions
15 |
16 | import java.io.File
17 | import java.util.ArrayList
18 |
19 | import droidninja.filepicker.PickerManager
20 | import droidninja.filepicker.R
21 | import droidninja.filepicker.models.PhotoDirectory
22 | import droidninja.filepicker.utils.AndroidLifecycleUtils
23 |
24 | class FolderGridAdapter(private val context: Context, private val glide: RequestManager, var items: List, private val showCamera: Boolean) : RecyclerView.Adapter() {
25 | private var imageSize: Int = 0
26 | private var folderGridAdapterListener: FolderGridAdapterListener? = null
27 |
28 | interface FolderGridAdapterListener {
29 | fun onCameraClicked()
30 | fun onFolderClicked(photoDirectory: PhotoDirectory)
31 | }
32 |
33 | init {
34 | setColumnNumber(context, 3)
35 | }
36 |
37 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder {
38 | val itemView = LayoutInflater.from(context).inflate(R.layout.item_folder_layout, parent, false)
39 |
40 | return PhotoViewHolder(itemView)
41 | }
42 |
43 | override fun getItemViewType(position: Int): Int {
44 | return if (showCamera)
45 | if (position == 0) ITEM_TYPE_CAMERA else ITEM_TYPE_PHOTO
46 | else
47 | ITEM_TYPE_PHOTO
48 | }
49 |
50 | override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
51 | if (getItemViewType(position) == ITEM_TYPE_PHOTO) {
52 |
53 | val photoDirectory = items[if (showCamera) position - 1 else position]
54 |
55 | if (AndroidLifecycleUtils.canLoadImage(holder.imageView.context)) {
56 | glide.load(photoDirectory.getCoverPath())
57 | .apply(RequestOptions
58 | .centerCropTransform()
59 | .override(imageSize, imageSize)
60 | .placeholder(R.drawable.image_placeholder))
61 | .thumbnail(0.5f)
62 | .into(holder.imageView)
63 | }
64 |
65 | holder.folderTitle.text = photoDirectory.name
66 | holder.folderCount.text = photoDirectory.medias.size.toString()
67 |
68 | holder.itemView.setOnClickListener {
69 | folderGridAdapterListener?.onFolderClicked(photoDirectory)
70 | }
71 | holder.bottomOverlay.visibility = View.VISIBLE
72 | } else {
73 | holder.imageView.setImageResource(PickerManager.cameraDrawable)
74 | holder.itemView.setOnClickListener {
75 | folderGridAdapterListener?.onCameraClicked()
76 | }
77 | holder.bottomOverlay.visibility = View.GONE
78 | }
79 | }
80 |
81 | fun setData(newItems: List) {
82 | this.items = newItems
83 | }
84 |
85 | private fun setColumnNumber(context: Context, columnNum: Int) {
86 | val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
87 | val metrics = DisplayMetrics()
88 | wm.defaultDisplay.getMetrics(metrics)
89 | val widthPixels = metrics.widthPixels
90 | imageSize = widthPixels / columnNum
91 | }
92 |
93 | override fun getItemCount(): Int {
94 | return if (showCamera) items.size + 1 else items.size
95 | }
96 |
97 | fun setFolderGridAdapterListener(onClickListener: FolderGridAdapterListener) {
98 | this.folderGridAdapterListener = onClickListener
99 | }
100 |
101 | class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
102 |
103 | var imageView: ImageView
104 | var folderTitle: TextView
105 | var folderCount: TextView
106 | var bottomOverlay: View
107 | var selectBg: View
108 |
109 | init {
110 | imageView = itemView.findViewById(R.id.iv_photo) as ImageView
111 | folderTitle = itemView.findViewById(R.id.folder_title) as TextView
112 | folderCount = itemView.findViewById(R.id.folder_count) as TextView
113 | bottomOverlay = itemView.findViewById(R.id.bottomOverlay)
114 | selectBg = itemView.findViewById(R.id.transparent_bg)
115 | }
116 | }
117 |
118 | companion object {
119 |
120 | val ITEM_TYPE_CAMERA = 100
121 | val ITEM_TYPE_PHOTO = 101
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/PhotoGridAdapter.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import androidx.recyclerview.widget.RecyclerView
6 | import android.util.DisplayMetrics
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.view.WindowManager
11 | import android.widget.ImageView
12 |
13 | import com.bumptech.glide.RequestManager
14 | import com.bumptech.glide.request.RequestOptions
15 |
16 | import java.io.File
17 | import java.util.ArrayList
18 |
19 | import droidninja.filepicker.FilePickerConst
20 | import droidninja.filepicker.PickerManager
21 | import droidninja.filepicker.R
22 | import droidninja.filepicker.models.Media
23 | import droidninja.filepicker.utils.AndroidLifecycleUtils
24 | import droidninja.filepicker.views.SmoothCheckBox
25 |
26 | class PhotoGridAdapter(private val context: Context,
27 | private val glide: RequestManager,
28 | medias: List,
29 | selectedPaths: MutableList,
30 | private val showCamera: Boolean,
31 | private val mListener: FileAdapterListener?) : SelectableAdapter(medias, selectedPaths) {
32 | private var imageSize: Int = 0
33 | private var cameraOnClickListener: View.OnClickListener? = null
34 |
35 | init {
36 | setColumnNumber(context, 3)
37 | }
38 |
39 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoViewHolder {
40 | val itemView = LayoutInflater.from(context).inflate(R.layout.item_photo_layout, parent, false)
41 |
42 | return PhotoViewHolder(itemView)
43 | }
44 |
45 | override fun getItemViewType(position: Int): Int {
46 | return if (showCamera)
47 | if (position == 0) ITEM_TYPE_CAMERA else ITEM_TYPE_PHOTO
48 | else
49 | ITEM_TYPE_PHOTO
50 | }
51 |
52 | override fun onBindViewHolder(holder: PhotoViewHolder, position: Int) {
53 | if (getItemViewType(position) == ITEM_TYPE_PHOTO) {
54 |
55 | val media = items[if (showCamera) position - 1 else position]
56 |
57 | if (AndroidLifecycleUtils.canLoadImage(holder.imageView.context)) {
58 | glide.load(media.path)
59 | .apply(RequestOptions
60 | .centerCropTransform()
61 | .override(imageSize, imageSize)
62 | .placeholder(R.drawable.image_placeholder))
63 | .thumbnail(0.5f)
64 | .into(holder.imageView)
65 | }
66 |
67 |
68 | if (media.mediaType == FilePickerConst.MEDIA_TYPE_VIDEO)
69 | holder.videoIcon.visibility = View.VISIBLE
70 | else
71 | holder.videoIcon.visibility = View.GONE
72 |
73 | holder.itemView.setOnClickListener { onItemClicked(holder, media) }
74 |
75 | //in some cases, it will prevent unwanted situations
76 | holder.checkBox.visibility = View.GONE
77 | holder.checkBox.setOnCheckedChangeListener(null)
78 | holder.checkBox.setOnClickListener { onItemClicked(holder, media) }
79 |
80 | //if true, your checkbox will be selected, else unselected
81 | holder.checkBox.isChecked = isSelected(media)
82 |
83 | holder.selectBg.visibility = if (isSelected(media)) View.VISIBLE else View.GONE
84 | holder.checkBox.visibility = if (isSelected(media)) View.VISIBLE else View.GONE
85 |
86 | holder.checkBox.setOnCheckedChangeListener(object : SmoothCheckBox.OnCheckedChangeListener {
87 | override fun onCheckedChanged(checkBox: SmoothCheckBox, isChecked: Boolean) {
88 | toggleSelection(media)
89 | holder.selectBg.visibility = if (isChecked) View.VISIBLE else View.GONE
90 |
91 | if (isChecked) {
92 | holder.checkBox.visibility = View.VISIBLE
93 | PickerManager.add(media.path, FilePickerConst.FILE_TYPE_MEDIA)
94 | } else {
95 | holder.checkBox.visibility = View.GONE
96 | PickerManager.remove(media.path, FilePickerConst.FILE_TYPE_MEDIA)
97 | }
98 |
99 | mListener?.onItemSelected()
100 | }
101 | })
102 |
103 | } else {
104 | holder.imageView.setImageResource(PickerManager.cameraDrawable)
105 | holder.checkBox.visibility = View.GONE
106 | holder.itemView.setOnClickListener(cameraOnClickListener)
107 | holder.videoIcon.visibility = View.GONE
108 | }
109 | }
110 |
111 | private fun onItemClicked(holder: PhotoViewHolder, media: Media) {
112 | if (PickerManager.getMaxCount() == 1) {
113 | PickerManager.add(media.path, FilePickerConst.FILE_TYPE_MEDIA)
114 | mListener?.onItemSelected()
115 | } else if (holder.checkBox.isChecked || PickerManager.shouldAdd()) {
116 | holder.checkBox.setChecked(!holder.checkBox.isChecked, true)
117 | }
118 | }
119 |
120 | private fun setColumnNumber(context: Context, columnNum: Int) {
121 | val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
122 | val metrics = DisplayMetrics()
123 | wm.defaultDisplay.getMetrics(metrics)
124 | val widthPixels = metrics.widthPixels
125 | imageSize = widthPixels / columnNum
126 | }
127 |
128 | override fun getItemCount(): Int {
129 | return if (showCamera) items.size + 1 else items.size
130 | }
131 |
132 | fun setCameraListener(onClickListener: View.OnClickListener) {
133 | this.cameraOnClickListener = onClickListener
134 | }
135 |
136 | class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
137 |
138 | var checkBox: SmoothCheckBox
139 |
140 | var imageView: ImageView
141 |
142 | var videoIcon: ImageView
143 |
144 | var selectBg: View
145 |
146 | init {
147 | checkBox = itemView.findViewById(R.id.checkbox) as SmoothCheckBox
148 | imageView = itemView.findViewById(R.id.iv_photo) as ImageView
149 | videoIcon = itemView.findViewById(R.id.video_icon) as ImageView
150 | selectBg = itemView.findViewById(R.id.transparent_bg)
151 | }
152 | }
153 |
154 | companion object {
155 |
156 | val ITEM_TYPE_CAMERA = 100
157 | val ITEM_TYPE_PHOTO = 101
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/SectionsPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.fragment.app.FragmentManager
5 | import androidx.fragment.app.FragmentStatePagerAdapter
6 | import java.util.*
7 |
8 | class SectionsPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
9 |
10 | private val mFragmentList = ArrayList()
11 | private val mFragmentTitles = ArrayList()
12 |
13 | override fun getItem(position: Int): Fragment {
14 | return mFragmentList[position]
15 | }
16 |
17 | override fun getCount(): Int {
18 | // Show 3 total pages.
19 | return mFragmentList.size
20 | }
21 |
22 | fun addFragment(fragment: Fragment, title: String) {
23 | mFragmentList.add(fragment)
24 | mFragmentTitles.add(title)
25 | }
26 |
27 | override fun getPageTitle(position: Int): CharSequence? {
28 | return mFragmentTitles[position]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/Selectable.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | /**
4 | * Created by donglua on 15/6/30.
5 | */
6 | interface Selectable {
7 |
8 | /**
9 | * Count the selected items
10 | *
11 | * @return Selected items count
12 | */
13 | val selectedItemCount: Int
14 |
15 |
16 | /**
17 | * Indicates if the item at position position is selected
18 | *
19 | * @param item to check
20 | * @return true if the item is selected, false otherwise
21 | */
22 | fun isSelected(item: T): Boolean
23 |
24 | /**
25 | * Toggle the selection status of the item at a given position
26 | *
27 | * @param item to toggle the selection status for
28 | */
29 | fun toggleSelection(item: T)
30 |
31 | /**
32 | * Clear the selection status for all items
33 | */
34 | fun clearSelection()
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/adapters/SelectableAdapter.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.adapters
2 |
3 | import android.net.Uri
4 | import androidx.recyclerview.widget.RecyclerView
5 |
6 | import java.util.ArrayList
7 |
8 | import droidninja.filepicker.PickerManager
9 | import droidninja.filepicker.models.BaseFile
10 |
11 | abstract class SelectableAdapter(var items: List, var selectedPaths: MutableList = mutableListOf()) : RecyclerView.Adapter(), Selectable {
12 |
13 | override val selectedItemCount: Int
14 | get() = selectedPaths.size
15 | /**
16 | * Indicates if the item at position where is selected
17 | *
18 | * @param photo Media of the item to check
19 | * @return true if the item is selected, false otherwise
20 | */
21 | override fun isSelected(item: T): Boolean {
22 | return selectedPaths.contains(item.path)
23 | }
24 |
25 | /**
26 | * Toggle the selection status of the item at a given position
27 | *
28 | * @param photo Media of the item to toggle the selection status for
29 | */
30 | override fun toggleSelection(item: T) {
31 | if (selectedPaths.contains(item.path)) {
32 | selectedPaths.remove(item.path)
33 | } else {
34 | selectedPaths.add(item.path)
35 | }
36 | }
37 |
38 | /**
39 | * Clear the selection status for all items
40 | */
41 | override fun clearSelection() {
42 | selectedPaths.clear()
43 | notifyDataSetChanged()
44 | }
45 |
46 | fun selectAll() {
47 | selectedPaths.clear()
48 | selectedPaths.addAll(items.map {
49 | it.path
50 | })
51 | notifyDataSetChanged()
52 | }
53 |
54 | fun setData(items: List, selectedPaths: MutableList) {
55 | this.items = items
56 | this.selectedPaths = selectedPaths
57 | notifyDataSetChanged()
58 | }
59 |
60 | companion object {
61 |
62 | private val TAG = "SelectableAdapter"
63 | }
64 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/fragments/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.fragments
2 |
3 | import androidx.fragment.app.Fragment
4 | import android.view.View
5 | import android.view.animation.Animation
6 | import android.view.animation.AnimationUtils
7 | import kotlinx.coroutines.CoroutineScope
8 | import kotlinx.coroutines.Dispatchers
9 |
10 | /**
11 | * A simple [Fragment] subclass.
12 | */
13 | abstract class BaseFragment : Fragment() {
14 |
15 | open val uiScope = CoroutineScope(Dispatchers.Main)
16 |
17 | companion object {
18 |
19 | val FILE_TYPE = "FILE_TYPE"
20 | }
21 | }// Required empty public constructor
22 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/fragments/DocFragment.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.fragments
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 | import androidx.appcompat.widget.SearchView
8 | import android.view.LayoutInflater
9 | import android.view.Menu
10 | import android.view.MenuInflater
11 | import android.view.MenuItem
12 | import android.view.View
13 | import android.view.ViewGroup
14 | import android.widget.TextView
15 | import droidninja.filepicker.FilePickerConst
16 | import droidninja.filepicker.PickerManager
17 | import droidninja.filepicker.R
18 | import droidninja.filepicker.adapters.FileAdapterListener
19 | import droidninja.filepicker.adapters.FileListAdapter
20 | import droidninja.filepicker.models.Document
21 | import droidninja.filepicker.models.FileType
22 |
23 | class DocFragment : BaseFragment(), FileAdapterListener {
24 | lateinit var recyclerView: RecyclerView
25 |
26 | lateinit var emptyView: TextView
27 |
28 | private var mListener: DocFragmentListener? = null
29 | private var selectAllItem: MenuItem? = null
30 | private var fileListAdapter: FileListAdapter? = null
31 |
32 | val fileType: FileType?
33 | get() = arguments?.getParcelable(BaseFragment.Companion.FILE_TYPE)
34 |
35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
36 | savedInstanceState: Bundle?): View? {
37 | // Inflate the layout for this fragment
38 | return inflater.inflate(R.layout.fragment_photo_picker, container, false)
39 | }
40 |
41 | override fun onAttach(context: Context) {
42 | super.onAttach(context)
43 | if (context is DocFragmentListener) {
44 | mListener = context
45 | } else {
46 | throw RuntimeException(
47 | "$context must implement PhotoPickerFragmentListener")
48 | }
49 | }
50 |
51 | override fun onDetach() {
52 | super.onDetach()
53 | mListener = null
54 | }
55 |
56 | override fun onCreate(savedInstanceState: Bundle?) {
57 | super.onCreate(savedInstanceState)
58 | setHasOptionsMenu(true)
59 | }
60 |
61 | override fun onItemSelected() {
62 | mListener?.onItemSelected()
63 | fileListAdapter?.let { adapter->
64 | selectAllItem?.let { menuItem ->
65 | if (adapter.itemCount == adapter.selectedItemCount) {
66 | menuItem.setIcon(R.drawable.ic_select_all)
67 | menuItem.isChecked = true
68 | }
69 | }
70 | }
71 | }
72 |
73 | interface DocFragmentListener {
74 | fun onItemSelected()
75 | }
76 |
77 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
78 | super.onViewCreated(view, savedInstanceState)
79 | initView(view)
80 | }
81 |
82 | private fun initView(view: View) {
83 | recyclerView = view.findViewById(R.id.recyclerview)
84 | emptyView = view.findViewById(R.id.empty_view)
85 | recyclerView.layoutManager = LinearLayoutManager(activity)
86 | recyclerView.visibility = View.GONE
87 | }
88 |
89 | fun updateList(dirs: List) {
90 | view?.let {
91 | if (dirs.isNotEmpty()) {
92 | recyclerView.visibility = View.VISIBLE
93 | emptyView.visibility = View.GONE
94 |
95 | context?.let {
96 | fileListAdapter = recyclerView.adapter as? FileListAdapter
97 | if (fileListAdapter == null) {
98 | fileListAdapter = FileListAdapter(it, dirs, PickerManager.selectedFiles,
99 | this)
100 |
101 | recyclerView.adapter = fileListAdapter
102 | } else {
103 | fileListAdapter?.setData(dirs, PickerManager.selectedFiles)
104 | }
105 | onItemSelected()
106 | }
107 | } else {
108 | recyclerView.visibility = View.GONE
109 | emptyView.visibility = View.VISIBLE
110 | }
111 | }
112 | }
113 |
114 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
115 | inflater.inflate(R.menu.doc_picker_menu, menu)
116 | selectAllItem = menu.findItem(R.id.action_select)
117 | if (PickerManager.hasSelectAll()) {
118 | selectAllItem?.isVisible = true
119 | onItemSelected()
120 | } else {
121 | selectAllItem?.isVisible = false
122 | }
123 |
124 | val search = menu.findItem(R.id.search)
125 | val searchView = search?.actionView as SearchView
126 | searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
127 | override fun onQueryTextSubmit(query: String): Boolean {
128 |
129 | return false
130 | }
131 |
132 | override fun onQueryTextChange(newText: String): Boolean {
133 | fileListAdapter?.filter?.filter(newText)
134 | return true
135 | }
136 | })
137 |
138 | super.onCreateOptionsMenu(menu, inflater)
139 | }
140 |
141 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
142 | val itemId = item.itemId
143 | if (itemId == R.id.action_select) {
144 | fileListAdapter?.let { adapter->
145 | selectAllItem?.let { menuItem ->
146 | if (menuItem.isChecked) {
147 | adapter.clearSelection()
148 | PickerManager.clearSelections()
149 |
150 | menuItem.setIcon(R.drawable.ic_deselect_all)
151 | } else {
152 | adapter.selectAll()
153 | PickerManager
154 | .add(adapter.selectedPaths, FilePickerConst.FILE_TYPE_DOCUMENT)
155 | menuItem.setIcon(R.drawable.ic_select_all)
156 | }
157 |
158 | menuItem.isChecked = !menuItem.isChecked
159 | mListener?.onItemSelected()
160 | }
161 | }
162 | return true
163 | } else {
164 | return super.onOptionsItemSelected(item)
165 | }
166 | }
167 |
168 | companion object {
169 |
170 | private val TAG = DocFragment::class.java.simpleName
171 |
172 | fun newInstance(fileType: FileType): DocFragment {
173 | val photoPickerFragment = DocFragment()
174 | val bun = Bundle()
175 | bun.putParcelable(BaseFragment.Companion.FILE_TYPE, fileType)
176 | photoPickerFragment.arguments = bun
177 | return photoPickerFragment
178 | }
179 | }
180 | }// Required empty public constructor
181 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/fragments/DocPickerFragment.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.fragments
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ProgressBar
9 | import androidx.lifecycle.Observer
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.viewpager.widget.ViewPager
12 | import com.google.android.material.tabs.TabLayout
13 | import droidninja.filepicker.PickerManager
14 | import droidninja.filepicker.R
15 | import droidninja.filepicker.adapters.SectionsPagerAdapter
16 | import droidninja.filepicker.models.Document
17 | import droidninja.filepicker.models.FileType
18 | import droidninja.filepicker.utils.TabLayoutHelper
19 | import droidninja.filepicker.viewmodels.VMDocPicker
20 |
21 | class DocPickerFragment : BaseFragment() {
22 |
23 | lateinit var tabLayout: TabLayout
24 | lateinit var viewModel: VMDocPicker
25 | lateinit var viewPager: ViewPager
26 | private var progressBar: ProgressBar? = null
27 | private var mListener: DocPickerFragmentListener? = null
28 |
29 | interface DocPickerFragmentListener
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | setHasOptionsMenu(true)
34 | viewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(requireActivity().application)).get(VMDocPicker::class.java)
35 | }
36 |
37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
38 | savedInstanceState: Bundle?): View? {
39 | // Inflate the layout for this fragment
40 | return inflater.inflate(R.layout.fragment_doc_picker, container, false)
41 | }
42 |
43 | override fun onAttach(context: Context) {
44 | super.onAttach(context)
45 | if (context is DocPickerFragmentListener) {
46 | mListener = context
47 | } else {
48 | throw RuntimeException("$context must implement DocPickerFragmentListener")
49 | }
50 | }
51 |
52 | override fun onDetach() {
53 | super.onDetach()
54 | mListener = null
55 | }
56 |
57 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
58 | super.onViewCreated(view, savedInstanceState)
59 | setViews(view)
60 | initView()
61 | }
62 |
63 | private fun initView() {
64 | setUpViewPager()
65 | viewModel.lvDocData.observe(viewLifecycleOwner, Observer { files ->
66 | progressBar?.visibility = View.GONE
67 | setDataOnFragments(files)
68 | })
69 | viewModel.getDocs(PickerManager.getFileTypes(), PickerManager.sortingType.comparator)
70 | }
71 |
72 | private fun setViews(view: View) {
73 | tabLayout = view.findViewById(R.id.tabs)
74 | viewPager = view.findViewById(R.id.viewPager)
75 | progressBar = view.findViewById(R.id.progress_bar)
76 |
77 | tabLayout.tabGravity = TabLayout.GRAVITY_FILL
78 | tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
79 | }
80 |
81 | private fun setDataOnFragments(filesMap: Map>) {
82 | view.let {
83 | val sectionsPagerAdapter = viewPager.adapter as SectionsPagerAdapter?
84 | if (sectionsPagerAdapter != null) {
85 | for (index in 0 until sectionsPagerAdapter.count) {
86 | val docFragment = sectionsPagerAdapter.getItem(index)
87 | if (docFragment is DocFragment) {
88 | val fileType = docFragment.fileType
89 | if (fileType != null) {
90 | val filesFilteredByType = filesMap[fileType]
91 | if (filesFilteredByType != null)
92 | docFragment.updateList(filesFilteredByType)
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | private fun setUpViewPager() {
101 | val adapter = SectionsPagerAdapter(childFragmentManager)
102 | val supportedTypes = PickerManager.getFileTypes()
103 | for (index in supportedTypes.indices) {
104 | adapter.addFragment(DocFragment.newInstance(supportedTypes[index]), supportedTypes[index].title)
105 | }
106 |
107 | viewPager.offscreenPageLimit = supportedTypes.size
108 | viewPager.adapter = adapter
109 | tabLayout.setupWithViewPager(viewPager)
110 |
111 | val mTabLayoutHelper = TabLayoutHelper(tabLayout, viewPager)
112 | mTabLayoutHelper.isAutoAdjustTabModeEnabled = true
113 | }
114 |
115 | companion object {
116 |
117 | private const val TAG = "DocPickerFragment"
118 | fun newInstance(): DocPickerFragment {
119 | return DocPickerFragment()
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/fragments/MediaFolderPickerFragment.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.fragments
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import androidx.recyclerview.widget.DefaultItemAnimator
8 | import androidx.recyclerview.widget.GridLayoutManager
9 | import androidx.recyclerview.widget.RecyclerView
10 | import android.view.LayoutInflater
11 | import android.view.View
12 | import android.view.ViewGroup
13 | import android.widget.TextView
14 | import android.widget.Toast
15 | import androidx.lifecycle.Observer
16 | import androidx.lifecycle.ViewModelProvider
17 | import com.bumptech.glide.Glide
18 | import com.bumptech.glide.RequestManager
19 | import droidninja.filepicker.FilePickerConst
20 | import droidninja.filepicker.MediaDetailsActivity
21 | import droidninja.filepicker.PickerManager
22 | import droidninja.filepicker.R
23 | import droidninja.filepicker.adapters.FolderGridAdapter
24 | import droidninja.filepicker.models.PhotoDirectory
25 | import droidninja.filepicker.utils.AndroidLifecycleUtils
26 | import droidninja.filepicker.utils.GridSpacingItemDecoration
27 | import droidninja.filepicker.utils.ImageCaptureManager
28 | import droidninja.filepicker.viewmodels.VMMediaPicker
29 | import kotlinx.coroutines.Dispatchers
30 | import kotlinx.coroutines.launch
31 | import kotlinx.coroutines.withContext
32 | import java.io.IOException
33 |
34 | class MediaFolderPickerFragment : BaseFragment(), FolderGridAdapter.FolderGridAdapterListener {
35 | lateinit var recyclerView: RecyclerView
36 |
37 | lateinit var emptyView: TextView
38 | lateinit var viewModel: VMMediaPicker
39 |
40 | private var mListener: PhotoPickerFragmentListener? = null
41 | private var photoGridAdapter: FolderGridAdapter? = null
42 | private var imageCaptureManager: ImageCaptureManager? = null
43 | private lateinit var mGlideRequestManager: RequestManager
44 | private var fileType: Int = 0
45 | private var imageFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE
46 | private var videoFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE
47 |
48 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
49 | savedInstanceState: Bundle?): View? {
50 | // Inflate the layout for this fragment
51 | return inflater.inflate(R.layout.fragment_media_folder_picker, container, false)
52 | }
53 |
54 | override fun onAttach(context: Context) {
55 | super.onAttach(context)
56 | if (context is PhotoPickerFragmentListener) {
57 | mListener = context
58 | } else {
59 | throw RuntimeException(
60 | "$context must implement PhotoPickerFragmentListener")
61 | }
62 | }
63 |
64 | override fun onDetach() {
65 | super.onDetach()
66 | mListener = null
67 | }
68 |
69 | override fun onCreate(savedInstanceState: Bundle?) {
70 | super.onCreate(savedInstanceState)
71 | mGlideRequestManager = Glide.with(this)
72 | viewModel = ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(requireActivity().application)).get(VMMediaPicker::class.java)
73 | }
74 |
75 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
76 | super.onViewCreated(view, savedInstanceState)
77 | initView(view)
78 | }
79 |
80 | private fun initView(view: View) {
81 | recyclerView = view.findViewById(R.id.recyclerview)
82 | emptyView = view.findViewById(R.id.empty_view)
83 | arguments?.let {
84 | fileType = it.getInt(BaseFragment.FILE_TYPE)
85 | imageFileSize = it.getInt(FilePickerConst.EXTRA_IMAGE_FILE_SIZE)
86 | videoFileSize = it.getInt(FilePickerConst.EXTRA_VIDEO_FILE_SIZE)
87 | fileType = it.getInt(FILE_TYPE)
88 |
89 |
90 | imageCaptureManager = ImageCaptureManager(requireContext())
91 | val spanCount = PickerManager.spanTypes[FilePickerConst.SPAN_TYPE.FOLDER_SPAN] ?: 2// default 2 columns
92 | val spacing = 5 // 5px
93 | val includeEdge = false
94 | val layoutManager = GridLayoutManager(activity, spanCount)
95 | recyclerView.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing, includeEdge))
96 | recyclerView.layoutManager = layoutManager
97 | recyclerView.itemAnimator = DefaultItemAnimator()
98 |
99 | recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
100 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
101 | super.onScrolled(recyclerView, dx, dy)
102 | // Log.d(">>> Picker >>>", "dy = " + dy);
103 | if (Math.abs(dy) > SCROLL_THRESHOLD) {
104 | mGlideRequestManager.pauseRequests()
105 | } else {
106 | resumeRequestsIfNotDestroyed()
107 | }
108 | }
109 |
110 | override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
111 | if (newState == RecyclerView.SCROLL_STATE_IDLE) {
112 | resumeRequestsIfNotDestroyed()
113 | }
114 | }
115 | })
116 |
117 | viewModel.lvPhotoDirsData.observe(viewLifecycleOwner, Observer { data ->
118 | updateList(data)
119 | })
120 |
121 | viewModel.lvDataChanged.observe(viewLifecycleOwner, Observer {
122 | viewModel.getPhotoDirs(mediaType = fileType, imageFileSize = imageFileSize, videoFileSize = videoFileSize)
123 | })
124 |
125 | viewModel.getPhotoDirs(mediaType = fileType, imageFileSize = imageFileSize, videoFileSize = videoFileSize)
126 | }
127 | }
128 |
129 | private fun updateList(dirs: List) {
130 | view?.let {
131 | if (dirs.isNotEmpty()) {
132 | emptyView.visibility = View.GONE
133 | recyclerView.visibility = View.VISIBLE
134 | } else {
135 | emptyView.visibility = View.VISIBLE
136 | recyclerView.visibility = View.GONE
137 | return
138 | }
139 |
140 | if (photoGridAdapter == null) {
141 | photoGridAdapter = FolderGridAdapter(requireContext(), mGlideRequestManager, dirs, fileType == FilePickerConst.MEDIA_TYPE_IMAGE && PickerManager.isEnableCamera)
142 | recyclerView.adapter = photoGridAdapter
143 | photoGridAdapter?.setFolderGridAdapterListener(this)
144 | } else {
145 | photoGridAdapter?.setData(dirs)
146 | photoGridAdapter?.notifyDataSetChanged()
147 | }
148 | }
149 | }
150 |
151 | override fun onCameraClicked() {
152 | try {
153 | uiScope.launch {
154 | val intent = withContext(Dispatchers.IO) { imageCaptureManager?.dispatchTakePictureIntent() }
155 | if (intent != null) {
156 | startActivityForResult(intent, ImageCaptureManager.REQUEST_TAKE_PHOTO)
157 | } else {
158 | Toast.makeText(requireContext(), R.string.no_camera_exists, Toast.LENGTH_SHORT).show()
159 | }
160 | }
161 | } catch (e: IOException) {
162 | e.printStackTrace()
163 | }
164 |
165 | }
166 |
167 | override fun onFolderClicked(photoDirectory: PhotoDirectory) {
168 | val intent = Intent(activity, MediaDetailsActivity::class.java)
169 | intent.putExtra(PhotoDirectory::class.java.simpleName, photoDirectory.apply {
170 | medias.clear()
171 | })
172 | intent.putExtra(FilePickerConst.EXTRA_FILE_TYPE, fileType)
173 | intent.putExtra(FilePickerConst.EXTRA_IMAGE_FILE_SIZE, imageFileSize)
174 | intent.putExtra(FilePickerConst.EXTRA_VIDEO_FILE_SIZE, videoFileSize)
175 | activity?.startActivityForResult(intent, FilePickerConst.REQUEST_CODE_MEDIA_DETAIL)
176 | }
177 |
178 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
179 | super.onActivityResult(requestCode, resultCode, data)
180 | when (requestCode) {
181 | ImageCaptureManager.REQUEST_TAKE_PHOTO -> if (resultCode == Activity.RESULT_OK) {
182 | val imagePath = imageCaptureManager?.currentPhotoPath
183 | if (imagePath != null) {
184 | if (PickerManager.getMaxCount() == 1) {
185 | PickerManager.add(imagePath, FilePickerConst.FILE_TYPE_MEDIA)
186 | mListener?.onItemSelected()
187 | }
188 | }
189 | } else {
190 | uiScope.launch(Dispatchers.IO) {
191 | imageCaptureManager?.deleteContentUri(imageCaptureManager?.currentPhotoPath)
192 | }
193 | }
194 | }
195 | }
196 |
197 | private fun resumeRequestsIfNotDestroyed() {
198 | if (!AndroidLifecycleUtils.canLoadImage(this)) {
199 | return
200 | }
201 |
202 | mGlideRequestManager.resumeRequests()
203 | }
204 |
205 | companion object {
206 |
207 | private val TAG = MediaFolderPickerFragment::class.java.simpleName
208 | private const val SCROLL_THRESHOLD = 30
209 |
210 | fun newInstance(fileType: Int, imageFileSize: Int, videoFileSize: Int): MediaFolderPickerFragment {
211 | val photoPickerFragment = MediaFolderPickerFragment()
212 | val bun = Bundle()
213 | bun.putInt(BaseFragment.FILE_TYPE, fileType)
214 | bun.putInt(FilePickerConst.EXTRA_IMAGE_FILE_SIZE, imageFileSize)
215 | bun.putInt(FilePickerConst.EXTRA_VIDEO_FILE_SIZE, videoFileSize)
216 | bun.putInt(FILE_TYPE, fileType)
217 | photoPickerFragment.arguments = bun
218 | return photoPickerFragment
219 | }
220 | }
221 | }// Required empty public constructor
222 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/fragments/MediaPickerFragment.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.fragments
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import com.google.android.material.tabs.TabLayout
6 | import androidx.viewpager.widget.ViewPager
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import droidninja.filepicker.FilePickerConst
11 | import droidninja.filepicker.PickerManager
12 | import droidninja.filepicker.R
13 | import droidninja.filepicker.adapters.SectionsPagerAdapter
14 |
15 |
16 | class MediaPickerFragment : BaseFragment() {
17 |
18 | lateinit var tabLayout: TabLayout
19 |
20 | lateinit var viewPager: ViewPager
21 |
22 | private var mListener: MediaPickerFragmentListener? = null
23 |
24 |
25 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
26 | savedInstanceState: Bundle?): View? {
27 | // Inflate the layout for this fragment
28 | return inflater.inflate(R.layout.fragment_media_picker, container, false)
29 | }
30 |
31 | override fun onAttach(context: Context) {
32 | super.onAttach(context)
33 | if (context is MediaPickerFragmentListener) {
34 | mListener = context
35 | } else {
36 | throw RuntimeException("$context must implement MediaPickerFragment")
37 | }
38 | }
39 |
40 | override fun onDetach() {
41 | super.onDetach()
42 | mListener = null
43 | }
44 |
45 | interface MediaPickerFragmentListener
46 |
47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 | super.onViewCreated(view, savedInstanceState)
49 | initView(view)
50 | }
51 |
52 | private fun initView(view: View) {
53 | tabLayout = view.findViewById(R.id.tabs)
54 | viewPager = view.findViewById(R.id.viewPager)
55 | tabLayout.tabGravity = TabLayout.GRAVITY_FILL
56 | tabLayout.tabMode = TabLayout.MODE_FIXED
57 |
58 | val adapter = SectionsPagerAdapter(childFragmentManager)
59 |
60 | if (PickerManager.showImages()) {
61 | if (PickerManager.isShowFolderView)
62 | adapter.addFragment(MediaFolderPickerFragment.newInstance(FilePickerConst.MEDIA_TYPE_IMAGE, PickerManager.imageFileSize, PickerManager.videoFileSize), getString(R.string.images))
63 | else
64 | adapter.addFragment(MediaDetailPickerFragment.newInstance(FilePickerConst.MEDIA_TYPE_IMAGE, PickerManager.imageFileSize, PickerManager.videoFileSize), getString(R.string.images))
65 | } else
66 | tabLayout.visibility = View.GONE
67 |
68 | if (PickerManager.showVideo()) {
69 | if (PickerManager.isShowFolderView)
70 | adapter.addFragment(MediaFolderPickerFragment.newInstance(FilePickerConst.MEDIA_TYPE_VIDEO, PickerManager.imageFileSize, PickerManager.videoFileSize), getString(R.string.videos))
71 | else
72 | adapter.addFragment(MediaDetailPickerFragment.newInstance(FilePickerConst.MEDIA_TYPE_VIDEO, PickerManager.imageFileSize, PickerManager.videoFileSize), getString(R.string.videos))
73 | } else
74 | tabLayout.visibility = View.GONE
75 |
76 | viewPager.adapter = adapter
77 | tabLayout.setupWithViewPager(viewPager)
78 | }
79 |
80 | companion object {
81 |
82 | fun newInstance(): MediaPickerFragment {
83 | return MediaPickerFragment()
84 | }
85 | }
86 | }// Required empty public constructor
87 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/fragments/PhotoPickerFragmentListener.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.fragments
2 |
3 | interface PhotoPickerFragmentListener {
4 | fun onItemSelected()
5 | fun setToolbarTitle(count: Int)
6 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/BaseFile.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models
2 |
3 | import android.net.Uri
4 | import android.os.Parcel
5 | import android.os.Parcelable
6 | import droidninja.filepicker.utils.FilePickerUtils.contains
7 | import kotlinx.android.parcel.Parcelize
8 |
9 | /**
10 | * Created by droidNinja on 29/07/16.
11 | */
12 | @Parcelize
13 | open class BaseFile(open var id: Long = 0,
14 | open var name: String,
15 | open var path: Uri
16 | ) : Parcelable
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/Document.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models
2 |
3 | import android.net.Uri
4 | import kotlinx.android.parcel.Parcelize
5 |
6 | @Parcelize
7 | class Document @JvmOverloads constructor(override var id: Long = 0,
8 | override var name: String,
9 | override var path: Uri,
10 | var mimeType: String? = null,
11 | var size: String? = null,
12 | var fileType: FileType? = null
13 | ) : BaseFile(id, name, path)
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/FileType.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.DrawableRes
5 | import kotlinx.android.parcel.Parcelize
6 |
7 | /**
8 | * Created by droidNinja on 29/07/16.
9 | */
10 | @Parcelize
11 | class FileType constructor(
12 | var title: String,
13 | var extensions : Array,
14 | @DrawableRes
15 | var drawable: Int
16 | ) : Parcelable
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/Media.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import androidx.annotation.DrawableRes
6 | import kotlinx.android.parcel.Parcelize
7 |
8 | @Parcelize
9 | class Media @JvmOverloads constructor(override var id: Long = 0,
10 | override var name: String,
11 | override var path: Uri,
12 | var mediaType: Int = 0) : BaseFile(id, name, path)
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/PhotoDirectory.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models
2 |
3 | import android.net.Uri
4 | import android.os.Parcelable
5 | import kotlinx.android.parcel.Parcelize
6 |
7 | @Parcelize
8 | class PhotoDirectory(
9 | var id: Long = 0,
10 | var bucketId: String? = null,
11 | private var coverPath: Uri? = null,
12 | var name: String? = null,
13 | var dateAdded: Long = 0,
14 | val medias: MutableList = mutableListOf()
15 | ) : Parcelable {
16 |
17 | fun getCoverPath(): Uri? {
18 | return when {
19 | medias.size > 0 -> medias[0].path
20 | coverPath != null -> coverPath
21 | else -> null
22 | };
23 | }
24 |
25 | fun setCoverPath(coverPath: Uri?) {
26 | this.coverPath = coverPath
27 | }
28 |
29 | fun addPhoto(imageId: Long, fileName: String, path: Uri, mediaType: Int) {
30 | medias.add(Media(imageId, fileName, path, mediaType))
31 | }
32 |
33 | override fun equals(other: Any?): Boolean {
34 | return this.bucketId == (other as? PhotoDirectory)?.bucketId
35 | }
36 |
37 | override fun hashCode(): Int {
38 | var result = id.hashCode()
39 | result = 31 * result + (bucketId?.hashCode() ?: 0)
40 | result = 31 * result + (coverPath?.hashCode() ?: 0)
41 | result = 31 * result + (name?.hashCode() ?: 0)
42 | result = 31 * result + dateAdded.hashCode()
43 | result = 31 * result + medias.hashCode()
44 | return result
45 | }
46 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/sort/NameComparator.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models.sort
2 |
3 | import droidninja.filepicker.models.Document
4 | import java.util.*
5 |
6 | /**
7 | * Created by gabriel on 10/2/17.
8 | */
9 | class NameComparator : Comparator {
10 | override fun compare(o1: Document, o2: Document): Int {
11 | return o1.name.toLowerCase(Locale.getDefault()).compareTo(o2.name.toLowerCase(Locale.getDefault()))
12 | }
13 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/models/sort/SortingTypes.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.models.sort
2 |
3 | import droidninja.filepicker.models.Document
4 | import java.util.*
5 |
6 | /**
7 | * Created by gabriel on 10/2/17.
8 | */
9 | enum class SortingTypes(val comparator: Comparator?) {
10 | NAME(NameComparator()), NONE(null);
11 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/AndroidLifecycleUtils.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.os.Build
6 | import androidx.fragment.app.Fragment
7 | import androidx.fragment.app.FragmentActivity
8 |
9 | object AndroidLifecycleUtils {
10 | fun canLoadImage(fragment: Fragment?): Boolean {
11 | if (fragment == null) {
12 | return true
13 | }
14 |
15 | val activity = fragment.activity
16 |
17 | return canLoadImage(activity)
18 | }
19 |
20 | fun canLoadImage(context: Context?): Boolean {
21 | if (context == null) {
22 | return true
23 | }
24 |
25 | if (context !is Activity) {
26 | return true
27 | }
28 |
29 | val activity = context as Activity?
30 | return canLoadImage(activity)
31 | }
32 |
33 | fun canLoadImage(activity: Activity?): Boolean {
34 | if (activity == null) {
35 | return true
36 | }
37 |
38 | val destroyed = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed
39 |
40 | return !(destroyed || activity.isFinishing)
41 |
42 | }
43 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/ContentUriUtils.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import android.content.ContentUris
4 | import android.content.Context
5 | import android.net.Uri
6 | import android.os.Build
7 | import android.os.Environment
8 | import android.provider.DocumentsContract
9 | import android.provider.MediaStore
10 | import androidx.annotation.WorkerThread
11 | import java.net.URISyntaxException
12 |
13 | object ContentUriUtils {
14 | /**
15 | * make sure to use this getFilePath method from worker thread
16 | */
17 | @WorkerThread
18 | @Throws(URISyntaxException::class)
19 | fun getFilePath(context: Context, fileUri: Uri): String? {
20 | var uri = fileUri
21 | var selection: String? = null
22 | var selectionArgs: Array? = null
23 | // Uri is different in versions after KITKAT (Android 4.4), we need to
24 | if (Build.VERSION.SDK_INT >= 19 && DocumentsContract.isDocumentUri(context.applicationContext, uri)) {
25 | if (isExternalStorageDocument(uri)) {
26 | val docId = DocumentsContract.getDocumentId(uri)
27 | val split = docId.split(":").toTypedArray()
28 | return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
29 | } else if (isDownloadsDocument(uri)) {
30 | val id = DocumentsContract.getDocumentId(uri)
31 | uri = ContentUris.withAppendedId(
32 | Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
33 | } else if (isMediaDocument(uri)) {
34 | val docId = DocumentsContract.getDocumentId(uri)
35 | val split = docId.split(":").toTypedArray()
36 | val type = split[0]
37 | if ("image" == type) {
38 | uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
39 | } else if ("video" == type) {
40 | uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
41 | } else if ("audio" == type) {
42 | uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
43 | }
44 | selection = "_id=?"
45 | selectionArgs = arrayOf(
46 | split[1]
47 | )
48 | }
49 | }
50 | if ("content".equals(uri.scheme, ignoreCase = true)) {
51 | if (isGooglePhotosUri(uri)) {
52 | return uri.lastPathSegment
53 | }
54 | val projection = arrayOf(
55 | MediaStore.Images.Media.DATA
56 | )
57 | try {
58 | val cursor = context.contentResolver?.query(uri, projection, selection, selectionArgs, null)
59 | var path: String? = null
60 | if (cursor != null) {
61 | val column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
62 | if (cursor.moveToFirst()) {
63 | path = cursor.getString(column_index)
64 | cursor.close()
65 | }
66 | }
67 | return path
68 | } catch (e: Exception) {
69 | e.printStackTrace()
70 | }
71 | } else if ("file".equals(uri.scheme, ignoreCase = true)) {
72 | return uri.path
73 | }
74 | return null
75 | }
76 |
77 | fun isExternalStorageDocument(uri: Uri): Boolean {
78 | return "com.android.externalstorage.documents" == uri.authority
79 | }
80 |
81 | fun isDownloadsDocument(uri: Uri): Boolean {
82 | return "com.android.providers.downloads.documents" == uri.authority
83 | }
84 |
85 | fun isMediaDocument(uri: Uri): Boolean {
86 | return "com.android.providers.media.documents" == uri.authority
87 | }
88 |
89 | fun isGooglePhotosUri(uri: Uri): Boolean {
90 | return "com.google.android.apps.photos.content" == uri.authority
91 | }
92 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/FilePickerProvider.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import androidx.core.content.FileProvider
4 |
5 | /**
6 | * Created by Donglu on 2017/6/7.
7 | */
8 |
9 | class FilePickerProvider : FileProvider()
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/FilePickerUtils.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import android.content.ContentResolver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.database.ContentObserver
7 | import android.net.Uri
8 | import android.os.Handler
9 | import android.text.TextUtils
10 | import android.webkit.MimeTypeMap
11 | import java.io.File
12 |
13 | /**
14 | * Created by droidNinja on 29/07/16.
15 | */
16 | object FilePickerUtils {
17 |
18 | fun getFileExtension(file: File): String {
19 | val name = file.name
20 | try {
21 | return name.substring(name.lastIndexOf(".") + 1)
22 | } catch (e: Exception) {
23 | return ""
24 | }
25 |
26 | }
27 |
28 | fun contains(types: Array, mimeType: String?): Boolean {
29 | for (type in types) {
30 | if(MimeTypeMap.getSingleton().getMimeTypeFromExtension(type) == mimeType){
31 | return true
32 | }
33 | }
34 | return false
35 | }
36 | }
37 |
38 | fun ContentResolver.registerObserver(
39 | uri: Uri,
40 | observer: (selfChange: Boolean) -> Unit
41 | ): ContentObserver {
42 | val contentObserver = object : ContentObserver(Handler()) {
43 | override fun onChange(selfChange: Boolean) {
44 | observer(selfChange)
45 | }
46 | }
47 | registerContentObserver(uri, true, contentObserver)
48 | return contentObserver
49 | }
50 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/FileUtils.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import android.text.TextUtils
4 |
5 | import java.io.File
6 |
7 | import droidninja.filepicker.FilePickerConst
8 | import droidninja.filepicker.R
9 |
10 | /**
11 | * Created by droidNinja on 08/03/17.
12 | */
13 |
14 | object FileUtils {
15 |
16 | fun getFileType(path: String): FilePickerConst.FILE_TYPE {
17 | val fileExtension = FilePickerUtils.getFileExtension(File(path))
18 | if (TextUtils.isEmpty(fileExtension))
19 | return FilePickerConst.FILE_TYPE.UNKNOWN
20 |
21 | if (isExcelFile(path))
22 | return FilePickerConst.FILE_TYPE.EXCEL
23 | if (isDocFile(path))
24 | return FilePickerConst.FILE_TYPE.WORD
25 | if (isPPTFile(path))
26 | return FilePickerConst.FILE_TYPE.PPT
27 | if (isPDFFile(path))
28 | return FilePickerConst.FILE_TYPE.PDF
29 | return if (isTxtFile(path))
30 | FilePickerConst.FILE_TYPE.TXT
31 | else
32 | FilePickerConst.FILE_TYPE.UNKNOWN
33 | }
34 |
35 | fun isExcelFile(path: String): Boolean {
36 | val types = arrayOf("xls", "xlsx")
37 | return FilePickerUtils.contains(types, path)
38 | }
39 |
40 | fun isDocFile(path: String): Boolean {
41 | val types = arrayOf("doc", "docx", "dot", "dotx")
42 | return FilePickerUtils.contains(types, path)
43 | }
44 |
45 | fun isPPTFile(path: String): Boolean {
46 | val types = arrayOf("ppt", "pptx")
47 | return FilePickerUtils.contains(types, path)
48 | }
49 |
50 | fun isPDFFile(path: String): Boolean {
51 | val types = arrayOf("pdf")
52 | return FilePickerUtils.contains(types, path)
53 | }
54 |
55 | fun isTxtFile(path: String): Boolean {
56 | val types = arrayOf("txt")
57 | return FilePickerUtils.contains(types, path)
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/FragmentUtil.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.fragment.app.FragmentTransaction
5 | import androidx.appcompat.app.AppCompatActivity
6 |
7 | import droidninja.filepicker.R
8 | import droidninja.filepicker.fragments.BaseFragment
9 |
10 |
11 | object FragmentUtil {
12 |
13 | fun hadFragment(activity: AppCompatActivity): Boolean {
14 | return activity.supportFragmentManager.backStackEntryCount != 0
15 | }
16 |
17 | fun replaceFragment(activity: AppCompatActivity, contentId: Int, fragment: BaseFragment) {
18 | val transaction = activity.supportFragmentManager.beginTransaction()
19 |
20 | transaction.setCustomAnimations(R.anim.slide_left_in, R.anim.slide_left_out)
21 |
22 | transaction.replace(contentId, fragment, fragment.javaClass.simpleName)
23 |
24 | transaction.addToBackStack(null)
25 | transaction.commit()
26 | }
27 |
28 |
29 | fun addFragment(activity: AppCompatActivity, contentId: Int, fragment: BaseFragment) {
30 | val transaction = activity.supportFragmentManager.beginTransaction()
31 |
32 | transaction.setCustomAnimations(R.anim.slide_left_in, R.anim.slide_left_out)
33 |
34 | transaction.add(contentId, fragment, fragment.javaClass.simpleName)
35 |
36 | transaction.commit()
37 | }
38 |
39 | fun removeFragment(activity: AppCompatActivity, fragment: BaseFragment) {
40 | activity.supportFragmentManager.beginTransaction()
41 | .remove(fragment)
42 | .commit()
43 | }
44 |
45 |
46 | fun showFragment(activity: AppCompatActivity, fragment: BaseFragment) {
47 | activity.supportFragmentManager.beginTransaction()
48 | .show(fragment)
49 | .commit()
50 | }
51 |
52 | fun hideFragment(activity: AppCompatActivity, fragment: BaseFragment) {
53 | activity.supportFragmentManager.beginTransaction()
54 | .hide(fragment)
55 | .commit()
56 | }
57 |
58 | fun attachFragment(activity: AppCompatActivity, fragment: BaseFragment) {
59 | activity.supportFragmentManager.beginTransaction()
60 | .attach(fragment)
61 | .commit()
62 | }
63 |
64 | fun detachFragment(activity: AppCompatActivity, fragment: BaseFragment) {
65 | activity.supportFragmentManager.beginTransaction()
66 | .detach(fragment)
67 | .commit()
68 | }
69 |
70 | fun getFragmentByTag(appCompatActivity: AppCompatActivity, tag: String): Fragment? {
71 | return appCompatActivity.supportFragmentManager.findFragmentByTag(tag)
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/GridSpacingItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import android.graphics.Rect
4 | import androidx.recyclerview.widget.RecyclerView
5 | import android.view.View
6 |
7 | class GridSpacingItemDecoration(private val spanCount: Int, private val spacing: Int, private val includeEdge: Boolean) : RecyclerView.ItemDecoration() {
8 |
9 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
10 | val position = parent.getChildAdapterPosition(view) // item position
11 | val column = position % spanCount // item column
12 |
13 | if (includeEdge) {
14 | outRect.left = spacing - column * spacing / spanCount // spacing - column * ((1f / spanCount) * spacing)
15 | outRect.right = (column + 1) * spacing / spanCount // (column + 1) * ((1f / spanCount) * spacing)
16 |
17 | if (position < spanCount) { // top edge
18 | outRect.top = spacing
19 | }
20 | outRect.bottom = spacing // item bottom
21 | } else {
22 | outRect.left = column * spacing / spanCount // column * ((1f / spanCount) * spacing)
23 | outRect.right = spacing - (column + 1) * spacing / spanCount // spacing - (column + 1) * ((1f / spanCount) * spacing)
24 | if (position >= spanCount) {
25 | outRect.top = spacing // item top
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/utils/ImageCaptureManager.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.utils
2 |
3 | import android.content.ContentValues
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.net.Uri
7 | import android.os.Build
8 | import android.os.Bundle
9 | import android.os.Environment
10 | import android.provider.MediaStore
11 | import android.provider.Settings
12 | import androidx.core.content.FileProvider
13 | import android.text.TextUtils
14 | import android.util.Log
15 | import androidx.annotation.WorkerThread
16 |
17 | import java.io.File
18 | import java.io.FileNotFoundException
19 | import java.io.IOException
20 | import java.text.SimpleDateFormat
21 | import java.util.Date
22 | import java.util.Locale
23 |
24 | import droidninja.filepicker.PickerManager
25 |
26 | class ImageCaptureManager(private val mContext: Context) {
27 |
28 | var currentPhotoPath: Uri? = null
29 |
30 | @Throws(IOException::class)
31 | private fun createImageFile(): Uri? {
32 | val imageFileName = "JPEG_" + System.currentTimeMillis() + ".jpg"
33 | val resolver = mContext.contentResolver
34 | val contentValues = ContentValues().apply {
35 | put(MediaStore.MediaColumns.DISPLAY_NAME, imageFileName)
36 | put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
37 | }
38 |
39 | currentPhotoPath = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
40 |
41 | return currentPhotoPath
42 | }
43 |
44 |
45 | @WorkerThread
46 | @Throws(IOException::class)
47 | fun dispatchTakePictureIntent(): Intent? {
48 | val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
49 | // Ensure that there's a camera activity to handle the intent
50 | if (takePictureIntent.resolveActivity(mContext.packageManager) != null) {
51 | // Create the File where the photo should go
52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
53 | val photoURI = createImageFile()
54 | takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
55 | takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
56 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
57 | } else {
58 | takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, createImageFile())
59 | }
60 | return takePictureIntent
61 | }
62 | return null
63 | }
64 |
65 |
66 | fun deleteContentUri(path: Uri?) {
67 | if(path != null){
68 | mContext.contentResolver.delete(path, null , null)
69 | }
70 | }
71 |
72 | companion object {
73 |
74 | private val CAPTURED_PHOTO_PATH_KEY = "mCurrentPhotoPath"
75 | val REQUEST_TAKE_PHOTO = 0x101
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/viewmodels/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.viewmodels
2 |
3 | import android.app.Application
4 | import androidx.lifecycle.AndroidViewModel
5 | import androidx.lifecycle.LiveData
6 | import androidx.lifecycle.MutableLiveData
7 | import androidx.lifecycle.ViewModel
8 | import kotlinx.coroutines.*
9 |
10 | open class BaseViewModel(application: Application) : AndroidViewModel(application) {
11 |
12 | private val viewModelJob = SupervisorJob()
13 |
14 | private val exceptionHandler = CoroutineExceptionHandler { _, t ->
15 | t.printStackTrace()
16 | }
17 |
18 | private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob + exceptionHandler)
19 |
20 | private val _lvError = MutableLiveData()
21 | open val lvError: LiveData
22 | get() = _lvError
23 |
24 | fun launchDataLoad(block: suspend (scope: CoroutineScope) -> Unit): Job {
25 | return uiScope.launch {
26 | try {
27 | block(this)
28 | } catch (error: Exception) {
29 | handleException(error)
30 | } finally {
31 | }
32 | }
33 | }
34 |
35 | private fun handleException(error: Exception) {
36 | error.printStackTrace()
37 | if (error !is CancellationException) {
38 | _lvError.value = error
39 | }
40 | }
41 |
42 | public override fun onCleared() {
43 | super.onCleared()
44 | viewModelJob.cancel()
45 | }
46 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/viewmodels/VMDocPicker.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.viewmodels
2 |
3 | import android.app.Application
4 | import android.content.ContentUris
5 | import android.database.Cursor
6 | import android.provider.BaseColumns
7 | import android.provider.MediaStore
8 | import android.text.TextUtils
9 | import androidx.annotation.WorkerThread
10 | import androidx.lifecycle.LiveData
11 | import androidx.lifecycle.MutableLiveData
12 | import droidninja.filepicker.FilePickerConst
13 | import droidninja.filepicker.PickerManager
14 | import droidninja.filepicker.models.Document
15 | import droidninja.filepicker.models.FileType
16 | import droidninja.filepicker.utils.FilePickerUtils
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.withContext
19 | import java.io.File
20 | import java.util.*
21 |
22 | class VMDocPicker(application: Application) : BaseViewModel(application) {
23 | private val _lvDocData = MutableLiveData>>()
24 | val lvDocData: LiveData>>
25 | get() = _lvDocData
26 |
27 |
28 | fun getDocs(fileTypes: List, comparator: Comparator?) {
29 | launchDataLoad {
30 | val dirs = queryDocs(fileTypes, comparator)
31 | _lvDocData.postValue(dirs)
32 | }
33 | }
34 |
35 | @WorkerThread
36 | suspend fun queryDocs(fileTypes: List, comparator: Comparator?): HashMap> {
37 | var data = HashMap>()
38 | withContext(Dispatchers.IO) {
39 |
40 | val selection = ("${MediaStore.Files.FileColumns.MEDIA_TYPE}!=${MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE}" +
41 | " AND ${MediaStore.Files.FileColumns.MEDIA_TYPE}!=${MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO}")
42 |
43 | val DOC_PROJECTION = arrayOf(MediaStore.Files.FileColumns._ID,
44 | MediaStore.Files.FileColumns.DATA,
45 | MediaStore.Files.FileColumns.MIME_TYPE,
46 | MediaStore.Files.FileColumns.SIZE,
47 | MediaStore.Files.FileColumns.DATE_ADDED,
48 | MediaStore.Files.FileColumns.TITLE)
49 |
50 | val cursor = getApplication().contentResolver.query(MediaStore.Files.getContentUri("external"), DOC_PROJECTION, selection, null, MediaStore.Files.FileColumns.DATE_ADDED + " DESC")
51 |
52 | if (cursor != null) {
53 | data = createDocumentType(fileTypes, comparator, getDocumentFromCursor(cursor))
54 | cursor.close()
55 | }
56 | }
57 | return data
58 | }
59 |
60 | @WorkerThread
61 | private fun createDocumentType(fileTypes: List, comparator: Comparator?, documents: MutableList): HashMap> {
62 | val documentMap = HashMap>()
63 |
64 | for (fileType in fileTypes) {
65 | val documentListFilteredByType = documents.filter { document -> FilePickerUtils.contains(fileType.extensions, document.mimeType) }
66 |
67 | comparator?.let {
68 | documentListFilteredByType.sortedWith(comparator)
69 | }
70 |
71 | documentMap[fileType] = documentListFilteredByType
72 | }
73 |
74 | return documentMap
75 | }
76 |
77 | @WorkerThread
78 | private fun getDocumentFromCursor(data: Cursor): MutableList {
79 | val documents = mutableListOf()
80 | while (data.moveToNext()) {
81 |
82 | val imageId = data.getLong(data.getColumnIndexOrThrow(BaseColumns._ID))
83 | val path = data.getString(data.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
84 | val title = data.getString(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.TITLE))
85 |
86 | if (path != null) {
87 |
88 | val fileType = getFileType(PickerManager.getFileTypes(), path)
89 | val file = File(path)
90 | val contentUri = ContentUris.withAppendedId(
91 | MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
92 | imageId
93 | )
94 | if (fileType != null && !file.isDirectory && file.exists()) {
95 |
96 | val document = Document(imageId, title, contentUri)
97 | document.fileType = fileType
98 |
99 | val mimeType = data.getString(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
100 | if (mimeType != null && !TextUtils.isEmpty(mimeType)) {
101 | document.mimeType = mimeType
102 | } else {
103 | document.mimeType = ""
104 | }
105 |
106 | document.size = data.getString(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE))
107 |
108 | if (!documents.contains(document)) documents.add(document)
109 | }
110 | }
111 | }
112 |
113 | return documents
114 | }
115 |
116 | private fun getFileType(types: ArrayList, path: String): FileType? {
117 | for (index in types.indices) {
118 | for (string in types[index].extensions) {
119 | if (path.endsWith(string)) return types[index]
120 | }
121 | }
122 | return null
123 | }
124 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/viewmodels/VMMediaPicker.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.viewmodels
2 |
3 | import android.app.Application
4 | import android.content.ContentUris
5 | import android.database.ContentObserver
6 | import android.database.Cursor
7 | import android.provider.BaseColumns
8 | import android.provider.MediaStore
9 | import android.webkit.MimeTypeMap
10 | import androidx.annotation.WorkerThread
11 | import androidx.lifecycle.LiveData
12 | import androidx.lifecycle.MutableLiveData
13 | import droidninja.filepicker.FilePickerConst
14 | import droidninja.filepicker.PickerManager
15 | import droidninja.filepicker.R
16 | import droidninja.filepicker.models.Media
17 | import droidninja.filepicker.models.PhotoDirectory
18 | import droidninja.filepicker.utils.registerObserver
19 | import kotlinx.coroutines.Dispatchers
20 | import kotlinx.coroutines.withContext
21 | import java.util.*
22 |
23 | class VMMediaPicker(application: Application) : BaseViewModel(application) {
24 |
25 | private val _lvMediaData = MutableLiveData>()
26 | val lvMediaData: LiveData>
27 | get() = _lvMediaData
28 |
29 | private val _lvPhotoDirsData = MutableLiveData>()
30 | val lvPhotoDirsData: LiveData>
31 | get() = _lvPhotoDirsData
32 |
33 | private val _lvDataChanged = MutableLiveData()
34 | val lvDataChanged: LiveData
35 | get() = _lvDataChanged
36 |
37 | private var contentObserver: ContentObserver? = null
38 |
39 | private fun registerContentObserver() {
40 | if (contentObserver == null) {
41 | contentObserver = getApplication().contentResolver.registerObserver(
42 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI
43 | ) {
44 | _lvDataChanged.value = true
45 | }
46 | }
47 | }
48 |
49 | fun getMedia(bucketId: String? = null, mediaType: Int = FilePickerConst.MEDIA_TYPE_IMAGE, imageFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE, videoFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE) {
50 | launchDataLoad {
51 | val medias = mutableListOf()
52 | queryImages(bucketId, mediaType, imageFileSize, videoFileSize).map { dir ->
53 | medias.addAll(dir.medias)
54 | }
55 | medias.sortWith(Comparator { a, b -> (b.id - a.id).toInt() })
56 |
57 | _lvMediaData.postValue(medias)
58 | registerContentObserver()
59 | }
60 | }
61 |
62 | fun getPhotoDirs(bucketId: String? = null, mediaType: Int = FilePickerConst.MEDIA_TYPE_IMAGE, imageFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE, videoFileSize: Int = FilePickerConst.DEFAULT_FILE_SIZE) {
63 | launchDataLoad {
64 | val dirs = queryImages(bucketId, mediaType, imageFileSize, videoFileSize)
65 | val photoDirectory = PhotoDirectory()
66 | photoDirectory.bucketId = null
67 |
68 | when (mediaType) {
69 | FilePickerConst.MEDIA_TYPE_VIDEO -> {
70 | photoDirectory.name = getApplication().applicationContext.getString(R.string.all_videos)
71 | }
72 | FilePickerConst.MEDIA_TYPE_IMAGE -> {
73 | photoDirectory.name = getApplication().applicationContext.getString(R.string.all_photos)
74 | }
75 | else -> {
76 | photoDirectory.name = getApplication().applicationContext.getString(R.string.all_files)
77 | }
78 | }
79 |
80 | if (dirs.isNotEmpty() && dirs[0].medias.size > 0) {
81 | photoDirectory.dateAdded = dirs[0].dateAdded
82 | photoDirectory.setCoverPath(dirs[0].medias[0].path)
83 | }
84 |
85 | for (i in dirs.indices) {
86 | photoDirectory.medias.addAll(dirs[i].medias)
87 | }
88 |
89 | dirs.add(0, photoDirectory)
90 | _lvPhotoDirsData.postValue(dirs)
91 | registerContentObserver()
92 | }
93 | }
94 |
95 | @WorkerThread
96 | suspend fun queryImages(bucketId: String?, mediaType: Int, imageFileSize: Int, videoFileSize: Int): MutableList {
97 | var data = mutableListOf()
98 | withContext(Dispatchers.IO) {
99 | val projection = null
100 | val uri = MediaStore.Files.getContentUri("external")
101 | val sortOrder = MediaStore.Images.Media._ID + " DESC"
102 | val selectionArgs = mutableListOf()
103 |
104 | var selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
105 | + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)
106 |
107 | if (mediaType == FilePickerConst.MEDIA_TYPE_VIDEO) {
108 | selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
109 | + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
110 |
111 | if (videoFileSize != FilePickerConst.DEFAULT_FILE_SIZE) {
112 | selection += " AND ${MediaStore.Video.Media.SIZE}<=?"
113 | selectionArgs.add("${videoFileSize * 1024 * 1024}")
114 | }
115 | }
116 |
117 | if (mediaType == FilePickerConst.MEDIA_TYPE_IMAGE && imageFileSize != FilePickerConst.DEFAULT_FILE_SIZE) {
118 | selection += " AND ${MediaStore.Images.Media.SIZE}<=?"
119 | selectionArgs.add("${imageFileSize * 1024 * 1024}")
120 | }
121 |
122 | if (!PickerManager.isShowGif) {
123 | selection += " AND " + MediaStore.Images.Media.MIME_TYPE + "!='" + MimeTypeMap.getSingleton().getMimeTypeFromExtension("gif") + "'"
124 | }
125 |
126 | if (bucketId != null)
127 | selection += " AND " + MediaStore.Images.Media.BUCKET_ID + "='" + bucketId + "'"
128 |
129 | val cursor = getApplication().contentResolver.query(uri, projection, selection, selectionArgs.toTypedArray(), sortOrder)
130 |
131 | if (cursor != null) {
132 | data = getPhotoDirectories(mediaType, cursor)
133 | cursor.close()
134 | }
135 | }
136 | return data
137 | }
138 |
139 | @WorkerThread
140 | private fun getPhotoDirectories(fileType: Int, data: Cursor): MutableList {
141 | val directories = mutableListOf()
142 |
143 | while (data.moveToNext()) {
144 |
145 | val imageId = data.getLong(data.getColumnIndexOrThrow(BaseColumns._ID))
146 | val bucketId = data.getString(data.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_ID))
147 | val name = data.getString(data.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME))
148 | val fileName = data.getString(data.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE))
149 | val mediaType = data.getInt(data.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE))
150 |
151 | val photoDirectory = PhotoDirectory()
152 | photoDirectory.id = imageId
153 | photoDirectory.bucketId = bucketId
154 | photoDirectory.name = name
155 |
156 | var contentUri = ContentUris.withAppendedId(
157 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
158 | imageId
159 | )
160 | if (fileType == FilePickerConst.MEDIA_TYPE_VIDEO) {
161 | contentUri = ContentUris.withAppendedId(
162 | MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
163 | imageId
164 | )
165 | }
166 | if (!directories.contains(photoDirectory)) {
167 | photoDirectory.addPhoto(imageId, fileName, contentUri, mediaType)
168 | photoDirectory.dateAdded = data.getLong(data.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED))
169 | directories.add(photoDirectory)
170 | } else {
171 | directories[directories.indexOf(photoDirectory)]
172 | .addPhoto(imageId, fileName, contentUri, mediaType)
173 | }
174 | }
175 |
176 | return directories
177 | }
178 |
179 | override fun onCleared() {
180 | contentObserver?.let {
181 | getApplication().contentResolver.unregisterContentObserver(it)
182 | }
183 | }
184 | }
--------------------------------------------------------------------------------
/filepicker/src/main/java/droidninja/filepicker/views/SquareRelativeLayout.kt:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker.views
2 |
3 | import android.annotation.TargetApi
4 | import android.content.Context
5 | import android.os.Build
6 | import android.util.AttributeSet
7 | import android.widget.RelativeLayout
8 |
9 | /** A RelativeLayout that will always be square -- same width and height,
10 | * where the height is based off the width. */
11 | class SquareRelativeLayout : RelativeLayout {
12 |
13 | constructor(context: Context) : super(context) {}
14 |
15 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
16 |
17 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
18 |
19 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
20 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
21 | }
22 |
23 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
24 | // Set getBitmap square layout.
25 | super.onMeasure(widthMeasureSpec, widthMeasureSpec)
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/filepicker/src/main/res/anim/slide_left_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/anim/slide_left_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/color/selector_tab_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/color/selector_tab_text_color_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/gallery_album_overlay.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/gallery_album_overlay.9.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/ic_camera.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/ic_deselect_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/ic_deselect_all.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/ic_play_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/ic_play_icon.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/ic_select_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/ic_select_all.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/icon_file_doc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/icon_file_doc.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/icon_file_pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/icon_file_pdf.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/icon_file_ppt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/icon_file_ppt.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/icon_file_unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/icon_file_unknown.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/icon_file_xls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/icon_file_xls.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable-xxhdpi/image_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/filepicker/src/main/res/drawable-xxhdpi/image_placeholder.png
--------------------------------------------------------------------------------
/filepicker/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/activity_file_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/activity_media_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/fragment_doc_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
16 |
21 |
22 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/fragment_media_folder_picker.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
21 |
22 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/fragment_media_picker.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/fragment_photo_picker.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
23 |
24 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/item_doc_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
38 |
39 |
55 |
56 |
64 |
65 |
66 |
74 |
75 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/item_folder_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
23 |
24 |
30 |
31 |
43 |
44 |
55 |
56 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/layout/item_photo_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
23 |
24 |
31 |
32 |
42 |
43 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/menu/doc_picker_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/menu/media_detail_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/menu/picker_menu.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/menu/select_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Ajustes
5 |
6 | Permiso de almacenamiento es necesario para acceder a los archivos.
7 | No hay ficheros
8 | (%d/%d)
9 | %d
10 | Selecciona un archivo multimedia
11 | Selecciona un documento
12 | No hay aplicación para abrir la cámara!
13 | PDF
14 | PPT
15 | DOC
16 | XLS
17 | TXT
18 | Vídeos
19 | Imágenes
20 | Todas las fotos
21 | Todos los vídeos
22 | Todos los ficheros
23 |
24 |
25 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values-fa/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | تنظیمات
5 |
6 | مجوز دسترسی به فایل، برای خواندن فایل ها لازم است.
7 | فایلی وجود ندارد
8 | (%d/%d)
9 | %d
10 | یک عکس انتخاب کنید
11 | یک مستند انتخاب کنید
12 | هیچ برنامه برای دوربین وجود ندارد!
13 | PDF
14 | PPT
15 | DOC
16 | XLS
17 | TXT
18 | فیلم ها
19 | تصاویر
20 | همه تصویرها
21 | همه فیلم ها
22 | همه فایل ها
23 |
24 |
25 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values-hi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | सेटिंग्स
5 |
6 |
7 | Hello blank fragment
8 | सभी तस्वीरें
9 | हो गया
10 |
11 | फ़ाइलों को एक्सेस करने के लिए Storage अनुमति की आवश्यकता है।
12 | कोई फाइल नहीं मिली
13 | (%d/%d)
14 | %d
15 | एक मीडिया का चयन करें
16 | एक दस्तावेज़ का चयन करें
17 | कैमरा के लिए कोई एप्लीकेशन मौजूद नहीं है!
18 | PDF
19 | PPT
20 | DOC
21 | XLS
22 | TXT
23 | वीडियो
24 | तस्वीरें
25 | सभी तस्वीरें
26 | सभी वीडियो
27 | सभी फाइलें
28 |
29 |
30 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Configurações
5 |
6 |
7 | Hello blank fragment
8 | Todas as imagens
9 | Pronto
10 |
11 | É necessária permissão de armazenamento para acessar arquivos.
12 | Nenhum arquivo encontrado
13 | (%d/%d)
14 | %d
15 | Selecione um arquivo de mídia
16 | Selecione um documento
17 | Nenhum aplicativo de câmera foi detectado!
18 | PDF
19 | PPT
20 | DOC
21 | XLS
22 | TXT
23 | Vídeos
24 | Imagens
25 | Todas as fotos
26 | Todos os vídeos
27 | Todos os arquivos
28 |
29 |
30 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 设置
4 |
5 |
6 | Hello blank fragment
7 | 所有图片
8 | 完成
9 |
10 | 访问文件需要存储权限
11 | 找不到文件
12 | (%d/%d)
13 | %d
14 | 选择照片
15 | 选择文档
16 | 没有相机的应用程序!
17 | PDF
18 | PPT
19 | DOC
20 | XLS
21 | TXT
22 |
23 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #efefef
4 | #da4d26
5 | #a93409
6 | #FF4081
7 | #ecedef
8 | #94333232
9 | #FB4846
10 | #DFDFDF
11 | @android:color/white
12 |
13 | @color/bg_gray
14 | @android:color/white
15 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 40dp
4 | 40dp
5 | 17sp
6 | 22dp
7 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Settings
5 |
6 |
7 | Hello blank fragment
8 | All Images
9 | Done
10 |
11 | Storage permission is needed to access files.
12 | No Files Found
13 | (%d/%d)
14 | %d
15 | Select a media
16 | Select a document
17 | No Application exists for camera!
18 | PDF
19 | PPT
20 | DOC
21 | XLS
22 | TXT
23 | Videos
24 | Images
25 | All Photos
26 | All Videos
27 | All Files
28 |
29 |
30 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
21 |
22 |
32 |
33 |
43 |
44 |
48 |
49 |
53 |
54 |
58 |
59 |
63 |
64 |
70 |
71 |
--------------------------------------------------------------------------------
/filepicker/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/filepicker/src/test/java/droidninja/filepicker/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package droidninja.filepicker;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | android.enableJetifier=true
20 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DroidNinja/Android-FilePicker/fe12cc36b20945d48ff4555149377e7f4e183493/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 16 23:31:59 IST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':filepicker'
2 |
--------------------------------------------------------------------------------