Ability to upload other file types (#1662)

This commit is contained in:
Elias Nahum 2018-05-11 18:05:49 -03:00 committed by GitHub
parent 76e664cdae
commit 23ca490525
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 285 additions and 2 deletions

View file

@ -67,7 +67,9 @@ post-install:
@# Must remove the .babelrc for 0.42.0 to work correctly
@# Need to copy custom ImagePickerModule.java that implements correct permission checks for android
@rm node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
@cp ./ImagePickerModule.java node_modules/react-native-image-picker/android/src/main/java/com/imagepicker
@cp ./native_modules/ImagePickerModule.java node_modules/react-native-image-picker/android/src/main/java/com/imagepicker
@# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS
@cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m
@rm -f node_modules/intl/.babelrc
@# Hack to get react-intl and its dependencies to work with react-native
@# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js

View file

@ -168,6 +168,7 @@ android {
}
dependencies {
compile project(':react-native-document-picker')
compile project(':react-native-doc-viewer')
compile project(':react-native-video')
compile fileTree(dir: "libs", include: ["*.jar"])

View file

@ -7,6 +7,7 @@ import android.content.Context;
import android.os.Bundle;
import com.facebook.react.ReactApplication;
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
import com.reactlibrary.RNReactNativeDocViewerPackage;
import com.brentvatne.react.ReactVideoPackage;
import com.horcrux.svg.SvgPackage;
@ -72,6 +73,7 @@ public class MainApplication extends NavigationApplication implements INotificat
new ReactNativeYouTube(),
new ReactVideoPackage(),
new RNReactNativeDocViewerPackage(),
new ReactNativeDocumentPicker(),
new SharePackage()
);
}

View file

