diff --git a/ReactNativeClient/android/app/build.gradle b/ReactNativeClient/android/app/build.gradle
index 5ca5f5a48..204354982 100644
--- a/ReactNativeClient/android/app/build.gradle
+++ b/ReactNativeClient/android/app/build.gradle
@@ -137,6 +137,7 @@ android {
}
dependencies {
+ compile project(':react-native-image-picker')
compile project(':react-native-vector-icons')
compile project(':react-native-fs')
compile fileTree(dir: "libs", include: ["*.jar"])
diff --git a/ReactNativeClient/android/app/src/main/AndroidManifest.xml b/ReactNativeClient/android/app/src/main/AndroidManifest.xml
index aec1174cf..1f233c49f 100644
--- a/ReactNativeClient/android/app/src/main/AndroidManifest.xml
+++ b/ReactNativeClient/android/app/src/main/AndroidManifest.xml
@@ -1,38 +1,37 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ package="net.cozic.joplin"
+ android:versionCode="2"
+ android:versionName="0.8.0">
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
+ android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+ android:windowSoftInputMode="adjustResize"
+ android:launchMode="singleInstance">
+
+
+
+
+
+
+
+
diff --git a/ReactNativeClient/android/app/src/main/java/net/cozic/joplin/MainApplication.java b/ReactNativeClient/android/app/src/main/java/net/cozic/joplin/MainApplication.java
index 9de2b477e..f0fed230f 100644
--- a/ReactNativeClient/android/app/src/main/java/net/cozic/joplin/MainApplication.java
+++ b/ReactNativeClient/android/app/src/main/java/net/cozic/joplin/MainApplication.java
@@ -3,6 +3,7 @@ package net.cozic.joplin;
import android.app.Application;
import com.facebook.react.ReactApplication;
+import com.imagepicker.ImagePickerPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
@@ -30,6 +31,7 @@ public class MainApplication extends Application implements ReactApplication {
return Arrays.asList(
new ImageResizerPackage(),
new MainReactPackage(),
+ new ImagePickerPackage(),
new ReactNativeDocumentPicker(),
new RNFetchBlobPackage(),
new RNFSPackage(),
diff --git a/ReactNativeClient/android/build.gradle b/ReactNativeClient/android/build.gradle
index eed9972b5..7b46d23f4 100644
--- a/ReactNativeClient/android/build.gradle
+++ b/ReactNativeClient/android/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.2.3'
+ classpath 'com.android.tools.build:gradle:2.2.+'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/ReactNativeClient/android/settings.gradle b/ReactNativeClient/android/settings.gradle
index 4f60d1d27..7bf4ddbd7 100644
--- a/ReactNativeClient/android/settings.gradle
+++ b/ReactNativeClient/android/settings.gradle
@@ -1,4 +1,6 @@
rootProject.name = 'Joplin'
+include ':react-native-image-picker'
+project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':react-native-image-resizer'
diff --git a/ReactNativeClient/ios/Joplin.xcodeproj/project.pbxproj b/ReactNativeClient/ios/Joplin.xcodeproj/project.pbxproj
index d87363aa0..c3f4ee937 100644
--- a/ReactNativeClient/ios/Joplin.xcodeproj/project.pbxproj
+++ b/ReactNativeClient/ios/Joplin.xcodeproj/project.pbxproj
@@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {
-
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@@ -55,6 +54,7 @@
EA501DCDCF4745E9B63ECE98 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7D46CBDF8846409890AD7A84 /* Octicons.ttf */; };
EC11356C90E9419799A2626F /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 51BCEC3BC28046C8BB19531F /* EvilIcons.ttf */; };
FBF57CE2F0F448FA9A8985E2 /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EB8BCAEA9AA41CAAE460443 /* libsqlite3.0.tbd */; };
+ AF99EEC6C55042F7BFC87583 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 44A39642217548C8ADA91CBA /* libRNImagePicker.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -404,6 +404,8 @@
F5E37D05726A4A08B2EE323A /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = ""; };
FD370E24D76E461D960DD85D /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; };
FF411B45E68B4A8CBCC35777 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = ""; };
+ A4716DB8654B431D894F89E1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; name = "RNImagePicker.xcodeproj"; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
+ 44A39642217548C8ADA91CBA /* libRNImagePicker.a */ = {isa = PBXFileReference; name = "libRNImagePicker.a"; path = "libRNImagePicker.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -439,6 +441,7 @@
929CA5ABC80E4D398AFC4E44 /* libSQLite.a in Frameworks */,
FBF57CE2F0F448FA9A8985E2 /* libsqlite3.0.tbd in Frameworks */,
AE6BB3A2FDA34D01864A721A /* libRNVectorIcons.a in Frameworks */,
+ AF99EEC6C55042F7BFC87583 /* libRNImagePicker.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -706,6 +709,7 @@
DF1C50EBC11E46A3AF87F80A /* RCTImageResizer.xcodeproj */,
CCDE9E9AF09B45F391B1C2AF /* SQLite.xcodeproj */,
711CBD21F0894B83A2D8E234 /* RNVectorIcons.xcodeproj */,
+ A4716DB8654B431D894F89E1 /* RNImagePicker.xcodeproj */,
);
name = Libraries;
sourceTree = "";
@@ -1392,6 +1396,7 @@
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
+ "$(SRCROOT)\..\node_modules\react-native-image-picker\ios",
);
INFOPLIST_FILE = JoplinTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@@ -1404,6 +1409,7 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1428,6 +1434,7 @@
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
+ "$(SRCROOT)\..\node_modules\react-native-image-picker\ios",
);
INFOPLIST_FILE = JoplinTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
@@ -1440,6 +1447,7 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1465,6 +1473,7 @@
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
+ "$(SRCROOT)\..\node_modules\react-native-image-picker\ios",
);
INFOPLIST_FILE = Joplin/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1492,6 +1501,7 @@
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
+ "$(SRCROOT)\..\node_modules\react-native-image-picker\ios",
);
INFOPLIST_FILE = Joplin/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1526,6 +1536,7 @@
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
+ "$(SRCROOT)\..\node_modules\react-native-image-picker\ios",
);
INFOPLIST_FILE = "Joplin-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1537,6 +1548,7 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1571,6 +1583,7 @@
"$(SRCROOT)/../node_modules/react-native-image-resizer/ios/RCTImageResizer",
"$(SRCROOT)/../node_modules/react-native-sqlite-storage/src/ios",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
+ "$(SRCROOT)\..\node_modules\react-native-image-picker\ios",
);
INFOPLIST_FILE = "Joplin-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1582,6 +1595,7 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
);
OTHER_LDFLAGS = (
"-ObjC",
@@ -1616,6 +1630,7 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.Joplin-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1646,6 +1661,7 @@
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
+ "\"$(SRCROOT)/$(TARGET_NAME)\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.Joplin-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/ReactNativeClient/ios/Joplin/Info.plist b/ReactNativeClient/ios/Joplin/Info.plist
index a973da938..3062cfbca 100644
--- a/ReactNativeClient/ios/Joplin/Info.plist
+++ b/ReactNativeClient/ios/Joplin/Info.plist
@@ -51,6 +51,10 @@
+ NSPhotoLibraryUsageDescription
+ To allow attaching images to a note
+ NSCameraUsageDescription
+ To allow attaching a photo to a note
UIAppFonts
Entypo.ttf
diff --git a/ReactNativeClient/lib/components/screens/note.js b/ReactNativeClient/lib/components/screens/note.js
index 359323861..03c82041a 100644
--- a/ReactNativeClient/lib/components/screens/note.js
+++ b/ReactNativeClient/lib/components/screens/note.js
@@ -25,6 +25,7 @@ const RNFetchBlob = require('react-native-fetch-blob').default;
const { DocumentPicker, DocumentPickerUtil } = require('react-native-document-picker');
const ImageResizer = require('react-native-image-resizer').default;
const shared = require('lib/components/shared/note-screen-shared.js');
+const ImagePicker = require('react-native-image-picker');
class NoteScreenComponent extends BaseScreenComponent {
@@ -185,7 +186,7 @@ class NoteScreenComponent extends BaseScreenComponent {
async pickDocument() {
return new Promise((resolve, reject) => {
- DocumentPicker.show({ filetype: [DocumentPickerUtil.images()] }, (error,res) => {
+ DocumentPicker.show({ filetype: [DocumentPickerUtil.allFiles()] }, (error,res) => {
if (error) {
// Also returns an error if the user doesn't pick a file
// so just resolve with null.
@@ -207,56 +208,77 @@ class NoteScreenComponent extends BaseScreenComponent {
});
}
- async attachFile_onPress() {
- const res = await this.pickDocument();
- if (!res) {
- reg.logger().info('Did not get any file (user cancel?)');
+ showImagePicker(options) {
+ return new Promise((resolve, reject) => {
+ ImagePicker.showImagePicker(options, (response) => {
+ resolve(response);
+ });
+ });
+ }
+
+ async resizeImage(localFilePath, targetPath, mimeType) {
+ const maxSize = Resource.IMAGE_MAX_DIMENSION;
+
+ let dimensions = await this.imageDimensions(localFilePath);
+
+ reg.logger().info('Original dimensions ', dimensions);
+ if (dimensions.width > maxSize || dimensions.height > maxSize) {
+ dimensions.width = maxSize;
+ dimensions.height = maxSize;
+ }
+ reg.logger().info('New dimensions ', dimensions);
+
+ const format = mimeType == 'image/png' ? 'PNG' : 'JPEG';
+ reg.logger().info('Resizing image ' + localFilePath);
+ const resizedImage = await ImageResizer.createResizedImage(localFilePath, dimensions.width, dimensions.height, format, 85);
+ const resizedImagePath = resizedImage.uri;
+ reg.logger().info('Resized image ', resizedImagePath);
+ RNFetchBlob.fs.cp(resizedImagePath, targetPath); // mv doesn't work ("source path does not exist") so need to do cp and unlink
+
+ try {
+ RNFetchBlob.fs.unlink(resizedImagePath);
+ } catch (error) {
+ reg.logger().info('Error when unlinking cached file: ', error);
+ }
+ }
+
+ async attachFile(pickerResponse, fileType) {
+ if (!pickerResponse) {
+ reg.logger().warn('Got no response from picker');
return;
}
- const localFilePath = res.uri;
+ if (pickerResponse.error) {
+ reg.logger().warn('Got error from picker', pickerResponse.error);
+ return;
+ }
+
+ if (pickerResponse.didCancel) {
+ reg.logger().info('User cancelled picker');
+ return;
+ }
+
+ const localFilePath = pickerResponse.uri;
reg.logger().info('Got file: ' + localFilePath);
- reg.logger().info('Got type: ' + res.type);
-
- // res.uri,
- // res.type, // mime type
- // res.fileName,
- // res.fileSize
+ reg.logger().info('Got type: ' + pickerResponse.type);
let resource = Resource.new();
resource.id = uuid.create();
- resource.mime = res.type;
- resource.title = res.fileName ? res.fileName : _('Untitled');
+ resource.mime = pickerResponse.type;
+ resource.title = pickerResponse.fileName ? pickerResponse.fileName : _('Untitled');
let targetPath = Resource.fullPath(resource);
- if (res.type == 'image/jpeg' || res.type == 'image/jpg' || res.type == 'image/png') {
- const maxSize = Resource.IMAGE_MAX_DIMENSION;
-
- let dimensions = await this.imageDimensions(localFilePath);
-
- reg.logger().info('Original dimensions ', dimensions);
- if (dimensions.width > maxSize || dimensions.height > maxSize) {
- dimensions.width = maxSize;
- dimensions.height = maxSize;
- }
- reg.logger().info('New dimensions ', dimensions);
-
- const format = res.type == 'image/png' ? 'PNG' : 'JPEG';
- reg.logger().info('Resizing image ' + localFilePath);
- const resizedImage = await ImageResizer.createResizedImage(localFilePath, dimensions.width, dimensions.height, format, 85);
- const resizedImagePath = resizedImage.uri;
- reg.logger().info('Resized image ', resizedImagePath);
- RNFetchBlob.fs.cp(resizedImagePath, targetPath); // mv doesn't work ("source path does not exist") so need to do cp and unlink
-
- try {
- RNFetchBlob.fs.unlink(resizedImagePath);
- } catch (error) {
- reg.logger().info('Error when unlinking cached file: ', error);
- }
+ if (pickerResponse.type == 'image/jpeg' || pickerResponse.type == 'image/jpg' || pickerResponse.type == 'image/png') {
+ await this.resizeImage(localFilePath, targetPath, pickerResponse.mime);
} else {
- RNFetchBlob.fs.cp(localFilePath, targetPath);
+ if (fileType === 'image') {
+ dialogs.error(this, _('Unsupported image type: %s', pickerResponse.type));
+ return;
+ } else {
+ RNFetchBlob.fs.cp(localFilePath, targetPath);
+ }
}
await Resource.save(resource, { isNew: true });
@@ -268,6 +290,19 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ note: newNote });
}
+ async attachImage_onPress() {
+ const options = {
+ mediaType: 'photo',
+ };
+ const response = await this.showImagePicker(options);
+ await this.attachFile(response, 'image');
+ }
+
+ async attachFile_onPress() {
+ const response = await this.pickDocument();
+ await this.attachFile(response, 'all');
+ }
+
toggleIsTodo_onPress() {
shared.toggleIsTodo_onPress(this);
}
@@ -294,7 +329,8 @@ class NoteScreenComponent extends BaseScreenComponent {
let output = [];
- output.push({ title: _('Attach file'), onPress: () => { this.attachFile_onPress(); } });
+ output.push({ title: _('Attach image'), onPress: () => { this.attachImage_onPress(); } });
+ output.push({ title: _('Attach any other file'), onPress: () => { this.attachFile_onPress(); } });
output.push({ title: _('Delete note'), onPress: () => { this.deleteNote_onPress(); } });
// if (isTodo) {
diff --git a/ReactNativeClient/package-lock.json b/ReactNativeClient/package-lock.json
index c6dc8af0c..3e4a5f3a4 100644
--- a/ReactNativeClient/package-lock.json
+++ b/ReactNativeClient/package-lock.json
@@ -4645,6 +4645,11 @@
"utf8": "2.1.2"
}
},
+ "react-native-image-picker": {
+ "version": "0.26.7",
+ "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.26.7.tgz",
+ "integrity": "sha1-rS7pV/f2zAE5aJPqA9hMsq2y43Y="
+ },
"react-native-image-resizer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-native-image-resizer/-/react-native-image-resizer-1.0.0.tgz",
diff --git a/ReactNativeClient/package.json b/ReactNativeClient/package.json
index fcdce7afd..b2821cb87 100644
--- a/ReactNativeClient/package.json
+++ b/ReactNativeClient/package.json
@@ -24,6 +24,7 @@
"react-native-document-picker": "^2.1.0",
"react-native-fetch-blob": "^0.10.6",
"react-native-fs": "^2.8.1",
+ "react-native-image-picker": "^0.26.7",
"react-native-image-resizer": "^1.0.0",
"react-native-material-dropdown": "^0.5.2",
"react-native-popup-dialog": "^0.9.35",