From 6eb574b7b24efd06bd5ce9dc93da76bf3b3a22e3 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Wed, 23 Oct 2019 22:50:17 -0700 Subject: [PATCH 01/17] Add redux-offline dependency (#3459) --- package-lock.json | 70 ++++++++++++++++++----------------------------- package.json | 1 + 2 files changed, 28 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index c92ce674e..e1a56c472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5627,7 +5627,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -5672,8 +5671,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true + "dev": true }, "is-glob": { "version": "4.0.1", @@ -8496,8 +8494,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", @@ -8518,14 +8515,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8540,20 +8535,17 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -8670,8 +8662,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -8683,7 +8674,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8698,7 +8688,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8706,14 +8695,12 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8732,7 +8719,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -8813,8 +8799,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -8826,7 +8811,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -8912,8 +8896,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", @@ -8949,7 +8932,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8969,7 +8951,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9013,14 +8994,12 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "optional": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } }, @@ -14166,7 +14145,6 @@ "redux": "4.0.4", "redux-action-buffer": "1.2.0", "redux-batched-actions": "0.4.1", - "redux-offline": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", "redux-persist": "4.9.1", "redux-thunk": "2.3.0", "reselect": "4.0.0", @@ -14201,6 +14179,13 @@ "symbol-observable": "^1.2.0" } }, + "redux-offline": { + "version": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", + "from": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", + "requires": { + "redux-persist": "^4.5.0" + } + }, "redux-persist": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-4.9.1.tgz", @@ -17245,8 +17230,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "optional": true + "dev": true }, "braces": { "version": "2.3.2", @@ -17509,8 +17493,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true, - "optional": true + "dev": true }, "micromatch": { "version": "3.1.10", @@ -17597,16 +17580,17 @@ } }, "redux-offline": { - "version": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", - "from": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", + "version": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", + "from": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", "requires": { + "@react-native-community/netinfo": "^4.1.3", "redux-persist": "^4.5.0" }, "dependencies": { "@react-native-community/netinfo": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.3.0.tgz", - "integrity": "sha512-bYlmjb5muMO1ag5Zzk95OjjxPWFVG1Nrr1x/I95/b99llgoFa3JZSSnlxxAMek2W+c/iknT13oyKfFpQbXysFQ==" + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.4.0.tgz", + "integrity": "sha512-qqNWMOsrDjj/daqV21ID2T8mNUjZD4pdx3PuWyE65gzKh2w+oMnzKb+J0NbLyZPn3wwLwU1+Cpf58A0ff5szjQ==" } } }, diff --git a/package.json b/package.json index c3010e5ed..ea5216c37 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "react-redux": "7.0.3", "redux": "4.0.1", "redux-batched-actions": "0.4.1", + "redux-offline": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", "redux-persist": "4.10.2", "redux-persist-transform-filter": "0.0.18", "redux-thunk": "2.3.0", From ee2a25df84bf121e2ab8d3ec4e56dc8abe95d581 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 24 Oct 2019 20:41:38 +0300 Subject: [PATCH 02/17] Fix iOS crash when pasting large images and included other file types (#3424) * Fix iOS crash when pasting large images and included other file types * Rename pasteImages to pasteFiles and fix copying heic images * remove comment * Feedback review --- .../post_textbox/post_textbox_base.js | 36 +++--- assets/base/i18n/en.json | 6 +- ios/Mattermost.xcodeproj/project.pbxproj | 32 +++-- ios/Mattermost/Mattermost+RCTUITextView.m | 15 +-- ios/Mattermost/NSData+MimeType.m | 8 +- ios/Mattermost/OnPasteEventManager.h | 2 +- ios/Mattermost/OnPasteEventManager.m | 2 +- ios/Mattermost/SwimeProxy.swift | 61 ++++++++++ ios/Mattermost/UIImage+vImageScaling.h | 16 +++ ios/Mattermost/UIImage+vImageScaling.m | 48 ++++++++ ios/Mattermost/UIPasteboard+GetImageInfo.h | 2 +- ios/Mattermost/UIPasteboard+GetImageInfo.m | 115 +++++++++++++----- ios/Podfile | 1 + ios/Podfile.lock | 6 +- ios/SwimeProxy.swift | 9 ++ package-lock.json | 48 +++++--- 16 files changed, 313 insertions(+), 94 deletions(-) create mode 100644 ios/Mattermost/SwimeProxy.swift create mode 100644 ios/Mattermost/UIImage+vImageScaling.h create mode 100644 ios/Mattermost/UIImage+vImageScaling.m create mode 100644 ios/SwimeProxy.swift diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js index 6a9b9446c..4de59dcb3 100644 --- a/app/components/post_textbox/post_textbox_base.js +++ b/app/components/post_textbox/post_textbox_base.js @@ -409,8 +409,8 @@ export default class PostTextBoxBase extends PureComponent { } }; - handleUploadFiles = (images) => { - this.props.actions.initUploadFiles(images, this.props.rootId); + handleUploadFiles = (files) => { + this.props.actions.initUploadFiles(files, this.props.rootId); }; isFileLoading = () => { @@ -685,21 +685,21 @@ export default class PostTextBoxBase extends PureComponent { }); }; - showPasteImageErrorDialog = () => { + showPasteFilesErrorDialog = () => { const {formatMessage} = this.context.intl; Alert.alert( formatMessage({ - id: 'mobile.image_paste.error_title', - defaultMessage: 'Paste Image failed', + id: 'mobile.files_paste.error_title', + defaultMessage: 'Paste failed', }), formatMessage({ - id: 'mobile.image_paste.error_description', - defaultMessage: 'An error occurred while pasting the image. Please try again.', + id: 'mobile.files_paste.error_description', + defaultMessage: 'An error occurred while pasting the file(s). Please try again.', }), [ { text: formatMessage({ - id: 'mobile.image_paste.error_dismiss', + id: 'mobile.files_paste.error_dismiss', defaultMessage: 'Dismiss', }), }, @@ -707,27 +707,27 @@ export default class PostTextBoxBase extends PureComponent { ); }; - handlePasteImages = (error, images) => { + handlePasteFiles = (error, files) => { if (this.props.screenId === EphemeralStore.getNavigationTopComponentId()) { if (error) { - this.showPasteImageErrorDialog(); + this.showPasteFilesErrorDialog(); return; } - const {maxFileSize, files} = this.props; - const availableCount = MAX_FILE_COUNT - files.length; - if (images.length > availableCount) { + const {maxFileSize} = this.props; + const availableCount = MAX_FILE_COUNT - this.props.files.length; + if (files.length > availableCount) { this.onShowFileMaxWarning(); return; } - const largeImage = images.find((image) => image.fileSize > maxFileSize); - if (largeImage) { - this.onShowFileSizeWarning(largeImage.fileName); + const largeFile = files.find((image) => image.fileSize > maxFileSize); + if (largeFile) { + this.onShowFileSizeWarning(largeFile.fileName); return; } - this.handleUploadFiles(images); + this.handleUploadFiles(files); } }; @@ -780,7 +780,7 @@ export default class PostTextBoxBase extends PureComponent { onEndEditing={this.handleEndEditing} disableFullscreenUI={true} editable={!channelIsReadOnly} - onPaste={this.handlePasteImages} + onPaste={this.handlePasteFiles} keyboardAppearance={getKeyboardAppearanceFromTheme(theme)} /> diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 6e9b3de02..bf85fdfef 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -263,12 +263,12 @@ "mobile.file_upload.max_warning": "Uploads limited to 5 files maximum.", "mobile.file_upload.unsupportedMimeType": "Only files of the following MIME type can be uploaded:\n{mimeTypes}", "mobile.file_upload.video": "Video Library", + "mobile.files_paste.error_description": "An error occurred while pasting the file(s). Please try again.", + "mobile.files_paste.error_dismiss": "Dismiss", + "mobile.files_paste.error_title": "Paste failed", "mobile.flagged_posts.empty_description": "Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.", "mobile.flagged_posts.empty_title": "No Flagged Posts", "mobile.help.title": "Help", - "mobile.image_paste.error_description": "An error occurred while pasting the image. Please try again.", - "mobile.image_paste.error_dismiss": "Dismiss", - "mobile.image_paste.error_title": "Paste Image failed", "mobile.image_preview.save": "Save Image", "mobile.image_preview.save_video": "Save Video", "mobile.intro_messages.default_message": "This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.", diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index a33a2f7b8..b5bd7e490 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -43,12 +43,14 @@ 552835DCC0C24FC691EE6CAB /* Roboto-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8F0B22D2C9924FAFA7FB681C /* Roboto-LightItalic.ttf */; }; 55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */; }; 5A0920184BD344979BCFCD5C /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B89192186C764B9FA473403A /* libRCTVideo.a */; }; + 5A7A8D4251B9E3CD0CC672DC /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2774193E64D242854830A632 /* libPods-MattermostTests.a */; }; 5E1AF7B72B8D4A4E9E53FF9D /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 005346E5C0E542BFABAE1411 /* FontAwesome.ttf */; }; 5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; 62A8448264674B4D95A5A7C2 /* OpenSans-Semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */; }; 69AC753E496743BABB7A7124 /* OpenSans-SemiboldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E617BF0F36D4E738F51D169 /* OpenSans-SemiboldItalic.ttf */; }; 71F30A436B5847DF9D319D15 /* Roboto-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */; }; 72FB67307C2E4BA197BEC567 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CF19152887874B7E996210B1 /* libz.tbd */; }; + 73278E8B36D98C81B6DDAA42 /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A755BC875640C049D8D40CA /* libPods-Mattermost.a */; }; 74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74D116AA208A8D3100CF8A79 /* libRNKeychain.a */; }; 7BD159C40A68467FB5A17141 /* FontAwesome5_Solid.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC1D660B55BE462A9C3B8028 /* FontAwesome5_Solid.ttf */; }; 7F151D3E221B062700FAD8F3 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F151D3D221B062700FAD8F3 /* RuntimeUtils.swift */; }; @@ -92,6 +94,8 @@ 7FABE00A2212650600D0F595 /* ChannelsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE0092212650600D0F595 /* ChannelsViewController.swift */; }; 7FABE04622137F5C00D0F595 /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; }; 7FABE0562213884700D0F595 /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; }; + 7FAE084E23562DE500762B6D /* UIImage+vImageScaling.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FAE084D23562DE500762B6D /* UIImage+vImageScaling.m */; }; + 7FAE089D23562E0E00762B6D /* SwimeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FAE089C23562E0E00762B6D /* SwimeProxy.swift */; }; 7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; }; 7FD1DD0823273C6000E0D948 /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F26C1CB219C463300FEB42D /* libRNCWebView.a */; }; 7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */; }; @@ -110,7 +114,6 @@ 92F9F0ED22F702E10035885A /* NSData+MimeType.m in Sources */ = {isa = PBXBuildFile; fileRef = 92F9F0EC22F702E10035885A /* NSData+MimeType.m */; }; 9358B95F95184EE0A4DCE629 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; }; A08D512E7ADC40CCAD055A9E /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */; }; - A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */; }; AA9605CFDA8E4E7CB8A041BF /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C5BD64DE829E455A997DCAD5 /* Roboto-Regular.ttf */; }; ABF5F93B1D0A47BAACEAC376 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34B20A903038487E8D7DEA1E /* Roboto-Light.ttf */; }; C035DB50ED2045F09923FFAE /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 04AA5E8EF3B54735A11E3B95 /* MaterialCommunityIcons.ttf */; }; @@ -125,7 +128,6 @@ EC6AF34E03F647389D377064 /* Roboto-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 12D0B0E475FD46E29907305E /* Roboto-ThinItalic.ttf */; }; F006936FC2884C24A1321FC0 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 356C9186FA374641A00EB2EA /* MaterialIcons.ttf */; }; F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */; }; - F23C99AA5FA10E457A76803A /* libPods-MattermostTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -770,6 +772,7 @@ 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; 17A75F5A3D0D4A4A995DCD76 /* libRNPasscodeStatus.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNPasscodeStatus.a; sourceTree = ""; }; + 2774193E64D242854830A632 /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = JailMonkey.xcodeproj; path = "../node_modules/jail-monkey/JailMonkey.xcodeproj"; sourceTree = ""; }; 2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = ""; }; 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = ""; }; @@ -786,19 +789,18 @@ 41465DE822E9429EB8B90CB4 /* Pods-MattermostTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MattermostTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MattermostTests/Pods-MattermostTests.release.xcconfig"; sourceTree = ""; }; 41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNReactNativeDocViewer.xcodeproj; path = "../node_modules/react-native-doc-viewer/ios/RNReactNativeDocViewer.xcodeproj"; sourceTree = ""; }; 41F3AFE83AAF4B74878AB78A /* OpenSans-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Italic.ttf"; path = "../assets/fonts/OpenSans-Italic.ttf"; sourceTree = ""; }; - 4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MattermostTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 476BA76E31644398A6012911 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNGestureHandler.a; sourceTree = ""; }; 49DF9A2A231D0CB3008CC96D /* RNReactNativeHapticFeedback.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNReactNativeHapticFeedback.xcodeproj; path = "../node_modules/react-native-haptic-feedback/ios/RNReactNativeHapticFeedback.xcodeproj"; sourceTree = ""; }; 4A22BC320BA04E18A7F2A4E6 /* libReactNativeExceptionHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libReactNativeExceptionHandler.a; sourceTree = ""; }; 4B19E38FBCB94C57BB03B8E6 /* libRNFetchBlob.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFetchBlob.a; sourceTree = ""; }; 51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; }; 59A2AAD141C34AA7B4655F27 /* FontAwesome5_Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Regular.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf"; sourceTree = ""; }; + 5A755BC875640C049D8D40CA /* libPods-Mattermost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Mattermost.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5C5FEE2D22F048709FB330F3 /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = ""; }; 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; 62A58E5E984E4CF1811620B8 /* RNCookieManagerIOS.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNCookieManagerIOS.xcodeproj; path = "../node_modules/react-native-cookies/ios/RNCookieManagerIOS.xcodeproj"; sourceTree = ""; }; 634A8F047C73D24A87850EC0 /* Pods-Mattermost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mattermost.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost.debug.xcconfig"; sourceTree = ""; }; 6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = ""; }; - 65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Mattermost.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeDocViewer.a; sourceTree = ""; }; 6EFF13DD24CE4E26953E598A /* Roboto-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Black.ttf"; path = "../assets/fonts/Roboto-Black.ttf"; sourceTree = ""; }; 71E626D4980A4560B26F0E1C /* Roboto-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Bold.ttf"; path = "../assets/fonts/Roboto-Bold.ttf"; sourceTree = ""; }; @@ -852,6 +854,9 @@ 7FABDFC12211A39000D0F595 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 7FABE0092212650600D0F595 /* ChannelsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsViewController.swift; sourceTree = ""; }; 7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = UploadAttachments.xcodeproj; path = UploadAttachments/UploadAttachments.xcodeproj; sourceTree = ""; }; + 7FAE084C23562DE500762B6D /* UIImage+vImageScaling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIImage+vImageScaling.h"; path = "Mattermost/UIImage+vImageScaling.h"; sourceTree = ""; }; + 7FAE084D23562DE500762B6D /* UIImage+vImageScaling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIImage+vImageScaling.m"; path = "Mattermost/UIImage+vImageScaling.m"; sourceTree = ""; }; + 7FAE089C23562E0E00762B6D /* SwimeProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwimeProxy.swift; path = Mattermost/SwimeProxy.swift; sourceTree = ""; }; 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = ""; }; 7FEB10961F6101710039A015 /* BlurAppScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BlurAppScreen.h; path = Mattermost/BlurAppScreen.h; sourceTree = ""; }; 7FEB10971F6101710039A015 /* BlurAppScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BlurAppScreen.m; path = Mattermost/BlurAppScreen.m; sourceTree = ""; }; @@ -905,7 +910,7 @@ DC1D660B55BE462A9C3B8028 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = ""; }; DE10E26B8E5644FB95A079FE /* AntDesign.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = AntDesign.ttf; path = "../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"; sourceTree = ""; }; DF9DAAAA482343F3910A1A4C /* RCTVideo.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTVideo.xcodeproj; path = "../node_modules/react-native-video/ios/RCTVideo.xcodeproj"; sourceTree = ""; }; - E2D17ED040D0465DBF9112D3 /* Mattermost-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Mattermost-Regular.otf"; path = "../assets/fonts/Mattermost-Regular.otf"; sourceTree = ""; }; + E2D17ED040D0465DBF9112D3 /* Mattermost-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Mattermost-Regular.otf"; path = "../assets/fonts/Mattermost-Regular.otf"; sourceTree = ""; }; EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNPasscodeStatus.xcodeproj; path = "../node_modules/react-native-passcode-status/ios/RNPasscodeStatus.xcodeproj"; sourceTree = ""; }; EDC04CBCF81642219D199CBB /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; EE3EE4548D3F4A49B1274722 /* libRNLocalAuth.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNLocalAuth.a; sourceTree = ""; }; @@ -920,7 +925,7 @@ buildActionMask = 2147483647; files = ( 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */, - F23C99AA5FA10E457A76803A /* libPods-MattermostTests.a in Frameworks */, + 5A7A8D4251B9E3CD0CC672DC /* libPods-MattermostTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -967,12 +972,12 @@ 7FD1DD0823273C6000E0D948 /* libRNCWebView.a in Frameworks */, 7F88A04422270FD400DC5DE2 /* libRNSentry.a in Frameworks */, 7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */, - A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */, 5A0920184BD344979BCFCD5C /* libRCTVideo.a in Frameworks */, 3F876A0DC6314E27B16C8A96 /* libRNReactNativeDocViewer.a in Frameworks */, 8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */, 3F55075810254369AB39F032 /* libRNGestureHandler.a in Frameworks */, 72FB67307C2E4BA197BEC567 /* libz.tbd in Frameworks */, + 73278E8B36D98C81B6DDAA42 /* libPods-Mattermost.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1150,6 +1155,9 @@ 9260D93222F83CA200F51A82 /* OnPasteEventManager.m */, 92F9F0EB22F702E10035885A /* NSData+MimeType.h */, 92F9F0EC22F702E10035885A /* NSData+MimeType.m */, + 7FAE089C23562E0E00762B6D /* SwimeProxy.swift */, + 7FAE084C23562DE500762B6D /* UIImage+vImageScaling.h */, + 7FAE084D23562DE500762B6D /* UIImage+vImageScaling.m */, ); name = Mattermost; sourceTree = ""; @@ -1255,10 +1263,10 @@ 7FFE32BF1FD9CCAA0038C7A0 /* Sentry.framework */, 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */, 7F43D5DF1F6BF994001FC614 /* libRNSVG.a */, - 65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */, - 4246DC09024BB33A3E491E25 /* libPods-MattermostTests.a */, CF19152887874B7E996210B1 /* libz.tbd */, 84596DD7229C853000981086 /* libReactNativeNavigation.a */, + 5A755BC875640C049D8D40CA /* libPods-Mattermost.a */, + 2774193E64D242854830A632 /* libPods-MattermostTests.a */, ); name = Frameworks; sourceTree = ""; @@ -1672,7 +1680,7 @@ 37DA4BA41E6F55AD002B058E /* Embed Frameworks */, AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */, 7FFE32A91FD9CB650038C7A0 /* Embed App Extensions */, - 27FBE48ACE3E38C56E5D745C /* [CP] Copy Pods Resources */, + 5B498B6F4D17FD3BA2C4C0F4 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -2632,7 +2640,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 27FBE48ACE3E38C56E5D745C /* [CP] Copy Pods Resources */ = { + 5B498B6F4D17FD3BA2C4C0F4 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2700,12 +2708,14 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, 7F151D3E221B062700FAD8F3 /* RuntimeUtils.swift in Sources */, 7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */, + 7FAE089D23562E0E00762B6D /* SwimeProxy.swift in Sources */, 92F9F0ED22F702E10035885A /* NSData+MimeType.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, 7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */, 9260D98222F846D700F51A82 /* UIPasteboard+GetImageInfo.m in Sources */, 7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */, 7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */, + 7FAE084E23562DE500762B6D /* UIImage+vImageScaling.m in Sources */, 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */, 9260D93322F83CA200F51A82 /* OnPasteEventManager.m in Sources */, ); diff --git a/ios/Mattermost/Mattermost+RCTUITextView.m b/ios/Mattermost/Mattermost+RCTUITextView.m index 05fdecddb..d89383a4a 100644 --- a/ios/Mattermost/Mattermost+RCTUITextView.m +++ b/ios/Mattermost/Mattermost+RCTUITextView.m @@ -32,10 +32,9 @@ return NO; } } - - // Allow copy and paste string and image - if (action == @selector(paste:)) { - return [UIPasteboard generalPasteboard].string != nil || [UIPasteboard generalPasteboard].image != nil; + + if (action == @selector(paste:) && [UIPasteboard generalPasteboard].numberOfItems > 0) { + return true; } return [super canPerformAction:action withSender:sender]; @@ -45,12 +44,14 @@ [super paste:sender]; UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; - if (!pasteboard.hasImages) { + if (pasteboard.hasURLs || pasteboard.hasStrings || pasteboard.hasColors) { return; } - NSArray *images = [pasteboard getCopiedImages]; - [OnPasteEventManager pasteImage:images]; + NSArray *files = [pasteboard getCopiedFiles]; + if (files != nil && files.count > 0) { + [OnPasteEventManager pasteFiles:files]; + } // Dismiss contextual menu [self resignFirstResponder]; diff --git a/ios/Mattermost/NSData+MimeType.m b/ios/Mattermost/NSData+MimeType.m index b8e31269e..c1e1867ca 100644 --- a/ios/Mattermost/NSData+MimeType.m +++ b/ios/Mattermost/NSData+MimeType.m @@ -39,17 +39,17 @@ switch (c) { case 0xFF: - return @".jpeg"; + return @"jpg"; break; case 0x89: - return @".png"; + return @"png"; break; case 0x47: - return @".gif"; + return @"gif"; break; case 0x49: case 0x4D: - return @".tiff"; + return @"tiff"; break; default: return @""; diff --git a/ios/Mattermost/OnPasteEventManager.h b/ios/Mattermost/OnPasteEventManager.h index 76deefe19..0733172a7 100644 --- a/ios/Mattermost/OnPasteEventManager.h +++ b/ios/Mattermost/OnPasteEventManager.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OnPasteEventManager : RCTEventEmitter -+(void)pasteImage:(NSArray *)data; ++(void)pasteFiles:(NSArray *)data; @end diff --git a/ios/Mattermost/OnPasteEventManager.m b/ios/Mattermost/OnPasteEventManager.m index 0e359bf79..cd9236d05 100644 --- a/ios/Mattermost/OnPasteEventManager.m +++ b/ios/Mattermost/OnPasteEventManager.m @@ -38,7 +38,7 @@ RCT_EXPORT_MODULE(); [self sendEventWithName:@"onPaste" body:data.userInfo[@"data"]]; } -+(void)pasteImage:(NSArray *)data { ++(void)pasteFiles:(NSArray *)data { [[NSNotificationCenter defaultCenter] postNotificationName:@"onPaste" object:data userInfo:@{ @"data": data }]; diff --git a/ios/Mattermost/SwimeProxy.swift b/ios/Mattermost/SwimeProxy.swift new file mode 100644 index 000000000..8b3418467 --- /dev/null +++ b/ios/Mattermost/SwimeProxy.swift @@ -0,0 +1,61 @@ +// +// SwimeProxy.swift +// Mattermost +// +// Created by Elias Nahum on 15-10-19. +// Copyright © 2019 Facebook. All rights reserved. +// + +import Foundation +import Swime + +@objc @objcMembers public class MimeTypeProxy: NSObject { + public init(mime: String, ext: String) { + self.mime = mime + self.ext = ext + } + + /// Mime type string representation. For example "application/pdf" + public let mime: String + + /// Mime type extension. For example "pdf" + public let ext: String +} + +@objc @objcMembers public class SwimeProxy: NSObject { + public class var shared :SwimeProxy { + struct Singleton { + static let instance = SwimeProxy() + } + return Singleton.instance + } + + public func getMimeFromUti(uti: String) -> MimeTypeProxy? { + switch uti { + case "org.openxmlformats.openxml": + return MimeTypeProxy(mime: "application/xml", ext: ".xml") + case "org.openxmlformats.wordprocessingml.document": + return MimeTypeProxy(mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", ext: ".docx") + case "org.openxmlformats.spreadsheetml.sheet": + return MimeTypeProxy(mime: "pplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ext: ".xlsx") + case "org.openxmlformats.presentationml.presentation": + return MimeTypeProxy(mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation", ext: ".pptx") + default: + return nil + } + } + + @objc public func getMimeAndExtension(data: Data, uti: String) -> MimeTypeProxy? { + let mime = getMimeFromUti(uti: uti); + if (mime != nil) { + return mime + } + + let proxy = Swime.mimeType(data: data) + if (proxy != nil) { + return MimeTypeProxy(mime: proxy!.mime, ext: proxy!.ext) + } + + return nil + } +} diff --git a/ios/Mattermost/UIImage+vImageScaling.h b/ios/Mattermost/UIImage+vImageScaling.h new file mode 100644 index 000000000..3f81f51bb --- /dev/null +++ b/ios/Mattermost/UIImage+vImageScaling.h @@ -0,0 +1,16 @@ +// +// UIImage+vImageScaling.h +// UIImage+vImageScaling +// +// Created by Matt Donnelly on 03/07/2013. +// Copyright (c) 2013 Matt Donnelly. All rights reserved. +// taken from https://gist.github.com/mattdonnelly/5924492 + +#import + +@interface UIImage (vImageScaling) + +- (UIImage *)vImageScaledImageWithSize:(CGSize)destSize; +- (UIImage *)vImageScaledImageWithSize:(CGSize)destSize contentMode:(UIViewContentMode)contentMode; + +@end diff --git a/ios/Mattermost/UIImage+vImageScaling.m b/ios/Mattermost/UIImage+vImageScaling.m new file mode 100644 index 000000000..e452ef603 --- /dev/null +++ b/ios/Mattermost/UIImage+vImageScaling.m @@ -0,0 +1,48 @@ +// +// UIImage+vImageScaling.m +// UIImage+vImageScaling +// +// Created by Matt Donnelly on 03/07/2013. +// Copyright (c) 2013 Matt Donnelly. All rights reserved. +// originally taken from https://gist.github.com/mattdonnelly/5924492 + +#import +#import + +@implementation UIImage (vImageScaling) + +- (UIImage *)vImageScaledImageWithSize:(CGSize)destSize { + UIGraphicsBeginImageContext(CGSizeMake(destSize.width, destSize.height)); + [self drawInRect:CGRectMake(0, 0, destSize.width, destSize.height)]; + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} + +- (UIImage *)vImageScaledImageWithSize:(CGSize)destSize contentMode:(UIViewContentMode)contentMode { + CGImageRef sourceRef = [self CGImage]; + NSUInteger sourceWidth = CGImageGetWidth(sourceRef); + NSUInteger sourceHeight = CGImageGetHeight(sourceRef); + CGFloat horizontalRatio = destSize.width / sourceWidth; + CGFloat verticalRatio = destSize.height / sourceHeight; + CGFloat ratio; + + switch (contentMode) { + case UIViewContentModeScaleAspectFill: + ratio = MAX(horizontalRatio, verticalRatio); + break; + + case UIViewContentModeScaleAspectFit: + ratio = MIN(horizontalRatio, verticalRatio); + break; + + default: + [NSException raise:NSInvalidArgumentException format:@"Unsupported content mode: %d", contentMode]; + } + + CGSize newSize = CGSizeMake(sourceWidth * ratio, sourceHeight * ratio); + + return [self vImageScaledImageWithSize:newSize]; +} + +@end diff --git a/ios/Mattermost/UIPasteboard+GetImageInfo.h b/ios/Mattermost/UIPasteboard+GetImageInfo.h index 74e3f79b9..a849aeb0d 100644 --- a/ios/Mattermost/UIPasteboard+GetImageInfo.h +++ b/ios/Mattermost/UIPasteboard+GetImageInfo.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN @interface UIPasteboard (GetImageInfo) --(NSArray *)getCopiedImages; +-(NSArray *)getCopiedFiles; @end diff --git a/ios/Mattermost/UIPasteboard+GetImageInfo.m b/ios/Mattermost/UIPasteboard+GetImageInfo.m index b13816f53..7c84f1eef 100644 --- a/ios/Mattermost/UIPasteboard+GetImageInfo.m +++ b/ios/Mattermost/UIPasteboard+GetImageInfo.m @@ -8,45 +8,94 @@ #import "UIPasteboard+GetImageInfo.h" #import "NSData+MimeType.h" +#import "Mattermost-Swift.h" +#import "UIImage+vImageScaling.h" @implementation UIPasteboard (GetImageInfo) --(NSArray *)getCopiedImages { - NSArray *images = self.images; - NSMutableArray *imageInfos = [[NSMutableArray alloc] init]; - for (int i = 0; i < images.count; i++) { - UIImage *image = images[i]; - NSString *uri = self.string; - NSArray *types = self.pasteboardTypes; - - NSData *imageData; - if ([types[0] isEqual:@"public.jpeg"]) { - imageData = UIImageJPEGRepresentation(image, 1.0); - } else if ([types[0] isEqual:@"public.png"]) { - imageData = UIImagePNGRepresentation(image); - } else { - imageData = [self dataForPasteboardType:types[0]]; +-(NSArray *)getCopiedFiles { + NSMutableArray *fileInfos = [[NSMutableArray alloc] init]; + NSArray *> *items = self.items; + for (int i = 0; i < items.count; i++) { + NSDictionary *item = items[i]; + BOOL added = NO; + for (int j = 0; j < item.allKeys.count; j++) { + if (added) { + continue; + } + + NSString *type = item.allKeys[j]; + + @try { + NSString *uri = self.string; + NSData *fileData = item[type]; + + if ([type isEqual:@"public.jpeg"] || [type isEqual:@"public.heic"] || [type isEqual:@"public.png"]) { + fileData = [self getDataForImageItem:item[type] type:type]; + } + + SwimeProxy *swimeProxy = [SwimeProxy shared]; + MimeTypeProxy *mimeProxy = [swimeProxy getMimeAndExtensionWithData:fileData uti:type]; + NSString *extension; + NSString *mimeType; + if (mimeProxy != nil) { + extension = mimeProxy.ext; + mimeType = mimeProxy.mime; + } else { + extension = [fileData extension]; + mimeType = [fileData mimeType]; + } + + if ([extension length] == 0) { + continue; + } + + NSString *tempFilename = [NSString stringWithFormat:@"%@.%@", [[NSProcessInfo processInfo] globallyUniqueString], extension]; + NSURL *tempFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tempFilename]]; + BOOL success = [fileData writeToURL:tempFileURL atomically:YES]; + if (success) { + added = YES; + uri = tempFileURL.absoluteString; + [fileInfos addObject:@{ + @"fileName": tempFilename, + @"fileSize": @([fileData length]), + @"type": mimeType, + @"uri": uri, + }]; + } + } @catch (NSException *exception) { + [fileInfos addObject:@{ + @"type": type, + @"error": exception.reason, + }]; + } } - NSString *extension = [imageData extension]; - NSString *mimeType = [imageData mimeType]; - - NSString *tempFilename = [NSString stringWithFormat:@"%@%@", [[NSProcessInfo processInfo] globallyUniqueString], extension]; - NSURL *tempFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tempFilename]]; - BOOL success = [imageData writeToURL:tempFileURL atomically:YES]; - - if (success) { - uri = tempFileURL.absoluteString; - } - - [imageInfos addObject:@{ - @"fileName": tempFilename, - @"fileSize": @([imageData length]), - @"type": mimeType, - @"uri": uri, - }]; } - return imageInfos; + return fileInfos; +} + +-(NSData *) getDataForImageItem:(NSData *)imageData type:(NSString *)type { + UIImage *image; + if ([type isEqual:@"public.heic"]) { + CFDataRef cfdata = CFDataCreate(NULL, [imageData bytes], [imageData length]); + CGImageSourceRef source = CGImageSourceCreateWithData(cfdata, nil); + CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil); + image = [[UIImage alloc] initWithCGImage:imageRef]; + } else { + image = (UIImage *)imageData; + } + size_t width = CGImageGetWidth(image.CGImage); + size_t height = CGImageGetHeight(image.CGImage); + if (width > 6048 || height > 4032) { + image = [image vImageScaledImageWithSize:CGSizeMake(2048, 2048) contentMode:UIViewContentModeScaleAspectFit]; + } + + if ([type isEqual:@"public.png"]) { + return UIImagePNGRepresentation(image); + } + + return UIImageJPEGRepresentation(image, 1.0); } @end diff --git a/ios/Podfile b/ios/Podfile index 3a701463e..425552f63 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -8,6 +8,7 @@ target 'Mattermost' do # Pods for Mattermost pod 'XCDYouTubeKit', '2.7.1' pod 'YoutubePlayer-in-WKWebView', '~> 0.3.1' + pod 'Swime', '3.0.6' target 'MattermostTests' do inherit! :search_paths diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a59d3a50e..357fa8032 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,20 +1,24 @@ PODS: + - Swime (3.0.6) - XCDYouTubeKit (2.7.1) - YoutubePlayer-in-WKWebView (0.3.3) DEPENDENCIES: + - Swime (= 3.0.6) - XCDYouTubeKit (= 2.7.1) - YoutubePlayer-in-WKWebView (~> 0.3.1) SPEC REPOS: https://github.com/cocoapods/specs.git: + - Swime - XCDYouTubeKit - YoutubePlayer-in-WKWebView SPEC CHECKSUMS: + Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b XCDYouTubeKit: c8567fd5cb388a3099fa26eee4b30df2a467847d YoutubePlayer-in-WKWebView: 7694e858c5c3472ed067d6fe34eb9b944845e63c -PODFILE CHECKSUM: e565d3af8eabb0e489b73c79433e140e30f8fa9c +PODFILE CHECKSUM: 74e6d20d391445b945031efe1c5d16c093114192 COCOAPODS: 1.7.5 diff --git a/ios/SwimeProxy.swift b/ios/SwimeProxy.swift new file mode 100644 index 000000000..63f8c6821 --- /dev/null +++ b/ios/SwimeProxy.swift @@ -0,0 +1,9 @@ +// +// SwimeProxy.swift +// Mattermost +// +// Created by Elias Nahum on 15-10-19. +// Copyright © 2019 Facebook. All rights reserved. +// + +import Foundation diff --git a/package-lock.json b/package-lock.json index e1a56c472..687066ca5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8494,7 +8494,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true }, "aproba": { "version": "1.2.0", @@ -8515,12 +8516,14 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -8535,17 +8538,20 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -8662,7 +8668,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true }, "ini": { "version": "1.3.5", @@ -8674,6 +8681,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -8688,6 +8696,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -8695,12 +8704,14 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -8719,6 +8730,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, "requires": { "minimist": "0.0.8" } @@ -8799,7 +8811,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true }, "object-assign": { "version": "4.1.1", @@ -8811,6 +8824,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } @@ -8896,7 +8910,8 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -8932,6 +8947,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8951,6 +8967,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -8994,12 +9011,14 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "optional": true } } }, @@ -17230,7 +17249,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "2.3.2", @@ -17493,7 +17513,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true }, "micromatch": { "version": "3.1.10", @@ -17583,7 +17604,6 @@ "version": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", "from": "github:mattermost/redux-offline#885024de96b6ec73650c340c8928066585c413df", "requires": { - "@react-native-community/netinfo": "^4.1.3", "redux-persist": "^4.5.0" }, "dependencies": { From 4bf6f6e9da72c6943ffd32cf01c177df6a22724a Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Thu, 24 Oct 2019 21:51:14 +0300 Subject: [PATCH 03/17] MM-18647 Request only needed Android permissions for storage and camera (#3463) * MM-18647 Request only needed Android permissions for storage and camera * Remove build folder from patch --- package-lock.json | 21 ++++-- .../react-native-image-picker+0.28.1.patch | 64 ++++++++++++++++++- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 687066ca5..7add6ff0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5627,6 +5627,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -5671,7 +5672,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "4.0.1", @@ -9505,9 +9507,9 @@ } }, "https-proxy-agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", - "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz", + "integrity": "sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q==", "requires": { "agent-base": "^4.3.0", "debug": "^3.1.0" @@ -14164,6 +14166,7 @@ "redux": "4.0.4", "redux-action-buffer": "1.2.0", "redux-batched-actions": "0.4.1", + "redux-offline": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df", "redux-persist": "4.9.1", "redux-thunk": "2.3.0", "reselect": "4.0.0", @@ -14171,6 +14174,11 @@ "shallow-equals": "1.0.0" }, "dependencies": { + "@react-native-community/netinfo": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-4.4.0.tgz", + "integrity": "sha512-qqNWMOsrDjj/daqV21ID2T8mNUjZD4pdx3PuWyE65gzKh2w+oMnzKb+J0NbLyZPn3wwLwU1+Cpf58A0ff5szjQ==" + }, "form-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.0.tgz", @@ -14199,9 +14207,10 @@ } }, "redux-offline": { - "version": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", - "from": "git+https://github.com/enahum/redux-offline.git#4bd85e7e3b279a2b11fb4d587808d583d2b5e7b5", + "version": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df", + "from": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df", "requires": { + "@react-native-community/netinfo": "^4.1.3", "redux-persist": "^4.5.0" } }, diff --git a/patches/react-native-image-picker+0.28.1.patch b/patches/react-native-image-picker+0.28.1.patch index 46b8ac1b5..e881691b6 100644 --- a/patches/react-native-image-picker+0.28.1.patch +++ b/patches/react-native-image-picker+0.28.1.patch @@ -1,3 +1,65 @@ +diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java +index b4311eb..9d3f3d2 100644 +--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java ++++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java +@@ -47,6 +47,7 @@ import java.io.InputStream; + import java.io.OutputStream; + import java.lang.ref.WeakReference; + import java.util.List; ++import java.util.ArrayList; + + import com.facebook.react.modules.core.PermissionListener; + import com.facebook.react.modules.core.PermissionAwareActivity; +@@ -542,14 +543,23 @@ public class ImagePickerModule extends ReactContextBaseJavaModule + @NonNull final Callback callback, + @NonNull final int requestCode) + { +- final int writePermission = ActivityCompat +- .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); +- final int cameraPermission = ActivityCompat ++ int selfCheckResult = 0; ++ switch (requestCode) { ++ case REQUEST_PERMISSIONS_FOR_CAMERA: ++ selfCheckResult = ActivityCompat + .checkSelfPermission(activity, Manifest.permission.CAMERA); ++ if (selfCheckResult == PackageManager.PERMISSION_GRANTED) { ++ selfCheckResult = ActivityCompat ++ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); ++ } ++ break; ++ case REQUEST_PERMISSIONS_FOR_LIBRARY: ++ selfCheckResult = ActivityCompat ++ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); ++ break; ++ } + +- final boolean permissionsGrated = writePermission == PackageManager.PERMISSION_GRANTED && +- cameraPermission == PackageManager.PERMISSION_GRANTED; +- ++ final boolean permissionsGrated = selfCheckResult == PackageManager.PERMISSION_GRANTED; + if (!permissionsGrated) + { + final Boolean dontAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA); +@@ -598,7 +608,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule + } + else + { +- String[] PERMISSIONS = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}; ++ List permissions = new ArrayList(); ++ if (requestCode == REQUEST_PERMISSIONS_FOR_CAMERA ) ++ { ++ permissions.add(Manifest.permission.CAMERA); ++ permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); ++ } ++ if (requestCode == REQUEST_PERMISSIONS_FOR_LIBRARY ) ++ { ++ permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); ++ } ++ ++ String[] PERMISSIONS = permissions.toArray(new String[0]); + if (activity instanceof ReactActivity) + { + ((ReactActivity) activity).requestPermissions(PERMISSIONS, requestCode, listener); diff --git a/node_modules/react-native-image-picker/ios/ImagePickerManager.m b/node_modules/react-native-image-picker/ios/ImagePickerManager.m index 28d5870..3f70983 100644 --- a/node_modules/react-native-image-picker/ios/ImagePickerManager.m @@ -27,4 +89,4 @@ index 28d5870..3f70983 100644 + } } } - + From 4c872283cac6e6462ffe8f56481129d18c772251 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Thu, 24 Oct 2019 14:01:44 -0700 Subject: [PATCH 04/17] [MM-18532] Remove reactions from long post screen (#3447) * Remove reactions from long post screen * Remove reaction post option * Update app/screens/long_post/long_post.test.js Co-Authored-By: Elias Nahum * Update snapshot test --- .../__snapshots__/long_post.test.js.snap | 345 ++++++++++++++++++ app/screens/long_post/index.js | 5 +- app/screens/long_post/long_post.js | 28 +- app/screens/long_post/long_post.test.js | 32 ++ 4 files changed, 380 insertions(+), 30 deletions(-) create mode 100644 app/screens/long_post/__snapshots__/long_post.test.js.snap create mode 100644 app/screens/long_post/long_post.test.js diff --git a/app/screens/long_post/__snapshots__/long_post.test.js.snap b/app/screens/long_post/__snapshots__/long_post.test.js.snap new file mode 100644 index 000000000..5b180e5ad --- /dev/null +++ b/app/screens/long_post/__snapshots__/long_post.test.js.snap @@ -0,0 +1,345 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LongPost should match snapshot 1`] = ` +LongPost { + "context": Object { + "intl": Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": "span", + "timeZone": null, + }, + }, + "goToThread": [Function], + "handleClose": [Function], + "handlePress": [Function], + "navigationEventListener": undefined, + "props": Object { + "actions": Object { + "loadThreadIfNecessary": [MockFunction], + "selectPost": [MockFunction], + }, + "fileIds": Array [], + "intl": Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": "span", + "timeZone": null, + }, + "isLandscape": false, + "postId": "post-id", + "theme": Object { + "awayIndicator": "#ffbc42", + "buttonBg": "#166de0", + "buttonColor": "#ffffff", + "centerChannelBg": "#ffffff", + "centerChannelColor": "#3d3c40", + "codeTheme": "github", + "dndIndicator": "#f74343", + "errorTextColor": "#fd5960", + "linkColor": "#2389d7", + "mentionBj": "#ffffff", + "mentionColor": "#145dbf", + "mentionHighlightBg": "#ffe577", + "mentionHighlightLink": "#166de0", + "newMessageSeparator": "#ff8800", + "onlineIndicator": "#06d6a0", + "sidebarBg": "#145dbf", + "sidebarHeaderBg": "#1153ab", + "sidebarHeaderTextColor": "#ffffff", + "sidebarText": "#ffffff", + "sidebarTextActiveBorder": "#579eff", + "sidebarTextActiveColor": "#ffffff", + "sidebarTextHoverBg": "#4578bf", + "sidebarUnreadText": "#ffffff", + "type": "Mattermost", + }, + }, + "refs": Object {}, + "setState": [Function], + "state": null, + "updater": Updater { + "_callbacks": Array [], + "_renderer": ReactShallowRenderer { + "_context": Object { + "intl": Object { + "defaultFormats": Object {}, + "defaultLocale": "en", + "formatDate": [Function], + "formatHTMLMessage": [Function], + "formatMessage": [Function], + "formatNumber": [Function], + "formatPlural": [Function], + "formatRelative": [Function], + "formatTime": [Function], + "formats": Object {}, + "formatters": Object { + "getDateTimeFormat": [Function], + "getMessageFormat": [Function], + "getNumberFormat": [Function], + "getPluralFormat": [Function], + "getRelativeFormat": [Function], + }, + "locale": "en", + "messages": Object {}, + "now": [Function], + "onError": [Function], + "textComponent": "span", + "timeZone": null, + }, + }, + "_didScheduleRenderPhaseUpdate": false, + "_dispatcher": Object { + "readContext": [Function], + "useCallback": [Function], + "useContext": [Function], + "useDebugValue": [Function], + "useEffect": [Function], + "useImperativeHandle": [Function], + "useLayoutEffect": [Function], + "useMemo": [Function], + "useReducer": [Function], + "useRef": [Function], + "useState": [Function], + }, + "_element": , + "_firstWorkInProgressHook": null, + "_forcedUpdate": false, + "_instance": [Circular], + "_isReRender": false, + "_newState": null, + "_numberOfReRenders": 0, + "_renderPhaseUpdates": null, + "_rendered": + + + + + + + + + + + + + + + + , + "_rendering": false, + "_updater": [Circular], + "_workInProgressHook": null, + }, + }, +} +`; diff --git a/app/screens/long_post/index.js b/app/screens/long_post/index.js index 6113c3e09..a0ab4f3e4 100644 --- a/app/screens/long_post/index.js +++ b/app/screens/long_post/index.js @@ -6,7 +6,7 @@ import {connect} from 'react-redux'; import {selectPost} from 'mattermost-redux/actions/posts'; import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; -import {getPost, makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts'; +import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {loadThreadIfNecessary} from 'app/actions/views/channel'; @@ -15,16 +15,13 @@ import LongPost from './long_post'; function makeMapStateToProps() { const getChannel = makeGetChannel(); - const getReactionsForPost = makeGetReactionsForPost(); return function mapStateToProps(state, ownProps) { const post = getPost(state, ownProps.postId); const channel = post ? getChannel(state, {id: post.channel_id}) : null; - const reactions = getReactionsForPost(state, post.id); return { channelName: channel ? channel.display_name : '', - hasReactions: (reactions && Object.keys(reactions).length > 0) || Boolean(post.has_reactions), inThreadView: Boolean(state.entities.posts.selectedPostId), fileIds: post ? post.file_ids : false, theme: getTheme(state), diff --git a/app/screens/long_post/long_post.js b/app/screens/long_post/long_post.js index ccc427a14..682d7a0e7 100644 --- a/app/screens/long_post/long_post.js +++ b/app/screens/long_post/long_post.js @@ -16,7 +16,6 @@ import {Navigation} from 'react-native-navigation'; import FileAttachmentList from 'app/components/file_attachment_list'; import FormattedText from 'app/components/formatted_text'; import Post from 'app/components/post'; -import Reactions from 'app/components/reactions'; import SafeAreaView from 'app/components/safe_area_view'; import {marginHorizontal as margin} from 'app/components/safe_area_view/iphone_x_spacing'; import {emptyFunction} from 'app/utils/general'; @@ -49,7 +48,6 @@ export default class LongPost extends PureComponent { }).isRequired, channelName: PropTypes.string, fileIds: PropTypes.array, - hasReactions: PropTypes.bool, isPermalink: PropTypes.bool, inThreadView: PropTypes.bool, managedConfig: PropTypes.object, @@ -136,28 +134,10 @@ export default class LongPost extends PureComponent { return attachments; } - renderReactions = (style) => { - const {hasReactions, postId} = this.props; - - if (!hasReactions) { - return null; - } - - return ( - - - - ); - }; - render() { const { channelName, fileIds, - hasReactions, managedConfig, onHashtagPress, onPermalinkPress, @@ -168,11 +148,10 @@ export default class LongPost extends PureComponent { const style = getStyleSheet(theme); let footer; - if (hasReactions || fileIds.length) { + if (fileIds.length) { footer = ( {this.renderFileAttachments(style)} - {this.renderReactions(style)} ); } @@ -222,6 +201,7 @@ export default class LongPost extends PureComponent { onPress={this.handlePress} isSearchResult={false} showLongPost={true} + showAddReaction={false} onHashtagPress={onHashtagPress} onPermalinkPress={onPermalinkPress} managedConfig={managedConfig} @@ -297,9 +277,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { height: 95, width: '100%', }, - reactions: { - height: 47, - width: '100%', - }, }; }); diff --git a/app/screens/long_post/long_post.test.js b/app/screens/long_post/long_post.test.js new file mode 100644 index 000000000..c6786b14a --- /dev/null +++ b/app/screens/long_post/long_post.test.js @@ -0,0 +1,32 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +import {shallowWithIntl} from 'test/intl-test-helper'; + +import LongPost from './long_post'; + +jest.mock('react-native-doc-viewer', () => ({ + openDoc: jest.fn(), +})); + +describe('LongPost', () => { + const baseProps = { + actions: { + loadThreadIfNecessary: jest.fn(), + selectPost: jest.fn(), + }, + postId: 'post-id', + theme: Preferences.THEMES.default, + isLandscape: false, + }; + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + + expect(wrapper.instance()).toMatchSnapshot(); + }); +}); From 968fec37bf68404f4f98c7b089af5c8f4f96c021 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 25 Oct 2019 13:04:56 +0300 Subject: [PATCH 05/17] Bump app build number to 241 (#3467) --- android/app/build.gradle | 2 +- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 649f12e4e..483f1c345 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -123,7 +123,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5" - versionCode 240 + versionCode 241 versionName "1.25.0" multiDexEnabled = true ndk { diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index b5bd7e490..4d217e68b 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2862,7 +2862,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2923,7 +2923,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 241; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 790fa1d0c..66887f273 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 240 + 241 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index c54861c9d..e255e6235 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.25.0 CFBundleVersion - 240 + 241 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 7f26f1133..f8024cc6d 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 240 + 241 diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index be4746606..d4fbeb2f1 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.25.0 CFBundleVersion - 240 + 241 NSExtension NSExtensionPointIdentifier From 1bde8ed4dc9ee06c08a66fc84902a2ee5447345f Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Fri, 25 Oct 2019 04:50:13 -0700 Subject: [PATCH 06/17] [MM-18983] Store insets in ephemeral store (#3457) * Store insets in ephemeral store * Address PR review --- .../safe_area_view/safe_area_view.ios.js | 66 +++++-- .../safe_area_view/safe_area_view.ios.test.js | 179 +++++++++++++++++- app/constants/view.js | 3 + app/store/ephemeral_store.js | 6 + 4 files changed, 232 insertions(+), 22 deletions(-) diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js index b517e86ba..541baf0b5 100644 --- a/app/components/safe_area_view/safe_area_view.ios.js +++ b/app/components/safe_area_view/safe_area_view.ios.js @@ -3,15 +3,17 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {Keyboard, NativeModules, View} from 'react-native'; +import {Dimensions, Keyboard, NativeModules, View} from 'react-native'; import SafeArea from 'react-native-safe-area'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {DeviceTypes} from 'app/constants'; +import {DeviceTypes, ViewTypes} from 'app/constants'; import mattermostManaged from 'app/mattermost_managed'; +import EphemeralStore from 'app/store/ephemeral_store'; const {StatusBarManager} = NativeModules; +const {PORTRAIT, LANDSCAPE} = ViewTypes; export default class SafeAreaIos extends PureComponent { static propTypes = { @@ -57,8 +59,12 @@ export default class SafeAreaIos extends PureComponent { componentDidMount() { this.mounted = true; - SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange); + Dimensions.addEventListener('change', this.getSafeAreaInsets); EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets); + if (EphemeralStore.safeAreaInsets[PORTRAIT] === null || EphemeralStore.safeAreaInsets[LANDSCAPE] === null) { + SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange); + } + this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide); @@ -66,14 +72,40 @@ export default class SafeAreaIos extends PureComponent { } componentWillUnmount() { - SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange); + Dimensions.removeEventListener('change', this.getSafeAreaInsets); EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets); + SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange); this.keyboardDidShowListener.remove(); this.keyboardDidHideListener.remove(); this.mounted = false; } + getSafeAreaInsets = async (dimensions) => { + this.getStatusBarHeight(); + + const safeAreaInsetsStored = EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null; + if ((DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) && !safeAreaInsetsStored) { + const window = dimensions?.window || Dimensions.get('window'); + const landscape = window.width > window.length; + const {safeAreaInsets} = await SafeArea.getSafeAreaInsetsForRootView(); + this.setSafeAreaInsets(safeAreaInsets, landscape); + } + } + + setSafeAreaInsets = (safeAreaInsets, landscape) => { + const orientation = landscape ? LANDSCAPE : PORTRAIT; + if (EphemeralStore.safeAreaInsets[orientation] === null) { + EphemeralStore.safeAreaInsets[orientation] = safeAreaInsets; + } + + if (this.mounted) { + this.setState({ + safeAreaInsets: EphemeralStore.safeAreaInsets[orientation], + }); + } + } + getStatusBarHeight = () => { try { StatusBarManager.getHeight( @@ -88,26 +120,18 @@ export default class SafeAreaIos extends PureComponent { } }; - getSafeAreaInsets = () => { - this.getStatusBarHeight(); + onSafeAreaInsetsForRootViewChange = ({safeAreaInsets}) => { + if (EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null) { + SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange); + return; + } if (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) { - SafeArea.getSafeAreaInsetsForRootView().then((result) => { - const {safeAreaInsets} = result; - - if (this.mounted) { - this.setState({safeAreaInsets}); - } - }); - } - }; - - onSafeAreaInsetsForRootViewChange = (result) => { - const {safeAreaInsets} = result; - - if (this.mounted && (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets)) { this.getStatusBarHeight(); - this.setState({safeAreaInsets}); + + const {width, height} = Dimensions.get('window'); + const landscape = width > height; + this.setSafeAreaInsets(safeAreaInsets, landscape); } } diff --git a/app/components/safe_area_view/safe_area_view.ios.test.js b/app/components/safe_area_view/safe_area_view.ios.test.js index 85c64b572..45861f552 100644 --- a/app/components/safe_area_view/safe_area_view.ios.test.js +++ b/app/components/safe_area_view/safe_area_view.ios.test.js @@ -6,11 +6,14 @@ import {shallow} from 'enzyme'; import Preferences from 'mattermost-redux/constants/preferences'; -import {DeviceTypes} from 'app/constants'; +import {DeviceTypes, ViewTypes} from 'app/constants'; import mattermostManaged from 'app/mattermost_managed'; +import EphemeralStore from 'app/store/ephemeral_store'; import SafeAreaIos from './safe_area_view.ios'; +const {PORTRAIT, LANDSCAPE} = ViewTypes; + describe('SafeAreaIos', () => { const baseProps = { children: [], @@ -37,10 +40,44 @@ describe('SafeAreaIos', () => { }, }; + const PORTRAIT_INSETS = { + safeAreaInsets: { + top: 111, + left: 111, + bottom: 111, + right: 111, + }, + }; + + const LANDSCAPE_INSETS = { + safeAreaInsets: { + top: 222, + left: 222, + bottom: 222, + right: 222, + }, + }; + + const IGNORED_INSETS = { + safeAreaInsets: { + top: 333, + left: 333, + bottom: 333, + right: 333, + }, + }; + SafeArea.getSafeAreaInsetsForRootView = jest.fn().mockImplementation(() => { return Promise.resolve(TEST_INSETS_1); }); + beforeEach(() => { + EphemeralStore.safeAreaInsets = { + [PORTRAIT]: null, + [LANDSCAPE]: null, + }; + }); + test('should match snapshot', () => { const wrapper = shallow( @@ -148,4 +185,144 @@ describe('SafeAreaIos', () => { instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2); expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets); }); + + test('should set portrait safe area insets', () => { + const wrapper = shallow( + + ); + + expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + + const landscape = false; + const instance = wrapper.instance(); + instance.setSafeAreaInsets(PORTRAIT_INSETS.safeAreaInsets, landscape); + expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + }); + + test('should set portrait safe area insets from EphemeralStore', () => { + const wrapper = shallow( + + ); + + EphemeralStore.safeAreaInsets[PORTRAIT] = PORTRAIT_INSETS.safeAreaInsets; + expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets); + + const landscape = false; + const instance = wrapper.instance(); + instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, landscape); + expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + }); + + test('should set landscape safe area insets', () => { + const wrapper = shallow( + + ); + + expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + + const landscape = true; + const instance = wrapper.instance(); + instance.setSafeAreaInsets(LANDSCAPE_INSETS.safeAreaInsets, landscape); + expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + }); + + test('should set landscape safe area insets from EphemeralStore', () => { + const wrapper = shallow( + + ); + + EphemeralStore.safeAreaInsets[LANDSCAPE] = LANDSCAPE_INSETS.safeAreaInsets; + expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets); + + const landscape = true; + const instance = wrapper.instance(); + instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, landscape); + expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + }); + + test('should add safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are not set', () => { + const addEventListener = jest.spyOn(SafeArea, 'addEventListener'); + + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + let wrapper = shallow( + + ); + let instance = wrapper.instance(); + expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange); + addEventListener.mockClear(); + + EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; + wrapper = shallow( + + ); + instance = wrapper.instance(); + expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange); + addEventListener.mockClear(); + + EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; + EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets; + wrapper = shallow( + + ); + instance = wrapper.instance(); + expect(addEventListener).not.toHaveBeenCalled(); + }); + + test('should remove safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are set', () => { + const removeEventListener = jest.spyOn(SafeArea, 'removeEventListener'); + + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + + instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1); + expect(removeEventListener).not.toHaveBeenCalled(); + + EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; + instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1); + expect(removeEventListener).not.toHaveBeenCalled(); + + EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets; + instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1); + expect(removeEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange); + }); + + test('getSafeAreaInsets should set safe area insets when not already in ephemeral store', async () => { + const wrapper = shallow( + + ); + const instance = wrapper.instance(); + const setSafeAreaInsets = jest.spyOn(instance, 'setSafeAreaInsets'); + + expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null); + expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null); + await instance.getSafeAreaInsets(); + expect(setSafeAreaInsets).toHaveBeenCalled(); + setSafeAreaInsets.mockClear(); + + EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; + await instance.getSafeAreaInsets(); + expect(setSafeAreaInsets).toHaveBeenCalled(); + setSafeAreaInsets.mockClear(); + + EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets; + await instance.getSafeAreaInsets(); + expect(setSafeAreaInsets).not.toHaveBeenCalled(); + }); }); diff --git a/app/constants/view.js b/app/constants/view.js index 33120e90d..e148c0bb8 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -95,6 +95,9 @@ const ViewTypes = keyMirror({ SELECTED_ACTION_MENU: null, SUBMIT_ATTACHMENT_MENU_ACTION: null, SELECT_CHANNEL_WITH_MEMBER: null, + + PORTRAIT: null, + LANDSCAPE: null, }); export default { diff --git a/app/store/ephemeral_store.js b/app/store/ephemeral_store.js index 574fb029f..3639ecdda 100644 --- a/app/store/ephemeral_store.js +++ b/app/store/ephemeral_store.js @@ -1,6 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {ViewTypes} from 'app/constants'; + class EphemeralStore { constructor() { this.appStarted = false; @@ -9,6 +11,10 @@ class EphemeralStore { this.navigationComponentIdStack = []; this.allNavigationComponentIds = []; this.currentServerUrl = null; + this.safeAreaInsets = { + [ViewTypes.PORTRAIT]: null, + [ViewTypes.LANDSCAPE]: null, + }; } getNavigationTopComponentId = () => this.navigationComponentIdStack[0]; From 64d22fd0f2a620132a3322c3ecbe77acbd3a3a75 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 25 Oct 2019 18:01:32 +0300 Subject: [PATCH 07/17] MM-18313 Fix update timezone automatically (#3462) * MM-18313 Fix update timezone automatically * add unit tests --- app/actions/views/login.js | 5 +++-- app/init/global_event_handler.js | 17 +++++++++++++++++ app/init/global_event_handler.test.js | 10 ++++++++++ app/screens/settings/timezone/timezone.js | 6 +++--- app/utils/timezone.js | 4 ++-- package-lock.json | 6 +++--- package.json | 2 +- 7 files changed, 39 insertions(+), 11 deletions(-) diff --git a/app/actions/views/login.js b/app/actions/views/login.js index 1f60a15a3..c2416b143 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -15,7 +15,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {ViewTypes} from 'app/constants'; import {setAppCredentials} from 'app/init/credentials'; import PushNotifications from 'app/push_notifications'; -import {getDeviceTimezone} from 'app/utils/timezone'; +import {getDeviceTimezoneAsync} from 'app/utils/timezone'; import {setCSRFFromCookie} from 'app/utils/security'; export function handleLoginIdChanged(loginId) { @@ -51,7 +51,8 @@ export function handleSuccessfulLogin() { const enableTimezone = isTimezoneEnabled(state); if (enableTimezone) { - dispatch(autoUpdateTimezone(getDeviceTimezone())); + const timezone = await getDeviceTimezoneAsync(); + dispatch(autoUpdateTimezone(timezone)); } dispatch({ diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js index dc3c099d4..c04ec9c16 100644 --- a/app/init/global_event_handler.js +++ b/app/init/global_event_handler.js @@ -7,11 +7,14 @@ import semver from 'semver'; import {setAppState, setServerVersion} from 'mattermost-redux/actions/general'; import {loadMe, logout} from 'mattermost-redux/actions/users'; +import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone'; import {close as closeWebSocket} from 'mattermost-redux/actions/websocket'; import {Client4} from 'mattermost-redux/client'; import {General} from 'mattermost-redux/constants'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'; +import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; +import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device'; import {selectDefaultChannel} from 'app/actions/views/channel'; @@ -24,6 +27,7 @@ import PushNotifications from 'app/push_notifications'; import {getCurrentLocale} from 'app/selectors/i18n'; import {t} from 'app/utils/i18n'; import {deleteFileCache} from 'app/utils/file'; +import {getDeviceTimezoneAsync} from 'app/utils/timezone'; import LocalConfig from 'assets/config'; @@ -47,6 +51,7 @@ class GlobalEventHandler { appActive = async () => { this.turnOnInAppNotificationHandling(); + this.setUserTimezone(); // if the app is being controlled by an EMM provider if (emmProvider.enabled && emmProvider.inAppPinCode) { @@ -261,6 +266,18 @@ class GlobalEventHandler { showOverlay(screen, passProps); } }; + + setUserTimezone = async () => { + const {dispatch, getState} = this.store; + const state = getState(); + const currentUserId = getCurrentUserId(state); + + const enableTimezone = isTimezoneEnabled(state); + if (enableTimezone && currentUserId) { + const timezone = await getDeviceTimezoneAsync(); + dispatch(autoUpdateTimezone(timezone)); + } + }; } export default new GlobalEventHandler(); diff --git a/app/init/global_event_handler.test.js b/app/init/global_event_handler.test.js index 19fcee89a..8f4c4f9f7 100644 --- a/app/init/global_event_handler.test.js +++ b/app/init/global_event_handler.test.js @@ -86,4 +86,14 @@ describe('GlobalEventHandler', () => { expect(appActive).not.toHaveBeenCalled(); expect(appInactive).not.toHaveBeenCalled(); }); + + it('should set the user TimeZone when the app becomes active', () => { + const onAppStateChange = jest.spyOn(GlobalEventHandler, 'onAppStateChange'); + const setUserTimezone = jest.spyOn(GlobalEventHandler, 'setUserTimezone'); + + GlobalEventHandler.configure({store}); + expect(GlobalEventHandler.store).not.toBeNull(); + expect(onAppStateChange).toHaveBeenCalledWith('active'); + expect(setUserTimezone).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/screens/settings/timezone/timezone.js b/app/screens/settings/timezone/timezone.js index 8e6e76a68..78fae9a3d 100644 --- a/app/screens/settings/timezone/timezone.js +++ b/app/screens/settings/timezone/timezone.js @@ -17,7 +17,7 @@ import StatusBar from 'app/components/status_bar'; import Section from 'app/screens/settings/section'; import SectionItem from 'app/screens/settings/section_item'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import {getDeviceTimezone} from 'app/utils/timezone'; +import {getDeviceTimezoneAsync} from 'app/utils/timezone'; import {goToScreen} from 'app/actions/navigation'; export default class Timezone extends PureComponent { @@ -61,13 +61,13 @@ export default class Timezone extends PureComponent { } } - updateAutomaticTimezone = (useAutomaticTimezone) => { + updateAutomaticTimezone = async (useAutomaticTimezone) => { const {userTimezone: {manualTimezone}} = this.props; let automaticTimezone = ''; this.setState({useAutomaticTimezone}); if (useAutomaticTimezone) { - automaticTimezone = getDeviceTimezone(); + automaticTimezone = await getDeviceTimezoneAsync(); this.submitUser({ useAutomaticTimezone, automaticTimezone, diff --git a/app/utils/timezone.js b/app/utils/timezone.js index 78e1875c5..2317f1f6b 100644 --- a/app/utils/timezone.js +++ b/app/utils/timezone.js @@ -5,8 +5,8 @@ import DeviceInfo from 'react-native-device-info'; import moment from 'moment-timezone'; -export function getDeviceTimezone() { - return DeviceInfo.getTimezone(); +export function getDeviceTimezoneAsync() { + return DeviceInfo.getTimezoneAsync(); } export function getDeviceUtcOffset() { diff --git a/package-lock.json b/package-lock.json index 7add6ff0f..67082adf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7718,7 +7718,7 @@ "dependencies": { "json5": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true } @@ -16656,8 +16656,8 @@ } }, "react-native-device-info": { - "version": "github:mattermost/react-native-device-info#58871122b6e1eb82c23674252430ad7e0a5afef3", - "from": "github:mattermost/react-native-device-info#58871122b6e1eb82c23674252430ad7e0a5afef3" + "version": "github:mattermost/react-native-device-info#f7175f10822d8f66b9806206e3313eaf2f4aabc6", + "from": "github:mattermost/react-native-device-info#f7175f10822d8f66b9806206e3313eaf2f4aabc6" }, "react-native-doc-viewer": { "version": "2.7.8", diff --git a/package.json b/package.json index ea5216c37..786bc4a08 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "react-native-calendars": "github:mattermost/react-native-calendars#4937ec5a3bf7e86f9f35fcd85eb4aa6133f45b58", "react-native-circular-progress": "1.1.0", "react-native-cookies": "github:joeferraro/react-native-cookies#f11374745deba9f18f7b8a9bb4b0b2573026f522", - "react-native-device-info": "github:mattermost/react-native-device-info#58871122b6e1eb82c23674252430ad7e0a5afef3", + "react-native-device-info": "github:mattermost/react-native-device-info#f7175f10822d8f66b9806206e3313eaf2f4aabc6", "react-native-doc-viewer": "2.7.8", "react-native-document-picker": "2.3.0", "react-native-exception-handler": "2.10.7", From 1557b08d59724dad0a62843ae205d5dfed8d31e0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Sat, 26 Oct 2019 01:27:24 +0300 Subject: [PATCH 08/17] Fix app deploy path (#3474) --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bd327dba..3b4d4fde5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -170,7 +170,7 @@ commands: - deploy: name: "Deploy apk to Google Play" working_directory: fastlane - command: bundle exec fastlane android deploy apk:../<> + command: bundle exec fastlane android deploy apk:./<> deploy-ios: description: "Deploy ipa to TestFlight" @@ -183,7 +183,7 @@ commands: - deploy: name: "Deploy ipa to TestFlight" working_directory: fastlane - command: bundle exec fastlane ios deploy ipa:../<> + command: bundle exec fastlane ios deploy ipa:./<> persist: description: "Persist mattermost-mobile directory" From 0f3728a185f050b3eb221c968c6ddbc69baf7d70 Mon Sep 17 00:00:00 2001 From: Amit Uttam Date: Sat, 26 Oct 2019 04:25:53 -0300 Subject: [PATCH 09/17] MM-13413 Make user names in system messages tappable (#3376) Opens profile pop-over for: * User changed channel purpose * User changed channel name * User changed channel header * User archived channel --- .../system_message_helpers.test.js.snap | 37 ++++ app/components/post_body/post_body.js | 8 + app/components/post_body/post_body.test.js | 8 + .../post_body/system_message_helpers.js | 160 ++++++++++++++++++ .../post_body/system_message_helpers.test.js | 90 ++++++++++ assets/base/i18n/en.json | 8 + 6 files changed, 311 insertions(+) create mode 100644 app/components/post_body/__snapshots__/system_message_helpers.test.js.snap create mode 100644 app/components/post_body/system_message_helpers.js create mode 100644 app/components/post_body/system_message_helpers.test.js diff --git a/app/components/post_body/__snapshots__/system_message_helpers.test.js.snap b/app/components/post_body/__snapshots__/system_message_helpers.test.js.snap new file mode 100644 index 000000000..e864ae21a --- /dev/null +++ b/app/components/post_body/__snapshots__/system_message_helpers.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = ` + +`; + +exports[`renderSystemMessage uses renderer for Channel Header update 1`] = ` + +`; + +exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = ` + +`; + +exports[`renderSystemMessage uses renderer for archived channel 1`] = ` + +`; diff --git a/app/components/post_body/post_body.js b/app/components/post_body/post_body.js index b891ee324..b49a69f92 100644 --- a/app/components/post_body/post_body.js +++ b/app/components/post_body/post_body.js @@ -13,6 +13,7 @@ import Icon from 'react-native-vector-icons/Ionicons'; import {Posts} from 'mattermost-redux/constants'; import CombinedSystemMessage from 'app/components/combined_system_message'; +import {renderSystemMessage} from './system_message_helpers'; import FormattedText from 'app/components/formatted_text'; import Markdown from 'app/components/markdown'; import MarkdownEmoji from 'app/components/markdown/markdown_emoji'; @@ -348,6 +349,13 @@ export default class PostBody extends PureComponent { const messageStyle = isSystemMessage ? [style.message, style.systemMessage] : style.message; const isPendingOrFailedPost = isPending || isFailed; + const messageStyles = {messageStyle, textStyles}; + const intl = this.context.intl; + const systemMessage = renderSystemMessage(this.props, messageStyles, intl); + if (systemMessage) { + return systemMessage; + } + let body; let messageComponent; if (hasBeenDeleted) { diff --git a/app/components/post_body/post_body.test.js b/app/components/post_body/post_body.test.js index 3c299f9ec..c8cb6d92a 100644 --- a/app/components/post_body/post_body.test.js +++ b/app/components/post_body/post_body.test.js @@ -9,6 +9,9 @@ import PostBodyAdditionalContent from 'app/components/post_body_additional_conte import {shallowWithIntl} from 'test/intl-test-helper'; import PostBody from './post_body.js'; +import * as SystemMessageHelpers from './system_message_helpers'; + +jest.mock('./system_message_helpers'); describe('PostBody', () => { const baseProps = { @@ -122,4 +125,9 @@ describe('PostBody', () => { instance.measurePost(event); expect(wrapper.state('isLongPost')).toEqual(false); }); + + test('should return system message as post body', () => { + shallowWithIntl(); + expect(SystemMessageHelpers.renderSystemMessage.mock.calls.length).toBe(1); + }); }); diff --git a/app/components/post_body/system_message_helpers.js b/app/components/post_body/system_message_helpers.js new file mode 100644 index 000000000..2a9c2f569 --- /dev/null +++ b/app/components/post_body/system_message_helpers.js @@ -0,0 +1,160 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Posts} from 'mattermost-redux/constants'; +import Markdown from 'app/components/markdown'; +import {t} from 'app/utils/i18n'; + +const renderUsername = (value) => { + return (value[0] === '@') ? value : `@${value}`; +}; + +const renderMessage = (postBodyProps, styles, intl, localeHolder, values) => { + const {onPress} = postBodyProps; + const {messageStyle, textStyles} = styles; + + return ( + + ); +}; + +const renderHeaderChangeMessage = (postBodyProps, styles, intl) => { + const {postProps} = postBodyProps; + let values; + + if (!postProps.username) { + return null; + } + + const username = renderUsername(postProps.username); + const oldHeader = postProps.old_header; + const newHeader = postProps.new_header; + let localeHolder; + + if (postProps.new_header) { + if (postProps.old_header) { + localeHolder = { + id: t('mobile.system_message.update_channel_header_message_and_forget.updated_from'), + defaultMessage: '{username} updated the channel header from: {oldHeader} to: {newHeader}', + }; + + values = {username, oldHeader, newHeader}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); + } + + localeHolder = { + id: t('mobile.system_message.update_channel_header_message_and_forget.updated_to'), + defaultMessage: '{username} updated the channel header to: {newHeader}', + }; + + values = {username, oldHeader, newHeader}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); + } else if (postProps.old_header) { + localeHolder = { + id: t('mobile.system_message.update_channel_header_message_and_forget.removed'), + defaultMessage: '{username} removed the channel header (was: {oldHeader})', + }; + + values = {username, oldHeader, newHeader}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); + } + + return null; +}; + +const renderPurposeChangeMessage = (postBodyProps, styles, intl) => { + const {postProps} = postBodyProps; + let values; + + if (!postProps.username) { + return null; + } + + const username = renderUsername(postProps.username); + const oldPurpose = postProps.old_purpose; + const newPurpose = postProps.new_purpose; + let localeHolder; + + if (postProps.new_purpose) { + if (postProps.old_purpose) { + localeHolder = { + id: t('mobile.system_message.update_channel_purpose_message.updated_from'), + defaultMessage: '{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}', + }; + + values = {username, oldPurpose, newPurpose}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); + } + + localeHolder = { + id: t('mobile.system_message.update_channel_purpose_message.updated_to'), + defaultMessage: '{username} updated the channel purpose to: {newPurpose}', + }; + + values = {username, oldPurpose, newPurpose}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); + } else if (postProps.old_purpose) { + localeHolder = { + id: t('mobile.system_message.update_channel_purpose_message.removed'), + defaultMessage: '{username} removed the channel purpose (was: {oldPurpose})', + }; + + values = {username, oldPurpose, newPurpose}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); + } + + return null; +}; + +const renderDisplayNameChangeMessage = (postBodyProps, styles, intl) => { + const {postProps} = postBodyProps; + const oldDisplayName = postProps.old_displayname; + const newDisplayName = postProps.new_displayname; + + if (!(postProps.username && postProps.old_displayname && postProps.new_displayname)) { + return null; + } + + const username = renderUsername(postProps.username); + const localeHolder = { + id: t('mobile.system_message.update_channel_displayname_message_and_forget.updated_from'), + defaultMessage: '{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}', + }; + + const values = {username, oldDisplayName, newDisplayName}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); +}; + +const renderArchivedMessage = (postBodyProps, styles, intl) => { + const {postProps} = postBodyProps; + + const username = renderUsername(postProps.username); + const localeHolder = { + id: t('mobile.system_message.channel_archived_message'), + defaultMessage: '{username} archived the channel', + }; + + const values = {username}; + return renderMessage(postBodyProps, styles, intl, localeHolder, values); +}; + +const systemMessageRenderers = { + [Posts.POST_TYPES.HEADER_CHANGE]: renderHeaderChangeMessage, + [Posts.POST_TYPES.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage, + [Posts.POST_TYPES.PURPOSE_CHANGE]: renderPurposeChangeMessage, + [Posts.POST_TYPES.CHANNEL_DELETED]: renderArchivedMessage, +}; + +export const renderSystemMessage = (postBodyProps, styles, intl) => { + const renderer = systemMessageRenderers[postBodyProps.postType]; + if (!renderer) { + return null; + } + return renderer(postBodyProps, styles, intl); +}; diff --git a/app/components/post_body/system_message_helpers.test.js b/app/components/post_body/system_message_helpers.test.js new file mode 100644 index 000000000..e533edda0 --- /dev/null +++ b/app/components/post_body/system_message_helpers.test.js @@ -0,0 +1,90 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as SystemMessageHelpers from './system_message_helpers'; +import {Posts} from 'mattermost-redux/constants'; + +const basePostBodyProps = { + postProps: { + username: 'username', + }, + onPress: jest.fn(), +}; + +const mockStyles = { + messageStyle: {}, + textStyles: {}, +}; + +const mockIntl = { + formatMessage: ({defaultMessage}) => defaultMessage, +}; + +describe('renderSystemMessage', () => { + test('uses renderer for Channel Header update', () => { + const postBodyProps = { + ...basePostBodyProps, + postProps: { + ...basePostBodyProps.postProps, + old_header: 'old header', + new_header: 'new header', + }, + postType: Posts.POST_TYPES.HEADER_CHANGE, + }; + const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl); + expect(renderedMessage).toMatchSnapshot(); + }); + + test('uses renderer for Channel Display Name update', () => { + const postBodyProps = { + ...basePostBodyProps, + postProps: { + ...basePostBodyProps.postProps, + old_displayname: 'old displayname', + new_displayname: 'new displayname', + }, + postType: Posts.POST_TYPES.DISPLAYNAME_CHANGE, + }; + + const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl); + expect(renderedMessage).toMatchSnapshot(); + }); + + test('uses renderer for Channel Purpose update', () => { + const postBodyProps = { + ...basePostBodyProps, + postProps: { + ...basePostBodyProps.postProps, + old_purpose: 'old purpose', + new_purpose: 'new purpose', + }, + postType: Posts.POST_TYPES.PURPOSE_CHANGE, + }; + + const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl); + expect(renderedMessage).toMatchSnapshot(); + }); + + test('uses renderer for archived channel', () => { + const postBodyProps = { + ...basePostBodyProps, + postProps: { + ...basePostBodyProps.postProps, + }, + postType: Posts.POST_TYPES.CHANNEL_DELETED, + }; + + const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl); + expect(renderedMessage).toMatchSnapshot(); + }); + + test('is null for non-qualifying system messages', () => { + const postBodyProps = { + ...basePostBodyProps, + postType: 'not_relevant', + }; + + const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl); + expect(renderedMessage).toBeNull(); + }); +}); diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index bf85fdfef..061d6bc87 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -463,6 +463,14 @@ "mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.", "mobile.storage_permission_denied_title": "{applicationName} would like to access your files", "mobile.suggestion.members": "Members", + "mobile.system_message.channel_archived_message": "{username} archived the channel", + "mobile.system_message.update_channel_displayname_message_and_forget.updated_from": "{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}", + "mobile.system_message.update_channel_header_message_and_forget.removed": "{username} removed the channel header (was: {oldHeader})", + "mobile.system_message.update_channel_header_message_and_forget.updated_from": "{username} updated the channel header from: {oldHeader} to: {newHeader}", + "mobile.system_message.update_channel_header_message_and_forget.updated_to": "{username} updated the channel header to: {newHeader}", + "mobile.system_message.update_channel_purpose_message.removed": "{username} removed the channel purpose (was: {oldPurpose})", + "mobile.system_message.update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}", + "mobile.system_message.update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {newPurpose}", "mobile.terms_of_service.alert_cancel": "Cancel", "mobile.terms_of_service.alert_ok": "OK", "mobile.terms_of_service.alert_retry": "Try Again", From 2ebe516296fe22412c184741198d846d56981aff Mon Sep 17 00:00:00 2001 From: Woolim Cho Date: Sat, 26 Oct 2019 17:27:47 +0900 Subject: [PATCH 10/17] Add pinnedpost count to channel info screen (#3290) --- app/screens/channel_info/channel_info.js | 3 +++ app/screens/channel_info/index.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js index 5519a65a8..208d10c3f 100644 --- a/app/screens/channel_info/channel_info.js +++ b/app/screens/channel_info/channel_info.js @@ -52,6 +52,7 @@ export default class ChannelInfo extends PureComponent { currentChannelCreatorName: PropTypes.string, currentChannelMemberCount: PropTypes.number, currentChannelGuestCount: PropTypes.number, + currentChannelPinnedPostCount: PropTypes.number, currentUserId: PropTypes.string, currentUserIsGuest: PropTypes.bool, status: PropTypes.string, @@ -414,6 +415,7 @@ export default class ChannelInfo extends PureComponent { actionsRows = (style, channelIsArchived) => { const { currentChannelMemberCount, + currentChannelPinnedPostCount, canManageUsers, canEditChannel, theme, @@ -475,6 +477,7 @@ export default class ChannelInfo extends PureComponent { Date: Sat, 26 Oct 2019 07:53:17 -0300 Subject: [PATCH 11/17] MM-13842 Add Autocomplete to post edit screen (#3295) Fixes https://github.com/mattermost/mattermost-server/issues/10159 --- app/screens/edit_post/edit_post.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/screens/edit_post/edit_post.js b/app/screens/edit_post/edit_post.js index 686fb8885..4cd3791b6 100644 --- a/app/screens/edit_post/edit_post.js +++ b/app/screens/edit_post/edit_post.js @@ -8,9 +8,11 @@ import { View, } from 'react-native'; import {Navigation} from 'react-native-navigation'; +import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view'; import {RequestStatus} from 'mattermost-redux/constants'; +import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete'; import ErrorText from 'app/components/error_text'; import Loading from 'app/components/loading'; import StatusBar from 'app/components/status_bar'; @@ -56,7 +58,11 @@ export default class EditPost extends PureComponent { constructor(props, context) { super(props); - this.state = {message: props.post.message}; + this.state = { + message: props.post.message, + cursorPosition: 0, + }; + this.rightButton.color = props.theme.sidebarHeaderTextColor; this.rightButton.text = context.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'}); @@ -152,6 +158,11 @@ export default class EditPost extends PureComponent { } }; + onPostSelectionChange = (event) => { + const cursorPosition = event.nativeEvent.selection.end; + this.setState({cursorPosition}); + }; + render() { const {deviceHeight, deviceWidth, theme, isLandscape} = this.props; const {editing, message, error} = this.state; @@ -200,9 +211,19 @@ export default class EditPost extends PureComponent { underlineColorAndroid='transparent' disableFullscreenUI={true} keyboardAppearance={getKeyboardAppearanceFromTheme(this.props.theme)} + onSelectionChange={this.onPostSelectionChange} /> + + + ); } @@ -238,5 +259,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { padding: 15, textAlignVertical: 'top', }, + autocompleteContainer: { + flex: 1, + justifyContent: 'flex-end', + }, }; }); From ec0be38217e49f1f2688fb361233c5608eafa60c Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Sun, 27 Oct 2019 00:39:15 -0700 Subject: [PATCH 12/17] [MM-17746] Map currentUser from state to props in NotificationSettingsEmail (#3357) * Map currentUser from state to props * Fix ref warning * Use optional chaining operator * Use componentDidUpdate * Don't forget to call super --- app/components/radio_button/radio_button_group.js | 1 - .../notification_settings.js | 7 ++----- .../settings/notification_settings_email/index.js | 9 +++++++++ .../notification_settings_email.android.test.js | 3 +++ .../notification_settings_email.ios.test.js | 3 +++ .../notification_settings_email_base.js | 15 ++++++--------- .../notification_settings_mobile.android.js | 8 ++++---- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/app/components/radio_button/radio_button_group.js b/app/components/radio_button/radio_button_group.js index 85b003cde..529793a6c 100644 --- a/app/components/radio_button/radio_button_group.js +++ b/app/components/radio_button/radio_button_group.js @@ -70,7 +70,6 @@ export default class RadioButtonGroup extends PureComponent { return ( { - const {currentUser, intl} = this.props; + const {intl} = this.props; const screen = 'NotificationSettingsEmail'; const title = intl.formatMessage({ id: 'mobile.notification_settings.email_title', defaultMessage: 'Email Notifications', }); - const passProps = { - currentUser, - }; - goToScreen(screen, title, passProps); + goToScreen(screen, title); }; goToNotificationSettingsMentions = () => { diff --git a/app/screens/settings/notification_settings_email/index.js b/app/screens/settings/notification_settings_email/index.js index 2debe732c..37658a7cf 100644 --- a/app/screens/settings/notification_settings_email/index.js +++ b/app/screens/settings/notification_settings_email/index.js @@ -9,15 +9,22 @@ import {Preferences} from 'mattermost-redux/constants'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {updateMe} from 'mattermost-redux/actions/users'; +import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; import { get as getPreference, getTheme, } from 'mattermost-redux/selectors/entities/preferences'; + import {isLandscape} from 'app/selectors/device'; +import {getNotificationProps} from 'app/utils/notify_props'; + import NotificationSettingsEmail from './notification_settings_email'; function mapStateToProps(state) { + const currentUser = getCurrentUser(state) || {}; + const notifyProps = getNotificationProps(currentUser); + const config = getConfig(state); const sendEmailNotifications = config.SendEmailNotifications === 'true'; const enableEmailBatching = config.EnableEmailBatching === 'true'; @@ -29,6 +36,8 @@ function mapStateToProps(state) { ); return { + currentUser, + notifyProps, enableEmailBatching, emailInterval, sendEmailNotifications, diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js b/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js index dc797176d..25cf7509d 100644 --- a/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js +++ b/app/screens/settings/notification_settings_email/notification_settings_email.android.test.js @@ -20,6 +20,9 @@ jest.mock('Platform', () => { describe('NotificationSettingsEmailAndroid', () => { const baseProps = { currentUser: {id: 'current_user_id'}, + notifyProps: { + email: 'true', + }, emailInterval: '30', enableEmailBatching: false, actions: { diff --git a/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js b/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js index 1fd757a61..07d7613cd 100644 --- a/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js +++ b/app/screens/settings/notification_settings_email/notification_settings_email.ios.test.js @@ -27,6 +27,9 @@ jest.mock('app/utils/theme', () => { describe('NotificationSettingsEmailIos', () => { const baseProps = { currentUser: {id: 'current_user_id'}, + notifyProps: { + email: 'true', + }, emailInterval: '30', enableEmailBatching: false, actions: { diff --git a/app/screens/settings/notification_settings_email/notification_settings_email_base.js b/app/screens/settings/notification_settings_email/notification_settings_email_base.js index 60f56ab8c..feebf7e7c 100644 --- a/app/screens/settings/notification_settings_email/notification_settings_email_base.js +++ b/app/screens/settings/notification_settings_email/notification_settings_email_base.js @@ -9,7 +9,6 @@ import {Navigation} from 'react-native-navigation'; import {Preferences} from 'mattermost-redux/constants'; import {getEmailInterval} from 'mattermost-redux/utils/notify_props'; -import {getNotificationProps} from 'app/utils/notify_props'; import {setNavigatorStyles} from 'app/utils/theme'; export default class NotificationSettingsEmailBase extends PureComponent { @@ -20,6 +19,7 @@ export default class NotificationSettingsEmailBase extends PureComponent { }), componentId: PropTypes.string, currentUser: PropTypes.object.isRequired, + notifyProps: PropTypes.object.isRequired, emailInterval: PropTypes.string.isRequired, enableEmailBatching: PropTypes.bool.isRequired, sendEmailNotifications: PropTypes.bool.isRequired, @@ -31,14 +31,12 @@ export default class NotificationSettingsEmailBase extends PureComponent { super(props); const { - currentUser, + notifyProps, emailInterval, enableEmailBatching, sendEmailNotifications, } = props; - const notifyProps = getNotificationProps(currentUser); - this.state = { emailInterval, newInterval: this.computeEmailInterval(notifyProps?.email === 'true' && sendEmailNotifications, enableEmailBatching, emailInterval), @@ -56,7 +54,7 @@ export default class NotificationSettingsEmailBase extends PureComponent { } const { - currentUser, + notifyProps, sendEmailNotifications, enableEmailBatching, emailInterval, @@ -65,10 +63,9 @@ export default class NotificationSettingsEmailBase extends PureComponent { if ( this.props.sendEmailNotifications !== sendEmailNotifications || this.props.enableEmailBatching !== enableEmailBatching || - this.props.emailInterval !== emailInterval + this.props.emailInterval !== emailInterval || + this.props.notifyProps?.email !== notifyProps?.email ) { - const notifyProps = getNotificationProps(currentUser); - this.setState({ emailInterval, newInterval: this.computeEmailInterval(notifyProps?.email === 'true' && sendEmailNotifications, enableEmailBatching, emailInterval), @@ -93,6 +90,7 @@ export default class NotificationSettingsEmailBase extends PureComponent { const { actions, currentUser, + notifyProps, sendEmailNotifications, } = this.props; @@ -101,7 +99,6 @@ export default class NotificationSettingsEmailBase extends PureComponent { email = 'true'; } - const notifyProps = getNotificationProps(currentUser); actions.updateMe({notify_props: {...notifyProps, email}}); const emailIntervalPreference = {category: Preferences.CATEGORY_NOTIFICATIONS, user_id: currentUser.id, name: Preferences.EMAIL_INTERVAL, value: newInterval}; diff --git a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js index bda1c76b0..cc45dca1d 100644 --- a/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js +++ b/app/screens/settings/notification_settings_mobile/notification_settings_mobile.android.js @@ -370,11 +370,11 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase { ); } - componentWillReceiveProps(nextProps) { - super.componentWillReceiveProps(nextProps); + componentDidUpdate(prevProps) { + super.componentDidUpdate(prevProps); - const {updateMeRequest, intl} = nextProps; - if (this.props.updateMeRequest !== updateMeRequest && updateMeRequest.status === RequestStatus.FAILURE) { + const {updateMeRequest, intl} = this.props; + if (updateMeRequest !== prevProps.updateMeRequest && updateMeRequest.status === RequestStatus.FAILURE) { Alert.alert( intl.formatMessage({ id: 'mobile.notification_settings.save_failed_title', From 6b7d043c050a822a220e006f6a998e9772008164 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 28 Oct 2019 13:21:31 +0200 Subject: [PATCH 13/17] Fix android and iOS deployment on circleCI (#3476) * Fix android and iOS deployment on circleCI * Feedback review * Fix circleci yml --- .circleci/config.yml | 61 +++++++++++++++++++++----------------------- fastlane/Fastfile | 6 ++--- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b4d4fde5..751e39933 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -159,39 +159,30 @@ commands: no_output_timeout: 30m command: bundle exec fastlane ios build || exit 1 - deploy-android: - description: "Deploy apk to Google Play" + deploy-to-store: + description: "Deploy build to store" parameters: - apk_path: + task: + type: string + target: + type: string + file: type: string steps: - attach_workspace: - at: ~/mattermost-mobile - - deploy: - name: "Deploy apk to Google Play" + at: / + - run: + name: <> working_directory: fastlane - command: bundle exec fastlane android deploy apk:./<> - - deploy-ios: - description: "Deploy ipa to TestFlight" - parameters: - ipa_path: - type: string - steps: - - attach_workspace: - at: ~/mattermost-mobile - - deploy: - name: "Deploy ipa to TestFlight" - working_directory: fastlane - command: bundle exec fastlane ios deploy ipa:./<> + command: bundle exec fastlane <> deploy file:$HOME/mattermost-mobile/<> persist: description: "Persist mattermost-mobile directory" steps: - persist_to_workspace: - root: ./ + root: / paths: - - ./ + - mattermost-mobile* save: description: "Save binaries artifacts" @@ -244,7 +235,6 @@ jobs: BRANCH_TO_BUILD: ${CIRCLE_BRANCH} steps: - build-android - - persist - save: filename: "Mattermost_Beta.apk" @@ -270,7 +260,6 @@ jobs: BRANCH_TO_BUILD: ${CIRCLE_BRANCH} steps: - build-ios - - persist - save: filename: "Mattermost_Beta.ipa" @@ -279,28 +268,36 @@ jobs: name: android resource_class: medium steps: - - deploy-android: - apk_path: Mattermost.apk + - deploy-to-store: + task: "Deploy to Google Play" + target: android + file: Mattermost.apk deploy-android-beta: executor: name: android resource_class: medium steps: - - deploy-android: - apk_path: Mattermost_Beta.apk + - deploy-to-store: + task: "Deploy to Google Play" + target: android + file: Mattermost_Beta.apk deploy-ios-release: executor: ios steps: - - deploy-ios: - ipa_path: Mattermost.ipa + - deploy-to-store: + task: "Deploy to TestFlight" + target: ios + file: Mattermost.ipa deploy-ios-beta: executor: ios steps: - - deploy-ios: - ipa_path: Mattermost_Beta.ipa + - deploy-to-store: + task: "Deploy to TestFlight" + target: ios + file: Mattermost_Beta.ipa workflows: version: 2 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8593d2e92..7de9d94d8 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -428,7 +428,7 @@ platform :ios do end lane :deploy do |options| - ipa_path = options[:ipa] + ipa_path = options[:file] unless ipa_path.nil? submit_to_testflight(ipa_path) @@ -612,7 +612,7 @@ platform :android do end lane :deploy do |options| - apk_path = options[:apk] + apk_path = options[:file] unless apk_path.nil? submit_to_google_play(apk_path) @@ -729,7 +729,7 @@ def submit_to_testflight(ipa_path) if(File.file?(ipa_path)) UI.success("ipa file #{ipa_path}") - upload_to_tesflight( + pilot( ipa: ipa_path ) else From 558f6ad2800965627d6754ee10d4bc00df9aced3 Mon Sep 17 00:00:00 2001 From: CJ <38697367+imisshtml@users.noreply.github.com> Date: Mon, 28 Oct 2019 12:22:50 -0400 Subject: [PATCH 14/17] MM-19308 Fixed date wrapping (#3445) * MM-19308 Fixed date wrapping Updated the displayName to check for the reply button and bot tag. If seen the displayName container is set from 60% to 55% to allow for the extra space. * MM-19308 Updated logic and added tests Updated logic based on feedback. Added Jest/Snapshots * MM-19308 Apply change to other sections Applied the change to autoresponder/webhook section. --- .../__snapshots__/post_header.test.js.snap | 507 ++++++++++++++++++ app/components/post_header/post_header.js | 14 +- .../post_header/post_header.test.js | 98 ++++ 3 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 app/components/post_header/__snapshots__/post_header.test.js.snap create mode 100644 app/components/post_header/post_header.test.js diff --git a/app/components/post_header/__snapshots__/post_header.test.js.snap b/app/components/post_header/__snapshots__/post_header.test.js.snap new file mode 100644 index 000000000..8a06a9d70 --- /dev/null +++ b/app/components/post_header/__snapshots__/post_header.test.js.snap @@ -0,0 +1,507 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PostHeader should match snapshot when just a base post 1`] = ` + + + + + + John Smith + + + + + + +`; + +exports[`PostHeader should match snapshot when post is autoresponder 1`] = ` + + + + + + John Smith + + + + + + + +`; + +exports[`PostHeader should match snapshot when post is from system message 1`] = ` + + + + + + + + + + +`; + +exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyButton 1`] = ` + + + + + + John Smith + + + + + + + + + 0 + + + + + + +`; + +exports[`PostHeader should match snapshot when post should display reply button 1`] = ` + + + + + + John Smith + + + + + + + + 0 + + + + + + +`; diff --git a/app/components/post_header/post_header.js b/app/components/post_header/post_header.js index db9b20cdb..7b2841f59 100644 --- a/app/components/post_header/post_header.js +++ b/app/components/post_header/post_header.js @@ -117,9 +117,16 @@ export default class PostHeader extends PureComponent { fromAutoResponder, overrideUsername, theme, + renderReplies, + shouldRenderReplyButton, + commentedOnDisplayName, + commentCount, + isBot, } = this.props; const style = getStyleSheet(theme); + const showReply = shouldRenderReplyButton || (!commentedOnDisplayName && commentCount > 0 && renderReplies); + const displayNameStyle = [style.displayNameContainer, showReply && (isBot || fromAutoResponder || fromWebHook) ? style.displayNameContainerBotReplyWidth : null]; if (fromAutoResponder || fromWebHook) { let name = displayName; @@ -128,7 +135,7 @@ export default class PostHeader extends PureComponent { } return ( - + { marginBottom: 3, lineHeight: 21, }, + displayNameContainerBotReplyWidth: { + maxWidth: '50%', + }, }; }); diff --git a/app/components/post_header/post_header.test.js b/app/components/post_header/post_header.test.js new file mode 100644 index 000000000..2baab41c1 --- /dev/null +++ b/app/components/post_header/post_header.test.js @@ -0,0 +1,98 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +import PostHeader from './post_header'; + +describe('PostHeader', () => { + const baseProps = { + commentCount: 0, + commentedOnDisplayName: '', + createAt: 0, + displayName: 'John Smith', + enablePostUsernameOverride: false, + fromWebHook: false, + isPendingOrFailedPost: false, + isSearchResult: false, + isSystemMessage: false, + fromAutoResponder: false, + militaryTime: false, + overrideUsername: '', + renderReplies: false, + shouldRenderReplyButton: false, + showFullDate: false, + theme: Preferences.THEMES.default, + username: 'JohnSmith', + isBot: false, + isGuest: false, + userTimezone: '', + enableTimezone: false, + previousPostExists: false, + post: {id: 'post'}, + beforePrevPostUserId: '0', + onPress: jest.fn(), + }; + + test('should match snapshot when just a base post', () => { + const wrapper = shallow( + + ); + expect(wrapper.getElement()).toMatchSnapshot(); + expect(wrapper.find('#ReplyIcon').exists()).toEqual(false); + }); + + test('should match snapshot when post isBot and shouldRenderReplyButton', () => { + const props = { + ...baseProps, + shouldRenderReplyButton: true, + isBot: true, + }; + + const wrapper = shallow( + + ); + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + test('should match snapshot when post should display reply button', () => { + const props = { + ...baseProps, + shouldRenderReplyButton: true, + }; + + const wrapper = shallow( + + ); + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + test('should match snapshot when post is autoresponder', () => { + const props = { + ...baseProps, + fromAutoResponder: true, + }; + + const wrapper = shallow( + + ); + expect(wrapper.getElement()).toMatchSnapshot(); + expect(wrapper.find('#ReplyIcon').exists()).toEqual(false); + }); + + test('should match snapshot when post is from system message', () => { + const props = { + ...baseProps, + isSystemMessage: true, + }; + + const wrapper = shallow( + + ); + expect(wrapper.getElement()).toMatchSnapshot(); + expect(wrapper.find('#ReplyIcon').exists()).toEqual(false); + }); +}); \ No newline at end of file From 87e899a5620f0a51337a9174e3c6d91aecd5423e Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 28 Oct 2019 21:18:41 +0200 Subject: [PATCH 15/17] MM-19734 Patch RN to remove the placeholder for emojis in a markdown paragraph (#3481) --- patches/react-native+0.59.9.patch | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 patches/react-native+0.59.9.patch diff --git a/patches/react-native+0.59.9.patch b/patches/react-native+0.59.9.patch new file mode 100644 index 000000000..a846cd491 --- /dev/null +++ b/patches/react-native+0.59.9.patch @@ -0,0 +1,25 @@ +diff --git a/node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.m b/node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.m +index d464e6a..6c397c5 100644 +--- a/node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.m ++++ b/node_modules/react-native/Libraries/Text/Text/RCTTextShadowView.m +@@ -170,6 +170,12 @@ - (void)postprocessAttributedText:(NSMutableAttributedString *)attributedText + + - (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize)size + { ++ static UIImage *placeholderImage; ++ static dispatch_once_t onceToken; ++ dispatch_once(&onceToken, ^{ ++ placeholderImage = [UIImage new]; ++ }); ++ + NSMutableAttributedString *attributedText = + [[NSMutableAttributedString alloc] initWithAttributedString:[self attributedTextWithBaseTextAttributes:nil]]; + +@@ -188,6 +194,7 @@ - (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize + maximumSize:size]; + NSTextAttachment *attachment = [NSTextAttachment new]; + attachment.bounds = (CGRect){CGPointZero, fittingSize}; ++ attachment.image = placeholderImage; + [attributedText addAttribute:NSAttachmentAttributeName value:attachment range:range]; + } + ]; From 1efb01deda81cc0ee0fa0a36af8c02e342272f11 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Mon, 28 Oct 2019 15:04:24 -0700 Subject: [PATCH 16/17] [MM-16593] Upgrade to RN 0.61.2 (#3455) * [MM-16593] [MM-17175] [MM-17164] [MM-17189] [MM-17181] Android - Upgrade to RN 0.60 (#3145) * Upgrade to react-native 0.60 * Use @sentry/react-native * Manually link @sentry/react-native * Address review comments * Jetify after install * Call jetify from node_modules * [MM-17785] iOS - Upgrade to RN 0.60 (#3153) * Upgrade to react-native 0.60 * Use @sentry/react-native * Manually link @sentry/react-native * Address review comments * Jetify after install * Call jetify from node_modules * Get app building for iOS * Revert react-native-image-picker upgrade * Minor version upgrade of react-native-image-picker * [MM-17142] Convert all string refs to callbacks (#3217) * Replace string refs * Fix tests * Don't use inline functions * Fix mattermost-redux reversion from master merge * [MM-18336] Upload Sentry debug symbols only when SENTRY_ENABLED is true (#3227) * Upgrade @sentry/react-native * Run Sentry gradle tasks only when enabled * Upgrade @sentry/react-native and remove extra Sentry build phase * [MM-17144] Use Hermes (#3226) * Replace string refs * Fix tests * Don't use inline functions * Fix mattermost-redux reversion from master merge * Use Hermes * bundleCommand ram-bundle no longer needed * Require harmony-reflect only for Android * Fix failing test * Path react-native's splitLayoutProps (#3337) * [MM-18867] [MM-17186] [MM-18866] [MM-19447] [MM-18967] Upgrade to RN 0.61.2 (#3423) * Upgrade to RN 0.61.2 * Update rn-fetch-blob commit hash * Update react-native-keyboard-tracking-view commit hash * Use react-native-youtube with AVPlayer fix * Fix jest mocks * Use updated document picker API * Remove unnecessary linking * Revert "MM-17759 Add code highlighting (#3072)" This reverts commit 26b999e8857575da803e68b362a060386c36e6d7. * Fix share extension * Revert "Revert "MM-17759 Add code highlighting (#3072)"" This reverts commit 52aca776b12dee3abe8646d0c90480f8528e86c1. * Address PR reviews * Rename patch to match version * Update react-native-youtube patch * Update dependencies * Fix RNDocViewer reference * Update tests and revert to redux-persist 4.10.2 * Revert "Revert "Revert "MM-17759 Add code highlighting (#3072)""" This reverts commit 5ef383be2619b1be6167c23f128ecb4b4ad25df9. * Android fixes after dep upgrades * Use fresco 2.0.0 * Use mattermost forks * Use React-Core in Mattermost.scheme instead of react * Remove packager (#3452) * Remove Pods from source control * Fix unit tests * Add new line before entering the keystore in gradle.properties * set ios as working directory for cocoapods * Cache cocoapods on circleCI builds * set ios as working dir * fix cocoapods cache key * Unify fastlane and npm dependencies command * Use package-lock.json checksum for npm cache * Fix package.json and use the checksum as the cache key * Fix package.json and use the checksum as the cache key * changes to circleci.yaml fastlane and removing pods from git * Fix Mattermost.xcodeproj * Update coocoapods to 1.7.5 and fix xcode header search paths * Update package-lock.json * Remove unused tooltip component * Fix incorrect ref * Disable Hermes (#3460) * Revert "Remove packager (#3452)" This reverts commit b2a79e184b3242124dfdb91ae095f6ce3f4eb986. * Disable Hermes :'( * Update preloaded modules * Fix packages moduleNames and modulePaths, update snapshots and update mm-redux (ts version) * remove document picker from modulePaths * Fix package-lock.json * Add eslint disable rules --- .flowconfig | 77 +- Makefile | 2 + NOTICE.txt | 35 - android/app/build.gradle | 63 +- android/app/proguard-rules.pro | 7 - android/app/src/main/AndroidManifest.xml | 6 +- .../com/mattermost/rnbeta/MainActivity.java | 16 +- .../mattermost/rnbeta/MainApplication.java | 33 +- .../NotificationReplyBroadcastReceiver.java | 2 +- .../rnbeta/RNPasteableTextInputManager.java | 6 +- .../mattermost/rnbeta/ReceiptDelivery.java | 2 +- .../com/mattermost/share/ShareModule.java | 2 +- .../app/src/main/res/layout/launch_screen.xml | 4 +- android/build.gradle | 2 +- android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/keystores/BUCK | 8 - android/keystores/debug.keystore.properties | 4 - android/settings.gradle | 4 +- .../profile_picture_button.test.js.snap | 1 + app/components/attachment_button/index.js | 15 +- .../attachment_button/index.test.js | 6 - app/components/badge.js | 12 +- .../__snapshots__/user_list_test.test.js.snap | 8 + .../emoji_picker/emoji_picker.android.js | 2 +- .../emoji_picker/emoji_picker.ios.js | 3 +- .../emoji_picker/emoji_picker_base.js | 21 +- .../file_attachment.test.js.snap | 1 + .../file_attachment_list.test.js.snap | 1 + .../file_attachment_document.js | 2 +- .../markdown_code_block.js | 51 +- .../markdown/markdown_image/markdown_image.js | 14 +- .../attachment_image/index.js | 14 +- .../pasteable_text_input/custom_text_input.js | 10 +- .../post_attachment_opengraph.js | 8 +- .../__snapshots__/post_textbox.test.js.snap | 2 + .../post_textbox/post_textbox.test.js | 8 - app/components/root/root.js | 12 +- .../safe_area_view/safe_area_view.ios.test.js | 4 +- .../search_bar/search_bar.android.js | 10 +- app/components/search_bar/search_bar.ios.js | 12 +- app/components/search_bar/search_box.js | 16 +- .../__snapshots__/channel_item.test.js.snap | 10 +- .../main/channels_list/channels_list.js | 10 +- .../sidebars/main/channels_list/list/list.js | 16 +- .../main/drawer_swiper/drawer_swiper.js | 4 +- app/components/sidebars/main/main_sidebar.js | 6 +- .../sidebars/main/main_sidebar.test.js | 1 + .../sidebars/settings/settings_sidebar.js | 64 +- .../text_input_with_localized_placeholder.js | 10 +- app/components/tooltip.js | 64 - app/init/emm_provider.js | 2 +- app/init/fetch.js | 2 +- app/init/global_event_handler.js | 2 +- app/init/global_event_handler.test.js | 4 + .../channel_nav_bar.test.js.snap | 1 + .../channel_drawer_button.test.js.snap | 2 +- .../channel_post_list/channel_post_list.js | 1 + .../channel_add_members.js | 6 +- .../__snapshots__/channel_info.test.js.snap | 11 + .../channel_info_header.test.js.snap | 6 + .../channel_members.test.js.snap | 1 + .../channel_members/channel_members.js | 6 +- app/screens/code/code.js | 35 +- .../__snapshots__/edit_profile.test.js.snap | 1 + app/screens/edit_profile/edit_profile.js | 4 +- .../error_teams_list.test.js.snap | 1 + .../__snapshots__/flagged_posts.test.js.snap | 1 + app/screens/flagged_posts/flagged_posts.js | 6 +- app/screens/image_preview/downloader.ios.js | 2 +- app/screens/image_preview/image_preview.js | 18 +- app/screens/image_preview/video_preview.js | 12 +- .../interactive_dialog.test.js.snap | 1 + .../__snapshots__/long_post.test.js.snap | 4 + app/screens/long_post/long_post.js | 10 +- .../__snapshots__/more_channels.test.js.snap | 1 + app/screens/more_channels/more_channels.js | 6 +- app/screens/more_dms/more_dms.js | 6 +- .../__snapshots__/selected_users.test.js.snap | 4 + app/screens/permalink/permalink.js | 14 +- app/screens/pinned_posts/pinned_posts.js | 6 +- .../__snapshots__/post_options.test.js.snap | 17 + app/screens/post_options/post_options.test.js | 6 - .../reaction_header.test.js.snap | 4 + .../__snapshots__/reaction_list.test.js.snap | 5 + .../recent_mentions.test.js.snap | 1 + .../recent_mentions/recent_mentions.js | 6 +- app/screens/search/search.js | 34 +- .../__snapshots__/select_team.test.js.snap | 2 + .../selector_screen.test.js.snap | 6 + .../selector_screen/selector_screen.js | 6 +- .../advanced_settings/advanced_settings.js | 2 +- .../display_settings.test.js.snap | 3 + ...cation_settings_email.android.test.js.snap | 1 + ...tification_settings_email.ios.test.js.snap | 3 + ...otification_settings_email.android.test.js | 7 +- .../notification_settings_email.ios.test.js | 6 - .../notification_settings_email_base.js | 6 +- ...on_settings_mentions_keywords.test.js.snap | 3 + .../__snapshots__/sidebar.test.js.snap | 2 + app/screens/settings/sidebar/sidebar.test.js | 1 + .../theme/__snapshots__/theme.test.js.snap | 4 + .../select_timezone/select_timezone.js | 6 +- .../terms_of_service.test.js.snap | 1 + .../__snapshots__/user_profile.test.js.snap | 3 + app/utils/network.js | 2 +- app/utils/sentry/index.js | 10 +- app/utils/sentry/middleware.js | 4 +- app/utils/theme.js | 9 - app/utils/theme.test.js | 39 +- index.js | 6 +- ios/Mattermost.xcodeproj/project.pbxproj | 2173 +- .../xcschemes/Mattermost.xcscheme | 38 +- .../xcschemes/NotificationService.xcscheme | 108 + .../xcshareddata/WorkspaceSettings.xcsettings | 5 +- ios/Mattermost/AppDelegate.m | 5 - ios/Mattermost/Info.plist | 4 +- ios/Mattermost/Mattermost+RCTUITextView.m | 4 +- ios/Podfile | 39 +- ios/Podfile.lock | 502 +- .../project.pbxproj | 1 - native_modules/RNCNetInfo.m | 228 - native_modules/RNCookieManagerIOS.m | 266 - native_modules/RNDocumentPicker.m | 114 - package-lock.json | 16597 +++++----------- package.json | 130 +- packager/README.md | 2 +- packager/moduleNames.js | 829 +- packager/modulePaths.js | 475 +- patches/react-native+0.61.2.patch | 152 + patches/react-native-cookies+3.3.0.patch | 56 + ...ch => react-native-navigation+3.2.0.patch} | 69 +- .../react-native-youtube+2.0.0-beta.3.patch | 35 + react-native.config.js | 8 + .../extension_channels/extension_channels.js | 6 +- .../android/extension_post/extension_post.js | 2 +- .../extension_post/extension_post.test.js | 11 +- share_extension/android/navigation.js | 3 +- test/setup.js | 53 +- 139 files changed, 7282 insertions(+), 15710 deletions(-) delete mode 100644 android/keystores/BUCK delete mode 100644 android/keystores/debug.keystore.properties delete mode 100644 app/components/tooltip.js create mode 100644 ios/Mattermost.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme delete mode 100644 native_modules/RNCNetInfo.m delete mode 100644 native_modules/RNCookieManagerIOS.m delete mode 100644 native_modules/RNDocumentPicker.m create mode 100644 patches/react-native+0.61.2.patch create mode 100644 patches/react-native-cookies+3.3.0.patch rename patches/{react-native-navigation+2.21.1.patch => react-native-navigation+3.2.0.patch} (64%) create mode 100644 patches/react-native-youtube+2.0.0-beta.3.patch create mode 100644 react-native.config.js diff --git a/.flowconfig b/.flowconfig index 8abcc8ecf..4afc766a2 100644 --- a/.flowconfig +++ b/.flowconfig @@ -5,26 +5,24 @@ ; Ignore "BUCK" generated dirs /\.buckd/ -; Ignore unexpected extra "@providesModule" -.*/node_modules/.*/node_modules/fbjs/.* - -; Ignore duplicate module providers -; For RN Apps installed via npm, "Libraries" folder is inside -; "node_modules/react-native" but in the source repo it is in the root -.*/Libraries/react-native/React.js - ; Ignore polyfills -.*/Libraries/polyfills/.* +node_modules/react-native/Libraries/polyfills/.* -; Ignore metro -.*/node_modules/metro/.* +; These should not be required directly +; require from fbjs/lib instead: require('fbjs/lib/warning') +node_modules/warning/.* + +; Flow doesn't support platforms +.*/Libraries/Utilities/LoadingView.js + +[untyped] +.*/node_modules/@react-native-community/cli/.*/.* [include] [libs] node_modules/react-native/Libraries/react-native/react-native-interface.js node_modules/react-native/flow/ -node_modules/react-native/flow-github/ [options] emoji=true @@ -32,39 +30,46 @@ emoji=true esproposal.optional_chaining=enable esproposal.nullish_coalescing=enable -module.system=haste -module.system.haste.use_name_reducers=true -# get basename -module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' -# strip .js or .js.flow suffix -module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' -# strip .ios suffix -module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' -module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' -module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' -module.system.haste.paths.blacklist=.*/__tests__/.* -module.system.haste.paths.blacklist=.*/__mocks__/.* -module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* -module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* +module.file_ext=.js +module.file_ext=.json +module.file_ext=.ios.js munge_underscores=true -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' - -module.file_ext=.js -module.file_ext=.jsx -module.file_ext=.json -module.file_ext=.native.js +module.name_mapper='^react-native$' -> '/node_modules/react-native/Libraries/react-native/react-native-implementation' +module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FlowFixMeProps suppress_type=$FlowFixMeState -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError +[lints] +sketchy-null-number=warn +sketchy-null-mixed=warn +sketchy-number=warn +untyped-type-import=warn +nonstrict-import=warn +deprecated-type=warn +unsafe-getters-setters=warn +inexact-spread=warn +unnecessary-invariant=warn +signature-verification-failure=warn +deprecated-utility=error + +[strict] +deprecated-type +nonstrict-import +sketchy-null +unclear-type +unsafe-getters-setters +untyped-import +untyped-type-import + [version] -^0.92.0 +^0.105.0 diff --git a/Makefile b/Makefile index 38d55dcb1..af3a95c2b 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,8 @@ clean: ## Cleans dependencies, previous builds and temp files @echo Cleanup finished post-install: + @./node_modules/.bin/jetify + @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 diff --git a/NOTICE.txt b/NOTICE.txt index 8561e27a0..86d532123 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2812,38 +2812,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- - -## react-native-syntax-highlighter - -This product contains 'react-native-syntax-highlighter' by Conor Hastings. - -A syntax highlighter for react native using https://github.com/conorhastings/react-syntax-highlighter under the hood - -* HOMEPAGE: - * https://github.com/conorhastings/react-native-syntax-highlighter#readme - -* LICENSE: MIT - -The MIT License (MIT) - -Copyright (c) 2019 Conor Hastings - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/android/app/build.gradle b/android/app/build.gradle index 483f1c345..44203c5bb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -74,8 +74,8 @@ import com.android.build.OutputFile project.ext.react = [ entryFile: "index.js", - bundleCommand: "ram-bundle", - bundleConfig: "metro.config.js" + bundleConfig: "metro.config.js", + enableHermes: false, ] apply from: "../../node_modules/react-native/react.gradle" @@ -87,7 +87,7 @@ if (System.getenv("SENTRY_ENABLED") == "true") { flavorAware: false ] - apply from: "../../node_modules/react-native-sentry/sentry.gradle" + apply from: "../../node_modules/@sentry/react-native/sentry.gradle" } /** @@ -105,6 +105,28 @@ def enableSeparateBuildPerCPUArchitecture = false */ def enableProguardInReleaseBuilds = false +/** + * The preferred build flavor of JavaScriptCore. + * + * For example, to use the international variant, you can use: + * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'org.webkit:android-jsc-intl:r241213' + +/** + * Whether to enable the Hermes VM. + * + * This should be set on project.ext.react and mirrored here. If it is not set + * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode + * and the benefits of using Hermes will therefore be sharply reduced. + */ +def enableHermes = project.ext.react.get("enableHermes", false); + android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -113,16 +135,11 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - packagingOptions { - pickFirst '**/libjsc.so' - pickFirst '**/libc++_shared.so' - } - defaultConfig { applicationId "com.mattermost.rnbeta" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5" + missingDimensionStrategy "RNN.reactNativeVersion", "reactNative60" versionCode 241 versionName "1.25.0" multiDexEnabled = true @@ -211,15 +228,21 @@ configurations.all { } dependencies { - // Make sure to put android-jsc at the top - implementation "org.webkit:android-jsc-intl:r241213" + if (enableHermes) { + def hermesPath = "../../node_modules/hermes-engine/android/"; + debugImplementation files(hermesPath + "hermes-debug.aar") + releaseImplementation files(hermesPath + "hermes-release.aar") + } else { + implementation jscFlavor + } implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" - implementation 'com.android.support:design:28.0.0' - implementation 'com.android.support:percent:28.0.0' - implementation "com.google.firebase:firebase-messaging:17.3.0" implementation "com.facebook.react:react-native:+" // From node_modules + + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation "com.google.firebase:firebase-messaging:17.3.0" implementation project(':react-native-document-picker') implementation project(':react-native-keychain') implementation project(':react-native-doc-viewer') @@ -235,22 +258,22 @@ dependencies { implementation project(':react-native-local-auth') implementation project(':jail-monkey') implementation project(':react-native-youtube') - implementation project(':react-native-sentry') implementation project(':react-native-exception-handler') implementation project(':rn-fetch-blob') implementation project(':react-native-webview') implementation project(':react-native-gesture-handler') implementation project(':@react-native-community_async-storage') implementation project(':@react-native-community_netinfo') + implementation project(':@sentry_react-native') implementation project(':react-native-android-open-settings') implementation project(':react-native-haptic-feedback') // For animated GIF support - implementation 'com.facebook.fresco:fresco:1.10.0' - implementation 'com.facebook.fresco:animated-gif:1.10.0' + implementation 'com.facebook.fresco:fresco:2.0.0' + implementation 'com.facebook.fresco:animated-gif:2.0.0' // For WebP support, including animated WebP - implementation 'com.facebook.fresco:animated-webp:1.10.0' - implementation 'com.facebook.fresco:webpsupport:1.10.0' + implementation 'com.facebook.fresco:animated-webp:2.0.0' + implementation 'com.facebook.fresco:webpsupport:2.0.0' } // Run this once to be able to run the application with BUCK diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index a92fa177e..11b025724 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -8,10 +8,3 @@ # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e9f5dba04..2130ce5bf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -29,7 +29,8 @@ android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:launchMode="singleInstance"> @@ -52,7 +53,8 @@ android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:label="@string/app_name" android:screenOrientation="portrait" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:taskAffinity="com.mattermost.share"> diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.java b/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.java index 023da9c70..c1ec6b302 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainActivity.java @@ -1,7 +1,7 @@ package com.mattermost.rnbeta; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.reactnativenavigation.NavigationActivity; @@ -10,19 +10,5 @@ public class MainActivity extends NavigationActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.launch_screen); - - /** - * Reference: https://stackoverflow.com/questions/7944338/resume-last-activity-when-launcher-icon-is-clicked - * 1. Open app from launcher/appDrawer - * 2. Go home - * 3. Send notification and open - * 4. It creates a new Activity and Destroys the old - * 5. Causing an unnecessary app restart - * 6. This solution short-circuits the restart - */ - if (!isTaskRoot()) { - finish(); - return; - } } } diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java index 904387cae..38eb0ee70 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java @@ -1,7 +1,7 @@ package com.mattermost.rnbeta; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.content.Context; import android.content.RestrictionsManager; import android.os.Bundle; @@ -22,15 +22,15 @@ import com.wix.reactnativenotifications.RNNotificationsModule; import io.tradle.react.LocalAuthModule; import com.gantix.JailMonkey.JailMonkeyModule; import com.RNFetchBlob.RNFetchBlob; -import io.sentry.RNSentryModule; -import io.sentry.RNSentryEventEmitter; import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule; import com.inprogress.reactnativeyoutube.YouTubeStandaloneModule; -import com.reactlibrary.RNReactNativeDocViewerModule; -import com.reactnativedocumentpicker.DocumentPicker; +import com.philipphecht.RNDocViewerModule; +import io.github.elyx0.reactnativedocumentpicker.DocumentPickerModule; import com.oblador.keychain.KeychainModule; import com.reactnativecommunity.asyncstorage.AsyncStorageModule; import com.reactnativecommunity.netinfo.NetInfoModule; +import com.reactnativecommunity.webview.RNCWebViewPackage; +import io.sentry.RNSentryModule; import com.levelasquez.androidopensettings.AndroidOpenSettings; import com.mkuczera.RNReactNativeHapticFeedbackModule; @@ -124,7 +124,7 @@ public class MainApplication extends NavigationApplication implements INotificat case "RNLocalAuth": return new LocalAuthModule(reactContext); case "JailMonkey": - return new JailMonkeyModule(reactContext); + return new JailMonkeyModule(reactContext, false); case "RNFetchBlob": return new RNFetchBlob(reactContext); case "MattermostManaged": @@ -133,20 +133,18 @@ public class MainApplication extends NavigationApplication implements INotificat return NotificationPreferencesModule.getInstance(instance, reactContext); case "RNTextInputReset": return new RNTextInputResetModule(reactContext); - case "RNSentry": - return new RNSentryModule(reactContext); - case "RNSentryEventEmitter": - return new RNSentryEventEmitter(reactContext); case "ReactNativeExceptionHandler": return new ReactNativeExceptionHandlerModule(reactContext); case "YouTubeStandaloneModule": return new YouTubeStandaloneModule(reactContext); - case "RNReactNativeDocViewer": - return new RNReactNativeDocViewerModule(reactContext); + case "RNDocViewer": + return new RNDocViewerModule(reactContext); case "RNDocumentPicker": - return new DocumentPicker(reactContext); + return new DocumentPickerModule(reactContext); case "RNKeychainManager": return new KeychainModule(reactContext); + case "RNSentry": + return new RNSentryModule(reactContext); case AsyncStorageModule.NAME: return new AsyncStorageModule(reactContext); case NetInfoModule.NAME: @@ -179,13 +177,12 @@ public class MainApplication extends NavigationApplication implements INotificat map.put("RNLocalAuth", new ReactModuleInfo("RNLocalAuth", "io.tradle.react.LocalAuthModule", false, false, false, false, false)); map.put("JailMonkey", new ReactModuleInfo("JailMonkey", "com.gantix.JailMonkey.JailMonkeyModule", false, false, true, false, false)); map.put("RNFetchBlob", new ReactModuleInfo("RNFetchBlob", "com.RNFetchBlob.RNFetchBlob", false, false, true, false, false)); - map.put("RNSentry", new ReactModuleInfo("RNSentry", "com.sentry.RNSentryModule", false, false, true, false, false)); - map.put("RNSentryEventEmitter", new ReactModuleInfo("RNSentryEventEmitter", "com.sentry.RNSentryEventEmitter", false, false, true, false, false)); map.put("ReactNativeExceptionHandler", new ReactModuleInfo("ReactNativeExceptionHandler", "com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule", false, false, false, false, false)); map.put("YouTubeStandaloneModule", new ReactModuleInfo("YouTubeStandaloneModule", "com.inprogress.reactnativeyoutube.YouTubeStandaloneModule", false, false, false, false, false)); - map.put("RNReactNativeDocViewer", new ReactModuleInfo("RNReactNativeDocViewer", "com.reactlibrary.RNReactNativeDocViewerModule", false, false, false, false, false)); - map.put("RNDocumentPicker", new ReactModuleInfo("RNDocumentPicker", "com.reactnativedocumentpicker.DocumentPicker", false, false, false, false, false)); + map.put("RNDocViewer", new ReactModuleInfo("RNDocViewer", "com.philipphecht.RNDocViewerModule", false, false, false, false, false)); + map.put("RNDocumentPicker", new ReactModuleInfo("RNDocumentPicker", "io.github.elyx0.reactnativedocumentpicker.DocumentPickerModule", false, false, false, false, false)); map.put("RNKeychainManager", new ReactModuleInfo("RNKeychainManager", "com.oblador.keychain.KeychainModule", false, false, true, false, false)); + map.put("RNSentry", new ReactModuleInfo("RNSentry", "com.sentry.RNSentryModule", false, false, true, false, false)); map.put(AsyncStorageModule.NAME, new ReactModuleInfo(AsyncStorageModule.NAME, "com.reactnativecommunity.asyncstorage.AsyncStorageModule", false, false, false, false, false)); map.put(NetInfoModule.NAME, new ReactModuleInfo(NetInfoModule.NAME, "com.reactnativecommunity.netinfo.NetInfoModule", false, false, false, false, false)); map.put("RNAndroidOpenSettings", new ReactModuleInfo("RNAndroidOpenSettings", "com.levelasquez.androidopensettings.AndroidOpenSettings", false, false, false, false, false)); diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java index dd02b935e..70f89ce69 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java @@ -10,7 +10,7 @@ import android.content.Intent; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.util.Log; import java.io.IOException; diff --git a/android/app/src/main/java/com/mattermost/rnbeta/RNPasteableTextInputManager.java b/android/app/src/main/java/com/mattermost/rnbeta/RNPasteableTextInputManager.java index e77d2194f..745a71a10 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/RNPasteableTextInputManager.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/RNPasteableTextInputManager.java @@ -1,8 +1,8 @@ package com.mattermost.rnbeta; -import android.support.v13.view.inputmethod.EditorInfoCompat; -import android.support.v13.view.inputmethod.InputConnectionCompat; -import android.support.v4.os.BuildCompat; +import androidx.core.view.inputmethod.EditorInfoCompat; +import androidx.core.view.inputmethod.InputConnectionCompat; +import androidx.core.os.BuildCompat; import android.text.InputType; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; diff --git a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java index ebace4853..d63f9de60 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java @@ -1,7 +1,7 @@ package com.mattermost.rnbeta; import android.content.Context; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.util.Log; import java.lang.System; 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 16dcff239..45fbe4224 100644 --- a/android/app/src/main/java/com/mattermost/share/ShareModule.java +++ b/android/app/src/main/java/com/mattermost/share/ShareModule.java @@ -77,7 +77,7 @@ public class ShareModule extends ReactContextBaseJavaModule { this.clear(); Activity currentActivity = getCurrentActivity(); if (currentActivity != null) { - currentActivity.finish(); + currentActivity.finishAndRemoveTask(); } if (data != null && data.hasKey("url")) { diff --git a/android/app/src/main/res/layout/launch_screen.xml b/android/app/src/main/res/layout/launch_screen.xml index 8e2032943..e638f4097 100644 --- a/android/app/src/main/res/layout/launch_screen.xml +++ b/android/app/src/main/res/layout/launch_screen.xml @@ -1,5 +1,5 @@ - - + diff --git a/android/build.gradle b/android/build.gradle index 10f12f4b7..fa4605ee7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,7 +15,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:3.4.2' classpath 'com.google.gms:google-services:4.2.0' // NOTE: Do not place your application dependencies here; they belong diff --git a/android/gradle.properties b/android/gradle.properties index 0bbbed316..31ef6e1be 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -20,3 +20,6 @@ org.gradle.jvmargs=-Xmx2048M #android.enableAapt2=false #android.useDeprecatedNdk=true + +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7919ae6e6..a0a439e01 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip diff --git a/android/keystores/BUCK b/android/keystores/BUCK deleted file mode 100644 index 88e4c31b2..000000000 --- a/android/keystores/BUCK +++ /dev/null @@ -1,8 +0,0 @@ -keystore( - name = "debug", - properties = "debug.keystore.properties", - store = "debug.keystore", - visibility = [ - "PUBLIC", - ], -) diff --git a/android/keystores/debug.keystore.properties b/android/keystores/debug.keystore.properties deleted file mode 100644 index 121bfb49f..000000000 --- a/android/keystores/debug.keystore.properties +++ /dev/null @@ -1,4 +0,0 @@ -key.store=debug.keystore -key.alias=androiddebugkey -key.store.password=android -key.alias.password=android diff --git a/android/settings.gradle b/android/settings.gradle index 30b886887..baaaf279c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'Mattermost' +include ':@sentry_react-native' +project(':@sentry_react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@sentry/react-native/android') include ':react-native-android-open-settings' project(':react-native-android-open-settings').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-android-open-settings/android') include ':react-native-haptic-feedback' @@ -15,8 +17,6 @@ include ':react-native-video' project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android') include ':react-native-youtube' project(':react-native-youtube').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-youtube/android') -include ':react-native-sentry' -project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android') include ':react-native-exception-handler' project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-exception-handler/android') include ':rn-fetch-blob' diff --git a/app/components/__snapshots__/profile_picture_button.test.js.snap b/app/components/__snapshots__/profile_picture_button.test.js.snap index 7e959089f..fab53b130 100644 --- a/app/components/__snapshots__/profile_picture_button.test.js.snap +++ b/app/components/__snapshots__/profile_picture_button.test.js.snap @@ -27,6 +27,7 @@ exports[`profile_picture_button should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/components/attachment_button/index.js b/app/components/attachment_button/index.js index 3bae7eecd..0a50f7ed9 100644 --- a/app/components/attachment_button/index.js +++ b/app/components/attachment_button/index.js @@ -14,7 +14,7 @@ import DeviceInfo from 'react-native-device-info'; import AndroidOpenSettings from 'react-native-android-open-settings'; import Icon from 'react-native-vector-icons/Ionicons'; -import {DocumentPicker} from 'react-native-document-picker'; +import DocumentPicker from 'react-native-document-picker'; import ImagePicker from 'react-native-image-picker'; import Permissions from 'react-native-permissions'; @@ -257,13 +257,8 @@ export default class AttachmentButton extends PureComponent { const hasPermission = await this.hasStoragePermission(); if (hasPermission) { - DocumentPicker.show({ - filetype: [browseFileTypes], - }, async (error, res) => { - if (error) { - return; - } - + try { + const res = await DocumentPicker.pick({type: [browseFileTypes]}); 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); @@ -278,7 +273,9 @@ export default class AttachmentButton extends PureComponent { res.uri = decodeURIComponent(res.uri); this.uploadFiles([res]); - }); + } catch (error) { + // Do nothing + } } }; diff --git a/app/components/attachment_button/index.test.js b/app/components/attachment_button/index.test.js index ceaabdd0f..3b876bc7b 100644 --- a/app/components/attachment_button/index.test.js +++ b/app/components/attachment_button/index.test.js @@ -15,12 +15,6 @@ import AttachmentButton from './index'; jest.mock('react-intl'); -jest.mock('Platform', () => { - const Platform = require.requireActual('Platform'); - Platform.OS = 'ios'; - return Platform; -}); - describe('AttachmentButton', () => { const formatMessage = jest.fn(); const baseProps = { diff --git a/app/components/badge.js b/app/components/badge.js index ad5a0eff5..9905067cf 100644 --- a/app/components/badge.js +++ b/app/components/badge.js @@ -59,19 +59,19 @@ export default class Badge extends PureComponent { this.mounted = false; } + setBadgeRef = (ref) => { + this.badgeRef = ref; + }; + handlePress = () => { if (this.props.onPress) { this.props.onPress(); } }; - setBadgeRef = (ref) => { - this.badgeContainerRef = ref; - }; - setNativeProps = (props) => { - if (this.mounted && this.badgeContainerRef) { - this.badgeContainerRef.setNativeProps(props); + if (this.mounted && this.badgeRef) { + this.badgeRef.setNativeProps(props); } }; diff --git a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap index fdb73806e..2d62ec3d1 100644 --- a/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap +++ b/app/components/custom_list/user_list_row/__snapshots__/user_list_test.test.js.snap @@ -74,6 +74,7 @@ exports[`UserListRow should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -105,6 +106,7 @@ exports[`UserListRow should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -202,6 +204,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -233,6 +236,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -332,6 +336,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -363,6 +368,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -473,6 +479,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", @@ -504,6 +511,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/components/emoji_picker/emoji_picker.android.js b/app/components/emoji_picker/emoji_picker.android.js index 7eb6a59e5..b480fc0f3 100644 --- a/app/components/emoji_picker/emoji_picker.android.js +++ b/app/components/emoji_picker/emoji_picker.android.js @@ -27,7 +27,7 @@ export default class EmojiPicker extends EmojiPickerBase { diff --git a/app/components/emoji_picker/emoji_picker_base.js b/app/components/emoji_picker/emoji_picker_base.js index 05a3afb61..cafb8788f 100644 --- a/app/components/emoji_picker/emoji_picker_base.js +++ b/app/components/emoji_picker/emoji_picker_base.js @@ -80,7 +80,6 @@ export default class EmojiPicker extends PureComponent { const emojis = this.renderableEmojis(props.emojisBySection, props.deviceWidth); const emojiSectionIndexByOffset = this.measureEmojiSections(emojis); - this.searchBarRef = React.createRef(); this.scrollToSectionTries = 0; this.state = { emojis, @@ -97,8 +96,8 @@ export default class EmojiPicker extends PureComponent { if (this.props.deviceWidth !== nextProps.deviceWidth) { this.rebuildEmojis = true; - if (this.searchBarRef?.current) { - this.searchBarRef.current.blur(); + if (this.searchBarRef) { + this.searchBarRef.blur(); } } @@ -108,6 +107,14 @@ export default class EmojiPicker extends PureComponent { } } + setSearchBarRef = (ref) => { + this.searchBarRef = ref; + } + + setSectionListRef = (ref) => { + this.sectionListRef = ref; + }; + setRebuiltEmojis = (searchBarAnimationComplete = true) => { if (this.rebuildEmojis && searchBarAnimationComplete) { this.rebuildEmojis = false; @@ -253,6 +260,7 @@ export default class EmojiPicker extends PureComponent { } else { listComponent = ( { - this.sectionList.scrollToLocation({ + this.sectionListRef.scrollToLocation({ sectionIndex: index, itemIndex: 0, viewOffset: 25, @@ -423,10 +430,6 @@ export default class EmojiPicker extends PureComponent { }); }; - attachSectionList = (c) => { - this.sectionList = c; - }; - renderFooter = () => { if (!this.state.missingPages) { return null; diff --git a/app/components/file_attachment_list/__snapshots__/file_attachment.test.js.snap b/app/components/file_attachment_list/__snapshots__/file_attachment.test.js.snap index 2e93e4b54..a26f8be29 100644 --- a/app/components/file_attachment_list/__snapshots__/file_attachment.test.js.snap +++ b/app/components/file_attachment_list/__snapshots__/file_attachment.test.js.snap @@ -42,6 +42,7 @@ exports[`FileAttachment should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/components/file_attachment_list/__snapshots__/file_attachment_list.test.js.snap b/app/components/file_attachment_list/__snapshots__/file_attachment_list.test.js.snap index 60e646370..f1387431d 100644 --- a/app/components/file_attachment_list/__snapshots__/file_attachment_list.test.js.snap +++ b/app/components/file_attachment_list/__snapshots__/file_attachment_list.test.js.snap @@ -48,6 +48,7 @@ exports[`PostAttachmentOpenGraph should match snapshot with a single image file "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js b/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js index eb6134f89..896c6d894 100644 --- a/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js +++ b/app/components/file_attachment_list/file_attachment_document/file_attachment_document.js @@ -69,7 +69,7 @@ export default class FileAttachmentDocument extends PureComponent { componentDidMount() { this.mounted = true; - this.eventEmitter = new NativeEventEmitter(NativeModules.RNReactNativeDocViewer); + this.eventEmitter = new NativeEventEmitter(NativeModules.RNDocViewer); this.eventEmitter.addListener('DoneButtonEvent', this.onDonePreviewingFile); } diff --git a/app/components/markdown/markdown_code_block/markdown_code_block.js b/app/components/markdown/markdown_code_block/markdown_code_block.js index b7589e01a..692cf3a6c 100644 --- a/app/components/markdown/markdown_code_block/markdown_code_block.js +++ b/app/components/markdown/markdown_code_block/markdown_code_block.js @@ -5,22 +5,19 @@ import {PropTypes} from 'prop-types'; import React from 'react'; import {intlShape} from 'react-intl'; import { - Platform, Clipboard, StyleSheet, Text, View, } from 'react-native'; -import SyntaxHighlighter from 'react-native-syntax-highlighter'; - import CustomPropTypes from 'app/constants/custom_prop_types'; import FormattedText from 'app/components/formatted_text'; import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import BottomSheet from 'app/utils/bottom_sheet'; -import {getDisplayNameForLanguage, getCodeFont} from 'app/utils/markdown'; +import {getDisplayNameForLanguage} from 'app/utils/markdown'; import {preventDoubleTap} from 'app/utils/tap'; -import {changeOpacity, makeStyleSheetFromTheme, getHighlightStyleFromTheme} from 'app/utils/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; import mattermostManaged from 'app/mattermost_managed'; import {goToScreen} from 'app/actions/navigation'; @@ -43,13 +40,11 @@ export default class MarkdownCodeBlock extends React.PureComponent { }; handlePress = preventDoubleTap(() => { - const {language, content, textStyle} = this.props; + const {language, content} = this.props; const {intl} = this.context; const screen = 'Code'; const passProps = { content, - language, - textStyle, }; const languageDisplayName = getDisplayNameForLanguage(language); @@ -155,26 +150,6 @@ export default class MarkdownCodeBlock extends React.PureComponent { ); } - let textComponent = null; - if (Platform.OS === 'ios') { - textComponent = ( - - {content} - - ); - } else { - textComponent = ( - - {content} - - ); - } - return ( - {textComponent} + + {content} + {plusMoreLines} @@ -227,15 +204,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flexDirection: 'column', flex: 1, paddingHorizontal: 6, - ...Platform.select({ - ios: { - paddingVertical: 0, - backgroundColor: getHighlightStyleFromTheme(theme).hljs.background, - }, - android: { - paddingVertical: 4, - }, - }), + paddingVertical: 4, }, code: { flexDirection: 'row', @@ -245,12 +214,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { color: changeOpacity(theme.centerChannelColor, 0.65), fontSize: 12, lineHeight: 18, - ...Platform.select({ - ios: { - fontFamily: getCodeFont(), - paddingVertical: 6, - }, - }), }, plusMoreLinesText: { color: changeOpacity(theme.centerChannelColor, 0.4), diff --git a/app/components/markdown/markdown_image/markdown_image.js b/app/components/markdown/markdown_image/markdown_image.js index 98ed272ab..61b235c61 100644 --- a/app/components/markdown/markdown_image/markdown_image.js +++ b/app/components/markdown/markdown_image/markdown_image.js @@ -92,6 +92,14 @@ export default class MarkdownImage extends React.Component { this.mounted = false; } + setImageRef = (ref) => { + this.imageRef = ref; + } + + setItemRef = (ref) => { + this.itemRef = ref; + } + getSource = () => { let source = this.props.source; @@ -195,7 +203,7 @@ export default class MarkdownImage extends React.Component { }, }]; - previewImageAtIndex([this.refs.item], 0, files); + previewImageAtIndex([this.itemRef], 0, files); }; loadImageSize = (source) => { @@ -252,7 +260,7 @@ export default class MarkdownImage extends React.Component { style={{width, height}} > {image} diff --git a/app/components/message_attachments/attachment_image/index.js b/app/components/message_attachments/attachment_image/index.js index 6948c3206..b30735032 100644 --- a/app/components/message_attachments/attachment_image/index.js +++ b/app/components/message_attachments/attachment_image/index.js @@ -52,6 +52,14 @@ export default class AttachmentImage extends PureComponent { } } + setImageRef = (ref) => { + this.imageRef = ref; + } + + setItemRef = (ref) => { + this.itemRef = ref; + } + handlePreviewImage = () => { const {imageUrl} = this.props; const { @@ -79,7 +87,7 @@ export default class AttachmentImage extends PureComponent { localPath: uri, }, }]; - previewImageAtIndex([this.refs.item], 0, files); + previewImageAtIndex([this.itemRef], 0, files); }; setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => { @@ -132,7 +140,7 @@ export default class AttachmentImage extends PureComponent { if (imageUri) { progressiveImage = ( {progressiveImage} diff --git a/app/components/pasteable_text_input/custom_text_input.js b/app/components/pasteable_text_input/custom_text_input.js index 9da2e0e13..d9f08d956 100644 --- a/app/components/pasteable_text_input/custom_text_input.js +++ b/app/components/pasteable_text_input/custom_text_input.js @@ -4,10 +4,14 @@ /* eslint-disable no-underscore-dangle */ import React from 'react'; -import {TextInput, Text, TouchableWithoutFeedback} from 'react-native'; -import UIManager from 'UIManager'; +import { + TextInput, + Text, + TouchableWithoutFeedback, + UIManager, + requireNativeComponent, +} from 'react-native'; import invariant from 'invariant'; -import requireNativeComponent from 'requireNativeComponent'; const AndroidTextInput = requireNativeComponent('PasteableTextInputAndroid'); diff --git a/app/components/post_attachment_opengraph/post_attachment_opengraph.js b/app/components/post_attachment_opengraph/post_attachment_opengraph.js index d39ae5182..07337c8f7 100644 --- a/app/components/post_attachment_opengraph/post_attachment_opengraph.js +++ b/app/components/post_attachment_opengraph/post_attachment_opengraph.js @@ -64,6 +64,10 @@ export default class PostAttachmentOpenGraph extends PureComponent { this.mounted = false; } + setItemRef = (ref) => { + this.itemRef = ref; + } + fetchData = (url, openGraphData) => { if (!openGraphData) { this.props.actions.getOpenGraphMetadata(url); @@ -205,7 +209,7 @@ export default class PostAttachmentOpenGraph extends PureComponent { }, }]; - previewImageAtIndex([this.refs.item], 0, files); + previewImageAtIndex([this.itemRef], 0, files); }; renderDescription = () => { @@ -247,7 +251,7 @@ export default class PostAttachmentOpenGraph extends PureComponent { return ( { - return { - alert: jest.fn(), - }; -}); - describe('PostTextBox', () => { const baseProps = { actions: { diff --git a/app/components/root/root.js b/app/components/root/root.js index b81588df8..73db81755 100644 --- a/app/components/root/root.js +++ b/app/components/root/root.js @@ -44,8 +44,12 @@ export default class Root extends PureComponent { } } + setProviderRef = (ref) => { + this.providerRef = ref; + } + handleNoTeams = () => { - if (!this.refs.provider) { + if (!this.providerRef) { setTimeout(this.handleNoTeams, 200); return; } @@ -53,7 +57,7 @@ export default class Root extends PureComponent { }; errorTeamsList = () => { - if (!this.refs.provider) { + if (!this.providerRef) { setTimeout(this.errorTeamsList, 200); return; } @@ -62,7 +66,7 @@ export default class Root extends PureComponent { navigateToTeamsPage = (screen) => { const {currentUrl, theme} = this.props; - const {intl} = this.refs.provider.getChildContext(); + const {intl} = this.providerRef.getChildContext(); let passProps = {theme}; const options = {topBar: {}}; @@ -99,7 +103,7 @@ export default class Root extends PureComponent { return ( diff --git a/app/components/safe_area_view/safe_area_view.ios.test.js b/app/components/safe_area_view/safe_area_view.ios.test.js index 45861f552..a1e47c149 100644 --- a/app/components/safe_area_view/safe_area_view.ios.test.js +++ b/app/components/safe_area_view/safe_area_view.ios.test.js @@ -316,12 +316,12 @@ describe('SafeAreaIos', () => { expect(setSafeAreaInsets).toHaveBeenCalled(); setSafeAreaInsets.mockClear(); - EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; + EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; /* eslint-disable-line require-atomic-updates */ await instance.getSafeAreaInsets(); expect(setSafeAreaInsets).toHaveBeenCalled(); setSafeAreaInsets.mockClear(); - EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets; + EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets; /* eslint-disable-line require-atomic-updates */ await instance.getSafeAreaInsets(); expect(setSafeAreaInsets).not.toHaveBeenCalled(); }); diff --git a/app/components/search_bar/search_bar.android.js b/app/components/search_bar/search_bar.android.js index 9ba535377..7a6575b80 100644 --- a/app/components/search_bar/search_bar.android.js +++ b/app/components/search_bar/search_bar.android.js @@ -76,6 +76,10 @@ export default class SearchBarAndroid extends PureComponent { }; } + setInputRef = (ref) => { + this.inputRef = ref; + } + cancel = () => { this.onCancelButtonPress(); }; @@ -123,11 +127,11 @@ export default class SearchBarAndroid extends PureComponent { }; blur = () => { - this.refs.input.blur(); + this.inputRef.blur(); }; focus = () => { - this.refs.input.focus(); + this.inputRef.focus(); }; render() { @@ -207,7 +211,7 @@ export default class SearchBarAndroid extends PureComponent { /> } { + this.searchRef = ref; + } + cancel = () => { - this.refs.search.onCancel(); + this.searchRef.onCancel(); }; onBlur = () => { @@ -94,18 +98,18 @@ export default class SearchBarIos extends PureComponent { }; blur = () => { - this.refs.search.blur(); + this.searchRef.blur(); }; focus = () => { - this.refs.search.focus(); + this.searchRef.focus(); }; render() { return ( { + this.searchContainerRef = ref; + } + + setInputKeywordRef = (ref) => { + this.inputKeywordRef = ref; + } + blur = () => { - this.refs.input_keyword.getNode().blur(); + this.inputKeywordRef.getNode().blur(); this.setState({expanded: false}); this.collapseAnimation(); }; focus = () => { InteractionManager.runAfterInteractions(() => { - const input = this.refs.input_keyword.getNode(); + const input = this.inputKeywordRef.getNode(); if (!input.isFocused()) { input.focus(); } @@ -384,7 +392,7 @@ export default class Search extends Component { return ( { + this.searchBarRef = ref; + } + cancelSearch = () => { - if (this.refs.search_bar) { - this.refs.search_bar.cancel(); + if (this.searchBarRef) { + this.searchBarRef.cancel(); } }; @@ -135,7 +139,7 @@ export default class ChannelsList extends PureComponent { const title = ( { + this.listRef = ref; + } + getSectionConfigByType = (props, sectionType) => { const {canCreatePrivateChannels, canJoinPublicChannels} = props; @@ -345,8 +349,8 @@ export default class List extends PureComponent { }; scrollToTop = () => { - if (this.refs.list) { - this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle + if (this.listRef?._wrapperListRef) { + this.listRef._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle x: 0, y: 0, animated: true, @@ -402,7 +406,7 @@ export default class List extends PureComponent { onLayout={this.onLayout} > { - if (this.refs.swiper) { - this.refs.swiper.runOnLayout = shouldRun; + if (this.swiperRef) { + this.swiperRef.runOnLayout = shouldRun; } }; diff --git a/app/components/sidebars/main/main_sidebar.js b/app/components/sidebars/main/main_sidebar.js index 58ae8d77a..64b85c63d 100644 --- a/app/components/sidebars/main/main_sidebar.js +++ b/app/components/sidebars/main/main_sidebar.js @@ -37,6 +37,7 @@ export default class ChannelSidebar extends Component { makeDirectChannel: PropTypes.func.isRequired, setChannelDisplayName: PropTypes.func.isRequired, setChannelLoading: PropTypes.func.isRequired, + joinChannel: PropTypes.func.isRequired, }).isRequired, blurPostTextBox: PropTypes.func.isRequired, children: PropTypes.node, @@ -230,10 +231,11 @@ export default class ChannelSidebar extends Component { const { joinChannel, makeDirectChannel, + setChannelLoading, } = actions; this.closeChannelDrawer(); - actions.setChannelLoading(channel.id !== currentChannelId); + setChannelLoading(channel.id !== currentChannelId); setTimeout(async () => { const displayValue = {displayName: channel.display_name}; @@ -263,7 +265,7 @@ export default class ChannelSidebar extends Component { } if (result.error || (!result.data && !result.data.channel)) { - actions.setChannelLoading(false); + setChannelLoading(false); return; } diff --git a/app/components/sidebars/main/main_sidebar.test.js b/app/components/sidebars/main/main_sidebar.test.js index da42e1eed..58ce8428b 100644 --- a/app/components/sidebars/main/main_sidebar.test.js +++ b/app/components/sidebars/main/main_sidebar.test.js @@ -20,6 +20,7 @@ describe('MainSidebar', () => { makeDirectChannel: jest.fn(), setChannelDisplayName: jest.fn(), setChannelLoading: jest.fn(), + joinChannel: jest.fn(), }, blurPostTextBox: jest.fn(), currentTeamId: 'current-team-id', diff --git a/app/components/sidebars/settings/settings_sidebar.js b/app/components/sidebars/settings/settings_sidebar.js index f7d3b4531..6958955ac 100644 --- a/app/components/sidebars/settings/settings_sidebar.js +++ b/app/components/sidebars/settings/settings_sidebar.js @@ -81,20 +81,32 @@ export default class SettingsDrawer extends PureComponent { Dimensions.removeEventListener('change', this.handleDimensions); } + setDrawerRef = (ref) => { + this.drawerRef = ref; + } + confirmReset = (status) => { const {intl} = this.context; confirmOutOfOfficeDisabled(intl, status, this.updateStatus); }; closeSettingsSidebar = () => { - if (this.refs.drawer && this.drawerOpened) { - this.refs.drawer.closeDrawer(); + if (this.drawerRef && this.drawerOpened) { + this.drawerRef.closeDrawer(); + } + }; + + openSettingsSidebar = () => { + this.props.blurPostTextBox(); + + if (this.drawerRef && !this.drawerOpened) { + this.drawerRef.openDrawer(); } }; handleAndroidBack = () => { - if (this.refs.drawer && this.drawerOpened) { - this.refs.drawer.closeDrawer(); + if (this.drawerRef && this.drawerOpened) { + this.drawerRef.closeDrawer(); return true; } @@ -227,12 +239,25 @@ export default class SettingsDrawer extends PureComponent { }); }; - openSettingsSidebar = () => { - this.props.blurPostTextBox(); + updateStatus = (status) => { + const {currentUser: {id: currentUserId}} = this.props; + this.props.actions.setStatus({ + user_id: currentUserId, + status, + }); + }; - if (this.refs.drawer && !this.drawerOpened) { - this.refs.drawer.openDrawer(); + setStatus = (status) => { + const {status: currentUserStatus} = this.props; + + if (currentUserStatus === General.OUT_OF_OFFICE) { + dismissModal(); + this.closeSettingsSidebar(); + this.confirmReset(status); + return; } + this.updateStatus(status); + EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL); }; renderUserStatusIcon = (userId) => { @@ -341,27 +366,6 @@ export default class SettingsDrawer extends PureComponent { ); }; - updateStatus = (status) => { - const {currentUser: {id: currentUserId}} = this.props; - this.props.actions.setStatus({ - user_id: currentUserId, - status, - }); - }; - - setStatus = (status) => { - const {status: currentUserStatus} = this.props; - - if (currentUserStatus === General.OUT_OF_OFFICE) { - dismissModal(); - this.closeSettingsSidebar(); - this.confirmReset(status); - return; - } - this.updateStatus(status); - EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL); - }; - render() { const {children} = this.props; const {deviceWidth, openDrawerOffset} = this.state; @@ -369,7 +373,7 @@ export default class SettingsDrawer extends PureComponent { return ( { + this.inputRef = ref; + } + blur = () => { - this.refs.input.blur(); + this.inputRef.blur(); }; focus = () => { - this.refs.input.focus(); + this.inputRef.focus(); }; render() { @@ -40,7 +44,7 @@ export default class TextInputWithLocalizedPlaceholder extends PureComponent { return ( { - if (this.props.onHide) { - this.props.onHide(); - } - setToolTipVisible(false); - }; - - handleShow = () => { - if (this.props.onShow) { - this.props.onShow(); - } - setToolTipVisible(); - }; - - hideMenu = () => { - if (this.refs.toolTip) { - this.refs.toolTip.hideMenu(); - } - }; - - showMenu = () => { - if (this.refs.toolTip) { - this.refs.toolTip.showMenu(); - } - }; - - componentDidUpdate(prevProps) { - if (prevProps.actions.length !== this.props.actions.length && getToolTipVisible()) { - this.refs.toolTip.hideMenu(); - setTimeout(() => this.refs.toolTip.showMenu(), 1); - } - } - - render() { - return ( - - ); - } -} diff --git a/app/init/emm_provider.js b/app/init/emm_provider.js index 975c494a6..db488854d 100644 --- a/app/init/emm_provider.js +++ b/app/init/emm_provider.js @@ -134,7 +134,7 @@ class EMMProvider { }; showNotSecuredAlert = (translations) => { - return new Promise(async (resolve) => { + return new Promise(async (resolve) => { /* eslint-disable-line no-async-promise-executor */ const options = []; if (Platform.OS === 'android') { diff --git a/app/init/fetch.js b/app/init/fetch.js index 1d1c1c40a..293c0fe57 100644 --- a/app/init/fetch.js +++ b/app/init/fetch.js @@ -102,7 +102,7 @@ Client4.doFetchWithResponse = async (url, options) => { if (headers[HEADER_X_CLUSTER_ID] || headers[HEADER_X_CLUSTER_ID.toLowerCase()]) { const clusterId = headers[HEADER_X_CLUSTER_ID] || headers[HEADER_X_CLUSTER_ID.toLowerCase()]; if (clusterId && Client4.clusterId !== clusterId) { - Client4.clusterId = clusterId; + Client4.clusterId = clusterId; /* eslint-disable-line require-atomic-updates */ } } diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js index c04ec9c16..fec276468 100644 --- a/app/init/global_event_handler.js +++ b/app/init/global_event_handler.js @@ -62,7 +62,7 @@ class GlobalEventHandler { await emmProvider.handleAuthentication(this.store, prompt); } - emmProvider.inBackgroundSince = null; + emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */ }; appInactive = () => { diff --git a/app/init/global_event_handler.test.js b/app/init/global_event_handler.test.js index 8f4c4f9f7..4d9649643 100644 --- a/app/init/global_event_handler.test.js +++ b/app/init/global_event_handler.test.js @@ -30,6 +30,10 @@ jest.mock('react-native-notifications', () => ({ NotificationCategory: jest.fn(), })); +jest.mock('react-native-status-bar-size', () => ({ + addEventListener: jest.fn(), +})); + const mockStore = configureMockStore([thunk]); const store = mockStore(intitialState); GlobalEventHandler.store = store; diff --git a/app/screens/channel/channel_nav_bar/__snapshots__/channel_nav_bar.test.js.snap b/app/screens/channel/channel_nav_bar/__snapshots__/channel_nav_bar.test.js.snap index 7db69c304..329721d83 100644 --- a/app/screens/channel/channel_nav_bar/__snapshots__/channel_nav_bar.test.js.snap +++ b/app/screens/channel/channel_nav_bar/__snapshots__/channel_nav_bar.test.js.snap @@ -38,6 +38,7 @@ exports[`ChannelNavBar should match, full snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/screens/channel/channel_nav_bar/channel_drawer_button/__snapshots__/channel_drawer_button.test.js.snap b/app/screens/channel/channel_nav_bar/channel_drawer_button/__snapshots__/channel_drawer_button.test.js.snap index 42aec1dc8..1f0dd3675 100644 --- a/app/screens/channel/channel_nav_bar/channel_drawer_button/__snapshots__/channel_drawer_button.test.js.snap +++ b/app/screens/channel/channel_nav_bar/channel_drawer_button/__snapshots__/channel_drawer_button.test.js.snap @@ -98,7 +98,7 @@ exports[`ChannelDrawerButton should match, full snapshot 2`] = ` onPress={[Function]} style={ Object { - "backgroundColor": undefined, + "backgroundColor": "#ffffff", "height": 19, "padding": 3, } diff --git a/app/screens/channel/channel_post_list/channel_post_list.js b/app/screens/channel/channel_post_list/channel_post_list.js index 40b66c292..163c79c7f 100644 --- a/app/screens/channel/channel_post_list/channel_post_list.js +++ b/app/screens/channel/channel_post_list/channel_post_list.js @@ -33,6 +33,7 @@ export default class ChannelPostList extends PureComponent { selectPost: PropTypes.func.isRequired, recordLoadTime: PropTypes.func.isRequired, refreshChannelWithRetry: PropTypes.func.isRequired, + setChannelRefreshing: PropTypes.func, }).isRequired, channelId: PropTypes.string.isRequired, channelRefreshingFailed: PropTypes.bool, diff --git a/app/screens/channel_add_members/channel_add_members.js b/app/screens/channel_add_members/channel_add_members.js index 58ec88b2f..114a22ad5 100644 --- a/app/screens/channel_add_members/channel_add_members.js +++ b/app/screens/channel_add_members/channel_add_members.js @@ -116,6 +116,10 @@ export default class ChannelAddMembers extends PureComponent { } } + setSearchBarRef = (ref) => { + this.searchBarRef = ref; + } + clearSearch = () => { this.setState({term: '', searchResults: []}); }; @@ -343,7 +347,7 @@ export default class ChannelAddMembers extends PureComponent { { + this.searchBarRef = ref; + } + clearSearch = () => { this.setState({term: '', searchResults: []}); }; @@ -371,7 +375,7 @@ export default class ChannelMembers extends PureComponent { - {this.props.content} - + ); } else { textComponent = ( @@ -159,7 +145,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flexGrow: 0, flexShrink: 1, width: '100%', - backgroundColor: getHighlightStyleFromTheme(theme).hljs.background, }, code: { paddingHorizontal: 6, @@ -167,6 +152,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { android: { paddingVertical: 4, }, + ios: { + top: -4, + }, }), }, codeText: { @@ -176,7 +164,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { lineHeight: 18, ...Platform.select({ ios: { - top: -11, + margin: 0, + padding: 0, }, }), }, diff --git a/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap b/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap index 2830f2a22..dc81c2143 100644 --- a/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap +++ b/app/screens/edit_profile/__snapshots__/edit_profile.test.js.snap @@ -40,6 +40,7 @@ exports[`edit_profile should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js index cfd96e528..29dc6aa59 100644 --- a/app/screens/edit_profile/edit_profile.js +++ b/app/screens/edit_profile/edit_profile.js @@ -7,7 +7,7 @@ import {intlShape} from 'react-intl'; import {Alert, View} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; -import {DocumentPickerUtil} from 'react-native-document-picker'; +import DocumentPicker from 'react-native-document-picker'; import {Navigation} from 'react-native-navigation'; import {Client4} from 'mattermost-redux/client'; @@ -521,7 +521,7 @@ export default class EditProfile extends PureComponent { currentUser={currentUser} theme={theme} blurTextBox={emptyFunction} - browseFileTypes={DocumentPickerUtil.images()} + browseFileTypes={DocumentPicker.types.images} canTakeVideo={false} canBrowseVideoLibrary={false} maxFileSize={MAX_SIZE} diff --git a/app/screens/error_teams_list/__snapshots__/error_teams_list.test.js.snap b/app/screens/error_teams_list/__snapshots__/error_teams_list.test.js.snap index 4f985c336..16c39697a 100644 --- a/app/screens/error_teams_list/__snapshots__/error_teams_list.test.js.snap +++ b/app/screens/error_teams_list/__snapshots__/error_teams_list.test.js.snap @@ -27,6 +27,7 @@ exports[`ErrorTeamsList should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/screens/flagged_posts/__snapshots__/flagged_posts.test.js.snap b/app/screens/flagged_posts/__snapshots__/flagged_posts.test.js.snap index e6c7cbacd..6c2a19af1 100644 --- a/app/screens/flagged_posts/__snapshots__/flagged_posts.test.js.snap +++ b/app/screens/flagged_posts/__snapshots__/flagged_posts.test.js.snap @@ -23,6 +23,7 @@ exports[`FlaggedPosts should match snapshot 1`] = ` "dndIndicator": "#f74343", "errorTextColor": "#fd5960", "linkColor": "#2389d7", + "mentionBg": "#ffffff", "mentionBj": "#ffffff", "mentionColor": "#145dbf", "mentionHighlightBg": "#ffe577", diff --git a/app/screens/flagged_posts/flagged_posts.js b/app/screens/flagged_posts/flagged_posts.js index 468bb5d3e..61e570c32 100644 --- a/app/screens/flagged_posts/flagged_posts.js +++ b/app/screens/flagged_posts/flagged_posts.js @@ -72,6 +72,10 @@ export default class FlaggedPosts extends PureComponent { } } + setListRef = (ref) => { + this.listRef = ref; + } + goToThread = (post) => { const {actions} = this.props; const channelId = post.channel_id; @@ -212,7 +216,7 @@ export default class FlaggedPosts extends PureComponent { } else if (postIds.length) { component = ( { + this.documents[this.state.index] = ref; + } + + setDownloaderRef = (ref) => { + this.downloaderRef = ref; + } + animateOpenAnimToValue = (toValue, onComplete) => { Animated.timing(this.openAnim, { ...ANIM_CONFIG, @@ -194,9 +202,7 @@ export default class ImagePreview extends PureComponent { return ( { - this.documents[this.state.index] = ref; - }} + ref={this.setDocumentRef} backgroundColor='transparent' canDownloadFiles={canDownloadFiles} file={file} @@ -278,7 +284,7 @@ export default class ImagePreview extends PureComponent { return ( { const file = this.getCurrentFile(); - if (this.refs.downloader) { + if (this.downloaderRef) { EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL); - this.refs.downloader.saveVideo(getLocalFilePathFromFile(VIDEOS_PATH, file)); + this.downloaderRef.saveVideo(getLocalFilePathFromFile(VIDEOS_PATH, file)); } }; diff --git a/app/screens/image_preview/video_preview.js b/app/screens/image_preview/video_preview.js index c58c9f4a9..8e812ac68 100644 --- a/app/screens/image_preview/video_preview.js +++ b/app/screens/image_preview/video_preview.js @@ -68,6 +68,14 @@ export default class VideoPreview extends PureComponent { EventEmitter.off('stop-video-playback', this.stopPlayback); } + setVideoPlayerRef = (ref) => { + this.videoPlayerRef = ref; + } + + setControlsRef = (ref) => { + this.controlsRef = ref; + } + async initializeComponent() { const {file} = this.props; const prefix = Platform.OS === 'android' ? 'file:/' : ''; @@ -210,7 +218,7 @@ export default class VideoPreview extends PureComponent { onPress={() => this.refs.controls.fadeInControls()} >