@ -64,7 +64,9 @@ public class RealPathUtil {
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
}
if ("content".equalsIgnoreCase(uri.getScheme())) {
// MediaStore (and general)
if (isGooglePhotosUri(uri)) {

View file

@ -89,6 +89,23 @@ public class ShareModule extends ReactContextBaseJavaModule {
promise.resolve(processIntent());
}
@ReactMethod
public void getFilePath(String filePath, Promise promise) {
Activity currentActivity = getCurrentActivity();
WritableMap map = Arguments.createMap();
if (currentActivity != null) {
Uri uri = Uri.parse(filePath);
String path = RealPathUtil.getRealPathFromURI(currentActivity, uri);
if (path != null) {
String text = "file://" + path;
map.putString("filePath", text);
}
}
promise.resolve(map);
}
public WritableArray processIntent() {
WritableMap map = Arguments.createMap();
WritableArray items = Arguments.createArray();

View file

@ -1,4 +1,6 @@
rootProject.name = 'Mattermost'
include ':react-native-document-picker'
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
include ':react-native-doc-viewer'
project(':react-native-doc-viewer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-doc-viewer/android')
include ':react-native-video'

View file

@ -3,17 +3,21 @@ import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {
Alert,
NativeModules,
Platform,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import {DocumentPicker} from 'react-native-document-picker';
import ImagePicker from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import {PermissionTypes} from 'app/constants';
import {changeOpacity} from 'app/utils/theme';
const ShareExtension = NativeModules.MattermostShare;
export default class AttachmentButton extends PureComponent {
static propTypes = {
blurTextBox: PropTypes.func.isRequired,
@ -141,6 +145,32 @@ export default class AttachmentButton extends PureComponent {
});
};
attachFileFromFiles = async () => {
const hasPermission = await this.hasStoragePermission();
if (hasPermission) {
DocumentPicker.show({
filetype: [Platform.OS === 'ios' ? 'public.item' : '*/*'],
}, async (error, res) => {
if (error) {
return;
}
if (Platform.OS === 'android') {
// For android we need to retrieve the realPath in case the file being imported is from the cloud
const newUri = await ShareExtension.getFilePath(res.uri);
if (newUri.filePath) {
res.uri = newUri.filePath;
} else {
return;
}
}
this.uploadFiles([res]);
});
}
};
hasPhotoPermission = async () => {
if (Platform.OS === 'ios') {
const {formatMessage} = this.context.intl;
@ -194,6 +224,59 @@ export default class AttachmentButton extends PureComponent {
return true;
};
hasStoragePermission = async () => {
if (Platform.OS === 'android') {
const {formatMessage} = this.context.intl;
let permissionRequest;
const hasPermissionToStorage = await Permissions.check('storage');
switch (hasPermissionToStorage) {
case PermissionTypes.UNDETERMINED:
permissionRequest = await Permissions.request('storage');
if (permissionRequest !== PermissionTypes.AUTHORIZED) {
return false;
}
break;
case PermissionTypes.DENIED: {
const canOpenSettings = await Permissions.canOpenSettings();
let grantOption = null;
if (canOpenSettings) {
grantOption = {
text: formatMessage({
id: 'mobile.android.permission_denied_retry',
defaultMessage: 'Set permission',
}),
onPress: () => Permissions.openSettings(),
};
}
Alert.alert(
formatMessage({
id: 'mobile.android.storage_permission_denied_title',
defaultMessage: 'File Storage access is required',
}),
formatMessage({
id: 'mobile.android.storage_permission_denied_description',
defaultMessage: 'To upload images from your Android device, please change your permission settings.',
}),
[
grantOption,
{
text: formatMessage({
id: 'mobile.android.permission_denied_dismiss',
defaultMessage: 'Dismiss',
}),
},
]
);
return false;
}
}
}
return true;
};
uploadFiles = (images) => {
this.props.uploadFiles(images);
};
@ -251,6 +334,15 @@ export default class AttachmentButton extends PureComponent {
});
}
options.items.push({
action: () => this.handleFileAttachmentOption(this.attachFileFromFiles),
text: {
id: 'mobile.file_upload.browse',
defaultMessage: 'Browse Files',
},
icon: 'file',
});
this.props.navigator.showModal({
screen: 'OptionsModal',
title: '',

View file

@ -2035,6 +2035,8 @@
"mobile.android.permission_denied_retry": "Set permission",
"mobile.android.photos_permission_denied_description": "To upload images from your library, please change your permission settings.",
"mobile.android.photos_permission_denied_title": "Photo library access is required",
"mobile.android.storage_permission_denied_title": "File Storage access is required",
"mobile.android.storage_permission_denied_description": "To upload images from your Android device, please change your permission settings.",
"mobile.android.videos_permission_denied_description": "To upload videos from your library, please change your permission settings.",
"mobile.android.videos_permission_denied_title": "Video library access is required",
"mobile.announcement_banner.dismiss": "Dismiss",
@ -2135,6 +2137,7 @@
"mobile.failed_network_action.description": "There seems to be a problem with your internet connection. Make sure you have an active connection and try again.",
"mobile.failed_network_action.retry": "Try Again",
"mobile.failed_network_action.title": "No internet connection",
"mobile.file_upload.browse": "Browse Files",
"mobile.file_upload.camera": "Take Photo or Video",
"mobile.file_upload.library": "Photo Library",
"mobile.file_upload.max_warning": "Uploads limited to 5 files maximum.",

View file

@ -94,6 +94,13 @@ platform :ios do
)
end
unless ENV['IOS_ICLOUD_CONTAINER'].nil? || ENV['IOS_ICLOUD_CONTAINER'].empty? || ENV['IOS_ICLOUD_CONTAINER'] == 'iCloud.com.mattermost.rnbeta'
update_icloud_container_identifiers(
entitlements_file: './ios/Mattermost/Mattermost.entitlements',
icloud_container_identifiers: [ENV['IOS_ICLOUD_CONTAINER']]
)
end
if ENV['IOS_APP_NAME'] != 'Mattermost Beta'
update_info_plist(
xcodeproj: './ios/Mattermost.xcodeproj',

View file

@ -65,6 +65,7 @@
7F50C96B203C6E80007CA374 /* SessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F50C969203C6E80007CA374 /* SessionManager.m */; };
7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
7F642DF1209353A400F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; };
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; };
7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6877B01E7835E50094B63F /* libToolTipMenu.a */; };
7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */; };
7F7D7F98201645E100D31155 /* libReactNativePermissions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F7D7F87201645D300D31155 /* libReactNativePermissions.a */; };
@ -422,6 +423,13 @@
remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32;
remoteInfo = "fishhook-tvOS";
};
7F5CA990208FE38F004F91CE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = E01DD9DB1D2311A600C39062;
remoteInfo = RNDocumentPicker;
};
7F63D2801E6C957C001FAE12 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */;
@ -697,6 +705,7 @@
7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-Mattermost.a"; path = "../../../../../../../Library/Developer/Xcode/DerivedData/Mattermost-czlinsdviifujheezzjvmisotjrm/Build/Products/Debug-iphonesimulator/libPods-Mattermost.a"; sourceTree = "<group>"; };
7F50C968203C6E80007CA374 /* SessionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionManager.h; sourceTree = "<group>"; };
7F50C969203C6E80007CA374 /* SessionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SessionManager.m; sourceTree = "<group>"; };
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDeviceInfo.xcodeproj; path = "../node_modules/react-native-device-info/ios/RNDeviceInfo.xcodeproj"; sourceTree = "<group>"; };
@ -802,6 +811,7 @@
7F43D5DD1F6BF95F001FC614 /* libRNCookieManagerIOS.a in Frameworks */,
7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */,
7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */,
7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */,
7F43D5DE1F6BF96A001FC614 /* libRCTYouTube.a in Frameworks */,
7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */,
A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */,
@ -1131,6 +1141,14 @@
name = Products;
sourceTree = "<group>";
};
7F5CA957208FE38F004F91CE /* Products */ = {
isa = PBXGroup;
children = (
7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */,
);
name = Products;
sourceTree = "<group>";
};
7F63D27C1E6C957C001FAE12 /* Products */ = {
isa = PBXGroup;
children = (
@ -1299,6 +1317,7 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */,
7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */,
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */,
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */,
@ -1470,6 +1489,9 @@
com.apple.Push = {
enabled = 1;
};
com.apple.iCloud = {
enabled = 1;
};
};
};
7FFE32991FD9CB640038C7A0 = {
@ -1597,6 +1619,10 @@
ProductGroup = 7F642DE82093530B00F3165E /* Products */;
ProjectRef = 7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */;
},
{
ProductGroup = 7F5CA957208FE38F004F91CE /* Products */;
ProjectRef = 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */;
},
{
ProductGroup = 3752181C1F4B9E320035444B /* Products */;
ProjectRef = 546CB0A5BB8F453890CBA559 /* RNFetchBlob.xcodeproj */;
@ -1957,6 +1983,13 @@
remoteRef = 7F4C1F851FBE572B0029D1DF /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNDocumentPicker.a;
remoteRef = 7F5CA990208FE38F004F91CE /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -2491,6 +2524,7 @@
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
"$(SRCROOT)/../node_modules/react-native-document-picker/**",
"$(SRCROOT)/../node_modules/react-native-device-info/ios/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
@ -2541,6 +2575,7 @@
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
"$(SRCROOT)/../node_modules/react-native-document-picker/**",
"$(SRCROOT)/../node_modules/react-native-device-info/ios/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;

View file

@ -4,6 +4,20 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.$(CFBundleIdentifier)</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.mattermost.rnbeta</string>

View file

@ -0,0 +1,100 @@
#import "RNDocumentPicker.h"
#if __has_include(<React/RCTConvert.h>)
#import <React/RCTConvert.h>
#import <React/RCTBridge.h>
#else // back compatibility for RN version < 0.40
#import "RCTConvert.h"
#import "RCTBridge.h"
#endif
#define IDIOM UI_USER_INTERFACE_IDIOM()
#define IPAD UIUserInterfaceIdiomPad
@interface RNDocumentPicker () <UIDocumentPickerDelegate>
@end
@implementation RNDocumentPicker {
NSMutableArray *composeViews;
NSMutableArray *composeCallbacks;
}
@synthesize bridge = _bridge;
- (instancetype)init
{
if ((self = [super init])) {
composeCallbacks = [[NSMutableArray alloc] init];
composeViews = [[NSMutableArray alloc] init];
}
return self;
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(show:(NSDictionary *)options
callback:(RCTResponseSenderBlock)callback) {
NSArray *allowedUTIs = [RCTConvert NSArray:options[@"filetype"]];
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:(NSArray *)allowedUTIs inMode:UIDocumentPickerModeImport];
[composeCallbacks addObject:callback];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
UIViewController *rootViewController = [[[[UIApplication sharedApplication]delegate] window] rootViewController];
while (rootViewController.modalViewController) {
rootViewController = rootViewController.modalViewController;
}
if ( IDIOM == IPAD ) {
NSNumber *top = [RCTConvert NSNumber:options[@"top"]];
NSNumber *left = [RCTConvert NSNumber:options[@"left"]];
[documentPicker.popoverPresentationController setSourceRect: CGRectMake([left floatValue], [top floatValue], 0, 0)];
[documentPicker.popoverPresentationController setSourceView: rootViewController.view];
}
[rootViewController presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
if (controller.documentPickerMode == UIDocumentPickerModeImport) {
RCTResponseSenderBlock callback = [composeCallbacks lastObject];
[composeCallbacks removeLastObject];
[url startAccessingSecurityScopedResource];
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;
[coordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingResolvesSymbolicLink error:&error byAccessor:^(NSURL *newURL) {
NSMutableDictionary* result = [NSMutableDictionary dictionary];
[result setValue:newURL.absoluteString forKey:@"uri"];
[result setValue:[newURL lastPathComponent] forKey:@"fileName"];
NSError *attributesError = nil;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:newURL.path error:&attributesError];
if(!attributesError) {
[result setValue:[fileAttributes objectForKey:NSFileSize] forKey:@"fileSize"];
} else {
NSLog(@"%@", attributesError);
}
callback(@[[NSNull null], result]);
}];
[url stopAccessingSecurityScopedResource];
}
}
@end

5
package-lock.json generated
View file

@ -14426,6 +14426,11 @@
"resolved": "https://registry.npmjs.org/react-native-doc-viewer/-/react-native-doc-viewer-2.7.8.tgz",
"integrity": "sha1-Q9BlMo+xt+yQ0yHlvSNHEegHyPY="
},
"react-native-document-picker": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-2.1.0.tgz",
"integrity": "sha512-BFCBXwz8xuLvHLVFVeQM+RhaY8yZ38PEWt9WSbq5VIoZ/VssP6uu51XxOfdwaMALOrAHIojK0SiYnd155upZAg=="
},
"react-native-drawer": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/react-native-drawer/-/react-native-drawer-2.5.0.tgz",

View file

@ -27,6 +27,7 @@
"react-native-cookies": "3.2.0",
"react-native-device-info": "0.21.5",
"react-native-doc-viewer": "2.7.8",
"react-native-document-picker": "2.1.0",
"react-native-drawer": "2.5.0",
"react-native-exception-handler": "2.7.5",
"react-native-fast-image": "4.0.8",