diff --git a/android/app/build.gradle b/android/app/build.gradle index 30d4a57c6..ed112a825 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,7 +113,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion 21 targetSdkVersion 23 - versionCode 115 + versionCode 117 versionName "1.9.2" ndk { abiFilters "armeabi-v7a", "x86" diff --git a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java index 8640f161f..b66ffd831 100644 --- a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java +++ b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java @@ -10,6 +10,8 @@ import android.content.ContentUris; import android.content.ContentResolver; import android.os.Environment; import android.webkit.MimeTypeMap; +import android.util.Log; +import android.text.TextUtils; import android.os.ParcelFileDescriptor; import java.io.*; @@ -37,10 +39,19 @@ public class RealPathUtil { // DownloadsProvider final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - return getDataColumn(context, contentUri, null, null); + if (!TextUtils.isEmpty(id)) { + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + try { + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } catch (NumberFormatException e) { + Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri.toString()); + return null; + } + } } else if (isMediaDocument(uri)) { // MediaProvider diff --git a/android/app/src/main/java/com/mattermost/share/ShareModule.java b/android/app/src/main/java/com/mattermost/share/ShareModule.java index 5ce9cd654..d77cd982c 100644 --- a/android/app/src/main/java/com/mattermost/share/ShareModule.java +++ b/android/app/src/main/java/com/mattermost/share/ShareModule.java @@ -147,6 +147,13 @@ public class ShareModule extends ReactContextBaseJavaModule { Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); text = "file://" + RealPathUtil.getRealPathFromURI(currentActivity, uri); map.putString("value", text); + + if (type.equals("image/*")) { + type = "image/jpeg"; + } else if (type.equals("video/*")) { + type = "video/mp4"; + } + map.putString("type", type); items.pushMap(map); } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { @@ -156,7 +163,15 @@ public class ShareModule extends ReactContextBaseJavaModule { map = Arguments.createMap(); text = "file://" + filePath; map.putString("value", text); - map.putString("type", RealPathUtil.getMimeTypeFromUri(currentActivity, uri)); + + type = RealPathUtil.getMimeTypeFromUri(currentActivity, uri); + if (type.equals("image/*")) { + type = "image/jpeg"; + } else if (type.equals("video/*")) { + type = "video/mp4"; + } + + map.putString("type", type); items.pushMap(map); } } diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index 4e2c07f2f..ac8be1af8 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -10,6 +10,8 @@ import { StyleSheet, } from 'react-native'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + import Post from 'app/components/post'; import {DATE_LINE, START_OF_NEW_MESSAGES} from 'app/selectors/post_list'; import mattermostManaged from 'app/mattermost_managed'; @@ -77,6 +79,7 @@ export default class PostList extends PureComponent { } componentDidMount() { + EventEmitter.on('reset_channel', this.scrollToBottomOffset); this.setManagedConfig(); } @@ -98,6 +101,7 @@ export default class PostList extends PureComponent { } componentWillUnmount() { + EventEmitter.off('reset_channel', this.scrollToBottomOffset); mattermostManaged.removeEventListener(this.listenerId); } diff --git a/app/screens/permalink/permalink.js b/app/screens/permalink/permalink.js index abfabf6b0..7c7d29a3b 100644 --- a/app/screens/permalink/permalink.js +++ b/app/screens/permalink/permalink.js @@ -14,6 +14,7 @@ import * as Animatable from 'react-native-animatable'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import {General} from 'mattermost-redux/constants'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; import FormattedText from 'app/components/formatted_text'; import Loading from 'app/components/loading'; @@ -200,22 +201,28 @@ export default class Permalink extends PureComponent { actions.selectPost(''); + if (channelId === currentChannelId) { + EventEmitter.emit('reset_channel'); + } else { + navigator.resetTo({ + screen: 'Channel', + animated: true, + animationType: 'fade', + navigatorStyle: { + navBarHidden: true, + statusBarHidden: false, + statusBarHideWithNavBar: false, + screenBackgroundColor: theme.centerChannelBg, + }, + }); + } + + navigator.dismissAllModals({animationType: 'slide-down'}); + if (onClose) { onClose(); } - navigator.resetTo({ - screen: 'Channel', - animated: true, - animationType: 'fade', - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - screenBackgroundColor: theme.centerChannelBg, - }, - }); - if (channelTeamId && currentTeamId !== channelTeamId) { handleTeamChange(channelTeamId, false); } diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 18017ecf2..679bc2642 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -2587,6 +2587,7 @@ "mobile.share_extension.error_close": "Close", "mobile.share_extension.error_message": "An error has occurred while using the share extension.", "mobile.share_extension.error_title": "Extension Error", + "mobile.share_extension.post_error": "An error has occurred while posting the message. Please try again.", "mobile.share_extension.send": "Send", "mobile.share_extension.team": "Team", "mobile.suggestion.members": "Members", diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index fe139720a..c693a45bb 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2533,7 +2533,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 115; + CURRENT_PROJECT_VERSION = 117; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2583,7 +2583,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 115; + CURRENT_PROJECT_VERSION = 117; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index ff11ab60f..891224c1f 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 115 + 117 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index dda69e6ca..4bde0fc1b 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -23,7 +23,7 @@ CFBundleShortVersionString 1.9.2 CFBundleVersion - 115 + 117 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostShare/SessionManager.h b/ios/MattermostShare/SessionManager.h index bb40d0c50..5a23cd3ee 100644 --- a/ios/MattermostShare/SessionManager.h +++ b/ios/MattermostShare/SessionManager.h @@ -4,6 +4,8 @@ @interface SessionManager : NSObject @property (nonatomic, copy) void (^savedCompletionHandler)(void); +@property (nonatomic, copy) void (^sendShareEvent)(NSString *); +@property (nonatomic, copy) void (^closeExtension)(void); @property MattermostBucket *bucket; @property (nonatomic, retain) KeyChainDataSource *keyChain; @property (nonatomic, strong) NSString *requestWithGroup; diff --git a/ios/MattermostShare/SessionManager.m b/ios/MattermostShare/SessionManager.m index 64f8ca91d..13298de60 100644 --- a/ios/MattermostShare/SessionManager.m +++ b/ios/MattermostShare/SessionManager.m @@ -78,16 +78,16 @@ NSString *channelId = [post objectForKey:@"channel_id"]; NSURLSession *session = [self createSessionForRequestRequest:requestWithGroup]; NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - + for (id file in files) { NSURL *filePath = [NSURL fileURLWithPath:[file objectForKey:@"filePath"]]; NSString *fileName = [file objectForKey:@"filename"]; - + NSError *err; NSURL *tempContainerURL = [self tempContainerURL:appGroupId]; NSURL *destinationURL = [tempContainerURL URLByAppendingPathComponent: fileName]; BOOL bVal = [[NSFileManager defaultManager] copyItemAtURL:filePath toURL:destinationURL error:&err]; - + NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet]; NSString *encodedFilename = [[file objectForKey:@"filename"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; NSString *url = [serverUrl stringByAppendingString:@"/api/v4/files"]; @@ -97,7 +97,7 @@ [uploadRequest setHTTPMethod:@"POST"]; [uploadRequest setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"]; [uploadRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - + NSURLSessionUploadTask *task = [session uploadTaskWithRequest:uploadRequest fromFile:destinationURL]; NSLog(@"Executing file request %@", fileName); [task resume]; @@ -113,7 +113,7 @@ if (fileIds != nil && [fileIds count] > 0) { [post setObject:fileIds forKey:@"file_ids"]; } - + NSError *error; NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:&error]; @@ -134,6 +134,9 @@ NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request]; NSLog(@"Executing post request"); [createTask resume]; + self.closeExtension(); + } else { + self.sendShareEvent(@"extensionPostFailed"); } } @@ -144,9 +147,10 @@ NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup]; if (credentials == nil) { + self.sendShareEvent(@"extensionPostFailed"); return; } - + if (files != nil && [files count] > 0) { [self createPost:post withFiles:files credentials:credentials requestWithGroup:requestWithGroup]; } @@ -158,10 +162,10 @@ -(void)sendPostRequestForId:(NSString *)requestWithGroup { NSDictionary *data = [self getDataForRequest:requestWithGroup]; NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup]; - + NSMutableDictionary *post = [[data objectForKey:@"post"] mutableCopy]; NSArray *fileIds = [data objectForKey:@"file_ids"]; - + [self createPost:post withFileIds:fileIds credentials:credentials]; } @@ -199,21 +203,20 @@ if ([emails count] > 0){ email = [emails objectAtIndex:0]; } - NSLog(@"completition %@ %@", tagstr, email); + NSLog(@"completion %@ %@", tagstr, email); const void *certs[] = {certificate}; CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; completionHandler(NSURLSessionAuthChallengeUseCredential,credential); - NSLog(@"completition handler for certificate"); + NSLog(@"completion handler for certificate"); return; } } } completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); - NSLog(@"completition handler that should not be reached"); } else { - NSLog(@"completition handler regular stuff"); + NSLog(@"completion handler regular stuff"); completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); } } @@ -226,7 +229,7 @@ requestWithGroup = self.requestWithGroup; } NSURL *requestUrl = [[dataTask originalRequest] URL]; - + if ([[requestUrl absoluteString] containsString:@"files"]) { NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; NSError *error; @@ -239,7 +242,7 @@ if (fileIds == nil && data != nil) { fileIds = [[NSMutableArray alloc] init]; } - + for (id file in fileInfos) { [fileIds addObject:[file objectForKey:@"id"]]; NSString * filename = [file objectForKey:@"name"]; @@ -250,6 +253,8 @@ } [data setObject:fileIds forKey:@"file_ids"]; [self setDataForRequest:data forRequestWithGroup:requestWithGroup]; + } else { + self.sendShareEvent(@"extensionPostFailed"); } } } @@ -266,14 +271,15 @@ NSLog(@"completition ERROR %@", [error userInfo]); NSLog(@"invalidating session %@", requestWithGroup); [session invalidateAndCancel]; + self.sendShareEvent(@"extensionPostFailed"); } else if (requestWithGroup != nil) { NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; NSURL *requestUrl = [[task originalRequest] URL]; - + NSDictionary *data = [self getDataForRequest:requestWithGroup]; NSArray *files = [data objectForKey:@"files"]; NSMutableArray *fileIds = [data objectForKey:@"file_ids"]; - + if ([[requestUrl absoluteString] containsString:@"files"] && [files count] == [fileIds count]) { [self sendPostRequestForId:requestWithGroup]; diff --git a/ios/MattermostShare/ShareViewController.m b/ios/MattermostShare/ShareViewController.m index 14f406dfc..56a4f17c6 100644 --- a/ios/MattermostShare/ShareViewController.m +++ b/ios/MattermostShare/ShareViewController.m @@ -12,6 +12,8 @@ NSExtensionContext* extensionContext; return YES; } +@synthesize bridge = _bridge; + - (UIView*) shareView { NSURL *jsCodeLocation; @@ -57,13 +59,23 @@ RCT_EXPORT_METHOD(close:(NSDictionary *)data appGroupId:(NSString *)appGroupId) if (tryToUploadInTheBackgound) { NSString *requestWithGroup = [NSString stringWithFormat:@"%@|%@", requestId, appGroupId]; + [SessionManager sharedSession].closeExtension = ^{ + [extensionContext completeRequestReturningItems:nil + completionHandler:nil]; + NSLog(@"Extension closed"); + }; + + [SessionManager sharedSession].sendShareEvent = ^(NSString* eventName) { + NSLog(@"Send Share Extension Event to JS"); + [_bridge enqueueJSCall:@"RCTDeviceEventEmitter" + method:@"emit" + args:@[eventName] + completion:nil]; + }; + [[SessionManager sharedSession] setRequestWithGroup:requestWithGroup certificateName:certificateName]; [[SessionManager sharedSession] setDataForRequest:data forRequestWithGroup:requestWithGroup]; [[SessionManager sharedSession] createPostForRequest:requestWithGroup]; - - [extensionContext completeRequestReturningItems:nil - completionHandler:nil]; - NSLog(@"Extension closed"); } else { NSDictionary *post = [data objectForKey:@"post"]; NSArray *files = [data objectForKey:@"files"]; diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 1d380a42f..609e14aa6 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 115 + 117 diff --git a/package-lock.json b/package-lock.json index e6bdf260a..98d892b1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14533,8 +14533,8 @@ } }, "react-native": { - "version": "github:enahum/react-native#d7aa4677f3290cb0650952782a797aea9729e47e", - "from": "github:enahum/react-native#text-0.55", + "version": "github:enahum/react-native#b295c057c20bf3432f339874965e14268410d70b", + "from": "github:enahum/react-native#mm", "requires": { "absolute-path": "^0.0.0", "art": "^0.10.0", @@ -14703,8 +14703,8 @@ } }, "react-native-fetch-blob": { - "version": "github:enahum/react-native-fetch-blob#27983c83d7fad1cb4821dd6ba8e405da4af64f3a", - "from": "github:enahum/react-native-fetch-blob#27983c83d7fad1cb4821dd6ba8e405da4af64f3a", + "version": "github:enahum/react-native-fetch-blob#729c4b49b2e2195b1793c4d3490ab67cb9a8e472", + "from": "github:enahum/react-native-fetch-blob#729c4b49b2e2195b1793c4d3490ab67cb9a8e472", "requires": { "base-64": "0.1.0", "glob": "7.0.6" diff --git a/package.json b/package.json index df04c7d14..91cbda90e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "prop-types": "15.6.1", "react": "16.3.2", "react-intl": "2.4.0", - "react-native": "github:enahum/react-native#text-0.55", + "react-native": "github:enahum/react-native#mm", "react-native-animatable": "1.2.4", "react-native-bottom-sheet": "1.0.3", "react-native-button": "2.3.0", @@ -32,7 +32,7 @@ "react-native-drawer-layout": "2.0.0", "react-native-exception-handler": "2.7.5", "react-native-fast-image": "4.0.8", - "react-native-fetch-blob": "enahum/react-native-fetch-blob.git#27983c83d7fad1cb4821dd6ba8e405da4af64f3a", + "react-native-fetch-blob": "enahum/react-native-fetch-blob.git#729c4b49b2e2195b1793c4d3490ab67cb9a8e472", "react-native-image-gallery": "enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f", "react-native-image-picker": "0.26.7", "react-native-keyboard-aware-scroll-view": "0.5.0", diff --git a/share_extension/android/extension_channels/extension_channels.js b/share_extension/android/extension_channels/extension_channels.js index b754f13e3..b7dd63034 100644 --- a/share_extension/android/extension_channels/extension_channels.js +++ b/share_extension/android/extension_channels/extension_channels.js @@ -190,6 +190,7 @@ export default class ExtensionTeam extends PureComponent { titleCancelColor={defaultTheme.centerChannelColor} onChangeText={this.handleSearch} autoCapitalize='none' + value={this.state.term} /> ); diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js index b4083eb9c..62484ffed 100644 --- a/share_extension/ios/extension_post.js +++ b/share_extension/ios/extension_post.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; import { ActivityIndicator, + DeviceEventEmitter, Dimensions, Image, NativeModules, @@ -93,6 +94,7 @@ export default class ExtensionPost extends PureComponent { } componentWillMount() { + this.event = DeviceEventEmitter.addListener('extensionPostFailed', this.handlePostFailed); this.loadData(); } @@ -100,6 +102,10 @@ export default class ExtensionPost extends PureComponent { this.focusInput(); } + componentWillUnmount() { + this.event.remove(); + } + componentDidUpdate() { this.focusInput(); } @@ -141,6 +147,10 @@ export default class ExtensionPost extends PureComponent { const {navigator, theme} = this.props; const {channel, entities, team} = this.state; + if (!entities || !team || !channel) { + return; + } + navigator.push({ component: ExtensionChannels, wrapperStyle: { @@ -161,7 +171,11 @@ export default class ExtensionPost extends PureComponent { goToTeams = preventDoubleTap(() => { const {navigator, theme} = this.props; const {formatMessage} = this.context.intl; - const {team} = this.state; + const {entities, team} = this.state; + + if (!entities || !team) { + return; + } navigator.push({ component: ExtensionTeams, @@ -171,7 +185,7 @@ export default class ExtensionPost extends PureComponent { backgroundColor: theme.centerChannelBg, }, passProps: { - entities: this.state.entities, + entities, currentTeamId: team.id, onSelectTeam: this.selectTeam, theme, @@ -187,6 +201,19 @@ export default class ExtensionPost extends PureComponent { this.setState({value}); }; + handlePostFailed = () => { + const {formatMessage} = this.context.intl; + this.setState({ + error: { + message: formatMessage({ + id: 'mobile.share_extension.post_error', + defaultMessage: 'An error has occurred while posting the message. Please try again.', + }), + }, + sending: false, + }); + }; + loadData = async () => { const {entities} = this.state; @@ -287,6 +314,16 @@ export default class ExtensionPost extends PureComponent { const serverMaxFileSize = getAllowedServerMaxFileSize(config); const maxSize = Math.min(MAX_FILE_SIZE, serverMaxFileSize); + if (error) { + return ( + + + {error.message} + + + ); + } + if (sending) { return ( @@ -336,16 +373,6 @@ export default class ExtensionPost extends PureComponent { ); } - if (error) { - return ( - - - {error.message} - - - ); - } - return ( @@ -552,7 +579,7 @@ export default class ExtensionPost extends PureComponent { this.setState({channel: townSquare}); } } else { - this.setState({error}); + this.setState({error, channel: null}); } }); }; @@ -629,15 +656,22 @@ export default class ExtensionPost extends PureComponent { render() { const {authenticated, theme} = this.props; - const {channel, totalSize, sending} = this.state; + const {channel, error, totalSize, sending} = this.state; const {formatMessage} = this.context.intl; const styles = getStyleSheet(theme); let postButtonText = formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'}); - if (totalSize >= MAX_FILE_SIZE || sending || !channel) { + if (totalSize >= MAX_FILE_SIZE || sending || error || !channel) { postButtonText = null; } + let cancelButton = formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'}); + if (sending) { + cancelButton = null; + } else if (error) { + cancelButton = formatMessage({id: 'mobile.share_extension.error_close', defaultMessage: 'Close'}); + } + return ( {this.renderBody(styles)} - {this.renderTeamButton(styles)} - {this.renderChannelButton(styles)} + {!error && this.renderTeamButton(styles)} + {!error && this.renderChannelButton(styles)} ); }