diff --git a/README.md b/README.md
index e5772d3..06a21b0 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,27 @@
[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-RxPaparazzo-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/3523)
-RxJava extension for Android to access camera and gallery to take images.
+RxJava extension for Android to take photos using the camera, select files or photos from the device and optionally crop or rotate any selected images.
# RxPaparazzo
+What is RX?
+
+> Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
+
What is a Paparazzo?
> A freelance photographer who aggressively pursues celebrities for the purpose of taking candid photographs.
-This library does that. Not really. But it was a funny name, thought. Was it?
+This library does that (well not really). But it is a cool name.
## Features:
- Runtime permissions. Not worries about the tricky Android runtime permissions system. RxPaparazzo relies on [RxPermissions](https://github.com/tbruyelle/RxPermissions) to deal with that.
-- Take a photo using the built-in camera.
-- Access to gallery.
-- Crop images. RxPaparazzo relies on [UCrop](https://github.com/Yalantis/uCrop) to perform beautiful cuts to any face, body or place.
+- Takes a photo using the built-in camera.
+- Access to gallery and other sources of photos.
+- Access to files and documents stored locally and on the cloud.
+- Crop and rotate images. RxPaparazzo relies on [UCrop](https://github.com/Yalantis/uCrop) to perform beautiful cuts to any face, body or place.
- Honors the observable chain (it means you can go crazy chaining operators). [RxOnActivityResult](https://github.com/VictorAlbertos/RxActivityResult) allows RxPaparazzo to transform every intent into an observable for a wonderful chaining process.
@@ -31,7 +36,7 @@ allprojects {
}
```
-And add next dependencies in the build.gradle of the module:
+Add dependencies in the build.gradle of the module:
```gradle
dependencies {
compile "com.github.miguelbcr:RxPaparazzo:0.4.4"
@@ -50,7 +55,7 @@ allprojects {
}
```
-And add next dependencies in the build.gradle of the module:
+Add dependencies in the build.gradle of the module:
```gradle
dependencies {
compile "com.github.miguelbcr:RxPaparazzo:0.4.4-2.x"
@@ -62,7 +67,7 @@ dependencies {
## Usage
Because RxPaparazzo uses RxActivityResult to deal with intent calls, all its requirements and features are inherited too.
-Before attempting to use RxPaparazzo, you need to call `RxPaparazzo.register` in your Android `Application` class, supplying as parameter the current instance.
+Before attempting to use RxPaparazzo, you need to call `RxPaparazzo.register` in your Android Application's `onCreate` supplying the current Application instance.
```java
public class SampleApp extends Application {
@@ -74,19 +79,53 @@ public class SampleApp extends Application {
}
```
-Every feature RxPaparazzo exposes can be accessed from both, an `activity` or a `fragment` instance.
+You will need to also add a FileProvider named `android.support.v4.content.FileProvider` to your `AndroidManifest.xml` and create a paths xml file in your src/main/res/xml directory.
+
+```xml
+
+
+
+```
+
+If you set the provider `android:authorities` attribute to a value other than `${applicationId}.file_provider` name you must set the configuration it using `RxPaparazzo.Builder.setFileProviderAuthority(String authority)`
+
+Example: file_provider_paths.xml
+```xml
+
+
+
+
+```
+
+The `file_provider_paths.xml` is where files are exposed in the FileProvider.
+If you set the files-path `path` attribute to a value other than `RxPaparazzo/` you must set the configuration using `RxPaparazzo.Builder.setFileProviderDirectory(String authority)`
-**Limitation:**: Your fragments need to extend from `android.support.v4.app.Fragment` instead of `android.app.Fragment`, otherwise they won't be notified.
+All features RxPaparazzo exposes can be accessed from both, an `activity` or a `fragment` instance.
-The generic type of the `observable` returned by RxPaparazzo when subscribing to any of its features is always an instance of [Response](https://github.com/miguelbcr/RxPaparazzo/blob/master/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Response.java) class.
+**Limitation:**: Your fragments need to extend from `android.support.v4.app.Fragment` instead of `android.app.Fragment`, otherwise they won't be notified.
+
+The generic type of the `observable` returned by RxPaparazzo when subscribing to any of its features is always an instance of [Response](https://github.com/miguelbcr/RxPaparazzo/blob/master/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Response.java) class.
This instance holds a reference to the current Activity/Fragment, accessible calling `targetUI()` method. Because the original one may be recreated it would be unsafe calling it. Instead, you must call any method/variable of your Activity/Fragment from this instance encapsulated in the `response` instance.
Also, this instance holds a reference to the data as the appropriate response, as such as the result code of the specific operation.
+
+### Saving files
+
+By default, the image / file is saved in a directory the same as the app name on the root of the external storage. You can choose to save the images in internal storage by using `.useInternalStorage()`
+
+The `response` in the callback function supplied to the `subscribe()` method holds a reference to the path where the image was persisted.
+
### Calling built-in camera to take a photo.
```java
-RxPaparazzo.takeImage(activityOrFragment)
+RxPaparazzo.single(activityOrFragment)
.usingCamera()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -101,18 +140,48 @@ RxPaparazzo.takeImage(activityOrFragment)
});
```
-The `response` instance holds a reference to the path where the image was persisted.
+### Calling the file picker to retrieve a file.
+```java
+RxPaparazzo.single(activityOrFragment)
+ .usingFile()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> {
+ // See response.resultCode() doc
+ if (response.resultCode() != RESULT_OK) {
+ response.targetUI().showUserCanceled();
+ return;
+ }
+
+ response.targetUI().loadImage(response.data());
+ });
+```
-By default, the path is under app name folder on the root of the external storage, but you can save the images in internal storage by using `.useInternalStorage()`
+### Calling the file picker to retrieve multiple files
+```java
+RxPaparazzo.multiple(activityOrFragment)
+ .usingFiles()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(response -> {
+ // See response.resultCode() doc
+ if (response.resultCode() != RESULT_OK) {
+ response.targetUI().showUserCanceled();
+ return;
+ }
+ if (response.data().size() == 1) response.targetUI().loadImage(response.data().get(0));
+ else response.targetUI().loadImages(response.data());
+ });
+```
### Calling the gallery to retrieve an image.
```java
-RxPaparazzo.takeImage(activityOrFragment)
+RxPaparazzo.single(activityOrFragment)
.usingGallery()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> {
+ .subscribe(response -> {
// See response.resultCode() doc
if (response.resultCode() != RESULT_OK) {
response.targetUI().showUserCanceled();
@@ -123,11 +192,9 @@ RxPaparazzo.takeImage(activityOrFragment)
});
```
-The `response` instance holds a reference to the path where the image was persisted. Same as the previous example.
-
-### Calling the gallery to retrieve multiple image
+### Calling the gallery to retrieve multiple image
```java
-RxPaparazzo.takeImages(activityOrFragment)
+RxPaparazzo.multiple(activityOrFragment)
.usingGallery()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -143,12 +210,10 @@ RxPaparazzo.takeImages(activityOrFragment)
});
```
-The `response` instance holds a reference to the paths where the images were persisted.
-
**Note**: if the level Android api device is minor than 18, only one image will be retrieved.
## Customizations
-When asking RxPaparazzo for an image -whether it was retrieved using the built-in camera or via gallery, it's possible to apply some configurations to the action.
+When asking RxPaparazzo for an photo / image / file it's possible to apply some configurations to the action.
### Size options
[Size](https://github.com/miguelbcr/RxPaparazzo/blob/master/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/size/Size.java) values can be used to set the size of the image to retrieve. There are 4 options:
@@ -161,7 +226,7 @@ When asking RxPaparazzo for an image -whether it was retrieved using the built-i
[ScreenSize](https://github.com/miguelbcr/RxPaparazzo/blob/master/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/size/ScreenSize.java) value will be set as default.
```java
-RxPaparazzo.takeImages(activityOrFragment)
+RxPaparazzo.multiple(activityOrFragment)
.size(new ScreenSize())
.usingGallery()
```
@@ -170,11 +235,11 @@ RxPaparazzo.takeImages(activityOrFragment)
This feature is available thanks to the amazing library [uCrop](https://github.com/Yalantis/uCrop) authored by [Yalantis](https://github.com/Yalantis) group.
```java
-RxPaparazzo.takeImages(activityOrFragment)
+RxPaparazzo.multiple(activityOrFragment)
.crop()
```
-By calling `crop()` method when building the observable instance, all they images retrieved will be able to be cropped, regardless if the images were retrieved using the built-in camera or gallery, even if multiple images were requested in a single call using `takeImages()` approach.
+By calling `crop()` method when building the observable instance, all they images retrieved will be able to be cropped, regardless if the images were retrieved using the built-in camera or gallery, even if multiple images were requested in a single call using `single()` approach.
Because uCrop Yalantis library exposes some configuration in order to customize the crop screen, RxPaparazzo exposes an overloaded method of `crop(UCrop.Options)` which allow to pass an instance of [UCrop.Options](https://github.com/Yalantis/uCrop/blob/master/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java#L211).
If you need to configure the aspect ratio, the max result size or using the source image aspect ratio, you must pass an instance of [Options](https://github.com/miguelbcr/RxPaparazzo/blob/master/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Options.java) class, which extends from `UCrop.Options` and adds the three missing properties.
@@ -182,7 +247,7 @@ If you need to configure the aspect ratio, the max result size or using the sour
UCrop.Options options = new UCrop.Options();
options.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));
-RxPaparazzo.takeImage(activityOrFragment).crop(options)
+RxPaparazzo.single(activityOrFragment).crop(options)
```
```java
@@ -190,9 +255,19 @@ Options options = new Options();
options.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));
options.setAspectRatio(25, 50);
-RxPaparazzo.takeImage(activityOrFragment)
+RxPaparazzo.single(activityOrFragment)
.crop(options)
```
+
+### Media scanning
+
+To send files to the media scanner so that they can be indexed and available in applications such as the Gallery use `sendToMediaScanner()`. If you are using `useInternalStorage()` then the media scanner will not be able to access the file.
+
+### Picking files
+
+If you wish to limit the type of images or files then use `setMimeType(String mimeType)` to specify a specific mime type for the Intent.
+By default `Intent.ACTION_GET_CONTENT` is used to request images and files. If you wish to edit the original file call `useDocumentPicker()`, this will allow greater, possiblty persistent access to the source file.
+
## Proguard
```
@@ -208,6 +283,13 @@ RxPaparazzo.takeImage(activityOrFragment)
long consumerNode;
}
```
+## Testing
+
+Testing has been done using the following Genymotion devices:
+
+* Genymotion - Google Nexus 5 5.0.0 API 21 1080x1920 480dpi
+* Genymotion - Google Nexus 7 5.1.0 API 22 800x1280 213dpi
+
## Credits
* Runtime permissions: [RxPermissions](https://github.com/tbruyelle/RxPermissions)
@@ -226,8 +308,13 @@ RxPaparazzo.takeImage(activityOrFragment)
*
*
+**James McIntosh**
+
+*
+*
+
## Another author's libraries using RxJava:
* [RxCache](https://github.com/VictorAlbertos/RxCache): Reactive caching library for Android and Java.
* [RxGcm](https://github.com/VictorAlbertos/RxGcm): RxJava extension for Gcm which acts as an architectural approach to easily satisfy the requirements of an android app when dealing with push notifications.
-* [RxActivityResult](https://github.com/VictorAlbertos/RxActivityResult): A reactive-tiny-badass-vindictive library to break with the OnActivityResult implementation as it breaks the observables chain.
+* [RxActivityResult](https://github.com/VictorAlbertos/RxActivityResult): A tiny reactive library to break with the OnActivityResult implementation as it breaks the observables chain.
diff --git a/app/build.gradle b/app/build.gradle
index 3109eb5..bf83036 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -48,6 +48,8 @@ dependencies {
compile 'com.squareup.picasso:picasso:2.5.2'
compile project(':rx_paparazzo')
+ debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
+ releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
androidTestCompile ("com.android.support.test:runner:0.4.1") {
exclude module: 'support-annotations'
diff --git a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/ApplicationTest.java b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/ApplicationTest.java
index 59d7a69..b795524 100644
--- a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/ApplicationTest.java
+++ b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/ApplicationTest.java
@@ -1,35 +1,12 @@
package com.miguelbcr.ui.rx_paparazzo2.sample;
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.RemoteException;
import android.support.test.InstrumentationRegistry;
-import android.support.test.espresso.contrib.RecyclerViewActions;
-import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.ImageView;
-import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
-import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
-import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.DownloadImage;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.GetDimens;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.GetPath;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.ImageUtils;
+
import com.miguelbcr.ui.rx_paparazzo2.sample.activities.StartActivity;
-import com.miguelbcr.ui.rx_paparazzo2.sample.activities.Testable;
-import java.io.File;
-import java.util.List;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
+
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Rule;
@@ -39,32 +16,28 @@
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static com.miguelbcr.ui.rx_paparazzo2.sample.recyclerview.RecyclerViewUtils.withRecyclerView;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.lessThan;
-import static org.hamcrest.core.CombinableMatcher.both;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
/**
* TESTED ON:
* - Google Nexus 5 5.0.0 API 21 1080x1920 480dpi
* - Google Nexus 7 5.1.0 API 22 800x1280 213dpi
+ * - Samsung Galaxy S6 - 6.0.0 API 23 1440x2560 640dpi
*/
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class ApplicationTest {
- @Rule public ActivityTestRule activityRule = new ActivityTestRule<>(StartActivity.class);
- private UiDevice uiDevice;
- private int[] imageDimens = {0, 0};
+public class ApplicationTest extends UiActions {
+
+ @Rule
+ public ActivityTestRule activityRule = new ActivityTestRule<>(StartActivity.class);
@Before
public void init() {
- uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ // You may need to change the profile if tests are failing because they can't close the gallery picker
+ DeviceConfig.CURRENT = DeviceConfig.GOOGLE_NEXUS;
+ DeviceConfig.WAIT_TIME_FUDGE_FACTOR = 1.0;
+
+ setUiDevice(UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()));
}
@Test
@@ -127,206 +100,4 @@ public void _10_PickUpPhotosFragmentCrop() {
pickUpPhoto(false);
}
- private void takePhoto(boolean crop) {
- if (crop) onView(withId(R.id.fab_camera_crop)).perform(click());
- else onView(withId(R.id.fab_camera)).perform(click());
- waitTime();
-
- clickBottomMiddleScreen();
- rotateDevice();
- clickBottomMiddleScreen();
-
- if (crop) {
- rotateDevice();
- clickTopRightScreen();
- }
-
- onView(withId(R.id.iv_image)).check(matches(getImageViewMatcher()));
-
- checkDimensions();
- }
-
- private void pickUpPhoto(boolean onlyOne) {
- // With 4 items the recycler view do not scroll properly and do not find the item view and test crash in pos=2
- int imagesToPick = onlyOne ? 1 : 2;
-
- if (onlyOne) onView(withId(R.id.fab_pickup_image)).perform(click());
- else onView(withId(R.id.fab_pickup_images)).perform(click());
-
- waitTime();
-
- clickImagesOnScreen(imagesToPick);
-
- // Open selected images
- if (!onlyOne) clickTopRightScreen();
-
- waitTime();
-
- // Close crop screen/s
- for (int i = 0; i < imagesToPick; i++) {
- clickTopRightScreen();
- }
-
- waitTime();
-
- if (onlyOne) onView(withId(R.id.iv_image)).check(matches(getImageViewMatcher()));
- else {
- for (int i = 0; i < imagesToPick; i++) {
- onView(withId(R.id.rv_images)).perform(RecyclerViewActions.scrollToPosition(i));
-
- onView(withRecyclerView(R.id.rv_images)
- .atPositionOnView(i, R.id.iv_image))
- .check(matches(getImageViewMatcher()));
-
- checkDimensions();
- }
- }
- }
-
- private Matcher getImageViewMatcher() {
- return new BoundedMatcher(ImageView.class) {
- @Override
- public void describeTo(Description description) {
- description.appendText("has drawable");
- }
-
- @Override
- public boolean matchesSafely(ImageView imageView) {
- Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
- imageDimens = new int[]{bitmap.getWidth(), bitmap.getHeight()};
- return imageView.getDrawable() != null;
- }
- };
- }
-
- private void checkDimensions() {
- Activity activity = ((SampleApplication) InstrumentationRegistry.getTargetContext().getApplicationContext()).getLiveActivity();
-
- if (activity instanceof Testable) {
- List filePaths = ((Testable) activity).getFilePaths();
- Size size = ((Testable) activity).getSize();
-
- for (String filePath : filePaths) {
- assertNotNull(filePath);
- assertNotEquals(filePath, "");
-
- getDimens(size).with(Uri.fromFile(new File(filePath))).react()
- .subscribe(dimens -> {
- int[] dimensPortrait = getDimensionsPortrait(dimens[0], dimens[1]);
- int[] imageDimensPortrait = getDimensionsPortrait(imageDimens[0], imageDimens[1]);
- int marginOfError = 150;
- assertThat(dimensPortrait[0], is(both(greaterThan(imageDimensPortrait[0] - marginOfError)).and(lessThan(imageDimensPortrait[0] + marginOfError))));
- assertThat(dimensPortrait[1], is(both(greaterThan(imageDimensPortrait[1] - marginOfError)).and(lessThan(imageDimensPortrait[1] + marginOfError))));
- });
- }
- }
- }
-
- private int[] getDimensionsPortrait(int width, int height) {
- if (width < height) return new int[]{width, height};
- else return new int[]{height, width};
- }
-
- private GetDimens getDimens(Size size) {
- Config config = new Config();
- config.setSize(size);
- TargetUi targetUi = new TargetUi(activityRule.getActivity());
- ImageUtils imageUtils = new ImageUtils(targetUi, config);
- DownloadImage downloadImage = new DownloadImage(targetUi, imageUtils);
- GetPath getPath = new GetPath(targetUi, downloadImage);
- return new GetDimens(targetUi, config, getPath);
- }
-
- private void cancelUserAction() {
- onView(withId(R.id.fab_camera)).perform(click());
- waitTime();
-
- clickBottomMiddleScreen();
- rotateDevice();
- uiDevice.pressBack();
-
- onView(withId(R.id.iv_image)).check(matches(new BoundedMatcher(ImageView.class) {
- @Override
- public void describeTo(Description description) {
- imageDimens = new int[]{0, 0};
- description.appendText("has not drawable");
- }
-
- @Override
- public boolean matchesSafely(ImageView imageView) {
- return imageView.getDrawable() == null;
- }
- }));
- }
-
- private void rotateDevice() {
- try {
- uiDevice.setOrientationLeft();
- waitTime();
- uiDevice.setOrientationNatural();
- waitTime();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- private void clickBottomMiddleScreen() {
- int screenDimens[] = getScreenDimensions();
- int width = screenDimens[0];
- int height = screenDimens[1];
-
- uiDevice.click(width / 2, height - 100);
- waitTime();
- }
-
- private void clickTopRightScreen() {
- int width = getScreenDimensions()[0];
-
- uiDevice.click(width - 50, 100);
- waitTime();
- }
-
- private void closeDrawer() {
- int screenDimens[] = getScreenDimensions();
- int width = screenDimens[0];
- int height = screenDimens[1];
-
- uiDevice.click(width - 50, height / 2);
- waitTime();
- }
-
- private void clickImagesOnScreen(int imagesToPick) {
- int screenDimens[] = getScreenDimensions();
- int width = screenDimens[0];
- int height = screenDimens[1];
- int y = 0;
-
- //closeDrawer();
-
- for (int i = 0; i < imagesToPick; i++) {
- int widthQuarter = width / 4;
- int x = (i % 2 == 0) ? widthQuarter : widthQuarter * 3;
- y += (i % 2 == 0) ? height / 4 : 0;
-
- if (imagesToPick == 1) uiDevice.click(x, y);
- else uiDevice.swipe(x, y, x, y, 500);
- }
- waitTime();
- }
-
- private int[] getScreenDimensions() {
- WindowManager wm = (WindowManager) InstrumentationRegistry.getTargetContext().getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- return new int[]{size.x, size.y};
- }
-
- private void waitTime() {
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/DeviceConfig.java b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/DeviceConfig.java
new file mode 100644
index 0000000..a075c30
--- /dev/null
+++ b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/DeviceConfig.java
@@ -0,0 +1,37 @@
+package com.miguelbcr.ui.rx_paparazzo2.sample;
+
+
+public abstract class DeviceConfig {
+
+ public static final DeviceConfig GOOGLE_NEXUS = new DeviceConfig() {
+ @Override
+ int getMultipleImageConfirmOffset() {
+ return 100;
+ }
+ };
+
+ public static final DeviceConfig SAMSUNG_GALAXY = new DeviceConfig() {
+ @Override
+ int getMultipleImageConfirmOffset() {
+ return 250;
+ }
+ };
+
+ public static DeviceConfig CURRENT = GOOGLE_NEXUS;
+ public static double WAIT_TIME_FUDGE_FACTOR = 1.0;
+
+ abstract int getMultipleImageConfirmOffset();
+
+ public long getShortWaitTime() {
+ double waitTime = DeviceConfig.WAIT_TIME_FUDGE_FACTOR * 100;
+
+ return (long) waitTime;
+ }
+
+ public long getLongWaitTime() {
+ double waitTime = DeviceConfig.WAIT_TIME_FUDGE_FACTOR * 500;
+
+ return (long) waitTime;
+ }
+
+}
diff --git a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/DimensionMatcher.java b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/DimensionMatcher.java
new file mode 100644
index 0000000..7c16ba5
--- /dev/null
+++ b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/DimensionMatcher.java
@@ -0,0 +1,108 @@
+package com.miguelbcr.ui.rx_paparazzo2.sample;
+
+import android.app.Activity;
+import android.graphics.BitmapFactory;
+import android.util.DisplayMetrics;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.CustomMaxSize;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.OriginalSize;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.ScreenSize;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.Dimensions;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+import static org.hamcrest.Matchers.allOf;
+
+class DimensionMatcher extends BaseMatcher {
+
+ private enum Dimension {
+ WIDTH, HEIGHT;
+ }
+
+ private enum Match {
+ EQUALS, LESS_THAN_EQUAL, GREATER_THAN;
+ }
+
+ private Dimension dimension;
+ private Match match;
+ private int expected;
+
+ public DimensionMatcher(Dimension dimension, Match match, int expected) {
+ this.dimension = dimension;
+ this.match = match;
+ this.expected = expected;
+ }
+
+ private int getDimension(FileData fileData) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(fileData.getFile().getAbsolutePath(), options);
+
+ switch (dimension) {
+ case WIDTH:
+ return options.outWidth;
+
+ case HEIGHT:
+ return options.outHeight;
+
+ default:
+ throw new IllegalStateException("Unknown dimension " + dimension);
+ }
+ }
+
+ @Override
+ public boolean matches(Object item) {
+ FileData fileData = (FileData) item;
+
+ int dimension = getDimension(fileData);
+ switch (match) {
+ case EQUALS:
+ return dimension == expected;
+
+ case LESS_THAN_EQUAL:
+ return dimension <= expected;
+
+ case GREATER_THAN:
+ return dimension > expected;
+
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ // TODO
+ }
+
+ public static Matcher fromSize(Activity activity, Size size, Dimensions originalSize) {
+ if (size instanceof OriginalSize) {
+ DimensionMatcher widthMatcher = new DimensionMatcher(Dimension.WIDTH, Match.EQUALS, originalSize.getWidth());
+ DimensionMatcher heightMatcher = new DimensionMatcher(Dimension.HEIGHT, Match.EQUALS, originalSize.getHeight());
+
+ return allOf(widthMatcher, heightMatcher);
+ } else if (size instanceof CustomMaxSize) {
+ CustomMaxSize maxSize = (CustomMaxSize) size;
+ int maxDimension = maxSize.getMaxImageSize();
+ DimensionMatcher widthMatcher = new DimensionMatcher(Dimension.WIDTH, Match.LESS_THAN_EQUAL, maxDimension);
+ DimensionMatcher heightMatcher = new DimensionMatcher(Dimension.HEIGHT, Match.LESS_THAN_EQUAL, maxDimension);
+
+ return allOf(widthMatcher, heightMatcher);
+ } else if (size instanceof ScreenSize) {
+ DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
+ DimensionMatcher widthMatcher = new DimensionMatcher(Dimension.WIDTH, Match.LESS_THAN_EQUAL, metrics.widthPixels);
+ DimensionMatcher heightMatcher = new DimensionMatcher(Dimension.HEIGHT, Match.LESS_THAN_EQUAL, metrics.heightPixels);
+
+ return allOf(widthMatcher, heightMatcher);
+ } else {
+ DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
+ DimensionMatcher widthMatcher = new DimensionMatcher(Dimension.WIDTH, Match.LESS_THAN_EQUAL, metrics.widthPixels / 8);
+ DimensionMatcher heightMatcher = new DimensionMatcher(Dimension.HEIGHT, Match.LESS_THAN_EQUAL, metrics.heightPixels / 8);
+
+ return allOf(widthMatcher, heightMatcher);
+ }
+ }
+}
diff --git a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/IsImageViewMatcher.java b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/IsImageViewMatcher.java
new file mode 100644
index 0000000..2cf9a80
--- /dev/null
+++ b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/IsImageViewMatcher.java
@@ -0,0 +1,24 @@
+package com.miguelbcr.ui.rx_paparazzo2.sample;
+
+import android.support.test.espresso.matcher.BoundedMatcher;
+import android.view.View;
+import android.widget.ImageView;
+
+import org.hamcrest.Description;
+
+class IsImageViewMatcher extends BoundedMatcher {
+
+ public IsImageViewMatcher() {
+ super(ImageView.class);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has drawable");
+ }
+
+ @Override
+ public boolean matchesSafely(ImageView imageView) {
+ return imageView.getDrawable() != null;
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/UiActions.java b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/UiActions.java
new file mode 100644
index 0000000..3fea2ac
--- /dev/null
+++ b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/UiActions.java
@@ -0,0 +1,225 @@
+package com.miguelbcr.ui.rx_paparazzo2.sample;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.contrib.RecyclerViewActions;
+import android.support.test.uiautomator.UiDevice;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.Dimensions;
+import com.miguelbcr.ui.rx_paparazzo2.sample.activities.Testable;
+
+import org.hamcrest.Matcher;
+
+import java.io.File;
+import java.util.List;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static com.miguelbcr.ui.rx_paparazzo2.sample.recyclerview.RecyclerViewUtils.withRecyclerView;
+import static org.junit.Assert.assertNotNull;
+
+class UiActions {
+
+ private UiDevice uiDevice;
+
+ void setUiDevice(UiDevice uiDevice) {
+ this.uiDevice = uiDevice;
+ }
+
+ void takePhoto(boolean crop) {
+ if (crop) {
+ onView(withId(R.id.fab_camera_crop)).perform(click());
+ } else {
+ onView(withId(R.id.fab_camera)).perform(click());
+ }
+
+ waitTimeLong();
+
+ clickBottomMiddleScreen();
+ rotateDevice();
+ clickBottomMiddleScreen();
+
+ if (crop) {
+ rotateDevice();
+ clickTopRightScreen();
+ }
+
+ waitTimeLong();
+
+ checkImageDimensions(0);
+ }
+
+ void pickUpPhoto(boolean onlyOne) {
+ int imagesToPick = onlyOne ? 1 : 2;
+
+ if (onlyOne) {
+ onView(withId(R.id.fab_pickup_image)).perform(click());
+ } else {
+ onView(withId(R.id.fab_pickup_images)).perform(click());
+ }
+
+ waitTimeLong();
+
+ clickImagesOnScreen(imagesToPick);
+
+ // Open selected images
+ if (!onlyOne) {
+ clickTopRightScreen(DeviceConfig.CURRENT.getMultipleImageConfirmOffset());
+ }
+
+ waitTimeLong();
+
+ // Close crop screen/s
+ for (int i = 0; i < imagesToPick; i++) {
+ clickTopRightScreen();
+ waitTimeLong();
+ }
+
+ for (int i = 0; i < imagesToPick; i++) {
+ checkImageDimensions(i);
+ }
+ }
+
+
+ private void checkImageDimensions(int index) {
+ onView(withId(R.id.rv_images)).perform(RecyclerViewActions.scrollToPosition(index));
+
+ waitTimeLong();
+
+ Matcher itemAtPosition = withRecyclerView(R.id.rv_images).atPositionOnView(index, R.id.iv_image);
+
+ IsImageViewMatcher isImageViewMatcher = new IsImageViewMatcher();
+ onView(itemAtPosition).check(matches(isImageViewMatcher));
+
+ checkDimensions();
+ }
+
+ private void checkDimensions() {
+ Testable testable = getTestable();
+ List fileDatas = testable.getFileDatas();
+
+ assertNotNull(fileDatas);
+ for (FileData fileData : fileDatas) {
+ assertNotNull(fileData);
+
+ File file = fileData.getFile();
+ assertNotNull(file);
+
+ Size expectedSize = testable.getSize();
+ Dimensions originalDimensions = fileData.getOriginalDimensions();
+ DimensionMatcher.fromSize(getActivity(), expectedSize, originalDimensions).matches(fileData);
+ }
+ }
+
+ private Testable getTestable() {
+ Activity activity = getActivity();
+ if (activity instanceof Testable) {
+ return ((Testable) activity);
+ }
+
+ throw new IllegalStateException("Expected Activity to implement Testable");
+ }
+
+ private Activity getActivity() {
+ return ((SampleApplication) InstrumentationRegistry.getTargetContext().getApplicationContext()).getLiveActivity();
+ }
+
+ void cancelUserAction() {
+ onView(withId(R.id.fab_camera)).perform(click());
+ waitTime();
+
+ clickBottomMiddleScreen();
+ rotateDevice();
+ uiDevice.pressBack();
+
+ withRecyclerView(R.id.rv_images).isEmpty();
+
+ }
+
+ private void rotateDevice() {
+ try {
+ uiDevice.setOrientationLeft();
+ waitTimeLong();
+ uiDevice.setOrientationNatural();
+ waitTimeLong();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void clickBottomMiddleScreen() {
+ int screenDimens[] = getScreenDimensions();
+ int width = screenDimens[0];
+ int height = screenDimens[1];
+
+ uiDevice.click(width / 2, height - 100);
+ waitTimeLong();
+ }
+
+ private void clickTopRightScreen() {
+ clickTopRightScreen(100);
+ }
+
+ private void clickTopRightScreen(int offset) {
+ int width = getScreenDimensions()[0];
+
+ uiDevice.click(width - offset, 150);
+ waitTime();
+ }
+
+ private void clickImagesOnScreen(int imagesToPick) {
+ int screenDimens[] = getScreenDimensions();
+ int width = screenDimens[0];
+ int height = screenDimens[1];
+ int y = 0;
+
+ for (int i = 0; i < imagesToPick; i++) {
+ int widthQuarter = width / 4;
+ int x = (i % 2 == 0) ? widthQuarter : widthQuarter * 3;
+ y += (i % 2 == 0) ? height / 4 : 0;
+
+ if (imagesToPick == 1) {
+ uiDevice.click(x, y);
+ } else {
+ uiDevice.swipe(x, y, x, y, 500);
+ }
+
+ waitTimeLong();
+ }
+ }
+
+ private int[] getScreenDimensions() {
+ WindowManager wm = (WindowManager) InstrumentationRegistry.getTargetContext().getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+
+ return new int[]{size.x, size.y};
+ }
+
+ private void waitTime() {
+ waitTime(DeviceConfig.CURRENT.getShortWaitTime());
+ }
+
+ private void waitTimeLong() {
+ waitTime(DeviceConfig.CURRENT.getLongWaitTime());
+ }
+
+ private void waitTime(long time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/recyclerview/RecyclerViewMatcher.java b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/recyclerview/RecyclerViewMatcher.java
index 2f9b01b..80be5d5 100644
--- a/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/recyclerview/RecyclerViewMatcher.java
+++ b/app/src/androidTest/java/com/miguelbcr/ui/rx_paparazzo2/sample/recyclerview/RecyclerViewMatcher.java
@@ -2,7 +2,9 @@
import android.content.res.Resources;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.view.View;
+
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -18,49 +20,103 @@ public Matcher atPosition(final int position) {
return atPositionOnView(position, -1);
}
+ public Matcher isEmpty() {
+ return new TypeSafeMatcher() {
+ Resources resources = null;
+
+ public void describeTo(Description description) {
+ String idDescription = getResourceName(resources, recyclerViewId);
+ description.appendText("Empty recycler view with id: " + idDescription);
+ }
+
+ public boolean matchesSafely(View view) {
+ this.resources = view.getResources();
+
+ RecyclerView recyclerView = (RecyclerView) view.getRootView().findViewById(recyclerViewId);
+ if (!(recyclerView != null && recyclerView.getId() == recyclerViewId)) {
+ Log.i("MATCHER", "Recycler view missing");
+
+ return false;
+ }
+
+ int childCount = recyclerView.getAdapter().getItemCount();
+ if (childCount > 0) {
+ Log.i("MATCHER", "Recycler view only has " + childCount + " items, expected it was empty");
+
+ return false;
+ }
+
+ return true;
+ }
+ };
+ }
+
+ private String getResourceName(Resources resources, int id) {
+ if (resources == null) {
+ return String.valueOf(id);
+ }
+ try {
+ return resources.getResourceName(id);
+ } catch (Resources.NotFoundException var4) {
+ return String.format("%s (resource name not found)", id);
+ }
+ }
+
public Matcher atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher() {
Resources resources = null;
- View childView;
public void describeTo(Description description) {
- String idDescription = Integer.toString(recyclerViewId);
- if (this.resources != null) {
- try {
- idDescription = this.resources.getResourceName(recyclerViewId);
- } catch (Resources.NotFoundException var4) {
- idDescription = String.format("%s (resource name not found)",
- new Object[] { Integer.valueOf
- (recyclerViewId) });
- }
+ if (targetViewId != -1) {
+ String targetDescription = getResourceName(resources, targetViewId);
+ description.appendText("child with id: " + targetDescription + " of ");
}
- description.appendText("with id: " + idDescription);
+ String idDescription = getResourceName(resources, recyclerViewId);
+ description.appendText("item at index #" + position + " in view with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
- if (childView == null) {
- RecyclerView recyclerView =
- (RecyclerView) view.getRootView().findViewById(recyclerViewId);
- if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
- childView = recyclerView.getChildAt(position);
- }
- else {
- return false;
- }
+ RecyclerView recyclerView = (RecyclerView) view.getRootView().findViewById(recyclerViewId);
+ if (!(recyclerView != null && recyclerView.getId() == recyclerViewId)) {
+ Log.i("MATCHER", "Recycler view missing");
+
+ return false;
+ }
+
+ int childCount = recyclerView.getAdapter().getItemCount();
+ if (position >= childCount) {
+ Log.i("MATCHER", "Recycler view only has " + childCount + " items, cannot get index " + position);
+
+ return false;
+ }
+
+ RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
+ if (viewHolder.itemView == null) {
+ Log.i("MATCHER", "Item view in view holder was null");
+
+ return false;
}
if (targetViewId == -1) {
- return view == childView;
- } else {
- View targetView = childView.findViewById(targetViewId);
- return view == targetView;
+ boolean isItemView = (view.equals(viewHolder.itemView));
+
+ View targetView = viewHolder.itemView;
+ Log.i("MATCHER", targetView.toString() + " == " + view + " = " + isItemView);
+
+ return isItemView;
}
+ View targetView = viewHolder.itemView.findViewById(targetViewId);
+ boolean isItemView = view.equals(targetView);
+
+ Log.i("MATCHER", targetView.toString() + " == " + view + " = " + isItemView);
+
+ return isItemView;
}
};
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d16def4..b9c758f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,22 +5,32 @@
-
+
+
+
+
+
-
+
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/SampleApplication.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/SampleApplication.java
index cc7297a..7f148eb 100644
--- a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/SampleApplication.java
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/SampleApplication.java
@@ -2,23 +2,42 @@
import android.app.Activity;
import android.app.Application;
+import android.content.Context;
import android.support.annotation.Nullable;
import com.miguelbcr.ui.rx_paparazzo2.RxPaparazzo;
+import com.squareup.leakcanary.LeakCanary;
+import com.squareup.leakcanary.RefWatcher;
/**
* Created by miguel on 16/03/2016.
*/
public class SampleApplication extends Application {
+ private RefWatcher refWatcher;
+
@Override public void onCreate() {
super.onCreate();
RxPaparazzo.register(this);
AppCare.YesSir.takeCareOn(this);
+
+ if (LeakCanary.isInAnalyzerProcess(this)) {
+ // This process is dedicated to LeakCanary for heap analysis.
+ // You should not init your app in this process.
+ return;
+ }
+ refWatcher = LeakCanary.install(this);
}
@Nullable
public Activity getLiveActivity(){
return AppCare.YesSir.getLiveActivityOrNull();
}
+
+ public static RefWatcher getRefWatcher(Context context) {
+ SampleApplication application = (SampleApplication) context.getApplicationContext();
+
+ return application.refWatcher;
+ }
+
}
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/HostActivitySampleFragment.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/HostActivitySampleFragment.java
index 901a86f..d05fb8c 100644
--- a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/HostActivitySampleFragment.java
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/HostActivitySampleFragment.java
@@ -3,6 +3,7 @@
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
import com.miguelbcr.ui.rx_paparazzo2.sample.R;
import com.miguelbcr.ui.rx_paparazzo2.sample.fragments.SampleFragment;
@@ -25,4 +26,9 @@ public class HostActivitySampleFragment extends AppCompatActivity implements Tes
@Override public Size getSize() {
return fragment.getSize();
}
+
+ @Override
+ public List getFileDatas() {
+ return fragment.getFileDatas();
+ }
}
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/PickerUtil.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/PickerUtil.java
new file mode 100644
index 0000000..a7c8a9c
--- /dev/null
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/PickerUtil.java
@@ -0,0 +1,36 @@
+package com.miguelbcr.ui.rx_paparazzo2.sample.activities;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.Toast;
+
+import com.miguelbcr.ui.rx_paparazzo2.RxPaparazzo;
+import com.miguelbcr.ui.rx_paparazzo2.sample.R;
+
+public class PickerUtil {
+
+ public static boolean checkResultCode(Context context, int code) {
+ if (code == RxPaparazzo.RESULT_DENIED_PERMISSION) {
+ showUserDidNotGrantPermissions(context);
+ } else if (code == RxPaparazzo.RESULT_DENIED_PERMISSION_NEVER_ASK) {
+ showUserDidNotGrantPermissionsNeverAsk(context);
+ } else if (code != Activity.RESULT_OK) {
+ showUserCanceled(context);
+ }
+
+ return code == Activity.RESULT_OK;
+ }
+
+ private static void showUserCanceled(Context context) {
+ Toast.makeText(context, context.getString(R.string.user_canceled), Toast.LENGTH_SHORT).show();
+ }
+
+ private static void showUserDidNotGrantPermissions(Context context) {
+ Toast.makeText(context, context.getString(R.string.user_did_not_grant_permissions), Toast.LENGTH_SHORT).show();
+ }
+
+ private static void showUserDidNotGrantPermissionsNeverAsk(Context context) {
+ Toast.makeText(context, context.getString(R.string.user_did_not_grant_permissions_never_ask), Toast.LENGTH_SHORT).show();
+ }
+
+}
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/SampleActivity.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/SampleActivity.java
index f2c8764..4aa7d78 100644
--- a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/SampleActivity.java
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/SampleActivity.java
@@ -9,9 +9,12 @@
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ImageView;
+import android.widget.TextView;
import android.widget.Toast;
import com.miguelbcr.ui.rx_paparazzo2.RxPaparazzo;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.Response;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.CustomMaxSize;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.OriginalSize;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
@@ -21,16 +24,21 @@
import com.squareup.picasso.Picasso;
import com.yalantis.ucrop.UCrop;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.schedulers.Schedulers;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
public class SampleActivity extends AppCompatActivity implements Testable {
- private Toolbar toolbar;
- private ImageView imageView;
+ private static final String STATE_FILES = "FILES";
+ public static final int ONE_MEGABYTE_IN_BYTES = 1000000;
+
private RecyclerView recyclerView;
- private List filesPaths;
+ private ArrayList fileDataList;
private Size size;
@Override
@@ -38,9 +46,16 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_layout);
configureToolbar();
+
+ fileDataList = new ArrayList<>();
+ if (savedInstanceState != null) {
+ if (savedInstanceState.containsKey(STATE_FILES)) {
+ List files = (List) savedInstanceState.getSerializable(STATE_FILES);
+ fileDataList.addAll(files);
+ }
+ }
+
initViews();
- filesPaths = new ArrayList<>();
- size = new OriginalSize();
}
@Override
@@ -48,79 +63,101 @@ public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putSerializable(STATE_FILES, fileDataList);
+ }
+
private void configureToolbar() {
- toolbar = (Toolbar) findViewById(R.id.toolbar);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(R.string.app_name);
}
private void initViews() {
- imageView = (ImageView) findViewById(R.id.iv_image);
- recyclerView = (RecyclerView) findViewById(R.id.rv_images);
-
- recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
+ recyclerView = (RecyclerView) findViewById(R.id.rv_images);
+ recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
findViewById(R.id.fab_camera).setOnClickListener(v -> captureImage());
findViewById(R.id.fab_camera_crop).setOnClickListener(v -> captureImageWithCrop());
findViewById(R.id.fab_pickup_image).setOnClickListener(v -> pickupImage());
findViewById(R.id.fab_pickup_images).setOnClickListener(v -> pickupImages());
+ findViewById(R.id.fab_pickup_file).setOnClickListener(v -> pickupFile());
+ findViewById(R.id.fab_pickup_files).setOnClickListener(v -> pickupFiles());
+
+ loadImages();
}
private void captureImage() {
- size = new CustomMaxSize(512);
- RxPaparazzo.takeImage(SampleActivity.this)
- .size(size)
- .usingCamera()
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
- response.targetUI().loadImage(response.data());
- }
- }, throwable -> {
- throwable.printStackTrace();
- Toast.makeText(getApplicationContext(), "ERROR " + throwable.getMessage(), Toast.LENGTH_SHORT).show();
- });
+ CustomMaxSize size = new CustomMaxSize(512);
+
+ Observable> takeOnePhoto = pickSingle(null, size)
+ .usingCamera();
+
+ processSingle(takeOnePhoto);
}
private void captureImageWithCrop() {
UCrop.Options options = new UCrop.Options();
options.setToolbarColor(ContextCompat.getColor(SampleActivity.this, R.color.colorAccent));
+ options.setToolbarTitle("Cropping single photo");
- size = new OriginalSize();
- RxPaparazzo.takeImage(SampleActivity.this)
- .size(size)
- .crop(options)
- .usingCamera()
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
- response.targetUI().loadImage(response.data());
- }
- }, throwable -> {
- throwable.printStackTrace();
- Toast.makeText(getApplicationContext(), "ERROR " + throwable.getMessage(), Toast.LENGTH_SHORT).show();
- });
+ OriginalSize size = new OriginalSize();
+ Observable> takePhotoAndCrop = pickSingle(options, size)
+ .usingCamera();
+
+ processSingle(takePhotoAndCrop);
}
private void pickupImage() {
UCrop.Options options = new UCrop.Options();
options.setToolbarColor(ContextCompat.getColor(SampleActivity.this, R.color.colorPrimaryDark));
+ options.setToolbarTitle("Cropping single image");
- size = new CustomMaxSize(500);
- RxPaparazzo.takeImage(SampleActivity.this)
- .useInternalStorage()
- .crop(options)
- .size(size)
- .usingGallery()
+ Observable> pickUsingGallery = pickSingle(options, new CustomMaxSize(500))
+ .usingGallery();
+
+ processSingle(pickUsingGallery);
+ }
+
+ private void pickupImages() {
+ Observable>> pickMultiple = pickMultiple(new SmallSize())
+ .usingGallery();
+
+ processMultiple(pickMultiple);
+ }
+
+ private void pickupFile() {
+ UCrop.Options options = new UCrop.Options();
+ options.setToolbarColor(ContextCompat.getColor(SampleActivity.this, R.color.colorPrimaryDark));
+ options.setToolbarTitle("Cropping single file");
+
+ Observable> pickUsingGallery = pickSingle(options, new CustomMaxSize(500))
+ .usingFiles();
+
+ processSingle(pickUsingGallery);
+ }
+
+ private void pickupFiles() {
+ Size size = new SmallSize();
+
+ Observable>> pickMultiple = pickMultiple(size)
+ .usingFiles();
+
+ processMultiple(pickMultiple);
+ }
+
+ private void processSingle(Observable> pickUsingGallery) {
+ pickUsingGallery
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
+ if (PickerUtil.checkResultCode(SampleActivity.this, response.resultCode())) {
response.targetUI().loadImage(response.data());
}
}, throwable -> {
@@ -129,20 +166,32 @@ private void pickupImage() {
});
}
- private void pickupImages() {
- size = new SmallSize();
- RxPaparazzo.takeImages(SampleActivity.this)
- .useInternalStorage()
- .crop()
+ private RxPaparazzo.SingleSelectionBuilder pickSingle(UCrop.Options options, Size size) {
+ this.size = size;
+
+ RxPaparazzo.SingleSelectionBuilder resized = RxPaparazzo.single(this)
+ .setMaximumFileSizeInBytes(ONE_MEGABYTE_IN_BYTES)
.size(size)
- .usingGallery()
+ .sendToMediaScanner();
+
+ if (options != null) {
+ resized.crop(options);
+ }
+
+ return resized;
+ }
+
+ private Disposable processMultiple(Observable>> pickMultiple) {
+ return pickMultiple
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
- if (response.data().size() == 1)
+ if (PickerUtil.checkResultCode(SampleActivity.this, response.resultCode())) {
+ if (response.data().size() == 1) {
response.targetUI().loadImage(response.data().get(0));
- else response.targetUI().loadImages(response.data());
+ } else {
+ response.targetUI().loadImages(response.data());
+ }
}
}, throwable -> {
throwable.printStackTrace();
@@ -150,53 +199,52 @@ private void pickupImages() {
});
}
- private boolean checkResultCode(int code) {
- if (code == RxPaparazzo.RESULT_DENIED_PERMISSION) {
- showUserDidNotGrantPermissions();
- } else if (code == RxPaparazzo.RESULT_DENIED_PERMISSION_NEVER_ASK) {
- showUserDidNotGrantPermissionsNeverAsk();
- } else if (code != RESULT_OK) {
- showUserCanceled();
- }
+ private RxPaparazzo.MultipleSelectionBuilder pickMultiple(Size size) {
+ this.size = size;
- return code == RESULT_OK;
+ return RxPaparazzo.multiple(this)
+ .setMaximumFileSizeInBytes(ONE_MEGABYTE_IN_BYTES)
+ .crop()
+ .sendToMediaScanner()
+ .size(size);
}
- private void loadImage(String filePath) {
- filesPaths.clear();
- filesPaths.add(filePath);
- imageView.setVisibility(View.VISIBLE);
- recyclerView.setVisibility(View.GONE);
- imageView.setImageDrawable(null);
- recyclerView.setAdapter(null);
+ private void loadImage(FileData fileData) {
+ this.fileDataList = new ArrayList<>();
+ this.fileDataList.add(fileData);
- Picasso.with(getApplicationContext()).setLoggingEnabled(true);
- Picasso.with(getApplicationContext()).invalidate("file://" + filePath);
- Picasso.with(getApplicationContext()).load("file://" + filePath).into(imageView);
+ loadImages();
}
- private void loadImages(List filesPaths) {
- this.filesPaths = filesPaths;
- imageView.setVisibility(View.GONE);
- recyclerView.setVisibility(View.VISIBLE);
- imageView.setImageDrawable(null);
- recyclerView.setAdapter(new ImagesAdapter(filesPaths));
- }
+ private void loadImages(List fileDataList) {
+ this.fileDataList = new ArrayList<>(fileDataList);
- private void showUserCanceled() {
- Toast.makeText(this, getString(R.string.user_canceled), Toast.LENGTH_SHORT).show();
+ loadImages();
}
- private void showUserDidNotGrantPermissions() {
- Toast.makeText(this, getString(R.string.user_did_not_grant_permissions), Toast.LENGTH_SHORT).show();
+ private void loadImages() {
+ if (fileDataList == null || fileDataList.isEmpty()) {
+ return;
+ }
+
+ recyclerView.setVisibility(View.VISIBLE);
+ recyclerView.setAdapter(new ImagesAdapter(fileDataList));
}
- private void showUserDidNotGrantPermissionsNeverAsk() {
- Toast.makeText(this, getString(R.string.user_did_not_grant_permissions_never_ask), Toast.LENGTH_SHORT).show();
+ @Override
+ public List getFileDatas() {
+ return fileDataList;
}
@Override
public List getFilePaths() {
+ List filesPaths = new ArrayList<>();
+ for (FileData fileData : fileDataList) {
+ File file = fileData.getFile();
+ if (file != null) {
+ filesPaths.add(file.getAbsolutePath());
+ }
+ }
return filesPaths;
}
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/Testable.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/Testable.java
index 767b972..0ec182d 100644
--- a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/Testable.java
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/activities/Testable.java
@@ -1,11 +1,14 @@
package com.miguelbcr.ui.rx_paparazzo2.sample.activities;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
import java.util.List;
-public interface Testable {
+public interface Testable {
+ List getFileDatas();
+
List getFilePaths();
Size getSize();
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/adapters/ImagesAdapter.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/adapters/ImagesAdapter.java
index aff45a5..e289149 100644
--- a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/adapters/ImagesAdapter.java
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/adapters/ImagesAdapter.java
@@ -1,23 +1,25 @@
package com.miguelbcr.ui.rx_paparazzo2.sample.adapters;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.TextView;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.sample.R;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.util.List;
-/**
- * Created by miguel on 16/03/2016.
- */public class ImagesAdapter extends RecyclerView.Adapter{
- private List urlImages;
+public class ImagesAdapter extends RecyclerView.Adapter {
+ private List urlImages;
- public ImagesAdapter(List urlImages) {
+ public ImagesAdapter(List urlImages) {
this.urlImages = urlImages;
}
@@ -37,16 +39,32 @@ public int getItemCount() {
return urlImages == null ? 0 : urlImages.size();
}
- protected static class ViewHolder extends RecyclerView.ViewHolder {
+ static class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
+ TextView filenameView;
ViewHolder(View itemView) {
super(itemView);
imageView = (ImageView)itemView.findViewById(R.id.iv_image);
+ filenameView = (TextView)itemView.findViewById(R.id.iv_filename);
}
- public void bind(String imageUrl) {
- Picasso.with(imageView.getContext()).load(new File(imageUrl)).into(imageView);
+ void bind(FileData fileData) {
+ filenameView.setText(fileData.describe());
+ File file = fileData.getFile();
+ if (file != null && file.exists()) {
+ Picasso.with(imageView.getContext())
+ .load(file)
+ .error(R.drawable.ic_description_black_48px)
+ .into(imageView);
+ } else {
+ if (fileData.isExceededMaximumFileSize()) {
+ filenameView.setText("MAXIMUM FILESIZE EXCEEDED");
+ }
+
+ Drawable drawable = AppCompatDrawableManager.get().getDrawable(imageView.getContext(), R.drawable.ic_description_black_48px);
+ imageView.setImageDrawable(drawable);
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/fragments/SampleFragment.java b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/fragments/SampleFragment.java
index 5a6369d..b4d46da 100644
--- a/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/fragments/SampleFragment.java
+++ b/app/src/main/java/com/miguelbcr/ui/rx_paparazzo2/sample/fragments/SampleFragment.java
@@ -1,6 +1,5 @@
package com.miguelbcr.ui.rx_paparazzo2.sample.fragments;
-import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
@@ -10,175 +9,237 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.TextView;
import android.widget.Toast;
import com.miguelbcr.ui.rx_paparazzo2.RxPaparazzo;
-import com.miguelbcr.ui.rx_paparazzo2.entities.Options;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.Response;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.CustomMaxSize;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.OriginalSize;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.SmallSize;
import com.miguelbcr.ui.rx_paparazzo2.sample.R;
+import com.miguelbcr.ui.rx_paparazzo2.sample.activities.PickerUtil;
import com.miguelbcr.ui.rx_paparazzo2.sample.activities.Testable;
import com.miguelbcr.ui.rx_paparazzo2.sample.adapters.ImagesAdapter;
import com.squareup.picasso.Picasso;
import com.yalantis.ucrop.UCrop;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.schedulers.Schedulers;
-import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
public class SampleFragment extends Fragment implements Testable {
- private ImageView imageView;
+ private static final String STATE_FILES = "FILES";
+
private RecyclerView recyclerView;
- private List filesPaths;
+ private ArrayList fileDataList;
private Size size;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.sample_layout, container, false);
- return view;
+ return inflater.inflate(R.layout.sample_layout, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- initViews();
- filesPaths = new ArrayList<>();
+
+ fileDataList = new ArrayList<>();
+ if (savedInstanceState != null) {
+ if (savedInstanceState.containsKey(STATE_FILES)) {
+ List files = (List) savedInstanceState.getSerializable(STATE_FILES);
+ fileDataList.addAll(files);
+ }
+ }
+
size = new OriginalSize();
+
+ initViews();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putSerializable(STATE_FILES, fileDataList);
}
private void initViews() {
- imageView = (ImageView) getView().findViewById(R.id.iv_image);
- recyclerView = (RecyclerView) getView().findViewById(R.id.rv_images);
+ View view = getView();
- recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
+ recyclerView = (RecyclerView) view.findViewById(R.id.rv_images);
+ recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(layoutManager);
- getView().findViewById(R.id.fab_camera).setOnClickListener(v -> captureImage());
- getView().findViewById(R.id.fab_camera_crop).setOnClickListener(v -> captureImageWithCrop());
- getView().findViewById(R.id.fab_pickup_image).setOnClickListener(v -> pickupImage());
- getView().findViewById(R.id.fab_pickup_images).setOnClickListener(v -> pickupImages());
+ view.findViewById(R.id.fab_camera).setOnClickListener(v -> captureImage());
+ view.findViewById(R.id.fab_camera_crop).setOnClickListener(v -> captureImageWithCrop());
+ view.findViewById(R.id.fab_pickup_image).setOnClickListener(v -> pickupImage());
+ view.findViewById(R.id.fab_pickup_images).setOnClickListener(v -> pickupImages());
+ view.findViewById(R.id.fab_pickup_file).setOnClickListener(v -> pickupFile());
+ view.findViewById(R.id.fab_pickup_files).setOnClickListener(v -> pickupFiles());
+
+ loadImages();
}
private void captureImage() {
- size = new SmallSize();
- RxPaparazzo.takeImage(this)
- .size(size)
- .usingCamera()
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
- response.targetUI().loadImage(response.data());
- }
- });
+ SmallSize size = new SmallSize();
+
+ Observable> takeOnePhoto = pickSingle(null, size)
+ .usingCamera();
+
+ processSingle(takeOnePhoto);
}
private void captureImageWithCrop() {
- Options options = new Options();
- options.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.colorAccent));
- options.setAspectRatio(25, 75);
+ UCrop.Options options = new UCrop.Options();
+ options.setToolbarColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
+ options.setToolbarTitle("Cropping single photo");
+ options.withAspectRatio(25, 75);
- size = new OriginalSize();
- RxPaparazzo.takeImage(this)
- .size(size)
- .crop(options)
- .usingCamera()
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
- response.targetUI().loadImage(response.data());
- }
- });
+ OriginalSize size = new OriginalSize();
+
+ Observable> takePhotoAndCrop = pickSingle(options, size)
+ .usingCamera();
+
+ processSingle(takePhotoAndCrop);
}
private void pickupImage() {
UCrop.Options options = new UCrop.Options();
- options.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));
-
- size = new SmallSize();
- RxPaparazzo.takeImage(this)
- .useInternalStorage()
- .crop(options)
- .size(size)
- .usingGallery()
+ options.setToolbarColor(ContextCompat.getColor(getContext(), R.color.colorPrimaryDark));
+ options.setToolbarTitle("Cropping single image");
+
+ Observable> pickUsingGallery = pickSingle(options, new SmallSize())
+ .usingGallery();
+
+ processSingle(pickUsingGallery);
+ }
+
+ private void pickupImages() {
+ Observable>> pickMultiple = pickMultiple(new SmallSize())
+ .usingGallery();
+
+ processMultiple(pickMultiple);
+ }
+
+ private void pickupFile() {
+ UCrop.Options options = new UCrop.Options();
+ options.setToolbarColor(ContextCompat.getColor(getContext(), R.color.colorPrimaryDark));
+ options.setToolbarTitle("Cropping single file");
+
+ Observable> pickUsingGallery = pickSingle(options, new CustomMaxSize(500))
+ .usingFiles();
+
+ processSingle(pickUsingGallery);
+ }
+
+ private void pickupFiles() {
+ Size size = new SmallSize();
+
+ Observable>> pickMultiple = pickMultiple(size)
+ .usingFiles();
+
+ processMultiple(pickMultiple);
+ }
+
+ private void processSingle(Observable> pickUsingGallery) {
+ pickUsingGallery
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
+ if (PickerUtil.checkResultCode(getContext(), response.resultCode())) {
response.targetUI().loadImage(response.data());
}
+ }, throwable -> {
+ throwable.printStackTrace();
+ Toast.makeText(getContext(), "ERROR " + throwable.getMessage(), Toast.LENGTH_SHORT).show();
});
}
- private void pickupImages() {
- size = new SmallSize();
- RxPaparazzo.takeImages(this)
- .useInternalStorage()
- .crop()
- .size(size)
- .usingGallery()
+ private RxPaparazzo.SingleSelectionBuilder pickSingle(UCrop.Options options, Size size) {
+ this.size = size;
+
+ RxPaparazzo.SingleSelectionBuilder resized = RxPaparazzo.single(this)
+ .sendToMediaScanner()
+ .size(size);
+
+ if (options != null) {
+ resized.crop(options);
+ }
+
+ return resized;
+ }
+
+ private Disposable processMultiple(Observable>> pickMultiple) {
+ return pickMultiple
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
- if (checkResultCode(response.resultCode())) {
- if (response.data().size() == 1)
+ if (PickerUtil.checkResultCode(getContext(), response.resultCode())) {
+ if (response.data().size() == 1) {
response.targetUI().loadImage(response.data().get(0));
- else response.targetUI().loadImages(response.data());
+ } else {
+ response.targetUI().loadImages(response.data());
+ }
}
+ }, throwable -> {
+ throwable.printStackTrace();
+ Toast.makeText(getContext(), "ERROR " + throwable.getMessage(), Toast.LENGTH_SHORT).show();
});
}
- private boolean checkResultCode(int code) {
- if (code == RxPaparazzo.RESULT_DENIED_PERMISSION) {
- showUserDidNotGrantPermissions();
- } else if (code == RxPaparazzo.RESULT_DENIED_PERMISSION_NEVER_ASK) {
- showUserDidNotGrantPermissionsNeverAsk();
- } else if (code != Activity.RESULT_OK) {
- showUserCanceled();
- }
+ private RxPaparazzo.MultipleSelectionBuilder pickMultiple(Size size) {
+ this.size = size;
- return code == Activity.RESULT_OK;
+ return RxPaparazzo.multiple(this)
+ .sendToMediaScanner()
+ .crop()
+ .size(size);
}
- private void loadImage(String filePath) {
- filesPaths.clear();
- filesPaths.add(filePath);
- imageView.setVisibility(View.VISIBLE);
- recyclerView.setVisibility(View.GONE);
- File imageFile = new File(filePath);
- Picasso.with(getActivity()).setLoggingEnabled(true);
- Picasso.with(getActivity()).invalidate(new File(filePath));
- Picasso.with(getActivity()).load(imageFile).into(imageView);
- }
+ private void loadImage(FileData fileData) {
+ this.fileDataList = new ArrayList<>();
+ this.fileDataList.add(fileData);
- private void loadImages(List filesPaths) {
- this.filesPaths = filesPaths;
- imageView.setVisibility(View.GONE);
- recyclerView.setVisibility(View.VISIBLE);
- recyclerView.setAdapter(new ImagesAdapter(filesPaths));
+ loadImages();
}
- private void showUserCanceled() {
- Toast.makeText(getActivity(), getString(R.string.user_canceled), Toast.LENGTH_SHORT).show();
+ private void loadImages() {
+ this.fileDataList = new ArrayList<>(fileDataList);
+
+ loadImages(fileDataList);
}
- private void showUserDidNotGrantPermissions() {
- Toast.makeText(getActivity(), getString(R.string.user_did_not_grant_permissions), Toast.LENGTH_SHORT).show();
+ private void loadImages(List fileDataList) {
+ if (fileDataList == null || fileDataList.isEmpty()) {
+ return;
+ }
+
+ recyclerView.setVisibility(View.VISIBLE);
+ recyclerView.setAdapter(new ImagesAdapter(fileDataList));
}
- private void showUserDidNotGrantPermissionsNeverAsk() {
- Toast.makeText(getActivity(), getString(R.string.user_did_not_grant_permissions_never_ask), Toast.LENGTH_SHORT).show();
+ @Override
+ public List getFileDatas() {
+ return fileDataList;
}
@Override
public List getFilePaths() {
+ List filesPaths = new ArrayList<>();
+ for (FileData fileData : fileDataList) {
+ filesPaths.add(fileData.getFile().getAbsolutePath());
+ }
+
return filesPaths;
}
diff --git a/app/src/main/res/drawable/ic_description_black_48px.xml b/app/src/main/res/drawable/ic_description_black_48px.xml
new file mode 100644
index 0000000..0b1281c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_description_black_48px.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_images.xml b/app/src/main/res/layout/item_images.xml
index efc4df6..1118e1e 100644
--- a/app/src/main/res/layout/item_images.xml
+++ b/app/src/main/res/layout/item_images.xml
@@ -2,15 +2,25 @@
+ android:orientation="vertical"
+ android:paddingRight="10dp"
+ android:paddingBottom="10dp">
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/sample_layout.xml b/app/src/main/res/layout/sample_layout.xml
index ac94a93..050695d 100644
--- a/app/src/main/res/layout/sample_layout.xml
+++ b/app/src/main/res/layout/sample_layout.xml
@@ -21,74 +21,112 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
-
+ android:orientation="horizontal"
+ >
-
+
+
+
+
+
+
+
- RxPaparazzo-2
+ RxPaparazzo
Settings
User canceled action
User has not accepted permissions
diff --git a/rx_paparazzo/src/main/res/xml/rx_paparazzo_file_paths.xml b/app/src/main/res/xml/rx_paparazzo_file_paths.xml
similarity index 100%
rename from rx_paparazzo/src/main/res/xml/rx_paparazzo_file_paths.xml
rename to app/src/main/res/xml/rx_paparazzo_file_paths.xml
diff --git a/gradle.properties b/gradle.properties
index 1d3591c..e4a345a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,4 +15,5 @@
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+# org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx1536M
\ No newline at end of file
diff --git a/rx_paparazzo/build.gradle b/rx_paparazzo/build.gradle
index a76a0ba..c7cc537 100644
--- a/rx_paparazzo/build.gradle
+++ b/rx_paparazzo/build.gradle
@@ -68,3 +68,17 @@ artifacts {
archives javadocJar
}
+install {
+ repositories.mavenInstaller {
+ pom {
+ project {
+ packaging 'aar'
+ name 'com.github.miguelbcr'
+ pom.groupId = 'com.github.miguelbcr'
+ pom.artifactId = 'RxPaparazzo'
+ pom.version = '2.x-agrimap'
+ }
+ }
+ }
+}
+
diff --git a/rx_paparazzo/src/main/AndroidManifest.xml b/rx_paparazzo/src/main/AndroidManifest.xml
index 40e5083..0b9ee14 100644
--- a/rx_paparazzo/src/main/AndroidManifest.xml
+++ b/rx_paparazzo/src/main/AndroidManifest.xml
@@ -9,16 +9,6 @@
android:label="@string/app_name"
android:supportsRtl="true">
-
-
-
-
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/RxPaparazzo.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/RxPaparazzo.java
index 08211e3..add1cb2 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/RxPaparazzo.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/RxPaparazzo.java
@@ -19,14 +19,19 @@
import android.app.Activity;
import android.app.Application;
import android.support.v4.app.Fragment;
+
import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.Response;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.ImageUtils;
import com.miguelbcr.ui.rx_paparazzo2.internal.di.ApplicationComponent;
import com.miguelbcr.ui.rx_paparazzo2.internal.di.ApplicationModule;
import com.yalantis.ucrop.UCrop;
-import io.reactivex.Observable;
+
import java.util.List;
+
+import io.reactivex.Observable;
import rx_activity_result2.RxActivityResult;
public final class RxPaparazzo {
@@ -37,146 +42,198 @@ public static void register(Application application) {
RxActivityResult.register(application);
}
- public static BuilderImage takeImage(T activity) {
- return new BuilderImage(activity);
+ public static SingleSelectionBuilder single(T activity) {
+ return new SingleSelectionBuilder(activity);
+ }
+
+ public static SingleSelectionBuilder single(T fragment) {
+ return new SingleSelectionBuilder(fragment);
}
/**
* Prior to API 18, only one image will be retrieved.
*/
- public static BuilderImages takeImages(T activity) {
- return new BuilderImages(activity);
- }
-
- public static BuilderImage takeImage(T fragment) {
- return new BuilderImage(fragment);
+ public static MultipleSelectionBuilder multiple(T activity) {
+ return new MultipleSelectionBuilder(activity);
}
/**
* Prior to API 18, only one image will be retrieved.
*/
- public static BuilderImages takeImages(T fragment) {
- return new BuilderImages(fragment);
+ public static MultipleSelectionBuilder multiple(T fragment) {
+ return new MultipleSelectionBuilder(fragment);
}
- private abstract static class Builder {
- protected final Config config;
- protected final ApplicationComponent applicationComponent;
+ private abstract static class Builder> {
+ private final Config config;
+ private final ApplicationComponent applicationComponent;
+ private final B self;
- public Builder(T ui) {
+ Builder(T ui) {
+ this.self = (B)this;
this.config = new Config();
this.applicationComponent = ApplicationComponent.create(new ApplicationModule(config, ui));
}
- }
- /**
- * Call it when just one image is required to retrieve.
- */
- public static class BuilderImage extends Builder {
+ ApplicationComponent getApplicationComponent() {
+ return applicationComponent;
+ }
- public BuilderImage(T ui) {
- super(ui);
+ Config getConfig() {
+ return config;
+ }
+
+ public B setMaximumFileSizeInBytes(long maximumFileSizeInBytes) {
+ this.config.setMaximumFileSize(maximumFileSizeInBytes);
+ return self;
+ }
+
+ /**
+ * Sets this to the value of name attribute of {@link android.support.v4.content.FileProvider} in AndroidManifest.xml
+ */
+ public B setFileProviderAuthority(String authority) {
+ this.config.setFileProviderAuthority(authority);
+ return self;
+ }
+
+ /**
+ * Sets this to the path to use in the {@link android.support.v4.content.FileProvider} xml file
+ */
+ public B setFileProviderPath(String authority) {
+ this.config.setFileProviderPath(authority);
+ return self;
+ }
+
+ /**
+ * Limits the file which can be selected to those obey {@link android.content.Intent}.CATEGORY_OPENABLE.
+ */
+ public B limitPickerToOpenableFilesOnly() {
+ this.config.setPickOpenableOnly(true);
+ return self;
}
/**
* Calling it the images will be saved in internal storage, otherwise in public storage
*/
- public BuilderImage useInternalStorage() {
- this.config.setUseInternalStorage();
- return this;
+ public B useInternalStorage() {
+ this.config.setUseInternalStorage(true);
+ return self;
}
/**
- * Sets the size for the retrieved image.
+ * Sets the image dimensions for the retrieved image.
*
* @see Size
*/
- public BuilderImage size(Size size) {
+ public B size(Size size) {
this.config.setSize(size);
- return this;
+ return self;
}
/**
- * Call it when crop option is required.
+ * Sets the mime type of the picker.
*/
- public BuilderImage crop() {
+ public B setMimeType(String mimeType) {
+ this.config.setPickMimeType(mimeType);
+ return self;
+ }
+
+ /**
+ * Enables cropping of images.
+ */
+ public B crop() {
this.config.setCrop();
- return this;
+ return self;
}
/**
- * Call it when crop option is required as such as configuring the options of the cropping
+ * Sets crop option is required as such as configuring the options of the cropping
* action.
*/
- public BuilderImage crop(O options) {
+ public B crop(O options) {
this.config.setCrop(options);
- return this;
+ return self;
}
/**
- * Use gallery to retrieve the image.
+ * Send result to media scanner
*/
- public Observable> usingGallery() {
- return applicationComponent.gallery().pickImage();
+ public B sendToMediaScanner() {
+ this.config.setSendToMediaScanner(true);
+ return self;
}
/**
- * Use camera to retrieve the image.
+ * Use Android Storage Access Framework document picker
*/
- public Observable> usingCamera() {
- return applicationComponent.camera().takePhoto();
+ public B useDocumentPicker() {
+ this.config.setUseDocumentPicker(true);
+ return self;
}
+
}
/**
- * Call it when multiple images are required to retrieve from gallery.
+ * Use when just one image is required.
*/
- public static class BuilderImages extends Builder {
+ public static class SingleSelectionBuilder extends Builder> {
- public BuilderImages(T ui) {
+ SingleSelectionBuilder(T ui) {
super(ui);
}
/**
- * Calling it the images will be saved in internal storage, otherwise in public storage
+ * Use file picker to retrieve only images.
*/
- public BuilderImages useInternalStorage() {
- this.config.setUseInternalStorage();
- return this;
+ public Observable> usingGallery() {
+ Config config = getConfig();
+ config.setPickMimeType(ImageUtils.MIME_TYPE_IMAGE_WILDCARD);
+ config.setFailCropIfNotImage(true);
+
+ return usingFiles();
}
/**
- * Sets the size for the retrieved image.
- *
- * @see Size
+ * Use camera to retrieve the image.
*/
- public BuilderImages size(Size size) {
- this.config.setSize(size);
- return this;
+ public Observable> usingCamera() {
+ getConfig().setFailCropIfNotImage(true);
+
+ return getApplicationComponent().camera().takePhoto();
}
/**
- * Call it when crop option is required.
+ * Use file picker to retrieve the files.
*/
- public BuilderImages crop() {
- this.config.setCrop();
- return this;
+ public Observable> usingFiles() {
+ return getApplicationComponent().files().pickFile();
+ }
+ }
+
+ /**
+ * Use when multiple images are required.
+ */
+ public static class MultipleSelectionBuilder extends Builder> {
+
+ MultipleSelectionBuilder(T ui) {
+ super(ui);
}
/**
- * Call it when crop option is required as such as configuring the options of the cropping
- * action.
+ * Use file picker to retrieve only images.
*/
- public BuilderImages crop(O options) {
- this.config.setCrop(options);
- return this;
+ public Observable>> usingGallery() {
+ getConfig().setPickMimeType(ImageUtils.MIME_TYPE_IMAGE_WILDCARD);
+
+ return usingFiles();
}
/**
- * Call it when crop option is required.
+ * Use file picker to retrieve the files.
*/
- public Observable>> usingGallery() {
- return applicationComponent.gallery().pickImages();
+ public Observable>> usingFiles() {
+ return getApplicationComponent().files().pickFiles();
}
+
}
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Config.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Config.java
index 1f7c75a..f272d96 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Config.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/Config.java
@@ -16,28 +16,58 @@
package com.miguelbcr.ui.rx_paparazzo2.entities;
+import android.content.Context;
+
import com.miguelbcr.ui.rx_paparazzo2.entities.size.ScreenSize;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.Size;
import com.yalantis.ucrop.UCrop;
public class Config {
+
+ private static final String DEFAULT_FILE_PROVIDER_PATH = "RxPaparazzo";
+ private static final String DEFAULT_FILE_PROVIDER_AUTHORITIES_SUFFIX = "file_provider";
+ private static final long NO_FILESIZE_LIMIT = Long.MAX_VALUE;
+
+ private UCrop.Options options;
+
+ private long maximumFileSize;
private Size size;
private boolean doCrop;
- private UCrop.Options options;
+ private boolean failCropIfNotImage;
private boolean useInternalStorage;
+ private String pickMimeType;
+ private boolean pickOpenableOnly;
+ private boolean useDocumentPicker;
+ private boolean sendToMediaScanner;
+
+ private String fileProviderAuthority;
+ private String fileProviderDirectory;
+
public Config() {
this.size = new ScreenSize();
this.doCrop = false;
this.useInternalStorage = false;
+ this.useDocumentPicker = false;
+ this.pickOpenableOnly = false;
+ this.pickMimeType = null;
+ this.sendToMediaScanner = false;
+ this.failCropIfNotImage = false;
+ this.fileProviderAuthority = null;
+ this.fileProviderDirectory = null;
+ this.maximumFileSize = NO_FILESIZE_LIMIT;
}
- public Size getSize() {
- return size;
+ public void setMaximumFileSize(long maximumFileSize) {
+ this.maximumFileSize = maximumFileSize;
}
- public boolean doCrop() {
- return doCrop;
+ public long getMaximumFileSize() {
+ return maximumFileSize;
+ }
+
+ public Size getSize() {
+ return size;
}
public void setCrop(UCrop.Options options) {
@@ -58,11 +88,84 @@ public void setSize(Size size) {
this.size = size;
}
- public boolean useInternalStorage() {
+ public boolean isUseInternalStorage() {
return useInternalStorage;
}
- public void setUseInternalStorage() {
- this.useInternalStorage = true;
+ public void setUseInternalStorage(boolean useInternalStorage) {
+ this.useInternalStorage = useInternalStorage;
+ }
+
+ public void setUseDocumentPicker(boolean useDocumentPicker) {
+ this.useDocumentPicker = useDocumentPicker;
+ }
+
+ public boolean isUseDocumentPicker() {
+ return useDocumentPicker;
+ }
+
+ public void setPickMimeType(String pickMimeType) {
+ this.pickMimeType = pickMimeType;
+ }
+
+ public String getMimeType(String defaultMimeType) {
+ if (this.pickMimeType == null) {
+ return defaultMimeType;
+ }
+
+ return pickMimeType;
+ }
+
+ public void setPickOpenableOnly(boolean pickOpenableOnly) {
+ this.pickOpenableOnly = pickOpenableOnly;
+ }
+
+ public boolean isPickOpenableOnly() {
+ return pickOpenableOnly;
+ }
+
+ public void setSendToMediaScanner(boolean sendToMediaScanner) {
+ this.sendToMediaScanner = sendToMediaScanner;
+ }
+
+ public boolean isSendToMediaScanner() {
+ return sendToMediaScanner;
}
+
+ public void setFailCropIfNotImage(boolean failCropIfNotImage) {
+ this.failCropIfNotImage = failCropIfNotImage;
+ }
+
+ public boolean isFailCropIfNotImage() {
+ return failCropIfNotImage;
+ }
+
+ public boolean isDoCrop() {
+ return doCrop;
+ }
+
+ public void setFileProviderAuthority(String fileProviderAuthority) {
+ this.fileProviderAuthority = fileProviderAuthority;
+ }
+
+ public String getFileProviderAuthority(Context context) {
+ if (fileProviderAuthority == null) {
+ return context.getPackageName() + "." + DEFAULT_FILE_PROVIDER_AUTHORITIES_SUFFIX;
+ }
+
+ return fileProviderAuthority;
+ }
+
+ public void setFileProviderPath(String fileProviderDirectory) {
+ this.fileProviderDirectory = fileProviderDirectory;
+ }
+
+ public String getFileProviderDirectory() {
+ if (fileProviderDirectory == null) {
+ return DEFAULT_FILE_PROVIDER_PATH;
+ }
+
+ return fileProviderDirectory;
+ }
+
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/FileData.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/FileData.java
new file mode 100644
index 0000000..62241ff
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/FileData.java
@@ -0,0 +1,121 @@
+package com.miguelbcr.ui.rx_paparazzo2.entities;
+
+import android.util.Log;
+
+import com.miguelbcr.ui.rx_paparazzo2.interactors.Dimensions;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class FileData implements Serializable {
+
+ private static final String FILENAME_MIMETYPE = "%s (%s)";
+ private static final String FILENAME_MIMETYPE_TITLE = "%s (%s) - %s";
+ private static final String FILENAME_TRANSIENT_MIMETYPE_TITLE = "%s %s (%s) - %s";
+
+ private File file;
+ private String filename;
+ private String mimeType;
+ private String title;
+ private boolean transientFile;
+ private boolean exceededMaximumFileSize;
+ private Dimensions originalDimensions;
+
+ public static FileData toFileDataDeleteSourceFileIfTransient(FileData source, File file, boolean transientFile, String mimeType) {
+ deleteSourceFile(source);
+
+ return new FileData(source, file, transientFile, mimeType);
+ }
+
+ public static void deleteSourceFile(FileData fileData) {
+ if (fileData.isTransientFile()) {
+ File file = fileData.getFile();
+ if (file != null && file.exists()) {
+ try {
+ Log.i(FileData.class.getSimpleName(), String.format("Removing source file '%s'", file.getAbsolutePath()));
+ if (!file.delete()) {
+ // silently fail delete
+ }
+ } catch (Exception e) {
+ Log.i(FileData.class.getSimpleName(), String.format("Could not remove source file '%s'", file.getAbsolutePath()), e);
+ }
+ }
+ }
+ }
+
+ public static FileData exceededMaximumFileSize(FileData source) {
+ return new FileData(null, true, source.getFilename(), source.getMimeType(), source.getTitle(), source.getOriginalDimensions(), true);
+ }
+
+ public FileData(FileData source, Dimensions dimensions) {
+ this(source.getFile(), source.isTransientFile(), source.getFilename(), source.getMimeType(), source.getTitle(), dimensions, source.isExceededMaximumFileSize());
+ }
+
+ public FileData(FileData source, File file, boolean transientFile, String mimeType) {
+ this(file, transientFile, source.getFilename(), mimeType, source.getTitle(), source.getOriginalDimensions(), source.isExceededMaximumFileSize());
+ }
+
+ public FileData(File file, boolean transientFile, String filename, String mimeType) {
+ this(file, transientFile, filename, mimeType, null, null, false);
+ }
+
+ public FileData(File file, boolean transientFile, String filename, String mimeType, String title) {
+ this(file, transientFile, filename, mimeType, title, null, false);
+ }
+
+// public FileData(File file, boolean transientFile, String filename, String mimeType, String title, Dimensions dimensions) {
+// this(file, transientFile, filename, mimeType, title, dimensions, false);
+// }
+
+ public FileData(File file, boolean transientFile, String filename, String mimeType, String title, Dimensions originalDimensions, boolean exceededMaximumFileSize) {
+ this.filename = filename;
+ this.transientFile = transientFile;
+ this.mimeType = mimeType;
+ this.file = file;
+ this.title = title;
+ this.exceededMaximumFileSize = exceededMaximumFileSize;
+ this.originalDimensions = originalDimensions;
+ }
+
+ public Dimensions getOriginalDimensions() {
+ return originalDimensions;
+ }
+
+ public File getFile() {
+ return file;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public boolean isTransientFile() {
+ return transientFile;
+ }
+
+ public boolean isExceededMaximumFileSize() {
+ return exceededMaximumFileSize;
+ }
+
+ public String describe() {
+ if (title == null) {
+ return String.format(FILENAME_MIMETYPE, filename, mimeType);
+ }
+
+ return String.format(FILENAME_MIMETYPE_TITLE, filename, mimeType, title);
+ }
+
+ @Override
+ public String toString() {
+ String transientDescription = transientFile ? "Transient" : "Not transient";
+ return String.format(FILENAME_TRANSIENT_MIMETYPE_TITLE, transientDescription, filename, mimeType, title);
+ }
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/TargetUi.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/TargetUi.java
index b8cc1c8..8a63996 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/TargetUi.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/entities/TargetUi.java
@@ -32,7 +32,8 @@ public Activity activity() {
return fragment() != null ? fragment().getActivity() : (Activity) ui;
}
- @Nullable public Fragment fragment() {
+ @Nullable
+ public Fragment fragment() {
if (ui instanceof Fragment) {
return (Fragment) ui;
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/Constants.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/Constants.java
deleted file mode 100644
index b4ec995..0000000
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/Constants.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016 Miguel Garcia
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.miguelbcr.ui.rx_paparazzo2.interactors;
-
-class Constants {
- static final String SUBDIR = "RxPaparazzo";
- static final String SHOOT_APPEND = "shoot.jpg";
- static final String CROP_APPEND = "cropped";
- static final String NO_CROP_APPEND = "no_cropped";
- static final String EXT_PNG = "png";
- /**
- * The same name that is placed on the manifest on provivder
tag
- */
- static final String FILE_PROVIDER = "file_provider";
- static final String DATE_FORMAT = "ddMMyyyy_HHmmss_SSS";
- static final String LOCALE_EN = "en";
-}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/CropImage.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/CropImage.java
index 8bb0258..11bf7b1 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/CropImage.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/CropImage.java
@@ -18,112 +18,142 @@
import android.content.Intent;
import android.net.Uri;
+
import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.Options;
import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
import com.yalantis.ucrop.UCrop;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
-import io.reactivex.functions.BiFunction;
import io.reactivex.functions.Function;
-import java.io.File;
-public final class CropImage extends UseCase {
+public final class CropImage extends UseCase {
+ private static final String CROP_APPEND = "CROPPED-";
+
private final Config config;
private final StartIntent startIntent;
- private final GetPath getPath;
private final TargetUi targetUi;
private final ImageUtils imageUtils;
- private Uri uri;
+ private FileData fileData;
- public CropImage(TargetUi targetUi, Config config, StartIntent startIntent, GetPath getPath,
- ImageUtils imageUtils) {
+ public CropImage(TargetUi targetUi, Config config, StartIntent startIntent, ImageUtils imageUtils) {
this.targetUi = targetUi;
this.config = config;
this.startIntent = startIntent;
- this.getPath = getPath;
this.imageUtils = imageUtils;
}
- public CropImage with(Uri uri) {
- this.uri = uri;
+ public CropImage with(FileData fileData) {
+ this.fileData = fileData;
return this;
}
- @Override public Observable react() {
- if (config.doCrop()) {
- return getIntent().flatMap(new Function>() {
- @Override public ObservableSource apply(Intent intent) throws Exception {
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- return startIntent.with(intent).react().map(new Function() {
- @Override public Uri apply(Intent intentResult) throws Exception {
- return UCrop.getOutput(intentResult);
- }
- });
+ @Override
+ public Observable react() {
+ if (config.isDoCrop()) {
+
+ // bypass cropping if not image
+ if (!isImage()) {
+ if (config.isFailCropIfNotImage()) {
+ throw new IllegalArgumentException("Expected an image file, cannot perform image crop");
+ } else {
+ return Observable.just(fileData);
}
- });
+ }
+
+ return cropImage();
}
- return getOutputUriNoCrop();
+ return Observable.just(fileData);
}
- private Observable getIntent() {
- return Observable.zip(getInputUri(), getOutputUriCrop(), new BiFunction() {
- @Override public Intent apply(Uri uri, Uri outputUri) throws Exception {
- UCrop.Options options = config.getOptions();
- if (options == null) return UCrop.of(uri, outputUri).getIntent(targetUi.getContext());
+ private Observable cropImage() {
+ final File outputFile = getOutputFile();
+ Uri outputUri = Uri.fromFile(outputFile);
+ Observable intent = Observable.just(getIntent(outputUri));
- if (options instanceof Options) {
- return getIntentWithOptions((Options) options, outputUri);
- } else {
- return UCrop.of(uri, outputUri)
- .withOptions(config.getOptions())
- .getIntent(targetUi.getContext());
- }
+ return intent.flatMap(new Function>() {
+ @Override
+ public ObservableSource apply(Intent intent) throws Exception {
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ return startIntent.with(intent).react()
+ .map(new Function() {
+ @Override
+ public Uri apply(Intent intentResult) throws Exception {
+ return UCrop.getOutput(intentResult);
+ }
+ })
+ .flatMap(new Function>() {
+ @Override
+ public ObservableSource apply(Uri uri) throws Exception {
+ if (!outputFile.exists()) {
+ throw new FileNotFoundException(String.format("Cropped file not saved", outputFile.getAbsolutePath()));
+ }
+
+ FileData result = FileData.toFileDataDeleteSourceFileIfTransient(fileData, outputFile, true, ImageUtils.MIME_TYPE_JPEG);
+
+ return Observable.just(result);
+ }
+ });
}
});
}
+ private boolean isImage() {
+ File file = fileData.getFile();
+
+ return imageUtils.isImage(file);
+ }
+
+ private Intent getIntent(Uri outputUri) {
+ Uri inputUri = getInputUri();
+
+ UCrop.Options options = config.getOptions();
+ if (options == null) {
+ return UCrop.of(inputUri, outputUri).getIntent(targetUi.getContext());
+ }
+
+ if (options instanceof Options) {
+ return getIntentWithOptions((Options) options, outputUri);
+ } else {
+ return UCrop.of(inputUri, outputUri)
+ .withOptions(config.getOptions())
+ .getIntent(targetUi.getContext());
+ }
+ }
+
private Intent getIntentWithOptions(Options options, Uri outputUri) {
- UCrop uCrop = UCrop.of(uri, outputUri);
+ Uri uri = Uri.fromFile(fileData.getFile());
- uCrop = uCrop.withOptions(options);
- if (options.getX() != 0) uCrop = uCrop.withAspectRatio(options.getX(), options.getY());
+ UCrop uCrop = UCrop.of(uri, outputUri).withOptions(options);
+ if (options.getX() != 0) {
+ uCrop.withAspectRatio(options.getX(), options.getY());
+ }
if (options.getWidth() != 0) {
- uCrop = uCrop.withMaxResultSize(options.getWidth(), options.getHeight());
+ uCrop.withMaxResultSize(options.getWidth(), options.getHeight());
}
return uCrop.getIntent(targetUi.getContext());
}
- private Observable getInputUri() {
- return getPath.with(uri).react().map(new Function() {
- @Override public Uri apply(String filePath) throws Exception {
- return Uri.fromFile(new File(filePath)).buildUpon().build();
- }
- });
+ private Uri getInputUri() {
+ return Uri.fromFile(fileData.getFile());
}
- private Observable getOutputUriCrop() {
- return getPath.with(uri).react().flatMap(new Function>() {
- @Override public ObservableSource apply(String filepath) throws Exception {
- String extension = imageUtils.getFileExtension(filepath);
- String filename = Constants.CROP_APPEND + extension;
- File file = imageUtils.getPrivateFile(filename);
- return Observable.just(Uri.fromFile(file).buildUpon().build());
- }
- });
- }
+ private File getOutputFile() {
+ String destination = fileData.getFile().getAbsolutePath();
+ String extension = imageUtils.getFileExtension(destination, ImageUtils.JPG_FILE_EXTENSION);
+ String directory = config.getFileProviderDirectory();
+ String outputFilename = imageUtils.createTimestampedFilename(CROP_APPEND, extension);
+ File outputFile = imageUtils.getPrivateFile(directory, outputFilename);
- private Observable getOutputUriNoCrop() {
- return getPath.with(uri).react().flatMap(new Function>() {
- @Override public ObservableSource apply(String filepath) throws Exception {
- String extension = imageUtils.getFileExtension(filepath);
- String filename = Constants.NO_CROP_APPEND + extension;
- File file = imageUtils.getPrivateFile(filename);
- imageUtils.copy(new File(filepath), file);
- return Observable.just(Uri.fromFile(file).buildUpon().build());
- }
- });
+ return outputFile;
}
+
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/Dimensions.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/Dimensions.java
new file mode 100644
index 0000000..d483f73
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/Dimensions.java
@@ -0,0 +1,39 @@
+package com.miguelbcr.ui.rx_paparazzo2.interactors;
+
+import java.io.Serializable;
+
+public final class Dimensions implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private int width;
+ private int height;
+
+ public Dimensions() {
+ }
+
+ public Dimensions(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public boolean hasSize() {
+ return width > 0 && height > 0;
+ }
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/DownloadFile.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/DownloadFile.java
new file mode 100644
index 0000000..ba86c29
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/DownloadFile.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2016 Miguel Garcia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.miguelbcr.ui.rx_paparazzo2.interactors;
+
+import android.net.Uri;
+import android.os.Build;
+import android.support.v4.provider.DocumentFile;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import io.reactivex.Observable;
+import io.reactivex.ObservableEmitter;
+import io.reactivex.ObservableOnSubscribe;
+import io.reactivex.schedulers.Schedulers;
+
+public final class DownloadFile extends UseCase {
+
+ private static final String DOWNLOADED_FILENAME_PREFIX = "DOWNLOAD-";
+ private static final String MATCH_ANYTHING_NOT_A_LETTER_OR_NUMBER = "[^A-Za-z0-9 ]";
+
+ private final TargetUi targetUi;
+ private final Config config;
+ private final ImageUtils imageUtils;
+ private Uri uri;
+ private FileData fileData;
+
+ public DownloadFile(TargetUi targetUi, Config config, ImageUtils imageUtils) {
+ this.targetUi = targetUi;
+ this.config = config;
+ this.imageUtils = imageUtils;
+ }
+
+ @Override
+ Observable react() {
+ return getObservableDownloadFile();
+ }
+
+ public DownloadFile with(Uri uri, FileData fileData) {
+ this.uri = uri;
+ this.fileData = fileData;
+
+ return this;
+ }
+
+ private Observable getObservableDownloadFile() {
+ return Observable.create(new ObservableOnSubscribe() {
+ @Override public void subscribe(ObservableEmitter subscriber) throws Exception {
+ if (!subscriber.isDisposed()) {
+ try {
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+ subscriber.onNext(getUsingContentResolver());
+ } else {
+ subscriber.onNext(downloadFile());
+ }
+
+ subscriber.onComplete();
+ } catch (FileNotFoundException e) {
+ subscriber.onError(e);
+ }
+ }
+ }
+ }).subscribeOn(Schedulers.io());
+ }
+
+ private FileData downloadFile() throws Exception {
+ String mimeType = imageUtils.getMimeType(targetUi.getContext(), uri);
+ String filename = getFilename(uri);
+ String fileExtension = imageUtils.getFileExtension(uri);
+ File file = imageUtils.getOutputFile(DOWNLOADED_FILENAME_PREFIX, fileExtension);
+
+ URL url = new URL(uri.toString());
+ URLConnection connection = url.openConnection();
+ connection.connect();
+ InputStream inputStream = new BufferedInputStream(url.openStream());
+ imageUtils.copy(inputStream, file);
+
+ return toFileData(mimeType, filename, file);
+ }
+
+ private FileData toFileData(String mimeType, String filename, File destination) {
+ if (fileData == null || fileData.getFilename() == null) {
+ return new FileData(destination, true, filename, mimeType);
+ } else {
+ // maintain existing filename and mime-type unless missing
+ String fileMimeType = fileData.getMimeType();
+ if (fileMimeType == null) {
+ fileMimeType = mimeType;
+ }
+
+ return FileData.toFileDataDeleteSourceFileIfTransient(fileData, destination, true, fileMimeType);
+ }
+ }
+
+ private FileData getUsingContentResolver() throws FileNotFoundException {
+ String mimeType = imageUtils.getMimeType(targetUi.getContext(), uri);
+ String uriFilename = getFilename(uri);
+ String fileExtension = imageUtils.getFileExtension(uri);
+ File file = imageUtils.getOutputFile(DOWNLOADED_FILENAME_PREFIX, fileExtension);
+
+ InputStream inputStream = targetUi.getContext().getContentResolver().openInputStream(uri);
+ imageUtils.copy(inputStream, file);
+
+ return toFileData(mimeType, uriFilename, file);
+ }
+
+ private String getFilename(Uri uri) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ DocumentFile file = DocumentFile.fromSingleUri(targetUi.getContext(), uri);
+ if (file != null) {
+ String fileName = file.getName();
+ if (fileName != null) {
+ return ImageUtils.stripPathFromFilename(fileName);
+ }
+ }
+ }
+
+ String fileName = uri.getLastPathSegment();
+ String safeFilename = fileName.replaceAll(MATCH_ANYTHING_NOT_A_LETTER_OR_NUMBER, "");
+
+ return safeFilename + "." + imageUtils.getFileExtension(uri);
+ }
+
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/DownloadImage.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/DownloadImage.java
deleted file mode 100644
index 47b41c1..0000000
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/DownloadImage.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2016 Miguel Garcia
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.miguelbcr.ui.rx_paparazzo2.interactors;
-
-import android.net.Uri;
-import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
-import io.reactivex.Observable;
-import io.reactivex.ObservableEmitter;
-import io.reactivex.ObservableOnSubscribe;
-import io.reactivex.schedulers.Schedulers;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-
-public final class DownloadImage extends UseCase {
- private final TargetUi targetUi;
- private final ImageUtils imageUtils;
- private Uri uri;
-
- public DownloadImage(TargetUi targetUi, ImageUtils imageUtils) {
- this.targetUi = targetUi;
- this.imageUtils = imageUtils;
- }
-
- @Override Observable react() {
- return getObservableDownloadFile();
- }
-
- public DownloadImage with(Uri uri) {
- this.uri = uri;
- return this;
- }
-
- private Observable getObservableDownloadFile() {
- return Observable.create(new ObservableOnSubscribe() {
- @Override public void subscribe(ObservableEmitter subscriber) throws Exception {
- if (!subscriber.isDisposed()) {
- try {
- if ("content".equalsIgnoreCase(uri.getScheme())) {
- subscriber.onNext(getContent());
- } else {
- subscriber.onNext(downloadFile());
- }
-
- subscriber.onComplete();
- } catch (FileNotFoundException e) {
- subscriber.onError(e);
- }
- }
- }
- }).subscribeOn(Schedulers.io());
- }
-
- private String downloadFile() throws Exception {
- URL url = new URL(uri.toString());
- URLConnection connection = url.openConnection();
- connection.connect();
- InputStream inputStream = new BufferedInputStream(url.openStream(), 1024);
- String filename = getFilename(uri);
- filename += imageUtils.getFileExtension(uri);
- File file = imageUtils.getPrivateFile(filename);
- imageUtils.copy(inputStream, file);
- return file.getAbsolutePath();
- }
-
- private String getContent() throws FileNotFoundException {
- InputStream inputStream = targetUi.getContext().getContentResolver().openInputStream(uri);
- String filename = getFilename(uri);
- filename += imageUtils.getFileExtension(uri);
- File file = imageUtils.getPrivateFile(filename);
- imageUtils.copy(inputStream, file);
- return file.getAbsolutePath();
- }
-
- private String getFilename(Uri uri) {
- // Remove non alphanumeric characters
- return uri.getLastPathSegment().replaceAll("[^A-Za-z0-9 ]", "");
- }
-}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetDimens.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetDimens.java
deleted file mode 100644
index 12f84d7..0000000
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetDimens.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2016 Miguel Garcia
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.miguelbcr.ui.rx_paparazzo2.interactors;
-
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.util.DisplayMetrics;
-import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
-import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
-import com.miguelbcr.ui.rx_paparazzo2.entities.size.CustomMaxSize;
-import com.miguelbcr.ui.rx_paparazzo2.entities.size.OriginalSize;
-import com.miguelbcr.ui.rx_paparazzo2.entities.size.ScreenSize;
-import io.reactivex.Observable;
-import io.reactivex.functions.Function;
-
-public final class GetDimens extends UseCase {
- private final TargetUi targetUi;
- private final Config config;
- private final GetPath getPath;
- private Uri uri;
-
- public GetDimens(TargetUi targetUi, Config config, GetPath getPath) {
- this.targetUi = targetUi;
- this.config = config;
- this.getPath = getPath;
- }
-
- public GetDimens with(Uri uri) {
- this.uri = uri;
- return this;
- }
-
- @Override public Observable react() {
- return getPath.with(uri).react().map(new Function() {
- @Override public int[] apply(String filePath) throws Exception {
- if (config.getSize() instanceof OriginalSize) {
- return GetDimens.this.getFileDimens(filePath);
- } else if (config.getSize() instanceof CustomMaxSize) {
- CustomMaxSize customMaxSize = (CustomMaxSize) config.getSize();
- return getCustomDimens(customMaxSize, filePath);
- } else if (config.getSize() instanceof ScreenSize) {
- return GetDimens.this.getScreenDimens();
- } else {
- int[] dimens = GetDimens.this.getScreenDimens();
- return new int[] { dimens[0] / 8, dimens[1] / 8 };
- }
- }
- });
- }
-
- private int[] getScreenDimens() {
- DisplayMetrics metrics = targetUi.getContext().getResources().getDisplayMetrics();
- return new int[] { metrics.widthPixels, metrics.heightPixels };
- }
-
- private int[] getFileDimens(String filePath) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(filePath, options);
- return new int[] { options.outWidth, options.outHeight };
- }
-
- private int[] getCustomDimens(CustomMaxSize customMaxSize, String filePath) {
- int maxSize = customMaxSize.getMaxImageSize();
- int[] dimens = GetDimens.this.getFileDimens(filePath);
- int maxFileSize = Math.max(dimens[0], dimens[1]);
- if (maxFileSize < maxSize) {
- return dimens;
- }
- float scaleFactor = (float) maxSize / maxFileSize;
- dimens[0] *= scaleFactor;
- dimens[1] *= scaleFactor;
- return dimens;
- }
-}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetPath.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetPath.java
index fd60b0e..3c3865c 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetPath.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GetPath.java
@@ -25,17 +25,43 @@
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
+import android.support.annotation.Nullable;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
+import java.io.File;
+
import io.reactivex.Observable;
-public final class GetPath extends UseCase {
+public final class GetPath extends UseCase {
+
+ private static final String URI_SCHEME_CONTENT = "content";
+ private static final String URI_SCHEME_FILE = "file";
+ private static final String PUBLIC_DOWNLOADS_URI = "content://downloads/public_downloads";
+ private static final String DOCUMENT_AUTHORITY_EXTERNAL_STORAGE = "com.android.externalstorage.documents";
+ private static final String DOCUMENT_AUTHORITY_DOWNLOADS = "com.android.providers.downloads.documents";
+ private static final String DOCUMENT_AUTHORITY_MEDIA = "com.android.providers.media.documents";
+ private static final String DOCUMENT_TYPE_PRIMARY = "primary";
+ private static final String DOCUMENT_TYPE_IMAGE = "image";
+ private static final String DOCUMENT_TYPE_VIDEO = "video";
+ private static final String DOCUMENT_TYPE_AUDIO = "audio";
+
+ private static class Document {
+ String type;
+ String id;
+ }
+
+ private final Config config;
private final TargetUi targetUi;
- private final DownloadImage downloadImage;
+ private final DownloadFile downloadFile;
private Uri uri;
- public GetPath(TargetUi targetUi, DownloadImage downloadImage) {
+ public GetPath(Config config, TargetUi targetUi, DownloadFile downloadFile) {
+ this.config = config;
this.targetUi = targetUi;
- this.downloadImage = downloadImage;
+ this.downloadFile = downloadFile;
}
public GetPath with(Uri uri) {
@@ -43,83 +69,141 @@ public GetPath with(Uri uri) {
return this;
}
- @Override public Observable react() {
- return getPath();
+ @Override
+ public Observable react() {
+ return getFileData();
}
- @SuppressLint("NewApi") private Observable getPath() {
- boolean isFromKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
- Context context = targetUi.activity();
- String filePath = null;
+ @SuppressLint("NewApi")
+ private Observable getFileData() {
+ Context context = targetUi.getContext();
- if (uri == null) {
+ if (uri == null || context == null) {
return null;
}
- if (isFromKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+ FileData fileData = getFileData(context);
+
+ if (fileData != null && fileData.getFile() != null) {
+ return Observable.just(fileData);
+ }
+
+ return downloadFile.with(uri, fileData).react();
+ }
+
+ @Nullable
+ private FileData getFileData(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
Document document = getDocument(uri);
- if ("primary".equalsIgnoreCase(document.type)) {
- filePath = Environment.getExternalStorageDirectory() + "/" + document.id;
+
+ if (DOCUMENT_TYPE_PRIMARY.equalsIgnoreCase(document.type)) {
+ return getPrimaryExternalDocument(document);
}
} else if (isDownloadsDocument(uri)) {
- String id = DocumentsContract.getDocumentId(uri);
- Uri contentUri =
- ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),
- Long.valueOf(id));
- filePath = getDataColumn(context, contentUri, null, null);
+ return getDownloadsDocument(context);
} else if (isMediaDocument(uri)) {
- Document document = getDocument(uri);
- Uri contentUri = null;
- if ("image".equals(document.type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(document.type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(document.type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- }
-
- filePath = getDataColumn(context, contentUri, MediaStore.Images.Media._ID + "=?",
- new String[] { document.id });
+ return getMediaDocument(context);
}
- } else if ("content".equalsIgnoreCase(uri.getScheme())) {
- filePath = getDataColumn(context, uri, null, null);
- } else if ("file".equalsIgnoreCase(uri.getScheme())) {
- filePath = uri.getPath();
+ } else if (URI_SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme())) {
+ if (!isFileProvider(context)) {
+ return getDataColumn(context, uri, null, null);
+ }
+ } else if (URI_SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
+ return getFile(context);
}
- if (filePath == null) {
- return downloadImage.with(uri).react();
+ return null;
+ }
+
+ private FileData getFile(Context context) {
+ File file = new File(uri.getPath());
+ String fileName = ImageUtils.getFileName(uri.getPath());
+ String mimeType = ImageUtils.getMimeType(context, uri);
+
+ return new FileData(file, false, fileName, mimeType);
+ }
+
+ private boolean isFileProvider(Context context) {
+ String authority = config.getFileProviderAuthority(context);
+
+ return uri.getPath().startsWith(authority);
+ }
+
+ private FileData getPrimaryExternalDocument(Document document) {
+ String mimeType = ImageUtils.getMimeType(document.id);
+ String fileName = ImageUtils.stripPathFromFilename(document.id);
+ String filePath = Environment.getExternalStorageDirectory() + "/" + document.id;
+ File file = new File(filePath);
+
+ return new FileData(file, false, fileName, mimeType);
+ }
+
+ private FileData getDownloadsDocument(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ String id = DocumentsContract.getDocumentId(uri);
+ Uri contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOADS_URI), Long.valueOf(id));
+
+ return getDataColumn(context, contentUri, null, null);
}
- return Observable.just(filePath);
+ throw new IllegalStateException("Minimum Android API version must be be KitKat to use DocumentsContract API");
}
- private class Document {
- String type;
- String id;
+ private FileData getMediaDocument(Context context) {
+ Document document = getDocument(uri);
+ Uri contentUri = null;
+ if (DOCUMENT_TYPE_IMAGE.equals(document.type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if (DOCUMENT_TYPE_VIDEO.equals(document.type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if (DOCUMENT_TYPE_AUDIO.equals(document.type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ return getDataColumn(context, contentUri, MediaStore.Images.Media._ID + "=?", new String[] { document.id });
}
- @SuppressLint("NewApi") private Document getDocument(Uri uri) {
+ @SuppressLint("NewApi")
+ private Document getDocument(Uri uri) {
Document document = new Document();
String docId = DocumentsContract.getDocumentId(uri);
String[] docArray = docId.split(":");
document.type = docArray[0];
document.id = docArray[1];
+
return document;
}
- private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
+ private FileData getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
- String column = MediaStore.Images.Media.DATA;
- String[] projection = { column };
+ String dataColumn = MediaStore.Images.Media.DATA;
+ String nameColumn = MediaStore.Images.Media.DISPLAY_NAME;
+ String mimeTypeColumn = MediaStore.Images.Media.MIME_TYPE;
+ String titleColumn = MediaStore.Images.Media.TITLE;
+
+ String[] projection = { dataColumn, nameColumn, mimeTypeColumn, titleColumn };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
- cursor.moveToFirst();
- return cursor.getString(cursor.getColumnIndexOrThrow(column));
+ if (cursor != null && cursor.moveToFirst()) {
+ String filePath = cursor.getString(cursor.getColumnIndexOrThrow(dataColumn));
+ String fileName = cursor.getString(cursor.getColumnIndexOrThrow(nameColumn));
+ String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(mimeTypeColumn));
+ String title = cursor.getString(cursor.getColumnIndexOrThrow(titleColumn));
+
+ File file;
+ if (filePath != null) {
+ file = new File(filePath);
+ } else {
+ file = null;
+ }
+
+ return new FileData(file, false, fileName, mimeType, title);
+ } else {
+ return null;
+ }
} catch (Exception e) {
- // throw Exceptions.propagate(e);
return null;
} finally {
if (cursor != null) {
@@ -129,14 +213,14 @@ private String getDataColumn(Context context, Uri uri, String selection, String[
}
private boolean isExternalStorageDocument(Uri uri) {
- return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ return DOCUMENT_AUTHORITY_EXTERNAL_STORAGE.equals(uri.getAuthority());
}
private boolean isDownloadsDocument(Uri uri) {
- return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ return DOCUMENT_AUTHORITY_DOWNLOADS.equals(uri.getAuthority());
}
private boolean isMediaDocument(Uri uri) {
- return "com.android.providers.media.documents".equals(uri.getAuthority());
+ return DOCUMENT_AUTHORITY_MEDIA.equals(uri.getAuthority());
}
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GrantPermissions.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GrantPermissions.java
index d19c03e..84d42f1 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GrantPermissions.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/GrantPermissions.java
@@ -57,7 +57,7 @@ public GrantPermissions with(String... permissions) {
return permissions;
}
})
- .flatMap(new Function>() {
+ .concatMap(new Function>() {
@Override public ObservableSource apply(Permission permission) throws Exception {
if (permission.granted) {
return Observable.just(Activity.RESULT_OK);
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ImageUtils.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ImageUtils.java
index ab9e4da..bad83ed 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ImageUtils.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ImageUtils.java
@@ -24,11 +24,14 @@
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
+import android.util.Log;
import android.webkit.MimeTypeMap;
+
import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
import com.miguelbcr.ui.rx_paparazzo2.entities.size.OriginalSize;
-import io.reactivex.exceptions.Exceptions;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -39,7 +42,22 @@
import java.util.Date;
import java.util.Locale;
+import io.reactivex.exceptions.Exceptions;
+
public final class ImageUtils {
+
+ private static final String TAG = ImageUtils.class.getSimpleName();
+ private static final String DEFAULT_EXTENSION = "";
+ public static final String JPG_FILE_EXTENSION = "jpg";
+ private static final String PNG_FILE_EXTENSION = "png";
+
+ public static final String MIME_TYPE_IMAGE_WILDCARD = "image/*";
+ public static final String MIME_TYPE_JPEG = "image/jpeg";
+ private static final String MIME_TYPE_PNG = "image/png";
+
+ private static final String DATE_FORMAT = "yyyyMMdd_HHmm_ssSSS";
+ private static final String LOCALE_EN = "en";
+
private final TargetUi targetUi;
private final Config config;
@@ -48,41 +66,52 @@ public ImageUtils(TargetUi targetUi, Config config) {
this.config = config;
}
- File getOutputFile(String extension) {
- String dirname = getApplicationName(targetUi.getContext());
- File dir = getDir(null, dirname);
- return getFile(dir, extension);
+ public File getOutputFile(String prefix, String extension) {
+ String fileProviderDirectory = config.getFileProviderDirectory();
+ File dir = getDir(null, fileProviderDirectory);
+
+ return createTimestampedFile(dir, prefix, extension);
}
- private File getFile(File dir, String extension) {
- SimpleDateFormat simpleDateFormat =
- new SimpleDateFormat(Constants.DATE_FORMAT, new Locale(Constants.LOCALE_EN));
- String datetime = simpleDateFormat.format(new Date());
- File file = new File(dir.getAbsolutePath(), "IMG-" + datetime + extension);
+ private File createTimestampedFile(File dir, String prefix, String extension) {
+ File file = new File(dir.getAbsolutePath(), createTimestampedFilename(prefix, extension));
while (file.exists()) {
- datetime = simpleDateFormat.format(new Date());
- file = new File(dir.getAbsolutePath(), "IMG-" + datetime + extension);
+ String filename = createTimestampedFilename(prefix, extension);
+ file = new File(dir.getAbsolutePath(), filename);
}
return file;
}
- File getPrivateFile(String filename) {
- File dir = new File(targetUi.getContext().getFilesDir(), Constants.SUBDIR);
+ public String createTimestampedFilename(String prefix, String extension) {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT, new Locale(LOCALE_EN));
+ String datetime = simpleDateFormat.format(new Date());
+
+ if (!TextUtils.isEmpty(extension) && !extension.startsWith(".")) {
+ extension = "." + extension;
+ }
+
+ return prefix + datetime + extension;
+ }
+
+ public File getPrivateFile(String directory, String filename) {
+ File dir = new File(targetUi.getContext().getFilesDir(), directory);
dir.mkdirs();
+
return new File(dir, filename);
}
private String getApplicationName(Context context) {
int stringId = context.getApplicationInfo().labelRes;
+
return context.getString(stringId);
}
private File getDir(String dirRoot, String dirname) {
File storageDir = null;
- if (!config.useInternalStorage()) {
+ if (!config.isUseInternalStorage()) {
storageDir = getPublicDir(dirRoot, dirname);
}
@@ -97,10 +126,14 @@ private File getPublicDir(String dirRoot, String dirname) {
File storageDir = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- File dir = (dirRoot != null) ? Environment.getExternalStoragePublicDirectory(dirRoot)
- : Environment.getExternalStorageDirectory();
- storageDir = new File(dir, dirname);
+ File dir;
+ if (dirRoot == null) {
+ dir = Environment.getExternalStorageDirectory();
+ } else {
+ dir = Environment.getExternalStoragePublicDirectory(dirRoot);
+ }
+ storageDir = new File(dir, dirname);
if (!storageDir.exists() && !storageDir.mkdirs()) {
storageDir = null;
}
@@ -121,70 +154,132 @@ private File getPrivateDir(String dirname) {
return storageDir;
}
- String getFileExtension(String filepath) {
+ public static String getFileName(String filepath) {
+ File file = new File(filepath);
+
+ return file.getName();
+ }
+
+ public static String stripPathFromFilename(String fileName) {
+ int lastSlash = fileName.lastIndexOf("/");
+ if (lastSlash == -1) {
+ return fileName;
+ } else {
+ return fileName.substring(lastSlash + 1);
+ }
+ }
+
+ public String getFileExtension(String filepath) {
+ return getFileExtension(filepath, DEFAULT_EXTENSION);
+ }
+
+ public String getFileExtension(String filepath, String defaultExtension) {
String extension = "";
if (filepath != null) {
int i = filepath.lastIndexOf('.');
- extension = i > 0 ? filepath.substring(i) : "";
+ if (i > 0) {
+ extension = filepath.substring(i + 1);
+ } else {
+ extension = "";
+ }
}
- return (TextUtils.isEmpty(extension)) ? ".jpg" : extension;
+ if (TextUtils.isEmpty(extension) && !TextUtils.isEmpty(defaultExtension)) {
+ extension = defaultExtension;
+ }
+
+ return extension;
}
String getFileExtension(Uri uri) {
+ String mimeType = getMimeType(targetUi.getContext(), uri);
+
+ if (TextUtils.isEmpty(mimeType)) {
+ return getFileExtension(uri.getLastPathSegment());
+ } else {
+ return mimeType.split("/")[1];
+ }
+ }
+
+ public static String getMimeType(Context context, Uri uri) {
String mimeType;
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
- mimeType = targetUi.getContext().getContentResolver().getType(uri);
+ mimeType = context.getContentResolver().getType(uri);
} else {
- String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase());
+ String path = uri.toString();
+ mimeType = getMimeType(path);
}
+ return mimeType;
+ }
- return (TextUtils.isEmpty(mimeType)) ? getFileExtension(uri.getLastPathSegment())
- : "." + mimeType.split("/")[1];
+ public static String getMimeType(String path) {
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(path);
+
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase());
}
- String scaleImage(String filePath, String filePathOutput, int[] dimens) {
+ public static Dimensions getImageDimensions(File file) {
+ String filePath = file.getAbsolutePath();
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(filePath, options);
+
+ return new Dimensions(options.outWidth, options.outHeight);
+ }
+
+ FileData scaleImage(FileData inputData, File destination, Dimensions dimens) {
+ File input = inputData.getFile();
+ String mimeType;
+
if (config.getSize() instanceof OriginalSize) {
- copyFileAndExifTags(filePath, filePathOutput, dimens);
- return filePathOutput;
- }
+ copyFileAndExifTags(input, destination, dimens);
+ mimeType = inputData.getMimeType();
+ } else {
+ Bitmap bitmap = sampleBitmap(input, dimens.getWidth(), dimens.getHeight());
+ if (bitmap == null) {
+ copyFileAndExifTags(input, destination, dimens);
+ mimeType = inputData.getMimeType();
+ } else {
+ Bitmap.CompressFormat compressFormat = getCompressFormat(destination.getName());
+ if (Bitmap.CompressFormat.JPEG == compressFormat) {
+ mimeType = MIME_TYPE_JPEG;
+ } else if (Bitmap.CompressFormat.PNG == compressFormat) {
+ mimeType = MIME_TYPE_PNG;
+ } else {
+ throw new IllegalStateException(String.format("Received unexpected compression format '%s'", compressFormat));
+ }
- Bitmap bitmap = handleBitmapSampling(filePath, dimens[0], dimens[1]);
- if (bitmap == null) {
- copyFileAndExifTags(filePath, filePathOutput, dimens);
- return filePathOutput;
+ bitmap2file(bitmap, destination, compressFormat);
+ copyExifTags(input, destination, dimens);
+ }
}
- bitmap2file(bitmap, new File(filePathOutput), getCompressFormat(filePathOutput));
- copyExifTags(filePath, filePathOutput, dimens);
-
- return filePathOutput;
+ return new FileData(inputData, destination, true, mimeType);
}
private Bitmap.CompressFormat getCompressFormat(String filePath) {
Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
- final String extension = getFileExtension(filePath);
+ String extension = getFileExtension(filePath);
- if (extension.toLowerCase().contains(Constants.EXT_PNG)) {
+ if (extension.toLowerCase().contains(PNG_FILE_EXTENSION)) {
compressFormat = Bitmap.CompressFormat.PNG;
}
return compressFormat;
}
- private void copyFileAndExifTags(String filePath, String filePathOutput, int[] dimens) {
- copy(new File(filePath), new File(filePathOutput));
- copyExifTags(filePath, filePathOutput, dimens);
+ private void copyFileAndExifTags(File input, File fileOutput, Dimensions dimens) {
+ copy(input, fileOutput);
+ copyExifTags(input, fileOutput, dimens);
}
- public void copy(InputStream in, File dst) {
+ public void copy(InputStream in, File destination) {
OutputStream out = null;
try {
- out = new FileOutputStream(dst);
+ out = new FileOutputStream(destination);
byte[] buffer = new byte[1024];
int length;
@@ -192,7 +287,9 @@ public void copy(InputStream in, File dst) {
out.write(buffer, 0, length);
}
} catch (IOException e) {
- e.printStackTrace();
+ String message = String.format("Could not copy file to '%s'", destination.getAbsolutePath());
+ Log.e(TAG, message, e);
+
throw Exceptions.propagate(e);
} finally {
try {
@@ -209,23 +306,27 @@ public void copy(InputStream in, File dst) {
}
}
- public void copy(File src, File dst) {
+ public void copy(File source, File destination) {
try {
- InputStream in = new FileInputStream(src);
- copy(in, dst);
+ InputStream in = new FileInputStream(source);
+ copy(in, destination);
} catch (IOException e) {
- e.printStackTrace();
+ String message = String.format("Could not copy file to '%s'", destination.getAbsolutePath());
+ Log.e(TAG, message, e);
+
throw Exceptions.propagate(e);
}
}
- private void bitmap2file(Bitmap bitmap, File file, Bitmap.CompressFormat compressFormat) {
+ private void bitmap2file(Bitmap bitmap, File destination, Bitmap.CompressFormat compressFormat) {
FileOutputStream fileOutputStream = null;
try {
- fileOutputStream = new FileOutputStream(file);
+ fileOutputStream = new FileOutputStream(destination);
bitmap.compress(compressFormat, 90, fileOutputStream);
} catch (Exception e) {
- e.printStackTrace();
+ String message = String.format("Could not save bitmap file to '%s'", destination.getAbsolutePath());
+ Log.e(TAG, message, e);
+
throw Exceptions.propagate(e);
} finally {
try {
@@ -239,26 +340,29 @@ private void bitmap2file(Bitmap bitmap, File file, Bitmap.CompressFormat compres
}
}
- private void copyExifTags(String srcFilePath, String dstFilePath, int[] dimens) {
- if (getCompressFormat(dstFilePath) == Bitmap.CompressFormat.JPEG) {
- try {
- ExifInterface exifSource = new ExifInterface(srcFilePath);
- ExifInterface exifDest = new ExifInterface(dstFilePath);
-
- for (String attribute : getExifTags()) {
- String tagValue = exifSource.getAttribute(attribute);
-
- if (!TextUtils.isEmpty(tagValue)) {
- exifDest.setAttribute(attribute, tagValue);
+ private void copyExifTags(File source, File destination, Dimensions dimens) {
+ try {
+ String destinationPath = destination.getAbsolutePath();
+ String sourcePath = source.getAbsolutePath();
+ if (getCompressFormat(destinationPath) == Bitmap.CompressFormat.JPEG) {
+ ExifInterface exifSource = new ExifInterface(sourcePath);
+ ExifInterface exifDest = new ExifInterface(destinationPath);
+
+ for (String attribute : getExifTags()) {
+ String tagValue = exifSource.getAttribute(attribute);
+
+ if (!TextUtils.isEmpty(tagValue)) {
+ exifDest.setAttribute(attribute, tagValue);
+ }
}
- }
- exifDest.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(dimens[0]));
- exifDest.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(dimens[1]));
- exifDest.saveAttributes();
- } catch (IOException e) {
- e.printStackTrace();
+ exifDest.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(dimens.getWidth()));
+ exifDest.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(dimens.getHeight()));
+ exifDest.saveAttributes();
}
+ } catch (IOException e) {
+ String message = String.format("Could not copy exif tags from '%s'", source.getAbsolutePath());
+ Log.d(TAG, message, e);
}
}
@@ -278,29 +382,50 @@ private String[] getExifTags() {
};
}
- private Bitmap handleBitmapSampling(String filePath, int maxWidth, int maxHeight) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(filePath, options);
- options.inSampleSize =
- (maxWidth <= 0 || maxHeight <= 0) ? 1 : calculateInSampleSize(options, maxWidth, maxHeight);
-
+ private Bitmap sampleBitmap(File input, int maxWidth, int maxHeight) {
+ BitmapFactory.Options options = sampleSize(input, maxWidth, maxHeight);
if (options.inSampleSize == 1) {
return null;
}
options.inJustDecodeBounds = false;
+
+ String filePath = input.getAbsolutePath();
+
return BitmapFactory.decodeFile(filePath, options);
}
+ public boolean isImage(File input) {
+ BitmapFactory.Options options = sampleSize(input, 0, 0);
+
+ return options.outWidth > 0 && options.outHeight > 0;
+ }
+
+ private BitmapFactory.Options sampleSize(File input, int maxWidth, int maxHeight) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+
+ // load dimensions
+ String filePath = input.getAbsolutePath();
+ BitmapFactory.decodeFile(filePath, options);
+
+ if (maxWidth <= 0 || maxHeight <= 0) {
+ options.inSampleSize = 1;
+ } else {
+ options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
+ }
+
+ return options;
+ }
+
private int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
int inSampleSize = 1;
- int[] dimensPortrait = getDimensionsPortrait(options.outWidth, options.outHeight);
- int[] maxDimensPortrait = getDimensionsPortrait(maxWidth, maxHeight);
- float width = dimensPortrait[0];
- float height = dimensPortrait[1];
- float newMaxWidth = maxDimensPortrait[0];
- float newMaxHeight = maxDimensPortrait[1];
+ Dimensions dimensPortrait = getDimensionsPortrait(options.outWidth, options.outHeight);
+ Dimensions maxDimensPortrait = getDimensionsPortrait(maxWidth, maxHeight);
+ float width = dimensPortrait.getWidth();
+ float height = dimensPortrait.getHeight();
+ float newMaxWidth = maxDimensPortrait.getWidth();
+ float newMaxHeight = maxDimensPortrait.getHeight();
if (height > newMaxHeight || width > newMaxWidth) {
int heightRatio = Math.round(height / newMaxHeight);
@@ -317,11 +442,11 @@ private int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, i
return inSampleSize;
}
- private int[] getDimensionsPortrait(int width, int height) {
+ private Dimensions getDimensionsPortrait(int width, int height) {
if (width < height) {
- return new int[] { width, height };
+ return new Dimensions(width, height);
} else {
- return new int[] { height, width };
+ return new Dimensions(height, width);
}
}
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PermissionUtil.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PermissionUtil.java
new file mode 100644
index 0000000..a4bd09a
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PermissionUtil.java
@@ -0,0 +1,65 @@
+package com.miguelbcr.ui.rx_paparazzo2.interactors;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Build;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
+import java.util.List;
+
+public class PermissionUtil {
+
+ public static final int READ_WRITE_PERMISSIONS = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
+ public static Intent requestReadWritePermission(TargetUi targetUi, Intent intent, Uri uri) {
+ intent.addFlags(READ_WRITE_PERMISSIONS);
+
+ grantFileReadWritePermissions(targetUi, intent, uri);
+
+ return intent;
+ }
+
+ public static void grantReadPermissionToUri(TargetUi targetUi, Uri uri) {
+ String uiPackageName = targetUi.getContext().getPackageName();
+
+ targetUi.getContext().grantUriPermission(uiPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+
+ /**
+ * Workaround for Android bug.
+ * See https://code.google.com/p/android/issues/detail?id=76683
+ * See http://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps
+ */
+ private static void grantFileReadWritePermissions(TargetUi targetUi, Intent intent, Uri uri) {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+ List resInfoList = targetUi.getContext()
+ .getPackageManager()
+ .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ for (ResolveInfo resolveInfo : resInfoList) {
+ String packageName = resolveInfo.activityInfo.packageName;
+ targetUi.getContext().grantUriPermission(packageName, uri, READ_WRITE_PERMISSIONS);
+ }
+ }
+ }
+
+ public static void revokeFileReadWritePermissions(TargetUi targetUi, Uri uri) {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+ targetUi.getContext().revokeUriPermission(uri, READ_WRITE_PERMISSIONS);
+ }
+ }
+
+ public static String[] getReadAndWriteStoragePermissions(boolean internal) {
+ if (internal) {
+ return new String[] { Manifest.permission.READ_EXTERNAL_STORAGE };
+ } else {
+ return new String[] {
+ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE
+ };
+ }
+ }
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickFile.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickFile.java
new file mode 100644
index 0000000..6bf9111
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickFile.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016 Miguel Garcia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.miguelbcr.ui.rx_paparazzo2.interactors;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.Nullable;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
+import io.reactivex.Observable;
+import io.reactivex.functions.Function;
+import rx_activity_result2.OnPreResult;
+
+public class PickFile extends UseCase {
+
+ public static final String DEFAULT_MIME_TYPE = "*/*";
+
+ private final Config config;
+ private final StartIntent startIntent;
+ private final TargetUi targetUi;
+
+ public PickFile(TargetUi targetUi, Config config, StartIntent startIntent) {
+ this.targetUi = targetUi;
+ this.config = config;
+ this.startIntent = startIntent;
+ }
+
+ public String getDefaultMimeType() {
+ return DEFAULT_MIME_TYPE;
+ }
+
+ @Override
+ public Observable react() {
+ return startIntent.with(getFileChooserIntent(), getOnPreResultProcessing())
+ .react()
+ .map(new Function() {
+ @Override public Uri apply(Intent intent) throws Exception {
+ return intent.getData();
+ }
+ });
+ }
+
+ private Intent getFileChooserIntent() {
+ String mimeType = config.getMimeType(getDefaultMimeType());
+ Intent intent = new Intent();
+ intent.setType(mimeType);
+
+ if (config.isUseDocumentPicker() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
+ intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+ } else {
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ }
+
+ if (config.isPickOpenableOnly()) {
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ }
+
+ return intent;
+ }
+
+ private OnPreResult getOnPreResultProcessing() {
+ return new OnPreResult() {
+ @Override
+ public Observable response(int responseCode, @Nullable final Intent intent) {
+ if (responseCode == Activity.RESULT_OK && intent != null && intent.getData() != null) {
+
+ Uri pickedUri = intent.getData();
+ PermissionUtil.grantReadPermissionToUri(targetUi, pickedUri);
+
+ return Observable.just(intent.getData());
+ } else {
+ return Observable.empty();
+ }
+ }
+ };
+ }
+
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickImages.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickFiles.java
similarity index 58%
rename from rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickImages.java
rename to rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickFiles.java
index 2edbe8f..8b4f4fc 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickImages.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickFiles.java
@@ -20,26 +20,51 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
-import io.reactivex.Observable;
-import io.reactivex.functions.Function;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-public final class PickImages extends UseCase> {
+import io.reactivex.Observable;
+import io.reactivex.functions.Function;
+
+public class PickFiles extends UseCase> {
+
+ public static final String DEFAULT_MIME_TYPE = "*/*";
+
+ private final TargetUi targetUi;
+ private final Config config;
private final StartIntent startIntent;
- public PickImages(StartIntent startIntent) {
+ public PickFiles(TargetUi targetUi, Config config, StartIntent startIntent) {
+ this.targetUi = targetUi;
+ this.config = config;
this.startIntent = startIntent;
}
+ public String getDefaultMimeType() {
+ return DEFAULT_MIME_TYPE;
+ }
+
@Override public Observable> react() {
return startIntent.with(getFileChooserIntent()).react().map(new Function>() {
@Override public List apply(Intent intent) throws Exception {
- if (intent.getData() != null) {
- return Arrays.asList(intent.getData());
+ if (intent == null) {
+ return new ArrayList<>();
+ }
+
+ intent.addFlags(PermissionUtil.READ_WRITE_PERMISSIONS);
+
+ Uri pickedUri = intent.getData();
+ if (pickedUri != null) {
+ PermissionUtil.grantReadPermissionToUri(targetUi, pickedUri);
+
+ return Arrays.asList(pickedUri);
} else {
- return PickImages.this.getUris(intent);
+ return getUris(intent);
}
}
});
@@ -53,6 +78,9 @@ private List getUris(Intent intent) {
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
Uri uri = item.getUri();
+
+ PermissionUtil.grantReadPermissionToUri(targetUi, uri);
+
uris.add(uri);
}
}
@@ -61,9 +89,19 @@ private List getUris(Intent intent) {
}
private Intent getFileChooserIntent() {
+ String mimeType = config.getMimeType(getDefaultMimeType());
Intent intent = new Intent();
- intent.setType("image/*");
- intent.setAction(Intent.ACTION_GET_CONTENT);
+ intent.setType(mimeType);
+
+ if (config.isUseDocumentPicker() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
+ intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+ } else {
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ }
+
+ if (config.isPickOpenableOnly()) {
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickImage.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickImage.java
deleted file mode 100644
index bc8c5ed..0000000
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/PickImage.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2016 Miguel Garcia
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.miguelbcr.ui.rx_paparazzo2.interactors;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import io.reactivex.Observable;
-import io.reactivex.ObservableSource;
-import io.reactivex.exceptions.Exceptions;
-import io.reactivex.functions.Consumer;
-import io.reactivex.functions.Function;
-import io.reactivex.schedulers.Schedulers;
-import java.io.File;
-import rx_activity_result2.OnPreResult;
-
-public final class PickImage extends UseCase {
- private final StartIntent startIntent;
- private final GetPath getPath;
-
- public PickImage(StartIntent startIntent, GetPath getPath) {
- this.startIntent = startIntent;
- this.getPath = getPath;
- }
-
- @Override public Observable react() {
- return startIntent.with(getFileChooserIntent(), getOnPreResultProcessing())
- .react()
- .map(new Function() {
- @Override public Uri apply(Intent intent) throws Exception {
- return intent.getData();
- }
- });
- }
-
- private Intent getFileChooserIntent() {
- Intent intent = new Intent();
- intent.setType("image/*");
- intent.setAction(Intent.ACTION_GET_CONTENT);
-
- return intent;
- }
-
- private OnPreResult getOnPreResultProcessing() {
- return new OnPreResult() {
- @Override
- public Observable response(int responseCode, @Nullable final Intent intent) {
- if (responseCode == Activity.RESULT_OK) {
- return getPath.with(intent.getData())
- .react()
- .subscribeOn(Schedulers.io())
- .map(new Function() {
- @Override public String apply(String filePath) throws Exception {
- intent.setData(Uri.fromFile(new File(filePath)));
- return filePath;
- }
- })
- .onErrorReturnItem("");
- } else {
- return Observable.just("");
- }
- }
- };
- }
-}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/SaveFile.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/SaveFile.java
new file mode 100644
index 0000000..fb665b1
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/SaveFile.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016 Miguel Garcia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.miguelbcr.ui.rx_paparazzo2.interactors;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import io.reactivex.Observable;
+import io.reactivex.ObservableSource;
+import io.reactivex.functions.Function;
+
+public final class SaveFile extends UseCase {
+
+ private static final String TAG = SaveFile.class.getSimpleName();
+ private static final String SAVED_FILE_PREFIX = "SAVED-";
+
+ private final TargetUi targetUi;
+ private final Config config;
+ private final ScaledImageDimensions scaledImageDimensions;
+ private final ImageUtils imageUtils;
+
+ private FileData fileData;
+
+ public SaveFile(TargetUi targetUi, Config config, ScaledImageDimensions scaledImageDimensions, ImageUtils imageUtils) {
+ this.targetUi = targetUi;
+ this.config = config;
+ this.scaledImageDimensions = scaledImageDimensions;
+ this.imageUtils = imageUtils;
+ }
+
+ public SaveFile with(FileData fileData) {
+ this.fileData = fileData;
+
+ return this;
+ }
+
+ @Override
+ public Observable react() {
+ return scaledImageDimensions.with(fileData).react().flatMap(new Function>() {
+ @Override
+ public ObservableSource apply(Dimensions scaledDimensions) throws Exception {
+ return saveAndSendToMediaScanner(scaledDimensions);
+ }
+ });
+ }
+
+ private ObservableSource saveAndSendToMediaScanner(Dimensions scaledDimensions) throws Exception {
+ FileData saved = save(scaledDimensions);
+
+ if (config.isSendToMediaScanner()) {
+ if (config.isUseInternalStorage()) {
+ File file = fileData.getFile();
+ String message = String.format("Media scanner will not be able to access internal storage '%s'", file.getAbsolutePath());
+ Log.w(TAG, message);
+ }
+
+ if (saved.getFile() != null && saved.getFile().exists()) {
+// sendToMediaScanner(saved);
+ sendToMediaScannerIntent(saved);
+ }
+ }
+
+ return Observable.just(saved);
+ }
+
+ private FileData save(Dimensions scaledDimensions) throws Exception {
+ Dimensions imageDimensions = ImageUtils.getImageDimensions(fileData.getFile());
+ boolean isImage = imageDimensions.hasSize();
+ if (isImage) {
+ FileData withDimensions = new FileData(fileData, imageDimensions);
+
+ return saveImageAndDeleteSourceFile(withDimensions, scaledDimensions);
+ } else {
+ return saveToDestinationAndDeleteSourceFile(fileData);
+ }
+ }
+
+ private FileData saveToDestinationAndDeleteSourceFile(FileData fileData) throws Exception {
+ File source = fileData.getFile();
+
+ if (isFileSizeLimitExceeded(source)) {
+ FileData.deleteSourceFile(fileData);
+
+ return FileData.exceededMaximumFileSize(fileData);
+ }
+
+ InputStream inputStream = new BufferedInputStream(new FileInputStream(source));
+ File destination = getOutputFile();
+ imageUtils.copy(inputStream, destination);
+
+ return FileData.toFileDataDeleteSourceFileIfTransient(fileData, destination, true, fileData.getMimeType());
+ }
+
+ private FileData saveImageAndDeleteSourceFile(FileData fileData, Dimensions dimensions) {
+ FileData scaled = imageUtils.scaleImage(fileData, getOutputFile(), dimensions);
+
+ if (isFileSizeLimitExceeded(scaled.getFile())) {
+ FileData.deleteSourceFile(fileData);
+ FileData.deleteSourceFile(scaled);
+
+ return FileData.exceededMaximumFileSize(fileData);
+ }
+
+ FileData.deleteSourceFile(fileData);
+
+ return scaled;
+ }
+
+ private boolean isFileSizeLimitExceeded(File scaledFile) {
+ return scaledFile.exists() && scaledFile.length() > config.getMaximumFileSize();
+ }
+
+ private void sendToMediaScannerIntent(FileData fileDataToScan) {
+ File file = fileDataToScan.getFile();
+ if (file.exists()) {
+ Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ Context context = targetUi.getContext();
+ Uri contentUri = Uri.fromFile(file);
+ mediaScanIntent.setData(contentUri);
+
+ context.sendBroadcast(mediaScanIntent);
+ }
+ }
+
+ private File getOutputFile() {
+ String fileName = fileData.getFilename();
+ String extension = imageUtils.getFileExtension(fileName);
+
+ return imageUtils.getOutputFile(SAVED_FILE_PREFIX, extension);
+ }
+
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/SaveImage.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/SaveImage.java
deleted file mode 100644
index 4a0f32d..0000000
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/SaveImage.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2016 Miguel Garcia
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.miguelbcr.ui.rx_paparazzo2.interactors;
-
-import android.media.MediaScannerConnection;
-import android.net.Uri;
-import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
-import io.reactivex.Observable;
-import io.reactivex.ObservableSource;
-import io.reactivex.functions.Function;
-import io.reactivex.functions.Function3;
-import java.io.File;
-
-public final class SaveImage extends UseCase {
- private final TargetUi targetUi;
- private final GetPath getPath;
- private final GetDimens getDimens;
- private final ImageUtils imageUtils;
- private Uri uri;
-
- public SaveImage(TargetUi targetUi, GetPath getPath, GetDimens getDimens, ImageUtils imageUtils) {
- this.targetUi = targetUi;
- this.getPath = getPath;
- this.getDimens = getDimens;
- this.imageUtils = imageUtils;
- }
-
- public SaveImage with(Uri uri) {
- this.uri = uri;
- return this;
- }
-
- @Override public Observable react() {
- return getOutputUri().flatMap(new Function>() {
- @Override public ObservableSource apply(Uri outputUri) throws Exception {
- return Observable.zip(getPath.with(uri).react(), getPath.with(outputUri).react(),
- getDimens.with(uri).react(), new Function3() {
- @Override public String apply(String filePath, String filePathOutput, int[] dimens)
- throws Exception {
- String filePathScaled = imageUtils.scaleImage(filePath, filePathOutput, dimens);
- new File(filePath).delete();
-
- MediaScannerConnection.scanFile(targetUi.getContext(),
- new String[] { filePathScaled }, new String[] { "image/*" }, null);
-
- return filePathScaled;
- }
- });
- }
- });
- }
-
- private Observable getOutputUri() {
- return getPath.with(uri).react().flatMap(new Function>() {
- @Override public ObservableSource apply(String filepath) throws Exception {
- String extension = imageUtils.getFileExtension(filepath);
- return Observable.just(Uri.fromFile(imageUtils.getOutputFile(extension)));
- }
- });
- }
-}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ScaledImageDimensions.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ScaledImageDimensions.java
new file mode 100644
index 0000000..8e0fd0d
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/ScaledImageDimensions.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016 Miguel Garcia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.miguelbcr.ui.rx_paparazzo2.interactors;
+
+import android.util.DisplayMetrics;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.CustomMaxSize;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.OriginalSize;
+import com.miguelbcr.ui.rx_paparazzo2.entities.size.ScreenSize;
+
+import java.io.File;
+
+import io.reactivex.Observable;
+
+public final class ScaledImageDimensions extends UseCase {
+ private final TargetUi targetUi;
+ private final Config config;
+ private FileData fileData;
+
+ public ScaledImageDimensions(TargetUi targetUi, Config config) {
+ this.targetUi = targetUi;
+ this.config = config;
+ }
+
+ public ScaledImageDimensions with(FileData fileData) {
+ this.fileData = fileData;
+ return this;
+ }
+
+ @Override public Observable react() {
+ return Observable.just(getDimensions());
+ }
+
+ private Dimensions getDimensions() {
+ File file = fileData.getFile();
+ if (config.getSize() instanceof OriginalSize) {
+ return ImageUtils.getImageDimensions(file);
+ } else if (config.getSize() instanceof CustomMaxSize) {
+ return getCustomDimens((CustomMaxSize) config.getSize(), file);
+ } else if (config.getSize() instanceof ScreenSize) {
+ return ScaledImageDimensions.this.getScreenDimens();
+ } else {
+ Dimensions dimens = ScaledImageDimensions.this.getScreenDimens();
+
+ return new Dimensions(dimens.getWidth() / 8, dimens.getHeight() / 8);
+ }
+ }
+
+ private Dimensions getScreenDimens() {
+ DisplayMetrics metrics = targetUi.getContext().getResources().getDisplayMetrics();
+
+ return new Dimensions(metrics.widthPixels, metrics.heightPixels);
+ }
+
+ private Dimensions getCustomDimens(CustomMaxSize customMaxSize, File file) {
+ int maxSize = customMaxSize.getMaxImageSize();
+ Dimensions dimensions = ImageUtils.getImageDimensions(file);
+
+ int maxFileSize = Math.max(dimensions.getWidth(), dimensions.getHeight());
+ if (maxFileSize < maxSize) {
+ return dimensions;
+ }
+
+ float scaleFactor = (float) maxSize / maxFileSize;
+ float scaledWidth = dimensions.getWidth() * scaleFactor;
+ float scaledHeight = dimensions.getHeight() * scaleFactor;
+
+ return new Dimensions((int)scaledWidth, (int)scaledHeight);
+ }
+}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/TakePhoto.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/TakePhoto.java
index 2d74841..25e2c69 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/TakePhoto.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/interactors/TakePhoto.java
@@ -18,76 +18,83 @@
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.net.Uri;
-import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
import io.reactivex.Observable;
import io.reactivex.functions.Function;
-import java.io.File;
-import java.util.List;
-public final class TakePhoto extends UseCase {
- private static final int READ_WRITE_PERMISSIONS =
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+public final class TakePhoto extends UseCase {
+ private static final String PHOTO_FILE_PREFIX = "PHOTO-";
+
+ private final Config config;
private final StartIntent startIntent;
private final TargetUi targetUi;
private final ImageUtils imageUtils;
- public TakePhoto(StartIntent startIntent, TargetUi targetUi, ImageUtils imageUtils) {
+ public TakePhoto(Config config, StartIntent startIntent, TargetUi targetUi, ImageUtils imageUtils) {
+ this.config = config;
this.startIntent = startIntent;
this.targetUi = targetUi;
this.imageUtils = imageUtils;
}
- @Override public Observable react() {
- final Uri uri = getUri();
- return startIntent.with(getIntentCamera(uri)).react().map(new Function() {
+ @Override
+ public Observable react() {
+ final File file = getOutputFile();
+ Uri uri = getUri(file);
+
+ return startIntent.with(getIntentCamera(uri))
+ .react()
+ .map(revokeFileReadWritePermissions(targetUi, uri))
+ .map(new Function() {
+ @Override
+ public FileData apply(Uri uri) throws Exception {
+ if (!file.exists()) {
+ throw new FileNotFoundException(String.format("Camera file not saved", file.getAbsolutePath()));
+ }
+
+ return new FileData(file, true, file.getName(), ImageUtils.MIME_TYPE_JPEG);
+ }
+ });
+ }
+
+ private Function revokeFileReadWritePermissions(final TargetUi targetUi, final Uri uri) {
+ return new Function() {
@Override public Uri apply(Intent data) throws Exception {
- revokeFileReadWritePermissions(uri);
+ PermissionUtil.revokeFileReadWritePermissions(targetUi, uri);
+
return uri;
}
- });
+ };
}
- private Uri getUri() {
+ private Uri getUri(File file) {
Context context = targetUi.getContext();
- File file = imageUtils.getPrivateFile(Constants.SHOOT_APPEND);
- String authority = context.getPackageName() + "." + Constants.FILE_PROVIDER;
+ String authority = config.getFileProviderAuthority(context);
+
return FileProvider.getUriForFile(context, authority, file);
}
+ private File getOutputFile() {
+ String filename = imageUtils.createTimestampedFilename(PHOTO_FILE_PREFIX, ImageUtils.JPG_FILE_EXTENSION);
+ String directory = config.getFileProviderDirectory();
+ return imageUtils.getPrivateFile(directory, filename);
+ }
+
private Intent getIntentCamera(Uri uri) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- intent.addFlags(READ_WRITE_PERMISSIONS);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
- grantFileReadWritePermissions(intent, uri);
- return intent;
- }
- /**
- * Workaround for Android bug.
- * See https://code.google.com/p/android/issues/detail?id=76683
- * See http://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps
- */
- private void grantFileReadWritePermissions(Intent intent, Uri uri) {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
- List resInfoList = targetUi.getContext()
- .getPackageManager()
- .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- for (ResolveInfo resolveInfo : resInfoList) {
- String packageName = resolveInfo.activityInfo.packageName;
- targetUi.getContext().grantUriPermission(packageName, uri, READ_WRITE_PERMISSIONS);
- }
- }
+ return PermissionUtil.requestReadWritePermission(targetUi, intent, uri);
}
- private void revokeFileReadWritePermissions(Uri uri) {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
- targetUi.getContext().revokeUriPermission(uri, READ_WRITE_PERMISSIONS);
- }
- }
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponent.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponent.java
index 0d53579..aba9378 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponent.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponent.java
@@ -18,13 +18,13 @@
import com.miguelbcr.ui.rx_paparazzo2.interactors.GetPath;
import com.miguelbcr.ui.rx_paparazzo2.workers.Camera;
-import com.miguelbcr.ui.rx_paparazzo2.workers.Gallery;
+import com.miguelbcr.ui.rx_paparazzo2.workers.Files;
public abstract class ApplicationComponent {
public abstract Camera camera();
- public abstract Gallery gallery();
+ public abstract Files files();
public abstract GetPath getPath();
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponentImpl.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponentImpl.java
index 0eaf201..4be376c 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponentImpl.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/internal/di/ApplicationComponentImpl.java
@@ -3,60 +3,46 @@
import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
import com.miguelbcr.ui.rx_paparazzo2.interactors.CropImage;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.DownloadImage;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.GetDimens;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.DownloadFile;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.ScaledImageDimensions;
import com.miguelbcr.ui.rx_paparazzo2.interactors.GetPath;
import com.miguelbcr.ui.rx_paparazzo2.interactors.GrantPermissions;
import com.miguelbcr.ui.rx_paparazzo2.interactors.ImageUtils;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.PickImage;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.PickImages;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.SaveImage;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.SaveFile;
import com.miguelbcr.ui.rx_paparazzo2.interactors.StartIntent;
import com.miguelbcr.ui.rx_paparazzo2.interactors.TakePhoto;
import com.miguelbcr.ui.rx_paparazzo2.workers.Camera;
-import com.miguelbcr.ui.rx_paparazzo2.workers.Gallery;
+import com.miguelbcr.ui.rx_paparazzo2.workers.Files;
class ApplicationComponentImpl extends ApplicationComponent {
- private final ImageUtils imageUtils;
- private final DownloadImage downloadImage;
- private final StartIntent startIntent;
private final GetPath getPath;
- private final GetDimens getDimens;
- private final TakePhoto takePhoto;
- private final CropImage cropImage;
- private final SaveImage saveImage;
- private final GrantPermissions grantPermissions;
- private final PickImages pickImages;
- private final PickImage pickImage;
private final Camera camera;
- private final Gallery gallery;
+ private final Files files;
public ApplicationComponentImpl(TargetUi ui, Config config) {
- startIntent = new StartIntent(ui);
- imageUtils = new ImageUtils(ui, config);
- downloadImage = new DownloadImage(ui, imageUtils);
- getPath = new GetPath(ui, downloadImage);
- takePhoto = new TakePhoto(startIntent, ui, imageUtils);
- getDimens = new GetDimens(ui, config, getPath);
- cropImage = new CropImage(ui, config, startIntent, getPath, imageUtils);
- saveImage = new SaveImage(ui, getPath, getDimens, imageUtils);
- grantPermissions = new GrantPermissions(ui);
- pickImages = new PickImages(startIntent);
- pickImage = new PickImage(startIntent, getPath);
- camera = new Camera(takePhoto, cropImage, saveImage, grantPermissions, ui, config);
- gallery =
- new Gallery(grantPermissions, pickImages, pickImage, cropImage, saveImage, ui, config);
+ StartIntent startIntent = new StartIntent(ui);
+ ImageUtils imageUtils = new ImageUtils(ui, config);
+ DownloadFile downloadFile = new DownloadFile(ui, config, imageUtils);
+ TakePhoto takePhoto = new TakePhoto(config, startIntent, ui, imageUtils);
+ ScaledImageDimensions scaledImageDimensions = new ScaledImageDimensions(ui, config);
+ CropImage cropImage = new CropImage(ui, config, startIntent, imageUtils);
+ SaveFile saveFile = new SaveFile(ui, config, scaledImageDimensions, imageUtils);
+ GrantPermissions grantPermissions = new GrantPermissions(ui);
+
+ this.getPath = new GetPath(config, ui, downloadFile);
+ this.camera = new Camera(takePhoto, cropImage, saveFile, grantPermissions, ui, config);
+ this.files = new Files(grantPermissions, startIntent, this.getPath, cropImage, saveFile, ui, config);
}
@Override public Camera camera() {
return camera;
}
- @Override public Gallery gallery() {
- return gallery;
- }
-
@Override public GetPath getPath() {
return getPath;
}
+
+ @Override public Files files() {
+ return files;
+ }
}
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Camera.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Camera.java
index c484828..dd75169 100644
--- a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Camera.java
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Camera.java
@@ -20,15 +20,20 @@
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.net.Uri;
+
import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
import com.miguelbcr.ui.rx_paparazzo2.entities.Ignore;
import com.miguelbcr.ui.rx_paparazzo2.entities.Response;
import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
import com.miguelbcr.ui.rx_paparazzo2.interactors.CropImage;
import com.miguelbcr.ui.rx_paparazzo2.interactors.GrantPermissions;
-import com.miguelbcr.ui.rx_paparazzo2.interactors.SaveImage;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.PermissionUtil;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.SaveFile;
import com.miguelbcr.ui.rx_paparazzo2.interactors.TakePhoto;
+
+import java.util.Arrays;
+
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.functions.Function;
@@ -36,76 +41,80 @@
public final class Camera extends Worker {
private final TakePhoto takePhoto;
private final CropImage cropImage;
- private final SaveImage saveImage;
+ private final SaveFile saveFile;
private final GrantPermissions grantPermissions;
private final TargetUi targetUi;
private final Config config;
- public Camera(TakePhoto takePhoto, CropImage cropImage, SaveImage saveImage,
+ public Camera(TakePhoto takePhoto, CropImage cropImage, SaveFile saveFile,
GrantPermissions grantPermissions, TargetUi targetUi, Config config) {
super(targetUi);
this.takePhoto = takePhoto;
this.cropImage = cropImage;
- this.saveImage = saveImage;
+ this.saveFile = saveFile;
this.grantPermissions = grantPermissions;
this.targetUi = targetUi;
this.config = config;
}
- public Observable> takePhoto() {
+ public Observable> takePhoto() {
return grantPermissions.with(permissions())
.react()
- .flatMap(new Function>() {
- @Override public ObservableSource apply(Ignore ignore) throws Exception {
+ .flatMap(new Function>() {
+ @Override public ObservableSource apply(Ignore ignore) throws Exception {
return takePhoto.react();
}
})
- .flatMap(new Function>() {
- @Override public ObservableSource apply(Uri uri) throws Exception {
- return cropImage.with(uri).react();
- }
- })
- .flatMap(new Function>() {
- @Override public ObservableSource apply(Uri uri) throws Exception {
- return saveImage.with(uri).react();
+ .flatMap(new Function>() {
+ @Override public ObservableSource apply(FileData fileData) throws Exception {
+ return handleSavingFile(fileData);
}
})
- .map(new Function>() {
- @Override public Response apply(String path) throws Exception {
- return new Response<>((T) targetUi.ui(), path, Activity.RESULT_OK);
+ .map(new Function>() {
+ @Override public Response apply(FileData fileData) throws Exception {
+ return new Response<>((T) targetUi.ui(), fileData, Activity.RESULT_OK);
}
})
- .compose(this.>applyOnError());
+ .compose(this.>applyOnError());
+ }
+
+ private Observable handleSavingFile(final FileData sourceFileData) {
+ return cropImage.with(sourceFileData).react()
+ .flatMap(new Function>() {
+ public ObservableSource apply(FileData cropped) throws Exception {
+ return saveFile.with(cropped).react();
+ }
+ });
}
private String[] permissions() {
- if (config.useInternalStorage()) {
- if (hasCameraPermissionInManifest()) {
- return new String[] { Manifest.permission.CAMERA };
- } else {
- return new String[] {};
- }
- } else {
- if (hasCameraPermissionInManifest()) {
- return new String[] {
- Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE
- };
- } else {
- return new String[] {
- Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE
- };
- }
+ String[] storagePermission = PermissionUtil.getReadAndWriteStoragePermissions(config.isUseInternalStorage());
+ String[] cameraPermission = getCameraPermission();
+
+ return concat(storagePermission, cameraPermission);
+ }
+
+ private String[] getCameraPermission() {
+ if (hasCameraPermissionInManifest()) {
+ return new String[] { Manifest.permission.CAMERA };
}
+
+ return new String[] {};
+ }
+
+ private static T[] concat(T[] first, T[] second) {
+ T[] result = Arrays.copyOf(first, first.length + second.length);
+ System.arraycopy(second, 0, result, first.length, second.length);
+
+ return result;
}
private boolean hasCameraPermissionInManifest() {
- final String packageName = targetUi.getContext().getPackageName();
try {
- final PackageInfo packageInfo = targetUi.getContext()
- .getPackageManager()
- .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- final String[] declaredPermissions = packageInfo.requestedPermissions;
+ String packageName = targetUi.getContext().getPackageName();
+ PackageManager pm = targetUi.getContext().getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+ String[] declaredPermissions = packageInfo.requestedPermissions;
if (declaredPermissions != null && declaredPermissions.length > 0) {
for (String p : declaredPermissions) {
if (p.equals(Manifest.permission.CAMERA)) {
diff --git a/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Files.java b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Files.java
new file mode 100644
index 0000000..8d6e284
--- /dev/null
+++ b/rx_paparazzo/src/main/java/com/miguelbcr/ui/rx_paparazzo2/workers/Files.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2016 Miguel Garcia
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.miguelbcr.ui.rx_paparazzo2.workers;
+
+import android.app.Activity;
+import android.net.Uri;
+
+import com.miguelbcr.ui.rx_paparazzo2.entities.Config;
+import com.miguelbcr.ui.rx_paparazzo2.entities.FileData;
+import com.miguelbcr.ui.rx_paparazzo2.entities.Ignore;
+import com.miguelbcr.ui.rx_paparazzo2.entities.Response;
+import com.miguelbcr.ui.rx_paparazzo2.entities.TargetUi;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.CropImage;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.GetPath;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.GrantPermissions;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.PermissionUtil;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.PickFile;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.PickFiles;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.SaveFile;
+import com.miguelbcr.ui.rx_paparazzo2.interactors.StartIntent;
+
+import java.util.List;
+
+import io.reactivex.Observable;
+import io.reactivex.ObservableSource;
+import io.reactivex.functions.Function;
+
+public final class Files extends Worker {
+ private final GrantPermissions grantPermissions;
+ private final StartIntent startIntent;
+ private final GetPath getPath;
+ private final CropImage cropImage;
+ private final SaveFile saveFile;
+ private final TargetUi targetUi;
+ private final Config config;
+
+ public Files(GrantPermissions grantPermissions, StartIntent startIntent, GetPath getPath,
+ CropImage cropImage, SaveFile saveFile, TargetUi targetUi, Config config) {
+ super(targetUi);
+ this.grantPermissions = grantPermissions;
+ this.startIntent = startIntent;
+ this.getPath = getPath;
+ this.cropImage = cropImage;
+ this.saveFile = saveFile;
+ this.targetUi = targetUi;
+ this.config = config;
+ }
+
+ public Observable> pickFile() {
+ PickFile pickFile = new PickFile(targetUi, config, startIntent);
+
+ return pickFile(pickFile);
+ }
+
+ public Observable>> pickFiles() {
+ PickFiles pickFiles = new PickFiles(targetUi, config, startIntent);
+
+ return pickFiles(pickFiles);
+ }
+
+ public Observable> pickFile(final PickFile pickFile) {
+ return grantPermissions.with(permissions())
+ .react()
+ .flatMap(new Function>() {
+ @Override public ObservableSource apply(Ignore ignore) throws Exception {
+ return pickFile.react();
+ }
+ })
+ .flatMap(new Function>() {
+ @Override
+ public ObservableSource apply(final Uri uri) throws Exception {
+ return getPath.with(uri).react();
+ }
+ })
+ .flatMap(new Function>() {
+ @Override
+ public ObservableSource apply(FileData fileData) throws Exception {
+ return handleSavingFile(fileData);
+ }
+ })
+ .map(new Function>() {
+ @Override public Response apply(FileData file) throws Exception {
+ return new Response<>((T) targetUi.ui(), file, Activity.RESULT_OK);
+ }
+ })
+ .compose(this.>applyOnError());
+ }
+
+ private Observable handleSavingFile(final FileData sourceFileData) {
+ return cropImage.with(sourceFileData).react()
+ .flatMap(new Function>() {
+ @Override
+ public ObservableSource apply(FileData cropped) throws Exception {
+ return saveFile.with(cropped).react();
+ }
+ });
+ }
+
+ public Observable>> pickFiles(final PickFiles pickFiles) {
+ return grantPermissions.with(permissions())
+ .react()
+ .flatMap(new Function>>() {
+ @Override public ObservableSource