diff --git a/NOTICE.txt b/NOTICE.txt index 54e9f2dce..ed8df0a81 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2086,43 +2086,6 @@ SOFTWARE. --- -## react-native-tableview - -This product contains 'react-native-tableview' by Pavlo Aksonov. - -Native iOS TableView wrapper for React Native - -* HOMEPAGE: - * https://github.com/aksonov/react-native-tableview#readme - -* LICENSE: BSD-2-Clause - -Copyright (c) 2015, aksonov -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---- - ## react-native-vector-icons This product contains 'react-native-vector-icons' by Joel Arvidsson. diff --git a/app/components/file_attachment_list/file_attachment_document.js b/app/components/file_attachment_list/file_attachment_document.js index af7ac727a..473463227 100644 --- a/app/components/file_attachment_list/file_attachment_document.js +++ b/app/components/file_attachment_list/file_attachment_document.js @@ -25,8 +25,6 @@ import {DeviceTypes} from 'app/constants/'; import mattermostBucket from 'app/mattermost_bucket'; import {changeOpacity} from 'app/utils/theme'; -import LocalConfig from 'assets/config'; - import FileAttachmentIcon from './file_attachment_icon'; const {DOCUMENTS_PATH} = DeviceTypes; @@ -114,7 +112,7 @@ export default class FileAttachmentDocument extends PureComponent { this.setState({didCancel: false}); try { - const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const certificate = await mattermostBucket.getPreference('cert'); const isDir = await RNFetchBlob.fs.isDir(DOCUMENTS_PATH); if (!isDir) { try { diff --git a/app/components/file_upload_preview/file_upload_item/file_upload_item.js b/app/components/file_upload_preview/file_upload_item/file_upload_item.js index 698f37583..fab33a2e3 100644 --- a/app/components/file_upload_preview/file_upload_item/file_upload_item.js +++ b/app/components/file_upload_preview/file_upload_item/file_upload_item.js @@ -15,7 +15,6 @@ import FileUploadRetry from 'app/components/file_upload_preview/file_upload_retr import FileUploadRemove from 'app/components/file_upload_preview/file_upload_remove'; import mattermostBucket from 'app/mattermost_bucket'; import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file'; -import LocalConfig from 'assets/config'; export default class FileUploadItem extends PureComponent { static propTypes = { @@ -137,7 +136,7 @@ export default class FileUploadItem extends PureComponent { Client4.trackEvent('api', 'api_files_upload'); - const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const certificate = await mattermostBucket.getPreference('cert'); const options = { timeout: 10000, certificate, diff --git a/app/components/network_indicator/network_indicator.js b/app/components/network_indicator/network_indicator.js index 9ab511f6e..1c89987f7 100644 --- a/app/components/network_indicator/network_indicator.js +++ b/app/components/network_indicator/network_indicator.js @@ -22,7 +22,6 @@ import mattermostBucket from 'app/mattermost_bucket'; import PushNotifications from 'app/push_notifications'; import networkConnectionListener, {checkConnection} from 'app/utils/network'; import {t} from 'app/utils/i18n'; -import LocalConfig from 'assets/config'; import {RequestStatus} from 'mattermost-redux/constants'; @@ -293,7 +292,7 @@ export default class NetworkIndicator extends PureComponent { const platform = Platform.OS; let certificate = null; if (platform === 'ios') { - certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + certificate = await mattermostBucket.getPreference('cert'); } initWebSocket(platform, null, null, null, {certificate, forceConnection: true}).catch(() => { diff --git a/app/fetch_preconfig.js b/app/fetch_preconfig.js index 7e4860c24..9e3336ca1 100644 --- a/app/fetch_preconfig.js +++ b/app/fetch_preconfig.js @@ -130,7 +130,7 @@ Client4.doFetchWithResponse = async (url, options) => { const initFetchConfig = async () => { let fetchConfig = {}; if (Platform.OS === 'ios') { - const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const certificate = await mattermostBucket.getPreference('cert'); fetchConfig = { auto: true, certificate, diff --git a/app/mattermost.js b/app/mattermost.js index acc650a90..7b282770e 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -322,7 +322,7 @@ const handleAuthentication = async (vendor) => { const translations = app.getTranslations(); if (isSecured) { try { - mattermostBucket.setPreference('emm', vendor, LocalConfig.AppGroupId); + mattermostBucket.setPreference('emm', vendor); await mattermostManaged.authenticate({ reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor), fallbackToPasscode: true, diff --git a/app/mattermost_bucket/index.js b/app/mattermost_bucket/index.js index a1a39eabe..c1458bc13 100644 --- a/app/mattermost_bucket/index.js +++ b/app/mattermost_bucket/index.js @@ -4,17 +4,17 @@ import {NativeModules, Platform} from 'react-native'; // TODO: Remove platform specific once android is implemented -const MattermostBucket = Platform.OS === 'ios' ? NativeModules.MattermostBucket : null; +const MattermostBucket = Platform.OS === 'ios' ? NativeModules.MattermostBucketModule : null; export default { - setPreference: (key, value, groupName) => { + setPreference: (key, value) => { if (MattermostBucket) { - MattermostBucket.setPreference(key, value, groupName); + MattermostBucket.setPreference(key, value); } }, - getPreference: async (key, groupName) => { + getPreference: async (key) => { if (MattermostBucket) { - const value = await MattermostBucket.getPreference(key, groupName); + const value = await MattermostBucket.getPreference(key); if (value) { try { return JSON.parse(value); @@ -26,19 +26,19 @@ export default { return null; }, - removePreference: (key, groupName) => { + removePreference: (key) => { if (MattermostBucket) { - MattermostBucket.removePreference(key, groupName); + MattermostBucket.removePreference(key); } }, - writeToFile: (fileName, content, groupName) => { + writeToFile: (fileName, content) => { if (MattermostBucket) { - MattermostBucket.writeToFile(fileName, content, groupName); + MattermostBucket.writeToFile(fileName, content); } }, - readFromFile: async (fileName, groupName) => { + readFromFile: async (fileName) => { if (MattermostBucket) { - const value = await MattermostBucket.readFromFile(fileName, groupName); + const value = await MattermostBucket.readFromFile(fileName); if (value) { try { return JSON.parse(value); @@ -50,9 +50,9 @@ export default { return null; }, - removeFile: (fileName, groupName) => { + removeFile: (fileName) => { if (MattermostBucket) { - MattermostBucket.removeFile(fileName, groupName); + MattermostBucket.removeFile(fileName); } }, }; diff --git a/app/screens/edit_profile/edit_profile.js b/app/screens/edit_profile/edit_profile.js index 8c8edf6d9..b3d1ddbc0 100644 --- a/app/screens/edit_profile/edit_profile.js +++ b/app/screens/edit_profile/edit_profile.js @@ -24,7 +24,7 @@ import StatusBar from 'app/components/status_bar/index'; import ProfilePictureButton from 'app/components/profile_picture_button'; import ProfilePicture from 'app/components/profile_picture'; import mattermostBucket from 'app/mattermost_bucket'; -import LocalConfig from 'assets/config'; + import {getFormattedFileSize} from 'mattermost-redux/utils/file_utils'; const MAX_SIZE = 20 * 1024 * 1024; @@ -235,7 +235,7 @@ export default class EditProfile extends PureComponent { type: fileData.type, }; - const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const certificate = await mattermostBucket.getPreference('cert'); const options = { timeout: 10000, certificate, diff --git a/app/screens/image_preview/downloader.ios.js b/app/screens/image_preview/downloader.ios.js index ee28e62c4..1c0d631ff 100644 --- a/app/screens/image_preview/downloader.ios.js +++ b/app/screens/image_preview/downloader.ios.js @@ -16,8 +16,6 @@ import mattermostBucket from 'app/mattermost_bucket'; import {getLocalFilePathFromFile} from 'app/utils/file'; import {emptyFunction} from 'app/utils/general'; -import LocalConfig from 'assets/config'; - import DownloaderBottomContent from './downloader_bottom_content.js'; const {View: AnimatedView} = Animated; @@ -236,7 +234,7 @@ export default class Downloader extends PureComponent { this.setState({didCancel: false}); } - const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const certificate = await mattermostBucket.getPreference('cert'); const imageUrl = Client4.getFileUrl(data.id); const options = { session: data.id, diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index 785e0e925..b9285967e 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -187,7 +187,7 @@ export default class SelectServer extends PureComponent { if (LocalConfig.ExperimentalClientSideCertEnable && Platform.OS === 'ios') { RNFetchBlob.cba.selectCertificate((certificate) => { if (certificate) { - mattermostBucket.setPreference('cert', certificate, LocalConfig.AppGroupId); + mattermostBucket.setPreference('cert', certificate); window.fetch = new RNFetchBlob.polyfill.Fetch({ auto: true, certificate, @@ -377,7 +377,7 @@ export default class SelectServer extends PureComponent { const url = this.getUrl(); RNFetchBlob.cba.selectCertificate((certificate) => { if (certificate) { - mattermostBucket.setPreference('cert', certificate, LocalConfig.AppGroupId); + mattermostBucket.setPreference('cert', certificate); fetchConfig().then(() => { this.pingServer(url, true); }); diff --git a/app/store/index.js b/app/store/index.js index bde927cd4..3cb203f3e 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -19,7 +19,6 @@ import {getSiteUrl, setSiteUrl} from 'app/utils/image_cache_manager'; import {createSentryMiddleware} from 'app/utils/sentry/middleware'; import mattermostBucket from 'app/mattermost_bucket'; -import Config from 'assets/config'; import {messageRetention} from './middleware'; import {createThunkMiddleware} from './thunk'; @@ -181,7 +180,7 @@ export default function configureAppStore(initialState) { profilesNotInChannel: [], }, }; - mattermostBucket.writeToFile('entities', JSON.stringify(entities), Config.AppGroupId); + mattermostBucket.writeToFile('entities', JSON.stringify(entities)); } }, 1000)); } @@ -216,9 +215,9 @@ export default function configureAppStore(initialState) { ])); // When logging out remove the data stored in the bucket - mattermostBucket.removePreference('cert', Config.AppGroupId); - mattermostBucket.removePreference('emm', Config.AppGroupId); - mattermostBucket.removeFile('entities', Config.AppGroupId); + mattermostBucket.removePreference('cert'); + mattermostBucket.removePreference('emm'); + mattermostBucket.removeFile('entities'); setSiteUrl(null); setTimeout(() => { diff --git a/app/utils/image_cache_manager.js b/app/utils/image_cache_manager.js index e911d3ba6..c990c6277 100644 --- a/app/utils/image_cache_manager.js +++ b/app/utils/image_cache_manager.js @@ -11,8 +11,6 @@ import {Client4} from 'mattermost-redux/client'; import {DeviceTypes} from 'app/constants'; import mattermostBucket from 'app/mattermost_bucket'; -import LocalConfig from 'assets/config'; - const {IMAGES_PATH} = DeviceTypes; let siteUrl; @@ -34,7 +32,7 @@ export default class ImageCacheManager { addListener(uri, listener); if (uri.startsWith('http')) { try { - const certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + const certificate = await mattermostBucket.getPreference('cert'); const options = { session: uri, timeout: 10000, diff --git a/app/utils/network.js b/app/utils/network.js index 78c02fedb..767eab7da 100644 --- a/app/utils/network.js +++ b/app/utils/network.js @@ -8,7 +8,6 @@ import RNFetchBlob from 'rn-fetch-blob'; import {Client4} from 'mattermost-redux/client'; import mattermostBucket from 'app/mattermost_bucket'; -import LocalConfig from 'assets/config'; let certificate = ''; let previousState; @@ -27,7 +26,7 @@ export async function checkConnection(isConnected) { }; if (Platform.OS === 'ios' && certificate === '') { - certificate = await mattermostBucket.getPreference('cert', LocalConfig.AppGroupId); + certificate = await mattermostBucket.getPreference('cert'); config.certificate = certificate; } diff --git a/assets/base/config.json b/assets/base/config.json index 0a34c1323..f85cd1e50 100644 --- a/assets/base/config.json +++ b/assets/base/config.json @@ -39,8 +39,6 @@ "MobileClientUpgradeAndroidApkLink": "https://about.mattermost.com/mattermost-android-app/", "MobileClientUpgradeIosIpaLink": "https://about.mattermost.com/mattermost-ios-app/", - "AppGroupId": "group.com.mattermost.rnbeta", - "ShowSentryDebugOptions": false, "CustomRequestHeaders": {} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index fb962d6ee..784662677 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -283,6 +283,13 @@ platform :ios do app_group_identifiers: [app_group_id] ) + find_replace_string( + path_to_file: './ios/UploadAttachments/UploadAttachments/Constants.m', + old_string: 'group.com.mattermost.rnbeta', + new_string: app_group_id + ) + + update_app_group_identifiers( entitlements_file: './ios/MattermostShare/MattermostShare.entitlements', app_group_identifiers: [app_group_id] @@ -290,7 +297,6 @@ platform :ios do # Save changes to the config.json file config_json = load_config_json - config_json['AppGroupId'] = app_group_id save_config_json(config_json) # Sync the provisioning profiles using match diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 8dfddba56..80e5c683f 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -50,12 +50,20 @@ 74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74D116AA208A8D3100CF8A79 /* libRNKeychain.a */; }; 7BD159C40A68467FB5A17141 /* FontAwesome5_Solid.ttf in Resources */ = {isa = PBXBuildFile; fileRef = DC1D660B55BE462A9C3B8028 /* FontAwesome5_Solid.ttf */; }; 7F0AA8C121A613A000E4370E /* libRNSentry.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F0AA8BB21A6138200E4370E /* libRNSentry.a */; }; + 7F151D3E221B062700FAD8F3 /* Dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F151D3D221B062700FAD8F3 /* Dummy.swift */; }; + 7F151D41221B069200FAD8F3 /* 0155-keys.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F151D40221B069200FAD8F3 /* 0155-keys.png */; }; + 7F240A1C220D3A2300637665 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240A1B220D3A2300637665 /* ShareViewController.swift */; }; + 7F240A1F220D3A2300637665 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F240A1D220D3A2300637665 /* MainInterface.storyboard */; }; + 7F240A23220D3A2300637665 /* MattermostShare.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F240A19220D3A2300637665 /* MattermostShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F240ACC220D460300637665 /* MattermostBucketModule.m */; }; + 7F240AD0220D4A6100637665 /* KeyChainDataSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7F240ACE220D4A6100637665 /* KeyChainDataSource.mm */; }; + 7F240ADB220E089300637665 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240ADA220E089300637665 /* Item.swift */; }; + 7F240ADD220E094A00637665 /* TeamsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F240ADC220E094A00637665 /* TeamsViewController.swift */; }; 7F2691A11EE1DC6A007574FE /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2691A01EE1DC51007574FE /* libRNNotifications.a */; }; + 7F26C1CC219C463D00FEB42D /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F26C1CB219C463300FEB42D /* libRNCWebView.a */; }; 7F292A711E8AB73400A450A3 /* SplashScreenResource in Resources */ = {isa = PBXBuildFile; fileRef = 7F292A701E8AB73400A450A3 /* SplashScreenResource */; }; 7F292AA61E8ABB1100A450A3 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */; }; 7F292AA71E8ABB1100A450A3 /* splash.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA51E8ABB1100A450A3 /* splash.png */; }; - 7F380D0B1FDB3CFC0061AAD2 /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F93F9D91FBB726B0088E416 /* libRCTVideo.a */; }; - 7F3F66021FE426EE0085CA0E /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */; }; 7F43D5D61F6BF8C2001FC614 /* libRNLocalAuth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49871F3DFC30003A22BA /* libRNLocalAuth.a */; }; 7F43D5D71F6BF8D0001FC614 /* libRNPasscodeStatus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49F81F3E0710003A22BA /* libRNPasscodeStatus.a */; }; 7F43D5D81F6BF8E9001FC614 /* libJailMonkey.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C4A621F3E21FB003A22BA /* libJailMonkey.a */; }; @@ -66,47 +74,24 @@ 7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */; }; 7F43D63F1F6BFA19001FC614 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D8FEC21E80B5230091F3BD /* libBVLinearGradient.a */; }; 7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */; }; - 7F50C96A203C6E80007CA374 /* SessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F50C969203C6E80007CA374 /* SessionManager.m */; }; - 7F50C96B203C6E80007CA374 /* SessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F50C969203C6E80007CA374 /* SessionManager.m */; }; 7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; }; 7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; }; - 7F642DF1209353A400F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; }; - 7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */; }; + 7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; }; + 7F72F2F0221123E200F98FFF /* GenericPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F72F2EF221123E200F98FFF /* GenericPreview.swift */; }; + 7F72F2F322112EC700F98FFF /* generic.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2F222112EC700F98FFF /* generic.png */; }; 7F7D7F98201645E100D31155 /* libReactNativePermissions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F7D7F87201645D300D31155 /* libReactNativePermissions.a */; }; - 7F95132F208646CE00616E3E /* 0155-keys.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F9512EA208646CD00616E3E /* 0155-keys.png */; }; - 7F97F2312217989F00F5BCCC /* libRNCWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F26C1CB219C463300FEB42D /* libRNCWebView.a */; }; + 7FABDFC22211A39000D0F595 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABDFC12211A39000D0F595 /* Section.swift */; }; + 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 */; }; 7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; }; 7FC200E81EBB65370099331B /* libReactNativeNavigation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */; }; - 7FC649EE1FE983660074E4C7 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 349FBA7338E74D9BBD709528 /* EvilIcons.ttf */; }; - 7FC649F01FE9B5D90074E4C7 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */; }; - 7FCC63CA221C5291002EC8A5 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 7FCC63CC221C53BC002EC8A5 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FCC63CB221C53BB002EC8A5 /* JavaScriptCore.framework */; }; - 7FD8DEDE2029EDB7001AAC5E /* libRNTableView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FD8DEDA2029ECDF001AAC5E /* libRNTableView.a */; }; 7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */; }; 7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB10971F6101710039A015 /* BlurAppScreen.m */; }; 7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; }; 7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */; }; 7FF2AF8B2086483E00FFBDF4 /* KeyShareConsumer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */; }; 7FF31C1421330B7900680B75 /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FF31C1321330B4200680B75 /* libRNFetchBlob.a */; }; - 7FF31C1521330BAD00680B75 /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FF31C1321330B4200680B75 /* libRNFetchBlob.a */; }; - 7FF7BE2C1FDEE4AE005E55FE /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FEB109A1F61019C0039A015 /* MattermostManaged.m */; }; - 7FF7BE6D1FDEE5E8005E55FE /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */; }; - 7FF7BE6E1FDEE5E8005E55FE /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */; }; - 7FF7BE6F1FDF3CE4005E55FE /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; }; - 7FF7BE701FDF3EE7005E55FE /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */; }; - 7FFDB3191FE3566C009E3BCF /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 005346E5C0E542BFABAE1411 /* FontAwesome.ttf */; }; - 7FFE329E1FD9CB650038C7A0 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFE329D1FD9CB650038C7A0 /* ShareViewController.m */; }; - 7FFE32A11FD9CB650038C7A0 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7FFE329F1FD9CB650038C7A0 /* MainInterface.storyboard */; }; - 7FFE32A51FD9CB650038C7A0 /* MattermostShare.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7FFE32E71FD9CCD00038C7A0 /* libART.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37ABD39C1F4CE13B001FDE6B /* libART.a */; }; - 7FFE32E81FD9CCDE0038C7A0 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3752184F1F4B9E980035444B /* libRCTCameraRoll.a */; }; - 7FFE32E91FD9CCF40038C7A0 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; }; - 7FFE32EB1FD9CD170038C7A0 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; - 7FFE32EC1FD9CD360038C7A0 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; - 7FFE32ED1FD9CD450038C7A0 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; - 7FFE32EE1FD9CD800038C7A0 /* libRNLocalAuth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49871F3DFC30003A22BA /* libRNLocalAuth.a */; }; - 7FFE32EF1FD9CD800038C7A0 /* libRNPasscodeStatus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49F81F3E0710003A22BA /* libRNPasscodeStatus.a */; }; - 7FFE32F11FD9D64E0038C7A0 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; 895C9A56B94A45C1BAF568FE /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */; }; 8D26455C994F46C39B1392F2 /* libRNSafeArea.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9263CF9B16054263B13EA23B /* libRNSafeArea.a */; }; @@ -384,6 +369,13 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = RNSentry; }; + 7F240A21220D3A2300637665 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7F240A18220D3A2300637665; + remoteInfo = MattermostShare; + }; 7F26919F1EE1DC51007574FE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */; @@ -433,6 +425,34 @@ remoteGlobalIDString = 3DBE0D0D1F3B181C0099AA32; remoteInfo = "fishhook-tvOS"; }; + 7F581C71221DF8DE0099E66B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EDEBC6D6214B3E7000DD5AC8; + remoteInfo = jsi; + }; + 7F581C73221DF8DE0099E66B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = EDEBC73B214B45A300DD5AC8; + remoteInfo = jsiexecutor; + }; + 7F581C75221DF8DE0099E66B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = ED296FB6214C9A0900B7C4FE; + remoteInfo = "jsi-tvOS"; + }; + 7F581C77221DF8DE0099E66B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = ED296FEE214C9CF800B7C4FE; + remoteInfo = "jsiexecutor-tvOS"; + }; 7F5CA990208FE38F004F91CE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */; @@ -531,6 +551,13 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = ReactNativeExceptionHandler; }; + 7FABE04422137F2A00D0F595 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 7FABE03622137F2900D0F595; + remoteInfo = UploadAttachments; + }; 7FC200DE1EBB65100099331B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */; @@ -538,41 +565,6 @@ remoteGlobalIDString = D8AFADBD1BEE6F3F00A4592D; remoteInfo = ReactNativeNavigation; }; - 7FCC63AA221C5270002EC8A5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = EDEBC6D6214B3E7000DD5AC8; - remoteInfo = jsi; - }; - 7FCC63AC221C5270002EC8A5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = EDEBC73B214B45A300DD5AC8; - remoteInfo = jsiexecutor; - }; - 7FCC63AE221C5270002EC8A5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = ED296FB6214C9A0900B7C4FE; - remoteInfo = "jsi-tvOS"; - }; - 7FCC63B0221C5270002EC8A5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = ED296FEE214C9CF800B7C4FE; - remoteInfo = "jsiexecutor-tvOS"; - }; - 7FD8DED92029ECDF001AAC5E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 873769951B7CD6E900F7C3C2; - remoteInfo = RNTableView; - }; 7FDB92A61F706F45006CDFD1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */; @@ -622,13 +614,6 @@ remoteGlobalIDString = 6DA7B8031F692C4C00FD1D50; remoteInfo = RNSafeArea; }; - 7FFE32A31FD9CB650038C7A0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 7FFE32991FD9CB640038C7A0; - remoteInfo = MattermostShare; - }; 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; @@ -655,7 +640,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - 7FFE32A51FD9CB650038C7A0 /* MattermostShare.appex in Embed App Extensions */, + 7F240A23220D3A2300637665 /* MattermostShare.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -728,28 +713,41 @@ 79CB6EBA24FE4ABFB0C155F0 /* YTPlayerView-iframe-player.html */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "YTPlayerView-iframe-player.html"; path = "../node_modules/react-native-youtube/assets/YTPlayerView-iframe-player.html"; sourceTree = ""; }; 7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = BVLinearGradient.xcodeproj; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = ""; }; 7F0AA87621A6138200E4370E /* RNSentry.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNSentry.xcodeproj; path = "../node_modules/react-native-sentry/ios/RNSentry.xcodeproj"; sourceTree = ""; }; + 7F151D3D221B062700FAD8F3 /* Dummy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Dummy.swift; path = Mattermost/Dummy.swift; sourceTree = ""; }; + 7F151D40221B069200FAD8F3 /* 0155-keys.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "0155-keys.png"; path = "Mattermost/0155-keys.png"; sourceTree = ""; }; + 7F151D42221B07F700FAD8F3 /* MattermostShare-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MattermostShare-Bridging-Header.h"; sourceTree = ""; }; + 7F151D43221B082A00FAD8F3 /* Mattermost-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Mattermost-Bridging-Header.h"; path = "Mattermost/Mattermost-Bridging-Header.h"; sourceTree = ""; }; + 7F240A19220D3A2300637665 /* MattermostShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MattermostShare.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 7F240A1B220D3A2300637665 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + 7F240A1E220D3A2300637665 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 7F240A20220D3A2300637665 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7F240A27220D3A7E00637665 /* MattermostShare.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MattermostShare.entitlements; sourceTree = ""; }; + 7F240ACB220D460300637665 /* MattermostBucketModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MattermostBucketModule.h; path = Mattermost/MattermostBucketModule.h; sourceTree = ""; }; + 7F240ACC220D460300637665 /* MattermostBucketModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MattermostBucketModule.m; path = Mattermost/MattermostBucketModule.m; sourceTree = ""; }; + 7F240ACE220D4A6100637665 /* KeyChainDataSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = KeyChainDataSource.mm; path = "../../node_modules/rn-fetch-blob/ios/KeyShareConsumer/KeyChainDataSource.mm"; sourceTree = ""; }; + 7F240ACF220D4A6100637665 /* KeyChainDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeyChainDataSource.h; path = "../../node_modules/rn-fetch-blob/ios/KeyShareConsumer/KeyChainDataSource.h"; sourceTree = ""; }; + 7F240ADA220E089300637665 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; + 7F240ADC220E094A00637665 /* TeamsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamsViewController.swift; sourceTree = ""; }; 7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = ""; }; 7F26C1C6219C463300FEB42D /* RNCWebView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNCWebView.xcodeproj; path = "../node_modules/react-native-webview/ios/RNCWebView.xcodeproj"; sourceTree = ""; }; 7F292A701E8AB73400A450A3 /* SplashScreenResource */ = {isa = PBXFileReference; lastKnownFileType = folder; path = SplashScreenResource; sourceTree = ""; }; 7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = LaunchScreen.xib; path = SplashScreenResource/LaunchScreen.xib; sourceTree = ""; }; 7F292AA51E8ABB1100A450A3 /* splash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = splash.png; path = SplashScreenResource/splash.png; sourceTree = ""; }; - 7F380D0A1FDB28160061AAD2 /* MattermostShare.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MattermostShare.entitlements; sourceTree = ""; }; 7F43D5DF1F6BF994001FC614 /* libRNSVG.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libRNSVG.a; path = "../../../../../../../Library/Developer/Xcode/DerivedData/Mattermost-czlinsdviifujheezzjvmisotjrm/Build/Products/Debug-iphonesimulator/libRNSVG.a"; sourceTree = ""; }; 7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-Mattermost.a"; path = "../../../../../../../Library/Developer/Xcode/DerivedData/Mattermost-czlinsdviifujheezzjvmisotjrm/Build/Products/Debug-iphonesimulator/libPods-Mattermost.a"; sourceTree = ""; }; - 7F50C968203C6E80007CA374 /* SessionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SessionManager.h; sourceTree = ""; }; - 7F50C969203C6E80007CA374 /* SessionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SessionManager.m; sourceTree = ""; }; 7F54ABFAE6CE4A6DB11D1ED7 /* Roboto-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-BlackItalic.ttf"; path = "../assets/fonts/Roboto-BlackItalic.ttf"; sourceTree = ""; }; 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = ""; }; 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = ""; }; 7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = ""; }; 7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDeviceInfo.xcodeproj; path = "../node_modules/react-native-device-info/ios/RNDeviceInfo.xcodeproj"; sourceTree = ""; }; - 7F6C47A31FE87E8C00F5A912 /* PerformRequests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformRequests.h; sourceTree = ""; }; - 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformRequests.m; sourceTree = ""; }; + 7F72F2ED2211220500F98FFF /* GenericPreview.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GenericPreview.xib; sourceTree = ""; }; + 7F72F2EF221123E200F98FFF /* GenericPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPreview.swift; sourceTree = ""; }; + 7F72F2F222112EC700F98FFF /* generic.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = generic.png; sourceTree = ""; }; 7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativePermissions.xcodeproj; path = "../node_modules/react-native-permissions/ios/ReactNativePermissions.xcodeproj"; sourceTree = ""; }; - 7F9512EA208646CD00616E3E /* 0155-keys.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "0155-keys.png"; sourceTree = ""; }; + 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 = ""; }; 7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; }; - 7FCC63CB221C53BB002EC8A5 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - 7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNTableView.xcodeproj; path = "../node_modules/react-native-tableview/RNTableView.xcodeproj"; 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 = ""; }; @@ -759,13 +757,6 @@ 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ImageEffects.m"; path = "Mattermost/UIImage+ImageEffects.m"; sourceTree = ""; }; 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyShareConsumer.storyboard; sourceTree = ""; }; 7FF31C0E21330B4200680B75 /* RNFetchBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNFetchBlob.xcodeproj; path = "../node_modules/rn-fetch-blob/ios/RNFetchBlob.xcodeproj"; sourceTree = ""; }; - 7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MattermostBucket.h; sourceTree = ""; }; - 7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = ""; }; - 7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MattermostShare.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 7FFE329C1FD9CB650038C7A0 /* ShareViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareViewController.h; sourceTree = ""; }; - 7FFE329D1FD9CB650038C7A0 /* ShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = ""; }; - 7FFE32A01FD9CB650038C7A0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; - 7FFE32A21FD9CB650038C7A0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7FFE32B51FD9CCAA0038C7A0 /* FLAnimatedImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FLAnimatedImage.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7FFE32B61FD9CCAA0038C7A0 /* KSCrash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KSCrash.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7FFE32B71FD9CCAA0038C7A0 /* KSCrash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KSCrash.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -822,6 +813,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7FABE04622137F5C00D0F595 /* libUploadAttachments.a in Frameworks */, 74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */, 37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */, 375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */, @@ -854,38 +846,22 @@ 7FDB92B11F706F58006CDFD1 /* libRNImagePicker.a in Frameworks */, 7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */, 7F43D5DE1F6BF96A001FC614 /* libRCTYouTube.a in Frameworks */, - 7F97F2312217989F00F5BCCC /* libRNCWebView.a in Frameworks */, + 7F26C1CC219C463D00FEB42D /* libRNCWebView.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 */, C3BD62ED5B5940AB8C0AC678 /* libz.tbd in Frameworks */, 3F55075810254369AB39F032 /* libRNGestureHandler.a in Frameworks */, - 7F43D6061F6BF9EB001FC614 /* libPods-Mattermost.a in Frameworks */, - A9B746D2CFA9CEBFB8AE2B5B /* libPods-Mattermost.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 7FFE32971FD9CB640038C7A0 /* Frameworks */ = { + 7F240A16220D3A2300637665 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7FCC63CC221C53BC002EC8A5 /* JavaScriptCore.framework in Frameworks */, - 7FCC63CA221C5291002EC8A5 /* libReact.a in Frameworks */, - 7FFE32E71FD9CCD00038C7A0 /* libART.a in Frameworks */, - 7F642DF1209353A400F3165E /* libRNDeviceInfo.a in Frameworks */, - 7FF7BE6F1FDF3CE4005E55FE /* libRNVectorIcons.a in Frameworks */, - 7F380D0B1FDB3CFC0061AAD2 /* libRCTVideo.a in Frameworks */, - 7FFE32F11FD9D64E0038C7A0 /* libRCTWebSocket.a in Frameworks */, - 7FFE32EE1FD9CD800038C7A0 /* libRNLocalAuth.a in Frameworks */, - 7FFE32EF1FD9CD800038C7A0 /* libRNPasscodeStatus.a in Frameworks */, - 7F3F66021FE426EE0085CA0E /* libRNSVG.a in Frameworks */, - 7FFE32ED1FD9CD450038C7A0 /* libRCTNetwork.a in Frameworks */, - 7FF31C1521330BAD00680B75 /* libRNFetchBlob.a in Frameworks */, - 7FFE32EC1FD9CD360038C7A0 /* libRCTText.a in Frameworks */, - 7FFE32EB1FD9CD170038C7A0 /* libRCTImage.a in Frameworks */, - 7FFE32E91FD9CCF40038C7A0 /* libRCTAnimation.a in Frameworks */, - 7FFE32E81FD9CCDE0038C7A0 /* libRCTCameraRoll.a in Frameworks */, - 7FD8DEDE2029EDB7001AAC5E /* libRNTableView.a in Frameworks */, + 7FABE0562213884700D0F595 /* libUploadAttachments.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1019,23 +995,25 @@ 13B07FAE1A68108700A75B9A /* Mattermost */ = { isa = PBXGroup; children = ( + 7F151D43221B082A00FAD8F3 /* Mattermost-Bridging-Header.h */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, 7FEB10961F6101710039A015 /* BlurAppScreen.h */, 7FEB10971F6101710039A015 /* BlurAppScreen.m */, + 7F151D3D221B062700FAD8F3 /* Dummy.swift */, 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, 7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */, 7FF2AF8A2086483E00FFBDF4 /* KeyShareConsumer.storyboard */, 13B07FB71A68108700A75B9A /* main.m */, 7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */, - 7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */, - 7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */, + 7F240ACB220D460300637665 /* MattermostBucketModule.h */, + 7F240ACC220D460300637665 /* MattermostBucketModule.m */, 7FEB10991F61019C0039A015 /* MattermostManaged.h */, 7FEB109A1F61019C0039A015 /* MattermostManaged.m */, 7FEB109B1F61019C0039A015 /* UIImage+ImageEffects.h */, 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */, - 7F9512EA208646CD00616E3E /* 0155-keys.png */, + 7F151D40221B069200FAD8F3 /* 0155-keys.png */, 7F292AA51E8ABB1100A450A3 /* splash.png */, ); name = Mattermost; @@ -1056,10 +1034,10 @@ 37DF8AF41F5F0D430079BF89 /* libthird-party.a */, 37DF8AF61F5F0D430079BF89 /* libdouble-conversion.a */, 37DF8AF81F5F0D430079BF89 /* libdouble-conversion.a */, - 7FCC63AB221C5270002EC8A5 /* libjsi.a */, - 7FCC63AD221C5270002EC8A5 /* libjsiexecutor.a */, - 7FCC63AF221C5270002EC8A5 /* libjsi-tvOS.a */, - 7FCC63B1221C5270002EC8A5 /* libjsiexecutor-tvOS.a */, + 7F581C72221DF8DE0099E66B /* libjsi.a */, + 7F581C74221DF8DE0099E66B /* libjsiexecutor.a */, + 7F581C76221DF8DE0099E66B /* libjsi-tvOS.a */, + 7F581C78221DF8DE0099E66B /* libjsiexecutor-tvOS.a */, ); name = Products; sourceTree = ""; @@ -1120,7 +1098,6 @@ 4B992D7BAAEDF8759DB525B5 /* Frameworks */ = { isa = PBXGroup; children = ( - 7FCC63CB221C53BB002EC8A5 /* JavaScriptCore.framework */, 74D116A3208A8C8400CF8A79 /* libRNKeychain.a */, 7FFE32B51FD9CCAA0038C7A0 /* FLAnimatedImage.framework */, 7FFE32B61FD9CCAA0038C7A0 /* KSCrash.framework */, @@ -1188,6 +1165,27 @@ name = Products; sourceTree = ""; }; + 7F240A1A220D3A2300637665 /* MattermostShare */ = { + isa = PBXGroup; + children = ( + 7F72F2E4221113DF00F98FFF /* Images */, + 7F240A20220D3A2300637665 /* Info.plist */, + 7F240ACF220D4A6100637665 /* KeyChainDataSource.h */, + 7F240ACE220D4A6100637665 /* KeyChainDataSource.mm */, + 7F240A27220D3A7E00637665 /* MattermostShare.entitlements */, + 7F240A1D220D3A2300637665 /* MainInterface.storyboard */, + 7F151D42221B07F700FAD8F3 /* MattermostShare-Bridging-Header.h */, + 7F240ADA220E089300637665 /* Item.swift */, + 7FABE0092212650600D0F595 /* ChannelsViewController.swift */, + 7F240A1B220D3A2300637665 /* ShareViewController.swift */, + 7F240ADC220E094A00637665 /* TeamsViewController.swift */, + 7F72F2ED2211220500F98FFF /* GenericPreview.xib */, + 7F72F2EF221123E200F98FFF /* GenericPreview.swift */, + 7FABDFC12211A39000D0F595 /* Section.swift */, + ); + path = MattermostShare; + sourceTree = ""; + }; 7F26919C1EE1DC51007574FE /* Products */ = { isa = PBXGroup; children = ( @@ -1246,6 +1244,14 @@ name = Products; sourceTree = ""; }; + 7F72F2E4221113DF00F98FFF /* Images */ = { + isa = PBXGroup; + children = ( + 7F72F2F222112EC700F98FFF /* generic.png */, + ); + path = Images; + sourceTree = ""; + }; 7F7D7F53201645D300D31155 /* Products */ = { isa = PBXGroup; children = ( @@ -1295,18 +1301,18 @@ name = Products; sourceTree = ""; }; - 7FC200BD1EBB65100099331B /* Products */ = { + 7FABE04122137F2900D0F595 /* Products */ = { isa = PBXGroup; children = ( - 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */, + 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */, ); name = Products; sourceTree = ""; }; - 7FD8DE982029ECDE001AAC5E /* Products */ = { + 7FC200BD1EBB65100099331B /* Products */ = { isa = PBXGroup; children = ( - 7FD8DEDA2029ECDF001AAC5E /* libRNTableView.a */, + 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */, ); name = Products; sourceTree = ""; @@ -1361,32 +1367,16 @@ name = Products; sourceTree = ""; }; - 7FFE329B1FD9CB650038C7A0 /* MattermostShare */ = { - isa = PBXGroup; - children = ( - 7F380D0A1FDB28160061AAD2 /* MattermostShare.entitlements */, - 7FFE329C1FD9CB650038C7A0 /* ShareViewController.h */, - 7FFE329D1FD9CB650038C7A0 /* ShareViewController.m */, - 7FFE329F1FD9CB650038C7A0 /* MainInterface.storyboard */, - 7FFE32A21FD9CB650038C7A0 /* Info.plist */, - 7F6C47A31FE87E8C00F5A912 /* PerformRequests.h */, - 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */, - 7F50C968203C6E80007CA374 /* SessionManager.h */, - 7F50C969203C6E80007CA374 /* SessionManager.m */, - ); - path = MattermostShare; - sourceTree = ""; - }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */, 7F0AA87621A6138200E4370E /* RNSentry.xcodeproj */, 7F26C1C6219C463300FEB42D /* RNCWebView.xcodeproj */, 7FF31C0E21330B4200680B75 /* RNFetchBlob.xcodeproj */, 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */, 7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */, 74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */, - 7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */, 7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */, 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */, 37ABD3971F4CE13B001FDE6B /* ART.xcodeproj */, @@ -1434,11 +1424,11 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( - 7F292A701E8AB73400A450A3 /* SplashScreenResource */, 13B07FAE1A68108700A75B9A /* Mattermost */, + 7F240A1A220D3A2300637665 /* MattermostShare */, + 7F292A701E8AB73400A450A3 /* SplashScreenResource */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* MattermostTests */, - 7FFE329B1FD9CB650038C7A0 /* MattermostShare */, 83CBBA001A601CBA00E9B192 /* Products */, 0156F464626F49C2977D7982 /* Resources */, 37DF8AC51F5F0D410079BF89 /* Recovered References */, @@ -1454,7 +1444,7 @@ children = ( 13B07F961A680F5B00A75B9A /* Mattermost.app */, 00E356EE1AD99517003FC87E /* MattermostTests.xctest */, - 7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */, + 7F240A19220D3A2300637665 /* MattermostShare.appex */, ); name = Products; sourceTree = ""; @@ -1497,21 +1487,20 @@ buildRules = ( ); dependencies = ( - 7FFE32A41FD9CB650038C7A0 /* PBXTargetDependency */, + 7F240A22220D3A2300637665 /* PBXTargetDependency */, ); name = Mattermost; productName = "Hello World"; productReference = 13B07F961A680F5B00A75B9A /* Mattermost.app */; productType = "com.apple.product-type.application"; }; - 7FFE32991FD9CB640038C7A0 /* MattermostShare */ = { + 7F240A18220D3A2300637665 /* MattermostShare */ = { isa = PBXNativeTarget; - buildConfigurationList = 7FFE32A61FD9CB650038C7A0 /* Build configuration list for PBXNativeTarget "MattermostShare" */; + buildConfigurationList = 7F240A24220D3A2300637665 /* Build configuration list for PBXNativeTarget "MattermostShare" */; buildPhases = ( - 7FFE32961FD9CB640038C7A0 /* Sources */, - 7FFE32971FD9CB640038C7A0 /* Frameworks */, - 7FFE32981FD9CB640038C7A0 /* Resources */, - 7FFE32F01FD9D1F00038C7A0 /* Bundle React Native code and images */, + 7F240A15220D3A2300637665 /* Sources */, + 7F240A16220D3A2300637665 /* Frameworks */, + 7F240A17220D3A2300637665 /* Resources */, ); buildRules = ( ); @@ -1519,7 +1508,7 @@ ); name = MattermostShare; productName = MattermostShare; - productReference = 7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */; + productReference = 7F240A19220D3A2300637665 /* MattermostShare.appex */; productType = "com.apple.product-type.app-extension"; }; /* End PBXNativeTarget section */ @@ -1528,6 +1517,7 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1010; LastUpgradeCheck = 820; ORGANIZATIONNAME = Facebook; TargetAttributes = { @@ -1537,6 +1527,7 @@ }; 13B07F861A680F5B00A75B9A = { DevelopmentTeam = UQ8HT4Q2XM; + LastSwiftMigration = 1010; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -1556,21 +1547,14 @@ }; }; }; - 7FFE32991FD9CB640038C7A0 = { - CreatedOnToolsVersion = 9.1; + 7F240A18220D3A2300637665 = { + CreatedOnToolsVersion = 10.1; DevelopmentTeam = UQ8HT4Q2XM; - LastSwiftMigration = 920; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; }; - com.apple.Keychain = { - enabled = 1; - }; - com.apple.iCloud = { - enabled = 1; - }; }; }; }; @@ -1731,20 +1715,20 @@ ProductGroup = 7FDF28C41E1F4B1F00DBBE56 /* Products */; ProjectRef = 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */; }, - { - ProductGroup = 7FD8DE982029ECDE001AAC5E /* Products */; - ProjectRef = 7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */; - }, { ProductGroup = 7FDF28EE1E1F4B4E00DBBE56 /* Products */; ProjectRef = 2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */; }, + { + ProductGroup = 7FABE04122137F2900D0F595 /* Products */; + ProjectRef = 7FABE04022137F2900D0F595 /* UploadAttachments.xcodeproj */; + }, ); projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* Mattermost */, 00E356ED1AD99517003FC87E /* MattermostTests */, - 7FFE32991FD9CB640038C7A0 /* MattermostShare */, + 7F240A18220D3A2300637665 /* MattermostShare */, ); }; /* End PBXProject section */ @@ -2044,6 +2028,34 @@ remoteRef = 7F4C1F851FBE572B0029D1DF /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 7F581C72221DF8DE0099E66B /* libjsi.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjsi.a; + remoteRef = 7F581C71221DF8DE0099E66B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7F581C74221DF8DE0099E66B /* libjsiexecutor.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libjsiexecutor.a; + remoteRef = 7F581C73221DF8DE0099E66B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7F581C76221DF8DE0099E66B /* libjsi-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libjsi-tvOS.a"; + remoteRef = 7F581C75221DF8DE0099E66B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7F581C78221DF8DE0099E66B /* libjsiexecutor-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libjsiexecutor-tvOS.a"; + remoteRef = 7F581C77221DF8DE0099E66B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -2142,6 +2154,13 @@ remoteRef = 7FA7950A1F61A1A500C02924 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libUploadAttachments.a; + remoteRef = 7FABE04422137F2A00D0F595 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 7FC200DF1EBB65100099331B /* libReactNativeNavigation.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -2149,41 +2168,6 @@ remoteRef = 7FC200DE1EBB65100099331B /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 7FCC63AB221C5270002EC8A5 /* libjsi.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libjsi.a; - remoteRef = 7FCC63AA221C5270002EC8A5 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 7FCC63AD221C5270002EC8A5 /* libjsiexecutor.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libjsiexecutor.a; - remoteRef = 7FCC63AC221C5270002EC8A5 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 7FCC63AF221C5270002EC8A5 /* libjsi-tvOS.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libjsi-tvOS.a"; - remoteRef = 7FCC63AE221C5270002EC8A5 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 7FCC63B1221C5270002EC8A5 /* libjsiexecutor-tvOS.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libjsiexecutor-tvOS.a"; - remoteRef = 7FCC63B0221C5270002EC8A5 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 7FD8DEDA2029ECDF001AAC5E /* libRNTableView.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRNTableView.a; - remoteRef = 7FD8DED92029ECDF001AAC5E /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 7FDB92A71F706F45006CDFD1 /* libRNImagePicker.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -2254,6 +2238,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7F151D41221B069200FAD8F3 /* 0155-keys.png in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 895C9A56B94A45C1BAF568FE /* Entypo.ttf in Resources */, 3D38ABA732A34A9BB3294F90 /* EvilIcons.ttf in Resources */, @@ -2273,7 +2258,6 @@ 55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */, D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */, 0111A42B7F264BCF8CBDE3ED /* OpenSans-ExtraBoldItalic.ttf in Resources */, - 7F95132F208646CE00616E3E /* 0155-keys.png in Resources */, 0C0D24F53F254F75869E5951 /* OpenSans-Italic.ttf in Resources */, 2D5296A8926B4D7FBAF2D6E2 /* OpenSans-Light.ttf in Resources */, F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */, @@ -2301,15 +2285,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7FFE32981FD9CB640038C7A0 /* Resources */ = { + 7F240A17220D3A2300637665 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7FC649F01FE9B5D90074E4C7 /* OpenSans-Bold.ttf in Resources */, - 7FC649EE1FE983660074E4C7 /* EvilIcons.ttf in Resources */, - 7FF7BE701FDF3EE7005E55FE /* Ionicons.ttf in Resources */, - 7FFDB3191FE3566C009E3BCF /* FontAwesome.ttf in Resources */, - 7FFE32A11FD9CB650038C7A0 /* MainInterface.storyboard in Resources */, + 7F240A1F220D3A2300637665 /* MainInterface.storyboard in Resources */, + 7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */, + 7F72F2F322112EC700F98FFF /* generic.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2348,20 +2330,6 @@ 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; }; - 7FFE32F01FD9D1F00038C7A0 /* Bundle React Native code and images */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Bundle React Native code and images"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = ./bundleReactNative.sh; - }; AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -2410,24 +2378,26 @@ buildActionMask = 2147483647; files = ( 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 7F151D3E221B062700FAD8F3 /* Dummy.swift in Sources */, 7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, - 7FF7BE6D1FDEE5E8005E55FE /* MattermostBucket.m in Sources */, 7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */, - 7F50C96A203C6E80007CA374 /* SessionManager.m in Sources */, 7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */, + 7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 7FFE32961FD9CB640038C7A0 /* Sources */ = { + 7F240A15220D3A2300637665 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7F50C96B203C6E80007CA374 /* SessionManager.m in Sources */, - 7FF7BE6E1FDEE5E8005E55FE /* MattermostBucket.m in Sources */, - 7FF7BE2C1FDEE4AE005E55FE /* MattermostManaged.m in Sources */, - 7FFE329E1FD9CB650038C7A0 /* ShareViewController.m in Sources */, - 7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */, + 7F240AD0220D4A6100637665 /* KeyChainDataSource.mm in Sources */, + 7F72F2F0221123E200F98FFF /* GenericPreview.swift in Sources */, + 7F240ADB220E089300637665 /* Item.swift in Sources */, + 7F240A1C220D3A2300637665 /* ShareViewController.swift in Sources */, + 7FABDFC22211A39000D0F595 /* Section.swift in Sources */, + 7FABE00A2212650600D0F595 /* ChannelsViewController.swift in Sources */, + 7F240ADD220E094A00637665 /* TeamsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2439,18 +2409,18 @@ target = 13B07F861A680F5B00A75B9A /* Mattermost */; targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; }; - 7FFE32A41FD9CB650038C7A0 /* PBXTargetDependency */ = { + 7F240A22220D3A2300637665 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 7FFE32991FD9CB640038C7A0 /* MattermostShare */; - targetProxy = 7FFE32A31FD9CB650038C7A0 /* PBXContainerItemProxy */; + target = 7F240A18220D3A2300637665 /* MattermostShare */; + targetProxy = 7F240A21220D3A2300637665 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 7FFE329F1FD9CB650038C7A0 /* MainInterface.storyboard */ = { + 7F240A1D220D3A2300637665 /* MainInterface.storyboard */ = { isa = PBXVariantGroup; children = ( - 7FFE32A01FD9CB650038C7A0 /* Base */, + 7F240A1E220D3A2300637665 /* Base */, ); name = MainInterface.storyboard; sourceTree = ""; @@ -2462,6 +2432,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 3820CC3BBC4B0129958555A9 /* Pods-MattermostTests.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -2496,6 +2467,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 41465DE822E9429EB8B90CB4 /* Pods-MattermostTests.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; HEADER_SEARCH_PATHS = ( @@ -2527,11 +2499,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 634A8F047C73D24A87850EC0 /* Pods-Mattermost.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 176; + CURRENT_PROJECT_VERSION = 175; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2557,6 +2531,7 @@ "$(SRCROOT)../node_modules/react-native-svg/ios/**", "$(SRCROOT)/../node_modules/react-native-sentry/ios/**", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/UploadAttachments/UploadAttachments/**", ); INFOPLIST_FILE = Mattermost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; @@ -2570,6 +2545,9 @@ PRODUCT_NAME = Mattermost; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mattermost/Mattermost-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; @@ -2579,11 +2557,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = BFB7025EA936C1B5DC9725C2 /* Pods-Mattermost.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 176; + CURRENT_PROJECT_VERSION = 175; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2609,6 +2589,7 @@ "$(SRCROOT)../node_modules/react-native-svg/ios/**", "$(SRCROOT)/../node_modules/react-native-sentry/ios/**", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/UploadAttachments/UploadAttachments/**", ); INFOPLIST_FILE = Mattermost/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; @@ -2622,129 +2603,90 @@ PRODUCT_NAME = Mattermost; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Mattermost/Mattermost-Bridging-Header.h"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; - 7FFE32A71FD9CB650038C7A0 /* Debug */ = { + 7F240A25220D3A2300637665 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = MattermostShare/MattermostShare.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UQ8HT4Q2XM; GCC_C_LANGUAGE_STANDARD = gnu11; - HEADER_SEARCH_PATHS = ( - "$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**", - "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", - "$(SRCROOT)/../node_modules/react-native-notifications/RNNotifications/**", - "$(SRCROOT)/../node_modules/react-native-local-auth", - "$(SRCROOT)/../node_modules/react-native-passcode-status/ios", - "$(SRCROOT)/../node_modules/jail-monkey/JailMonkey", - "$(SRCROOT)/../node_modules/react-native/Libraries/**", - "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", - "$(SRCROOT)/../node_modules/react-native-local-auth/**", - "$(SRCROOT)/../node_modules/react-native-passcode-status/ios/**", - "$(SRCROOT)/../node_modules/jail-monkey/JailMonkey/**", - "$(SRCROOT)/../node_modules/rn-fetch-blob/ios/**", - "$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS/**", - "$(SRCROOT)/../node_modules/react-native-video/ios/**", - "$(SRCROOT)/../node_modules/react-native-tableview/**", - "$(SRCROOT)/../node_modules/react-native-device-info/ios/**", - "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", - ); + HEADER_SEARCH_PATHS = "$(SRCROOT)/UploadAttachments/UploadAttachments"; INFOPLIST_FILE = MattermostShare/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SRCROOT)/$(TARGET_NAME)\"", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta.MattermostShare; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OBJC_BRIDGING_HEADER = "MattermostShare/MattermostShare-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 7FFE32A81FD9CB650038C7A0 /* Release */ = { + 7F240A26220D3A2300637665 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = MattermostShare/MattermostShare.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = UQ8HT4Q2XM; GCC_C_LANGUAGE_STANDARD = gnu11; - HEADER_SEARCH_PATHS = ( - "$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**", - "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", - "$(SRCROOT)/../node_modules/react-native-notifications/RNNotifications/**", - "$(SRCROOT)/../node_modules/react-native-local-auth", - "$(SRCROOT)/../node_modules/react-native-passcode-status/ios", - "$(SRCROOT)/../node_modules/jail-monkey/JailMonkey", - "$(SRCROOT)/../node_modules/react-native/Libraries/**", - "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", - "$(SRCROOT)/../node_modules/react-native-local-auth/**", - "$(SRCROOT)/../node_modules/react-native-passcode-status/ios/**", - "$(SRCROOT)/../node_modules/jail-monkey/JailMonkey/**", - "$(SRCROOT)/../node_modules/rn-fetch-blob/ios/**", - "$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS/**", - "$(SRCROOT)/../node_modules/react-native-video/ios/**", - "$(SRCROOT)/../node_modules/react-native-tableview/**", - "$(SRCROOT)/../node_modules/react-native-device-info/ios/**", - "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", - ); + HEADER_SEARCH_PATHS = "$(SRCROOT)/UploadAttachments/UploadAttachments"; INFOPLIST_FILE = MattermostShare/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SRCROOT)/$(TARGET_NAME)\"", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - ); + MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta.MattermostShare; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "MattermostShare/MattermostShare-Bridging-Header.h"; - SWIFT_VERSION = 3.0; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -2854,11 +2796,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7FFE32A61FD9CB650038C7A0 /* Build configuration list for PBXNativeTarget "MattermostShare" */ = { + 7F240A24220D3A2300637665 /* Build configuration list for PBXNativeTarget "MattermostShare" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7FFE32A71FD9CB650038C7A0 /* Debug */, - 7FFE32A81FD9CB650038C7A0 /* Release */, + 7F240A25220D3A2300637665 /* Debug */, + 7F240A26220D3A2300637665 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme b/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme index bc78e5103..1ad8ca513 100644 --- a/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme +++ b/ios/Mattermost.xcodeproj/xcshareddata/xcschemes/Mattermost.xcscheme @@ -6,6 +6,20 @@ parallelizeBuildables = "NO" buildImplicitDependencies = "YES"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Mattermost.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Mattermost.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/ios/Mattermost.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Mattermost.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Mattermost.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/ios/Mattermost.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/ios/0155-keys.png b/ios/Mattermost/0155-keys.png similarity index 100% rename from ios/0155-keys.png rename to ios/Mattermost/0155-keys.png diff --git a/ios/Mattermost/AppDelegate.m b/ios/Mattermost/AppDelegate.m index 312b4b0f9..fb9954efc 100644 --- a/ios/Mattermost/AppDelegate.m +++ b/ios/Mattermost/AppDelegate.m @@ -19,13 +19,17 @@ #endif #import "RCCManager.h" #import "RNNotifications.h" -#import "SessionManager.h" +#import #import @implementation AppDelegate NSString* const NotificationClearAction = @"clear"; +-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { + [[UploadSession shared] attachSessionWithIdentifier:identifier completionHandler:completionHandler]; +} + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Clear keychain on first run in case of reinstallation @@ -139,11 +143,6 @@ NSString* const NotificationClearAction = @"clear"; [RNNotifications handleActionWithIdentifier:identifier forRemoteNotification:userInfo withResponseInfo:responseInfo completionHandler:completionHandler]; } --(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)(void))completionHandler { - [SessionManager sharedSession].savedCompletionHandler = completionHandler; - [[SessionManager sharedSession] createSessionForRequestRequest:identifier]; -} - // Required for deeplinking - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url diff --git a/ios/Mattermost/Dummy.swift b/ios/Mattermost/Dummy.swift new file mode 100644 index 000000000..c4369bc8d --- /dev/null +++ b/ios/Mattermost/Dummy.swift @@ -0,0 +1,6 @@ +// +// Dummy.swift +// Mattermost +// +// DO NOT delete this file as is needed by the project to add support for swift compilation +// diff --git a/ios/Mattermost/Mattermost-Bridging-Header.h b/ios/Mattermost/Mattermost-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/ios/Mattermost/Mattermost-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/ios/Mattermost/MattermostBucketModule.h b/ios/Mattermost/MattermostBucketModule.h new file mode 100644 index 000000000..ff69dc22e --- /dev/null +++ b/ios/Mattermost/MattermostBucketModule.h @@ -0,0 +1,7 @@ +#import +#import "React/RCTBridgeModule.h" +#import "MattermostBucket.h" + +@interface MattermostBucketModule : NSObject +@property MattermostBucket *bucket; +@end diff --git a/ios/Mattermost/MattermostBucketModule.m b/ios/Mattermost/MattermostBucketModule.m new file mode 100644 index 000000000..f17e4b170 --- /dev/null +++ b/ios/Mattermost/MattermostBucketModule.m @@ -0,0 +1,67 @@ +#import "MattermostBucketModule.h" +#import "MattermostBucket.h" + +@implementation MattermostBucketModule + +RCT_EXPORT_MODULE(); + ++(BOOL)requiresMainQueueSetup +{ + return YES; +} + +- (instancetype)init { + self = [super init]; + if (self) { + self.bucket = [[MattermostBucket alloc] init]; + } + return self; +} + +RCT_EXPORT_METHOD(writeToFile:(NSString *)fileName + content:(NSString *)content) { + [self.bucket writeToFile:fileName content:content]; +} + +RCT_EXPORT_METHOD(readFromFile:(NSString *)fileName + getWithResolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + id value = [self.bucket readFromFile:fileName]; + + if (value == nil) { + value = [NSNull null]; + } + + resolve(value); +} + +RCT_EXPORT_METHOD(removeFile:(NSString *)fileName) +{ + [self.bucket removeFile:fileName]; +} + +RCT_EXPORT_METHOD(setPreference:(NSString *) key + value:(NSString *) value) +{ + [self.bucket setPreference:key value:value]; +} + +RCT_EXPORT_METHOD(getPreference:(NSString *) key + getWithResolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) +{ + id value = [self.bucket getPreference:key]; + + if (value == nil) { + value = [NSNull null]; + } + + resolve(value); +} + +RCT_EXPORT_METHOD(removePreference:(NSString *) key) +{ + [self.bucket removePreference:key]; +} +@end diff --git a/ios/MattermostBucket.h b/ios/MattermostBucket.h deleted file mode 100644 index 79bda4b8a..000000000 --- a/ios/MattermostBucket.h +++ /dev/null @@ -1,7 +0,0 @@ -#import -#import "React/RCTBridgeModule.h" - -@interface MattermostBucket : NSObject -- (NSUserDefaults *)bucketByName:(NSString*)name; --(NSString *)readFromFile:(NSString *)fileName appGroupId:(NSString *)appGroupId; -@end diff --git a/ios/MattermostBucket.m b/ios/MattermostBucket.m deleted file mode 100644 index 3816a9428..000000000 --- a/ios/MattermostBucket.m +++ /dev/null @@ -1,126 +0,0 @@ -// -// MattermostBucket.m -// Mattermost -// -// Created by Elias Nahum on 12/11/17. -// Copyright © 2017 Facebook. All rights reserved. -// - -#import "MattermostBucket.h" - -@implementation MattermostBucket - -+(BOOL)requiresMainQueueSetup -{ - return YES; -} - -RCT_EXPORT_MODULE(); - -RCT_EXPORT_METHOD(writeToFile:(NSString *)fileName - content:(NSString *)content - bucketName:(NSString *)bucketName) { - [self writeToFile:fileName content:content appGroupId:bucketName]; -} - -RCT_EXPORT_METHOD(readFromFile:(NSString *)fileName - bucketName:(NSString*)bucketName - getWithResolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) -{ - id value = [self readFromFile:fileName appGroupId:bucketName]; - - if (value == nil) { - value = [NSNull null]; - } - - resolve(value); -} - -RCT_EXPORT_METHOD(removeFile:(NSString *)fileName - bucketName:(NSString*)bucketName) -{ - [self removeFile:fileName appGroupId:bucketName]; -} - -RCT_EXPORT_METHOD(setPreference:(NSString *) key - value:(NSString *) value - bucketName:(NSString*) bucketName) -{ - [self setPreference:key value:value appGroupId:bucketName]; -} - -RCT_EXPORT_METHOD(getPreference:(NSString *) key - bucketName:(NSString*) bucketName - getWithResolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) -{ - id value = [self getPreference:key appGroupId:bucketName]; - - if (value == nil) { - value = [NSNull null]; - } - - resolve(value); -} - -RCT_EXPORT_METHOD(removePreference:(NSString *) key - bucketName:(NSString*) bucketName) -{ - [self removePreference:key appGroupId:bucketName]; -} - --(NSString *)fileUrl:(NSString *)fileName appGroupdId:(NSString *)appGroupId { - NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId]; - return [NSString stringWithFormat:@"%@/%@", fileManagerURL.path, fileName]; -} - --(void)writeToFile:(NSString *)fileName content:(NSString *)content appGroupId:(NSString *)appGroupId { - NSString *filePath = [self fileUrl:fileName appGroupdId:appGroupId]; - NSFileManager *fileManager= [NSFileManager defaultManager]; - if(![fileManager fileExistsAtPath:filePath]) { - [fileManager createFileAtPath:filePath contents:nil attributes:nil]; - } - if ([content length] > 0) { - [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; - } -} - --(NSString *)readFromFile:(NSString *)fileName appGroupId:(NSString *)appGroupId { - NSString *filePath = [self fileUrl:fileName appGroupdId:appGroupId]; - NSFileManager *fileManager= [NSFileManager defaultManager]; - if(![fileManager fileExistsAtPath:filePath]) { - return nil; - } - return [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; -} - --(void)removeFile:(NSString *)fileName appGroupId:(NSString *)appGroupId { - NSString *filePath = [self fileUrl:fileName appGroupdId:appGroupId]; - NSFileManager *fileManager= [NSFileManager defaultManager]; - if([fileManager isDeletableFileAtPath:filePath]) { - [fileManager removeItemAtPath:filePath error:nil]; - } -} - --(NSUserDefaults *)bucketByName:(NSString*)name { - return [[NSUserDefaults alloc] initWithSuiteName: name]; -} - --(void) setPreference:(NSString *)key value:(NSString *) value appGroupId:(NSString*)appGroupId { - NSUserDefaults* bucket = [self bucketByName: appGroupId]; - if (bucket && [key length] > 0 && [value length] > 0) { - [bucket setObject:value forKey:key]; - } -} - --(id) getPreference:(NSString *)key appGroupId:(NSString*)appGroupId { - NSUserDefaults* bucket = [self bucketByName: appGroupId]; - return [bucket objectForKey:key]; -} - --(void) removePreference:(NSString *)key appGroupId:(NSString*)appGroupId { - NSUserDefaults* bucket = [self bucketByName: appGroupId]; - [bucket removeObjectForKey: key]; -} -@end diff --git a/ios/MattermostShare/Base.lproj/MainInterface.storyboard b/ios/MattermostShare/Base.lproj/MainInterface.storyboard index a14188503..286a50894 100644 --- a/ios/MattermostShare/Base.lproj/MainInterface.storyboard +++ b/ios/MattermostShare/Base.lproj/MainInterface.storyboard @@ -1,5 +1,5 @@ - + @@ -9,7 +9,7 @@ - + diff --git a/ios/MattermostShare/ChannelsViewController.swift b/ios/MattermostShare/ChannelsViewController.swift new file mode 100644 index 000000000..a68eb13f0 --- /dev/null +++ b/ios/MattermostShare/ChannelsViewController.swift @@ -0,0 +1,134 @@ +import UIKit + +class ChannelsViewController: UIViewController { + + let searchController = UISearchController(searchResultsController: nil) + + lazy var tableView: UITableView = { + let tableView = UITableView(frame: self.view.frame) + tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = .clear + tableView.register(UITableViewCell.self, forCellReuseIdentifier: Identifiers.ChannelCell) + + return tableView + }() + + var channelDecks = [Section]() + var filteredDecks: [Section]? + weak var delegate: ChannelsViewControllerDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + filteredDecks = channelDecks + title = "Channels" + configureSearchBar() + view.addSubview(tableView) + } + + func configureSearchBar() { + searchController.searchResultsUpdater = self + searchController.hidesNavigationBarDuringPresentation = false + searchController.dimsBackgroundDuringPresentation = false + searchController.searchBar.searchBarStyle = .minimal + searchController.searchBar.autocapitalizationType = .none + + if #available(iOS 11.0, *) { + // For iOS 11 and later, place the search bar in the navigation bar. + self.definesPresentationContext = true + + // Make the search bar always visible. + navigationItem.hidesSearchBarWhenScrolling = true + + navigationItem.searchController = searchController + } else { + // For iOS 10 and earlier, place the search controller's search bar in the table view's header. + tableView.tableHeaderView = searchController.searchBar + } + } + +} + +private extension ChannelsViewController { + struct Identifiers { + static let ChannelCell = "channelCell" + } +} + +extension ChannelsViewController: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return filteredDecks?.count ?? 0 + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + let sec = filteredDecks?[section] + if (sec?.items.count)! > 0 { + return sec?.title + } + + return nil + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return filteredDecks?[section].items.count ?? 0 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let section = filteredDecks?[indexPath.section] + let cell = tableView.dequeueReusableCell(withIdentifier: Identifiers.ChannelCell, for: indexPath) + let item = section?.items[indexPath.row] + cell.textLabel?.text = item?.title + if item?.selected ?? false { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + cell.backgroundColor = .clear + return cell + } +} + +protocol ChannelsViewControllerDelegate: class { + func selectedChannel(deck: Item) +} + +extension ChannelsViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let section = filteredDecks?[indexPath.section] + if (section?.items != nil) { + delegate?.selectedChannel(deck: (section?.items[indexPath.row])!) + } + } +} + +extension ChannelsViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + if let searchText = searchController.searchBar.text, !searchText.isEmpty { + filteredDecks = channelDecks.map {section in + let s = section.copy() as! Section + let items = section.items.filter{($0.title?.lowercased().contains(searchText.lowercased()))!} + s.items = items + return s + } + } else { + filteredDecks = channelDecks + } + + tableView.reloadData() + } +} + +extension ChannelsViewController: UISearchBarDelegate { + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + searchBar.showsCancelButton = false + searchBar.text = "" + searchBar.resignFirstResponder() + tableView.reloadData() + } + + func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { + searchBar.showsCancelButton = true + } +} diff --git a/ios/MattermostShare/GenericPreview.swift b/ios/MattermostShare/GenericPreview.swift new file mode 100644 index 000000000..1647451f0 --- /dev/null +++ b/ios/MattermostShare/GenericPreview.swift @@ -0,0 +1,25 @@ +import UIKit + +class GenericPreview: UIView { + + @IBOutlet var contentView: UIView! + @IBOutlet weak var mainLabel: UILabel! + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + + private func commonInit() { + Bundle.main.loadNibNamed("GenericPreview", owner: self, options: nil) + addSubview(contentView) + contentView.frame = self.bounds + contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth] + } +} diff --git a/ios/MattermostShare/GenericPreview.xib b/ios/MattermostShare/GenericPreview.xib new file mode 100644 index 000000000..80b4b05b0 --- /dev/null +++ b/ios/MattermostShare/GenericPreview.xib @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/MattermostShare/Images/generic.png b/ios/MattermostShare/Images/generic.png new file mode 100644 index 000000000..34a17f91f Binary files /dev/null and b/ios/MattermostShare/Images/generic.png differ diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 58c291d26..ac287c426 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -2,10 +2,6 @@ - BundleEntryFilename - share.ios.js - BundleForced - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -54,12 +50,5 @@ NSExtensionPointIdentifier com.apple.share-services - UIAppFonts - - Ionicons.ttf - FontAwesome.ttf - EvilIcons.ttf - OpenSans-Bold.ttf - diff --git a/ios/MattermostShare/Item.swift b/ios/MattermostShare/Item.swift new file mode 100644 index 000000000..ecf99299a --- /dev/null +++ b/ios/MattermostShare/Item.swift @@ -0,0 +1,7 @@ +import Foundation + +final class Item { + var id: String? + var title: String? + var selected: Bool = false +} diff --git a/ios/MattermostShare/MattermostShare-Bridging-Header.h b/ios/MattermostShare/MattermostShare-Bridging-Header.h new file mode 100644 index 000000000..75ead341d --- /dev/null +++ b/ios/MattermostShare/MattermostShare-Bridging-Header.h @@ -0,0 +1,6 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "StoreManager.h" +#import "Constants.h" diff --git a/ios/MattermostShare/MattermostShare.entitlements b/ios/MattermostShare/MattermostShare.entitlements index c3808dbda..55fdcdea6 100644 --- a/ios/MattermostShare/MattermostShare.entitlements +++ b/ios/MattermostShare/MattermostShare.entitlements @@ -2,27 +2,9 @@ - com.apple.developer.icloud-container-identifiers - - iCloud.$(CFBundleIdentifier) - - com.apple.developer.icloud-services - - CloudDocuments - - com.apple.developer.ubiquity-container-identifiers - - iCloud.$(CFBundleIdentifier) - - com.apple.developer.ubiquity-kvstore-identifier - $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.application-groups group.com.mattermost.rnbeta - keychain-access-groups - - $(AppIdentifierPrefix)com.mattermost.rnbeta - diff --git a/ios/MattermostShare/PerformRequests.h b/ios/MattermostShare/PerformRequests.h deleted file mode 100644 index 748f887ae..000000000 --- a/ios/MattermostShare/PerformRequests.h +++ /dev/null @@ -1,23 +0,0 @@ -#import -#import "MattermostBucket.h" - -@interface PerformRequests : NSObject -@property (nonatomic, strong) NSString *appGroupId; -@property (nonatomic, strong) NSString *requestId; -@property (nonatomic, strong) NSMutableArray *fileIds; -@property (nonatomic, strong) NSArray *files; -@property (nonatomic, strong) NSDictionary *post; - -@property (nonatomic, strong) NSString *serverUrl; -@property (nonatomic, strong) NSString *token; -@property (nonatomic, strong) NSExtensionContext *extensionContext; -@property MattermostBucket *bucket; - -- (id) initWithPost:(NSDictionary *) post - withFiles:(NSArray *) files - forRequestId:(NSString *)requestId - inAppGroupId:(NSString *) appGroupId - inContext:(NSExtensionContext *) extensionContext; - --(void)createPost; -@end diff --git a/ios/MattermostShare/PerformRequests.m b/ios/MattermostShare/PerformRequests.m deleted file mode 100644 index 3d582406d..000000000 --- a/ios/MattermostShare/PerformRequests.m +++ /dev/null @@ -1,154 +0,0 @@ -#import "PerformRequests.h" -#import "MattermostBucket.h" -#import "SessionManager.h" - -@implementation PerformRequests - -- (id) initWithPost:(NSDictionary *) post - withFiles:(NSArray *) files - forRequestId:(NSString *)requestId - inAppGroupId:(NSString *) appGroupId - inContext:(NSExtensionContext *) context { - self = [super init]; - if (self) { - self.post = post; - self.files = files; - self.appGroupId = appGroupId; - self.requestId = requestId; - self.extensionContext = context; - - self.bucket = [[MattermostBucket alloc] init]; - [self setCredentials]; - } - return self; -} - --(void)setCredentials { - NSString *entitiesString = [self.bucket readFromFile:@"entities" appGroupId:self.appGroupId]; - NSData *entitiesData = [entitiesString dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary *entities = [NSJSONSerialization JSONObjectWithData:entitiesData options:NSJSONReadingMutableContainers error:nil]; - NSDictionary *credentials = [[entities objectForKey:@"general"] objectForKey:@"credentials"]; - self.serverUrl = [credentials objectForKey:@"url"]; - self.token = [credentials objectForKey:@"token"]; -} - --(void)URLSession:(NSURLSession *)session task:(NSURLSessionDataTask *)task didCompleteWithError:(nullable NSError *)error { - if(error != nil) { - NSLog(@"ERROR %@", [error userInfo]); - [self.extensionContext completeRequestReturningItems:nil - completionHandler:nil]; - NSLog(@"invalidating session %@", self.requestId); - [session finishTasksAndInvalidate]; - } -} - --(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - NSString *sessionRequestId = [[session configuration] identifier]; - - if ([sessionRequestId containsString:@"files"]) { - NSLog(@"Got file response"); - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - if (json != nil) { - NSArray *fileInfos = [json objectForKey:@"file_infos"]; - self.fileIds = [[NSMutableArray alloc] init]; - for (id file in fileInfos) { - [self.fileIds addObject:[file objectForKey:@"id"]]; - } - NSLog(@"Calling sendPostRequest"); - [self sendPostRequest]; - } - - NSLog(@"Cleaning temp files"); - [self cleanUpTempFiles]; - } -} - --(void)createPost { - NSString *channelId = [self.post objectForKey:@"channel_id"]; - - NSURL *filesUrl = [NSURL URLWithString:[self.serverUrl stringByAppendingString:@"/api/v4/files"]]; - - if (self.files != nil && [self.files count] > 0) { - NSString *POST_BODY_BOUNDARY = @"mobile.client.file.upload"; - NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.requestId stringByAppendingString:@"-files"]]; - config.sharedContainerIdentifier = self.appGroupId; - - NSMutableURLRequest *uploadRequest = [NSMutableURLRequest requestWithURL:filesUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:120.0]; - [uploadRequest setHTTPMethod:@"POST"]; - [uploadRequest setValue:[@"Bearer " stringByAppendingString:self.token] forHTTPHeaderField:@"Authorization"]; - [uploadRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - - NSString *contentTypeValue = [NSString stringWithFormat:@"multipart/form-data;boundary=%@", POST_BODY_BOUNDARY]; - [uploadRequest addValue:contentTypeValue forHTTPHeaderField:@"Content-Type"]; - - NSMutableData *dataForm = [NSMutableData alloc]; - [dataForm appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", POST_BODY_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]]; - [dataForm appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"channel_id\";\r\n\r\n%@", channelId] dataUsingEncoding:NSUTF8StringEncoding]]; - - for (id file in self.files) { - NSData *fileData = [NSData dataWithContentsOfFile:[file objectForKey:@"filePath"]]; - NSString *mimeType = [file objectForKey:@"mimeType"]; - NSLog(@"MimeType %@", mimeType); - [dataForm appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", POST_BODY_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]]; - [dataForm appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"files\"; filename=\"%@\"\r\n", - [file objectForKey:@"filename"]] dataUsingEncoding:NSUTF8StringEncoding]]; - [dataForm appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]]; - [dataForm appendData:[NSData dataWithData:fileData]]; - } - - [dataForm appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", POST_BODY_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]]; - [uploadRequest setHTTPBody:dataForm]; - NSURLSession *uploadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; - NSURLSessionDataTask *uploadTask = [uploadSession dataTaskWithRequest:uploadRequest]; - NSLog(@"Executing file request"); - [uploadTask resume]; - } else { - [self sendPostRequest]; - } -} - --(void)sendPostRequest { - NSMutableDictionary *post = [self.post mutableCopy]; - [post setValue:self.fileIds forKey:@"file_ids"]; - NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:nil]; - NSString* postAsString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding]; - - NSURL *createUrl = [NSURL URLWithString:[self.serverUrl stringByAppendingString:@"/api/v4/posts"]]; - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:createUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; - [request setHTTPMethod:@"POST"]; - [request setValue:[@"Bearer " stringByAppendingString:self.token] forHTTPHeaderField:@"Authorization"]; - [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:[postAsString dataUsingEncoding:NSUTF8StringEncoding]]; - - NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; - NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request]; - NSLog(@"Executing post request"); - [createTask resume]; - [self.extensionContext completeRequestReturningItems:nil - completionHandler:nil]; - NSLog(@"Extension closed"); -} - -- (void) cleanUpTempFiles { - NSURL *tmpDirectoryURL = [[SessionManager sharedSession] tempContainerURL:self.appGroupId]; - if (tmpDirectoryURL == nil) { - return; - } - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error; - NSArray *tmpFiles = [fileManager contentsOfDirectoryAtPath:[tmpDirectoryURL path] error:&error]; - if (error) { - return; - } - - for (NSString *file in tmpFiles) - { - error = nil; - [fileManager removeItemAtPath:[[tmpDirectoryURL URLByAppendingPathComponent:file] path] error:&error]; - } -} -@end diff --git a/ios/MattermostShare/Section.swift b/ios/MattermostShare/Section.swift new file mode 100644 index 000000000..9a5eed3b8 --- /dev/null +++ b/ios/MattermostShare/Section.swift @@ -0,0 +1,13 @@ +import Foundation + +class Section: NSObject, NSCopying { + var title: String? + var items: [Item] = [] + + func copy(with zone: NSZone? = nil) -> Any { + let copy = Section() + copy.title = title + copy.items = items + return copy + } +} diff --git a/ios/MattermostShare/SessionManager.h b/ios/MattermostShare/SessionManager.h deleted file mode 100644 index 5a23cd3ee..000000000 --- a/ios/MattermostShare/SessionManager.h +++ /dev/null @@ -1,23 +0,0 @@ -#import -#import "MattermostBucket.h" -#import "KeyChainDataSource.h" - -@interface SessionManager : NSObject -@property (nonatomic, copy) void (^savedCompletionHandler)(void); -@property (nonatomic, copy) void (^sendShareEvent)(NSString *); -@property (nonatomic, copy) void (^closeExtension)(void); -@property MattermostBucket *bucket; -@property (nonatomic, retain) KeyChainDataSource *keyChain; -@property (nonatomic, strong) NSString *requestWithGroup; -@property (nonatomic, strong) NSString *certificateName; -@property (nonatomic) BOOL isBackground; - - -+(instancetype)sharedSession; --(NSString *)getAppGroupIdFromRequestIdentifier:(NSString *) requestWithGroup; --(NSURLSession *)createSessionForRequestRequest:(NSString *)requestId; --(void)setRequestWithGroup:(NSString *)requestWithGroup certificateName:(NSString *)certificateName; --(void)setDataForRequest:(NSDictionary *)data forRequestWithGroup:(NSString *)requestId; --(void)createPostForRequest:(NSString *)requestId; --(NSURL*)tempContainerURL:(NSString*)appGroupId; -@end diff --git a/ios/MattermostShare/SessionManager.m b/ios/MattermostShare/SessionManager.m deleted file mode 100644 index 7259774b9..000000000 --- a/ios/MattermostShare/SessionManager.m +++ /dev/null @@ -1,300 +0,0 @@ -#import "SessionManager.h" -#import "MattermostBucket.h" - -@implementation SessionManager - -@synthesize keyChain; - -+ (instancetype)sharedSession { - static id sharedMyManager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedMyManager = [[self alloc] init]; - }); - return sharedMyManager; -} - -- (instancetype)init { - self = [super init]; - if (self) { - self.bucket = [[MattermostBucket alloc] init]; - self.keyChain = [[KeyChainDataSource alloc] initWithMode:KSM_Identities]; - } - return self; -} - --(void)setRequestWithGroup:(NSString *)requestWithGroup certificateName:(NSString *)certificateName { - self.requestWithGroup = requestWithGroup; - self.certificateName = certificateName; - self.isBackground = [certificateName length] == 0; -} - --(void)setDataForRequest:(NSDictionary *)data forRequestWithGroup:(NSString *)requestWithGroup { - NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - [[self.bucket bucketByName:appGroupId] setObject:data forKey:requestWithGroup]; -} - - --(NSString *)getAppGroupIdFromRequestIdentifier:(NSString *) requestWithGroup { - return [requestWithGroup componentsSeparatedByString:@"|"][1]; -} - --(NSDictionary *)getCredentialsForRequest:(NSString *)requestWithGroup { - NSString * appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - NSString *entitiesString = [self.bucket readFromFile:@"entities" appGroupId:appGroupId]; - NSData *entitiesData = [entitiesString dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - NSDictionary *entities = [NSJSONSerialization JSONObjectWithData:entitiesData options:NSJSONReadingMutableContainers error:&error]; - if (error != nil) { - return nil; - } - return [[entities objectForKey:@"general"] objectForKey:@"credentials"]; -} - --(NSDictionary *)getDataForRequest:(NSString *)requestWithGroup { - NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - return [[self.bucket bucketByName:appGroupId] objectForKey:requestWithGroup]; -} - --(NSURLSession *)createSessionForRequestRequest:(NSString *)requestWithGroup { - NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - NSURLSessionConfiguration* config; - if (self.isBackground) { - config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:requestWithGroup]; - } else { - config = [NSURLSessionConfiguration defaultSessionConfiguration]; - } - - config.sharedContainerIdentifier = appGroupId; - return [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; -} - --(void) createPost:(NSDictionary *) post - withFiles:(NSArray *)files - credentials:(NSDictionary *) credentials - requestWithGroup:(NSString *)requestWithGroup { - NSString *serverUrl = [credentials objectForKey:@"url"]; - NSString *token = [credentials objectForKey:@"token"]; - NSString *channelId = [post objectForKey:@"channel_id"]; - NSURLSession *session = [self createSessionForRequestRequest:requestWithGroup]; - NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - - for (id file in files) { - NSURL *filePath = [NSURL fileURLWithPath:[file objectForKey:@"filePath"]]; - NSString *fileName = [file objectForKey:@"filename"]; - - NSError *err; - NSURL *tempContainerURL = [self tempContainerURL:appGroupId]; - NSURL *destinationURL = [tempContainerURL URLByAppendingPathComponent: fileName]; - BOOL bVal = [[NSFileManager defaultManager] copyItemAtURL:filePath toURL:destinationURL error:&err]; - - NSCharacterSet *allowedCharacters = [NSCharacterSet URLQueryAllowedCharacterSet]; - NSString *encodedFilename = [[file objectForKey:@"filename"] stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; - NSString *url = [serverUrl stringByAppendingString:@"/api/v4/files"]; - NSString *queryString = [NSString stringWithFormat:@"?channel_id=%@&filename=%@", channelId, encodedFilename]; - NSURL *filesUrl = [NSURL URLWithString:[url stringByAppendingString:queryString]]; - NSMutableURLRequest *uploadRequest = [NSMutableURLRequest requestWithURL:filesUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:120.0]; - [uploadRequest setHTTPMethod:@"POST"]; - [uploadRequest setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"]; - [uploadRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - - NSURLSessionUploadTask *task = [session uploadTaskWithRequest:uploadRequest fromFile:destinationURL]; - NSLog(@"Executing file request %@", fileName); - [task resume]; - } -} - --(void) createPost:(NSMutableDictionary *)post - withFileIds:(NSArray *)fileIds - credentials:(NSDictionary *) credentials { - NSString *serverUrl = [credentials objectForKey:@"url"]; - NSString *token = [credentials objectForKey:@"token"]; - - if (fileIds != nil && [fileIds count] > 0) { - [post setObject:fileIds forKey:@"file_ids"]; - } - - NSError *error; - NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:&error]; - - if (error == nil) { - NSString* postAsString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding]; - - NSURL *createUrl = [NSURL URLWithString:[serverUrl stringByAppendingString:@"/api/v4/posts"]]; - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:createUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; - [request setHTTPMethod:@"POST"]; - [request setValue:[@"Bearer " stringByAppendingString:token] forHTTPHeaderField:@"Authorization"]; - [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:[postAsString dataUsingEncoding:NSUTF8StringEncoding]]; - - NSURLSessionConfiguration* config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; - NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request]; - NSLog(@"Executing post request"); - [createTask resume]; - self.closeExtension(); - } else { - self.sendShareEvent(@"extensionPostFailed"); - } -} - --(void)createPostForRequest:(NSString *)requestWithGroup { - NSDictionary *data = [self getDataForRequest:requestWithGroup]; - NSDictionary *post = [data objectForKey:@"post"]; - NSArray *files = [data objectForKey:@"files"]; - NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup]; - - if (credentials == nil) { - self.sendShareEvent(@"extensionPostFailed"); - return; - } - - if (files != nil && [files count] > 0) { - [self createPost:post withFiles:files credentials:credentials requestWithGroup:requestWithGroup]; - } - else { - [self createPost:[post mutableCopy] withFileIds:nil credentials:credentials]; - } -} - --(void)sendPostRequestForId:(NSString *)requestWithGroup { - NSDictionary *data = [self getDataForRequest:requestWithGroup]; - NSDictionary *credentials = [self getCredentialsForRequest:requestWithGroup]; - - NSMutableDictionary *post = [[data objectForKey:@"post"] mutableCopy]; - NSArray *fileIds = [data objectForKey:@"file_ids"]; - - [self createPost:post withFileIds:fileIds credentials:credentials]; -} - --(NSURL*)tempContainerURL:(NSString*)appGroupId { - NSFileManager *manager = [NSFileManager defaultManager]; - NSURL *containerURL = [manager containerURLForSecurityApplicationGroupIdentifier:appGroupId]; - NSURL *tempDirectoryURL = [containerURL URLByAppendingPathComponent:@"shareTempItems"]; - if (![manager fileExistsAtPath:[tempDirectoryURL path]]) { - NSError *err; - [manager createDirectoryAtURL:tempDirectoryURL withIntermediateDirectories:YES attributes:nil error:&err]; - if (err) { - return nil; - } - } - - return tempDirectoryURL; -} - - --(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { - NSLog(@"completition handler from normal challenge"); - if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) { - if (self.certificateName) { - SecIdentityRef identity = [keyChain GetIdentityByName:self.certificateName]; - if (identity != nil) { - SecCertificateRef certificate = NULL; - OSStatus status = SecIdentityCopyCertificate(identity, &certificate); - if (!status) { - CFArrayRef emailAddresses = NULL; - SecCertificateCopyEmailAddresses(certificate, &emailAddresses); - NSArray *emails = (NSArray *)CFBridgingRelease(emailAddresses); - CFStringRef summaryRef = SecCertificateCopySubjectSummary(certificate); - NSString *tagstr = (NSString*)CFBridgingRelease(summaryRef); - NSString *email = @""; - if ([emails count] > 0){ - email = [emails objectAtIndex:0]; - } - NSLog(@"completion %@ %@", tagstr, email); - const void *certs[] = {certificate}; - CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); - NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; - [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; - completionHandler(NSURLSessionAuthChallengeUseCredential,credential); - NSLog(@"completion handler for certificate"); - return; - } - } - } - completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); - } else { - NSLog(@"completion handler regular stuff"); - completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); - } -} - --(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - NSString *requestWithGroup; - if (self.isBackground) { - requestWithGroup = [[session configuration] identifier]; - } else { - requestWithGroup = self.requestWithGroup; - } - NSURL *requestUrl = [[dataTask originalRequest] URL]; - - if ([[requestUrl absoluteString] containsString:@"files"]) { - NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - NSError *error; - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if (error == nil && json != nil) { - NSArray *fileInfos = [json objectForKey:@"file_infos"]; - NSDictionary *dataFromBucket = [self getDataForRequest:requestWithGroup]; - NSMutableDictionary *data = [dataFromBucket mutableCopy]; - NSMutableArray *fileIds = [[data objectForKey:@"file_ids"] mutableCopy]; - if (fileIds == nil && data != nil) { - fileIds = [[NSMutableArray alloc] init]; - } - - for (id file in fileInfos) { - [fileIds addObject:[file objectForKey:@"id"]]; - NSString * filename = [file objectForKey:@"name"]; - NSLog(@"got file id %@ %@", [file objectForKey:@"id"], filename); - NSURL *tempContainerURL = [self tempContainerURL:appGroupId]; - NSURL *destinationURL = [tempContainerURL URLByAppendingPathComponent: filename]; - [[NSFileManager defaultManager] removeItemAtURL:destinationURL error:nil]; - } - [data setObject:fileIds forKey:@"file_ids"]; - [self setDataForRequest:data forRequestWithGroup:requestWithGroup]; - } else { - self.sendShareEvent(@"extensionPostFailed"); - } - } -} - --(void)URLSession:(NSURLSession *)session task:(NSURLSessionDataTask *)task didCompleteWithError:(nullable NSError *)error { - NSString *requestWithGroup; - if (self.isBackground) { - requestWithGroup = [[session configuration] identifier]; - } else { - requestWithGroup = self.requestWithGroup; - } - - if(error != nil) { - NSLog(@"completition ERROR %@", [error userInfo]); - NSLog(@"invalidating session %@", requestWithGroup); - [session invalidateAndCancel]; - self.sendShareEvent(@"extensionPostFailed"); - } else if (requestWithGroup != nil) { - NSString *appGroupId = [self getAppGroupIdFromRequestIdentifier:requestWithGroup]; - NSURL *requestUrl = [[task originalRequest] URL]; - - NSDictionary *data = [self getDataForRequest:requestWithGroup]; - NSArray *files = [data objectForKey:@"files"]; - NSMutableArray *fileIds = [data objectForKey:@"file_ids"]; - - if ([[requestUrl absoluteString] containsString:@"files"] && - [files count] == [fileIds count]) { - [self sendPostRequestForId:requestWithGroup]; - [[self.bucket bucketByName:appGroupId] removeObjectForKey:requestWithGroup]; - } - } else { - NSLog(@"SOMETHING ELSE"); - } -} - --(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { - if (self.savedCompletionHandler) { - self.savedCompletionHandler(); - self.savedCompletionHandler = nil; - } -} - -@end diff --git a/ios/MattermostShare/ShareViewController.h b/ios/MattermostShare/ShareViewController.h deleted file mode 100644 index 390c05d41..000000000 --- a/ios/MattermostShare/ShareViewController.h +++ /dev/null @@ -1,5 +0,0 @@ -#import -#import "React/RCTBridgeModule.h" - -@interface ShareViewController : UIViewController -@end diff --git a/ios/MattermostShare/ShareViewController.m b/ios/MattermostShare/ShareViewController.m deleted file mode 100644 index 70ac27fce..000000000 --- a/ios/MattermostShare/ShareViewController.m +++ /dev/null @@ -1,285 +0,0 @@ -#import "ShareViewController.h" -#import -#import -#import "PerformRequests.h" -#import "SessionManager.h" - -NSExtensionContext* extensionContext; - -@implementation ShareViewController -+ (BOOL)requiresMainQueueSetup -{ - return YES; -} - -@synthesize bridge = _bridge; - -- (UIView*) shareView { - NSURL *jsCodeLocation; - - jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"share.ios" fallbackResource:nil]; - - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation - moduleName:@"MattermostShare" - initialProperties:nil - launchOptions:nil]; - rootView.backgroundColor = nil; - return rootView; -} - -RCT_EXPORT_MODULE(MattermostShare); - -- (void)viewDidLoad { - [super viewDidLoad]; - extensionContext = self.extensionContext; - UIView *rootView = [self shareView]; - if (rootView.backgroundColor == nil) { - rootView.backgroundColor = [[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:0.1]; - } - - self.view = rootView; -} - -RCT_REMAP_METHOD(getOrientation, - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) { - if([UIScreen mainScreen].bounds.size.width < [UIScreen mainScreen].bounds.size.height) { - resolve(@"PORTRAIT"); - } else { - resolve(@"LANDSCAPE"); - } -} - -RCT_EXPORT_METHOD(close:(NSDictionary *)data appGroupId:(NSString *)appGroupId) { - if (data != nil) { - NSString *requestId = [data objectForKey:@"requestId"]; - NSString *useBackgroundUpload = [data objectForKey:@"useBackgroundUpload"]; - NSString *certificateName = [data objectForKey:@"certificate"]; - BOOL tryToUploadInTheBackgound = useBackgroundUpload ? [useBackgroundUpload boolValue] : NO; - - if (tryToUploadInTheBackgound) { - NSString *requestWithGroup = [NSString stringWithFormat:@"%@|%@", requestId, appGroupId]; - [SessionManager sharedSession].closeExtension = ^{ - [extensionContext completeRequestReturningItems:nil - completionHandler:nil]; - NSLog(@"Extension closed"); - }; - - [SessionManager sharedSession].sendShareEvent = ^(NSString* eventName) { - NSLog(@"Send Share Extension Event to JS"); - [_bridge enqueueJSCall:@"RCTDeviceEventEmitter" - method:@"emit" - args:@[eventName] - completion:nil]; - }; - - [[SessionManager sharedSession] setRequestWithGroup:requestWithGroup certificateName:certificateName]; - [[SessionManager sharedSession] setDataForRequest:data forRequestWithGroup:requestWithGroup]; - [[SessionManager sharedSession] createPostForRequest:requestWithGroup]; - } else { - NSDictionary *post = [data objectForKey:@"post"]; - NSArray *files = [data objectForKey:@"files"]; - PerformRequests *request = [[PerformRequests alloc] initWithPost:post withFiles:files forRequestId:requestId inAppGroupId:appGroupId inContext:extensionContext]; - [request createPost]; - } - } else { - [extensionContext completeRequestReturningItems:nil - completionHandler:nil]; - NSLog(@"Extension closed"); - } -} - -RCT_REMAP_METHOD(data, - appGroupId: (NSString *)appGroupId - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) -{ - [self extractDataFromContext: extensionContext withAppGroup: appGroupId andCallback:^(NSArray* items ,NSError* err) { - if (err) { - reject(@"data", @"Failed to extract attachment content", err); - return; - } - resolve(items); - }]; -} - -typedef void (^ProviderCallback)(NSString *content, NSString *contentType, BOOL owner, NSError *err); - -- (void)extractDataFromContext:(NSExtensionContext *)context withAppGroup:(NSString *) appGroupId andCallback:(void(^)(NSArray *items ,NSError *err))callback { - @try { - NSExtensionItem *item = [context.inputItems firstObject]; - NSArray *attachments = item.attachments; - NSMutableArray *items = [[NSMutableArray alloc] init]; - - __block int attachmentIdx = 0; - __block ProviderCallback providerCb = nil; - __block __weak ProviderCallback weakProviderCb = nil; - providerCb = ^ void (NSString *content, NSString *contentType, BOOL owner, NSError *err) { - if (err) { - callback(nil, err); - return; - } - - if (content != nil) { - [items addObject:@{ - @"type": contentType, - @"value": content, - @"owner": [NSNumber numberWithBool:owner], - }]; - } - - ++attachmentIdx; - if (attachmentIdx == [attachments count]) { - callback(items, nil); - } else { - [self extractDataFromProvider:attachments[attachmentIdx] withAppGroup:appGroupId andCallback: weakProviderCb]; - } - }; - weakProviderCb = providerCb; - [self extractDataFromProvider:attachments[0] withAppGroup:appGroupId andCallback: providerCb]; - } - @catch (NSException *exc) { - NSError *error = [NSError errorWithDomain:@"fiftythree.paste" code:1 userInfo:@{ - @"reason": [exc description] - }]; - callback(nil, error); - } -} - -- (void)extractDataFromProvider:(NSItemProvider *)provider withAppGroup:(NSString *) appGroupId andCallback:(void(^)(NSString* content, NSString* contentType, BOOL owner, NSError *err))callback { - if([provider hasItemConformingToTypeIdentifier:@"public.movie"]) { - [provider loadItemForTypeIdentifier:@"public.movie" options:nil completionHandler:^(id item, NSError *error) { - @try { - if ([item isKindOfClass: NSURL.class]) { - NSURL *url = (NSURL *)item; - return callback([url absoluteString], @"public.movie", NO, nil); - } - return callback(nil, nil, NO, nil); - } - @catch(NSException *exc) { - NSError *error = [NSError errorWithDomain:@"fiftythree.paste" code:2 userInfo:@{ - @"reason": [exc description] - }]; - callback(nil, nil, NO, error); - } - }]; - return; - } - if([provider hasItemConformingToTypeIdentifier:@"public.image"]) { - [provider loadItemForTypeIdentifier:@"public.image" options:nil completionHandler:^(id item, NSError *error) { - if (error) { - callback(nil, nil, NO, error); - return; - } - - @try { - if ([item isKindOfClass: NSURL.class]) { - NSURL *url = (NSURL *)item; - return callback([url absoluteString], @"public.image", NO, nil); - } else if ([item isKindOfClass: UIImage.class]) { - UIImage *image = (UIImage *)item; - NSString *fileName = [NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]]; - NSURL *tempContainerURL = [[SessionManager sharedSession] tempContainerURL:appGroupId]; - if (tempContainerURL == nil){ - return callback(nil, nil, NO, nil); - } - - NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName]; - BOOL created = [UIImageJPEGRepresentation(image, 0.8) writeToFile:[tempFileURL path] atomically:YES]; - if (created) { - return callback([tempFileURL absoluteString], @"public.image", YES, nil); - } else { - return callback(nil, nil, NO, nil); - } - } else if ([item isKindOfClass: NSData.class]) { - NSString *fileName = [NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]]; - NSData *data = (NSData *)item; - UIImage *image = [UIImage imageWithData:data]; - NSURL *tempContainerURL = [[SessionManager sharedSession] tempContainerURL:appGroupId]; - if (tempContainerURL == nil){ - return callback(nil, nil, NO, nil); - } - NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName]; - BOOL created = [UIImageJPEGRepresentation(image, 0.8) writeToFile:[tempFileURL path] atomically:YES]; - if (created) { - return callback([tempFileURL absoluteString], @"public.image", YES, nil); - } else { - return callback(nil, nil, NO, nil); - } - } else { - // Do nothing, some type we don't support. - return callback(nil, nil, NO, nil); - } - } - @catch(NSException *exc) { - NSError *error = [NSError errorWithDomain:@"fiftythree.paste" code:2 userInfo:@{ - @"reason": [exc description] - }]; - callback(nil, nil, NO, error); - } - }]; - return; - } - - if([provider hasItemConformingToTypeIdentifier:@"public.file-url"]) { - [provider loadItemForTypeIdentifier:@"public.file-url" options:nil completionHandler:^(id item, NSError *error) { - if (error) { - callback(nil, nil, NO, error); - return; - } - - if ([item isKindOfClass:NSURL.class]) { - return callback([(NSURL *)item absoluteString], @"public.file-url", NO, nil); - } else if ([item isKindOfClass:NSString.class]) { - return callback((NSString *)item, @"public.file-url", NO, nil); - } - callback(nil, nil, NO, nil); - }]; - return; - } - - if([provider hasItemConformingToTypeIdentifier:@"public.url"]) { - [provider loadItemForTypeIdentifier:@"public.url" options:nil completionHandler:^(id item, NSError *error) { - if (error) { - callback(nil, nil, NO, error); - return; - } - - if ([item isKindOfClass:NSURL.class]) { - return callback([(NSURL *)item absoluteString], @"public.url", NO, nil); - } else if ([item isKindOfClass:NSString.class]) { - return callback((NSString *)item, @"public.url", NO, nil); - } - }]; - return; - } - - if([provider hasItemConformingToTypeIdentifier:@"public.plain-text"]) { - [provider loadItemForTypeIdentifier:@"public.plain-text" options:nil completionHandler:^(id item, NSError *error) { - if (error) { - callback(nil, nil, NO, error); - return; - } - - if ([item isKindOfClass:NSString.class]) { - return callback((NSString *)item, @"public.plain-text", NO, nil); - } else if ([item isKindOfClass:NSAttributedString.class]) { - NSAttributedString *str = (NSAttributedString *)item; - return callback([str string], @"public.plain-text", NO, nil); - } else if ([item isKindOfClass:NSData.class]) { - NSString *str = [[NSString alloc] initWithData:(NSData *)item encoding:NSUTF8StringEncoding]; - if (str) { - return callback(str, @"public.plain-text", NO, nil); - } else { - return callback(nil, nil, NO, nil); - } - } else { - return callback(nil, nil, NO, nil); - } - }]; - return; - } - - callback(nil, nil, NO, nil); -} -@end diff --git a/ios/MattermostShare/ShareViewController.swift b/ios/MattermostShare/ShareViewController.swift new file mode 100644 index 000000000..67da8d192 --- /dev/null +++ b/ios/MattermostShare/ShareViewController.swift @@ -0,0 +1,405 @@ +import UIKit +import Social +import MobileCoreServices +import UploadAttachments + +extension Bundle { + var displayName: String? { + return object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + } +} + +class ShareViewController: SLComposeServiceViewController { + + private var dispatchGroup = DispatchGroup() + private var attachments = AttachmentArray() + private var store = StoreManager.shared() as StoreManager + private var entities: [AnyHashable:Any]? = nil + private var sessionToken: String? + private var serverURL: String? + private var message: String? + private var publicURL: String? + + fileprivate var selectedChannel: Item? + fileprivate var selectedTeam: Item? + + // MARK: - Lifecycle methods + override func viewDidLoad() { + super.viewDidLoad() + + title = Bundle.main.displayName + placeholder = "Write a message..." + entities = store.getEntities(true) as [AnyHashable:Any]? + sessionToken = store.getToken() + serverURL = store.getServerUrl() + + extractDataFromContext() + + if sessionToken == nil { + showErrorMessage(title: "", message: "Authentication required: Please first login using the app.", VC: self) + } + } + + override func isContentValid() -> Bool { + // Do validation of contentText and/or NSExtensionContext attachments here + if (attachments.count > 0) { + let maxFileSize = store.getMaxFileSize() + if attachments.hasAttachementLargerThan(fileSize: maxFileSize) { + let readableMaxSize = formatFileSize(bytes: Double(maxFileSize)) + showErrorMessage(title: "", message: "File attachments shared in Mattermost must be less than \(readableMaxSize).", VC: self) + } + } + + return serverURL != nil && + sessionToken != nil && + attachmentsCount() == attachments.count && + selectedTeam != nil && + selectedChannel != nil + } + + override func didSelectCancel() { + UploadSessionManager.shared.clearTempDirectory() + super.didSelectCancel() + } + + override func didSelectPost() { + // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. + if publicURL != nil { + self.message = "\(contentText!)\n\n\(publicURL!)" + } else { + self.message = contentText + } + + UploadManager.shared.uploadFiles(baseURL: serverURL!, token: sessionToken!, channelId: selectedChannel!.id!, message: message, attachments: attachments, callback: { + // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + }) + } + + override func loadPreviewView() -> UIView! { + if attachments.findBy(type: kUTTypeFileURL as String) { + let genericPreview = GenericPreview() + genericPreview.contentMode = .scaleAspectFit + genericPreview.clipsToBounds = true + genericPreview.isUserInteractionEnabled = false + genericPreview.addConstraints([ + NSLayoutConstraint(item: genericPreview, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1.0, constant: 70), + NSLayoutConstraint(item: genericPreview, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 70) + ]) + + if attachments.count > 1 { + genericPreview.mainLabel.text = "\(attachments.count) Items" + } + + return genericPreview + } + return super.loadPreviewView(); + } + + override func configurationItems() -> [Any]! { + var items: [SLComposeSheetConfigurationItem] = [] + + // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. + let teamDecks = getTeamItems() + if let teams = SLComposeSheetConfigurationItem() { + teams.title = "Team" + teams.value = selectedTeam?.title + teams.tapHandler = { + let vc = TeamsViewController() + vc.teamDecks = teamDecks + vc.delegate = self + self.pushConfigurationViewController(vc) + } + items.append(teams) + } + + let channelDecks = getChannelItems(forTeamId: selectedTeam?.id) + if let channels = SLComposeSheetConfigurationItem() { + channels.title = "Channels" + channels.value = selectedChannel?.title + channels.valuePending = channelDecks == nil + channels.tapHandler = { + let vc = ChannelsViewController() + vc.channelDecks = channelDecks! + vc.delegate = self + self.pushConfigurationViewController(vc) + } + + items.append(channels) + } + + validateContent() + return items + } + + // MARK: - Extension Builder + + func attachmentsCount() -> Int { + var count = 0 + for item in extensionContext?.inputItems as! [NSExtensionItem] { + guard let attachments = item.attachments else {return 0} + for itemProvider in attachments { + if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) || + itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) || + itemProvider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) { + count = count + 1 + } + } + } + + return count + } + + func buildChannelSection(channels: NSArray, currentChannelId: String, key: String, title:String) -> Section { + let section = Section() + section.title = title + for channel in channels as! [NSDictionary] { + let item = Item() + let id = channel.object(forKey: "id") as? String + item.id = id + item.title = channel.object(forKey: "display_name") as? String + if id == currentChannelId { + item.selected = true + selectedChannel = item + } + section.items.append(item) + } + return section + } + + func extractDataFromContext() { + for item in extensionContext?.inputItems as! [NSExtensionItem] { + guard let attachments = item.attachments else {continue} + for itemProvider in attachments { + if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) { + dispatchGroup.enter() + itemProvider.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil, completionHandler: ({item, error in + if error == nil { + let attachment = self.saveAttachment(url: item as! URL) + if (attachment != nil) { + attachment?.type = kUTTypeMovie as String + self.attachments.append(attachment!) + } + } + self.dispatchGroup.leave() + })) + } else if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { + dispatchGroup.enter() + itemProvider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil, completionHandler: ({item, error in + if error == nil { + let attachment = self.saveAttachment(url: item as! URL) + if (attachment != nil) { + attachment?.type = kUTTypeImage as String + self.attachments.append(attachment!) + } + } + self.dispatchGroup.leave() + })) + } else if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) { + dispatchGroup.enter() + itemProvider.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil, completionHandler: ({item, error in + if error == nil { + let attachment = self.saveAttachment(url: item as! URL) + if (attachment != nil) { + attachment?.type = kUTTypeFileURL as String + self.attachments.append(attachment!) + } + } + self.dispatchGroup.leave() + })) + } else if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { + itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: ({item, error in + if let url = item as? URL { + self.publicURL = url.absoluteString + } + })) + } + } + } + dispatchGroup.notify(queue: DispatchQueue.main) { + self.validateContent() + } + } + + func getChannelsFromServerAndReload(forTeamId: String) { + var currentChannel = store.getCurrentChannel() as NSDictionary? + if currentChannel?.object(forKey: "team_id") as! String != forTeamId { + currentChannel = store.getDefaultChannel(forTeamId) as NSDictionary? + } + + // If currentChannel is nil it means we don't have the channels for this team + if (currentChannel == nil) { + let urlString = "\(serverURL!)/api/v4/users/me/teams/\(forTeamId)/channels" + let url = URL(string: urlString) + var request = URLRequest(url: url!) + let auth = "Bearer \(sessionToken!)" as String + request.setValue(auth, forHTTPHeaderField: "Authorization") + + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in + guard let dataResponse = data, + error == nil else { + print(error?.localizedDescription ?? "Response Error") + return + + } + + do{ + //here dataResponse received from a network request + let jsonArray = try JSONSerialization.jsonObject(with: dataResponse, options: []) as! NSArray + let channels = jsonArray.filter {element in + let channel = element as! NSDictionary + let type = channel.object(forKey: "type") as! String + return type == "O" || type == "P" + } + let ent = self.store.getEntities(false)! as NSDictionary + let mutableEntities = ent.mutableCopy() as! NSMutableDictionary + let entitiesChannels = NSDictionary(dictionary: mutableEntities.object(forKey: "channels") as! NSMutableDictionary) + .object(forKey: "channels") as! NSMutableDictionary + + for item in channels { + let channel = item as! NSDictionary + entitiesChannels.setValue(channel, forKey: channel.object(forKey: "id") as! String) + } + + if let entitiesData: NSData = try? JSONSerialization.data(withJSONObject: ent, options: JSONSerialization.WritingOptions.prettyPrinted) as NSData { + let jsonString = String(data: entitiesData as Data, encoding: String.Encoding.utf8)! as String + self.store.updateEntities(jsonString) + self.store.getEntities(true) + self.reloadConfigurationItems() + self.view.setNeedsDisplay() + } + } catch let parsingError { + print("Error", parsingError) + } + } + task.resume() + } + } + + func getChannelItems(forTeamId: String?) -> [Section]? { + var channelDecks = [Section]() + var currentChannel = store.getCurrentChannel() as NSDictionary? + if currentChannel?.object(forKey: "team_id") as? String != forTeamId { + currentChannel = store.getDefaultChannel(forTeamId) as NSDictionary? + } + + if currentChannel == nil { + return nil + } + + let channelsInTeamBySections = store.getChannelsBySections(forTeamId) as NSDictionary + channelDecks.append(buildChannelSection( + channels: channelsInTeamBySections.object(forKey: "public") as! NSArray, + currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String, + key: "public", + title: "Public Channels" + )) + + channelDecks.append(buildChannelSection( + channels: channelsInTeamBySections.object(forKey: "private") as! NSArray, + currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String, + key: "private", + title: "Private Channels" + )) + + channelDecks.append(buildChannelSection( + channels: channelsInTeamBySections.object(forKey: "direct") as! NSArray, + currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String, + key: "direct", + title: "Direct Channels" + )) + + return channelDecks + } + + func getTeamItems() -> [Item] { + var teamDecks = [Item]() + let currentTeamId = store.getCurrentTeamId() + let teams = store.getMyTeams() as NSArray? + + for case let team as NSDictionary in teams! { + let item = Item() + item.title = team.object(forKey: "display_name") as! String? + item.id = team.object(forKey: "id") as! String? + item.selected = false + if (item.id == (selectedTeam?.id ?? currentTeamId)) { + item.selected = true + selectedTeam = item + } + teamDecks.append(item) + } + + return teamDecks + } + + func saveAttachment(url: URL) -> AttachmentItem? { + let tempURL: URL? = UploadSessionManager.shared.tempContainerURL() as URL? + let fileMgr = FileManager.default + let fileName = url.lastPathComponent + let tempFileURL = tempURL?.appendingPathComponent(fileName) + + do { + try? FileManager.default.removeItem(at: tempFileURL!) + try fileMgr.copyItem(at: url, to: tempFileURL!) + let attr = try fileMgr.attributesOfItem(atPath: (tempFileURL?.path)!) as NSDictionary + + let attachment = AttachmentItem() + attachment.fileName = fileName + attachment.fileURL = tempFileURL + attachment.fileSize = attr.fileSize() + return attachment + } catch { + return nil + } + } + + // MARK: - Utiilities + + func showErrorMessage(title: String, message: String, VC: UIViewController) { + let alert: UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert) + let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.default) { + UIAlertAction in + self.cancel() + } + alert.addAction(okAction) + VC.present(alert, animated: true, completion: nil) + } + + func formatFileSize(bytes: Double) -> String { + guard bytes > 0 else { + return "0 bytes" + } + + // Adapted from http://stackoverflow.com/a/18650828 + let suffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + let k: Double = 1024 + let i = floor(log(bytes) / log(k)) + + // Format number with thousands separator and everything below 1 GB with no decimal places. + let numberFormatter = NumberFormatter() + numberFormatter.maximumFractionDigits = i < 3 ? 0 : 1 + numberFormatter.numberStyle = .decimal + + let numberString = numberFormatter.string(from: NSNumber(value: bytes / pow(k, i))) ?? "Unknown" + let suffix = suffixes[Int(i)] + return "\(numberString) \(suffix)" + } +} + +extension ShareViewController: TeamsViewControllerDelegate { + func selectedTeam(deck: Item) { + selectedTeam = deck + selectedChannel = nil + self.getChannelsFromServerAndReload(forTeamId: deck.id!) + reloadConfigurationItems() + popConfigurationViewController() + } +} + +extension ShareViewController: ChannelsViewControllerDelegate { + func selectedChannel(deck: Item) { + selectedChannel = deck + reloadConfigurationItems() + popConfigurationViewController() + } +} diff --git a/ios/MattermostShare/TeamsViewController.swift b/ios/MattermostShare/TeamsViewController.swift new file mode 100644 index 000000000..14768f0cc --- /dev/null +++ b/ios/MattermostShare/TeamsViewController.swift @@ -0,0 +1,57 @@ +import UIKit + +class TeamsViewController: UIViewController { + + lazy var tableView: UITableView = { + let tableView = UITableView(frame: self.view.frame) + tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = .clear + tableView.register(UITableViewCell.self, forCellReuseIdentifier: Identifiers.TeamCell) + return tableView + }() + var teamDecks = [Item]() + weak var delegate: TeamsViewControllerDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Teams" + view.addSubview(tableView) + } + +} + +private extension TeamsViewController { + struct Identifiers { + static let TeamCell = "teamCell" + } +} + +extension TeamsViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return teamDecks.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: Identifiers.TeamCell, for: indexPath) + cell.textLabel?.text = teamDecks[indexPath.row].title + if teamDecks[indexPath.row].selected { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + cell.backgroundColor = .clear + return cell + } +} + +protocol TeamsViewControllerDelegate: class { + func selectedTeam(deck: Item) +} + +extension TeamsViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + delegate?.selectedTeam(deck: teamDecks[indexPath.row]) + } +} diff --git a/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj b/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj new file mode 100644 index 000000000..0e60d8d3b --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj @@ -0,0 +1,371 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE04C2213818900D0F595 /* MattermostBucket.m */; }; + 7FABE055221387B500D0F595 /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE053221387B400D0F595 /* Constants.m */; }; + 7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE057221388D600D0F595 /* UploadSessionManager.swift */; }; + 7FABE05B2213892200D0F595 /* AttachmentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE0592213892100D0F595 /* AttachmentItem.swift */; }; + 7FABE05C2213892200D0F595 /* AttachmentArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE05A2213892100D0F595 /* AttachmentArray.swift */; }; + 7FABE0F7221466F900D0F595 /* UploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE0F6221466F900D0F595 /* UploadManager.swift */; }; + 7FABE0FA2214674200D0F595 /* StoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE0F82214674200D0F595 /* StoreManager.m */; }; + 7FABE0FC2214800F00D0F595 /* UploadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE0FB2214800F00D0F595 /* UploadSession.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 7FABE03422137F2900D0F595 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 7FABE03622137F2900D0F595 /* libUploadAttachments.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUploadAttachments.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7FABE04B2213818900D0F595 /* UploadAttachments-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UploadAttachments-Bridging-Header.h"; sourceTree = ""; }; + 7FABE04C2213818900D0F595 /* MattermostBucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = ""; }; + 7FABE04D2213818A00D0F595 /* MattermostBucket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MattermostBucket.h; sourceTree = ""; }; + 7FABE053221387B400D0F595 /* Constants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Constants.m; sourceTree = ""; }; + 7FABE054221387B500D0F595 /* Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; + 7FABE057221388D600D0F595 /* UploadSessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadSessionManager.swift; sourceTree = ""; }; + 7FABE0592213892100D0F595 /* AttachmentItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentItem.swift; sourceTree = ""; }; + 7FABE05A2213892100D0F595 /* AttachmentArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentArray.swift; sourceTree = ""; }; + 7FABE0F6221466F900D0F595 /* UploadManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadManager.swift; sourceTree = ""; }; + 7FABE0F82214674200D0F595 /* StoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoreManager.m; sourceTree = ""; }; + 7FABE0F92214674200D0F595 /* StoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoreManager.h; sourceTree = ""; }; + 7FABE0FB2214800F00D0F595 /* UploadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadSession.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7FABE03322137F2900D0F595 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7FABE02D22137F2900D0F595 = { + isa = PBXGroup; + children = ( + 7FABE03822137F2900D0F595 /* UploadAttachments */, + 7FABE03722137F2900D0F595 /* Products */, + ); + sourceTree = ""; + }; + 7FABE03722137F2900D0F595 /* Products */ = { + isa = PBXGroup; + children = ( + 7FABE03622137F2900D0F595 /* libUploadAttachments.a */, + ); + name = Products; + sourceTree = ""; + }; + 7FABE03822137F2900D0F595 /* UploadAttachments */ = { + isa = PBXGroup; + children = ( + 7FABE05A2213892100D0F595 /* AttachmentArray.swift */, + 7FABE0592213892100D0F595 /* AttachmentItem.swift */, + 7FABE054221387B500D0F595 /* Constants.h */, + 7FABE053221387B400D0F595 /* Constants.m */, + 7FABE04D2213818A00D0F595 /* MattermostBucket.h */, + 7FABE04C2213818900D0F595 /* MattermostBucket.m */, + 7FABE0F92214674200D0F595 /* StoreManager.h */, + 7FABE0F82214674200D0F595 /* StoreManager.m */, + 7FABE04B2213818900D0F595 /* UploadAttachments-Bridging-Header.h */, + 7FABE0F6221466F900D0F595 /* UploadManager.swift */, + 7FABE057221388D600D0F595 /* UploadSessionManager.swift */, + 7FABE0FB2214800F00D0F595 /* UploadSession.swift */, + ); + path = UploadAttachments; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7FABE03522137F2900D0F595 /* UploadAttachments */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7FABE03D22137F2900D0F595 /* Build configuration list for PBXNativeTarget "UploadAttachments" */; + buildPhases = ( + 7FABE03222137F2900D0F595 /* Sources */, + 7FABE03322137F2900D0F595 /* Frameworks */, + 7FABE0472213800800D0F595 /* Generate header files */, + 7FABE03422137F2900D0F595 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = UploadAttachments; + productName = UploadAttachments; + productReference = 7FABE03622137F2900D0F595 /* libUploadAttachments.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7FABE02E22137F2900D0F595 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = Mattermost; + TargetAttributes = { + 7FABE03522137F2900D0F595 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + }; + }; + buildConfigurationList = 7FABE03122137F2900D0F595 /* Build configuration list for PBXProject "UploadAttachments" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 7FABE02D22137F2900D0F595; + productRefGroup = 7FABE03722137F2900D0F595 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7FABE03522137F2900D0F595 /* UploadAttachments */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 7FABE0472213800800D0F595 /* Generate header files */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Generate header files"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\ntarget_dir=${BUILT_PRODUCTS_DIR}/include/${PRODUCT_MODULE_NAME}/\n\n# Ensure the target include path exists\nmkdir -p ${target_dir}\n\n# Copy any file that looks like a Swift generated header to the include path\ncp ${DERIVED_SOURCES_DIR}/*-Swift.h ${target_dir}\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7FABE03222137F2900D0F595 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7FABE05C2213892200D0F595 /* AttachmentArray.swift in Sources */, + 7FABE0FA2214674200D0F595 /* StoreManager.m in Sources */, + 7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */, + 7FABE0F7221466F900D0F595 /* UploadManager.swift in Sources */, + 7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */, + 7FABE055221387B500D0F595 /* Constants.m in Sources */, + 7FABE05B2213892200D0F595 /* AttachmentItem.swift in Sources */, + 7FABE0FC2214800F00D0F595 /* UploadSession.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7FABE03B22137F2900D0F595 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7FABE03C22137F2900D0F595 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7FABE03E22137F2900D0F595 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UQ8HT4Q2XM; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "$(SRCROOT)/UploadAttachments/module.modulemap"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "UploadAttachments/UploadAttachments-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7FABE03F22137F2900D0F595 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = UQ8HT4Q2XM; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "$(SRCROOT)/UploadAttachments/module.modulemap"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "UploadAttachments/UploadAttachments-Bridging-Header.h"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7FABE03122137F2900D0F595 /* Build configuration list for PBXProject "UploadAttachments" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7FABE03B22137F2900D0F595 /* Debug */, + 7FABE03C22137F2900D0F595 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7FABE03D22137F2900D0F595 /* Build configuration list for PBXNativeTarget "UploadAttachments" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7FABE03E22137F2900D0F595 /* Debug */, + 7FABE03F22137F2900D0F595 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7FABE02E22137F2900D0F595 /* Project object */; +} diff --git a/ios/UploadAttachments/UploadAttachments.xcodeproj/xcshareddata/xcschemes/UploadAttachments.xcscheme b/ios/UploadAttachments/UploadAttachments.xcodeproj/xcshareddata/xcschemes/UploadAttachments.xcscheme new file mode 100644 index 000000000..8dd4cbe61 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments.xcodeproj/xcshareddata/xcschemes/UploadAttachments.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/UploadAttachments/UploadAttachments/AttachmentArray.swift b/ios/UploadAttachments/UploadAttachments/AttachmentArray.swift new file mode 100644 index 000000000..0377a237d --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/AttachmentArray.swift @@ -0,0 +1,269 @@ +/// A thread-safe array. +public class AttachmentArray: NSObject { + fileprivate let queue = DispatchQueue(label: "com.mattermost.SynchronizedArray", attributes: .concurrent) + fileprivate var array = [Element]() + + public override init() {} +} + +// MARK: - Properties +public extension AttachmentArray { + + /// The first element of the collection. + var first: Element? { + var result: Element? + queue.sync { result = self.array.first } + return result + } + + /// The last element of the collection. + var last: Element? { + var result: Element? + queue.sync { result = self.array.last } + return result + } + + /// The number of elements in the array. + var count: Int { + var result = 0 + queue.sync { result = self.array.count } + return result + } + + /// A Boolean value indicating whether the collection is empty. + var isEmpty: Bool { + var result = false + queue.sync { result = self.array.isEmpty } + return result + } +} + +// MARK: - Immutable +public extension AttachmentArray { + /// Returns the first element of the sequence that satisfies the given predicate or nil if no such element is found. + /// + /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. + /// - Returns: The first match or nil if there was no match. + func first(where predicate: (Element) -> Bool) -> Element? { + var result: Element? + queue.sync { result = self.array.first(where: predicate) } + return result + } + + /// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate. + /// + /// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array. + /// - Returns: An array of the elements that includeElement allowed. + func filter(_ isIncluded: (Element) -> Bool) -> [Element] { + var result = [Element]() + queue.sync { result = self.array.filter(isIncluded) } + return result + } + + /// Returns the first index in which an element of the collection satisfies the given predicate. + /// + /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match. + /// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil. + func index(where predicate: (Element) -> Bool) -> Int? { + var result: Int? + queue.sync { result = self.array.index(where: predicate) } + return result + } + + /// Returns the elements of the collection, sorted using the given predicate as the comparison between elements. + /// + /// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false. + /// - Returns: A sorted array of the collection’s elements. + func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] { + var result = [Element]() + queue.sync { result = self.array.sorted(by: areInIncreasingOrder) } + return result + } + + /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence. + /// + /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value. + /// - Returns: An array of the non-nil results of calling transform with each element of the sequence. + func flatMap(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] { + var result = [ElementOfResult]() + queue.sync { result = self.array.flatMap(transform) } + return result + } + + /// Calls the given closure on each element in the sequence in the same order as a for-in loop. + /// + /// - Parameter body: A closure that takes an element of the sequence as a parameter. + func forEach(_ body: (Element) -> Void) { + queue.sync { self.array.forEach(body) } + } + + /// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate. + /// + /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match. + /// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false. + func contains(where predicate: (Element) -> Bool) -> Bool { + var result = false + queue.sync { result = self.array.contains(where: predicate) } + return result + } +} + +// MARK: - Mutable +public extension AttachmentArray { + + /// Adds a new element at the end of the array. + /// + /// - Parameter element: The element to append to the array. + func append( _ element: Element) { + queue.async(flags: .barrier) { + self.array.append(element) + } + } + + /// Adds a new element at the end of the array. + /// + /// - Parameter element: The element to append to the array. + func append( _ elements: [Element]) { + queue.async(flags: .barrier) { + self.array += elements + } + } + + /// Inserts a new element at the specified position. + /// + /// - Parameters: + /// - element: The new element to insert into the array. + /// - index: The position at which to insert the new element. + func insert( _ element: Element, at index: Int) { + queue.async(flags: .barrier) { + self.array.insert(element, at: index) + } + } + + /// Removes and returns the element at the specified position. + /// + /// - Parameters: + /// - index: The position of the element to remove. + /// - completion: The handler with the removed element. + func remove(at index: Int, completion: ((Element) -> Void)? = nil) { + queue.async(flags: .barrier) { + let element = self.array.remove(at: index) + + DispatchQueue.main.async { + completion?(element) + } + } + } + + /// Removes and returns the element at the specified position. + /// + /// - Parameters: + /// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. + /// - completion: The handler with the removed element. + func remove(where predicate: @escaping (Element) -> Bool, completion: ((Element) -> Void)? = nil) { + queue.async(flags: .barrier) { + guard let index = self.array.index(where: predicate) else { return } + let element = self.array.remove(at: index) + + DispatchQueue.main.async { + completion?(element) + } + } + } + + /// Removes all elements from the array. + /// + /// - Parameter completion: The handler with the removed elements. + func removeAll(completion: (([Element]) -> Void)? = nil) { + queue.async(flags: .barrier) { + let elements = self.array + self.array.removeAll() + + DispatchQueue.main.async { + completion?(elements) + } + } + } +} + +public extension AttachmentArray { + + /// Accesses the element at the specified position if it exists. + /// + /// - Parameter index: The position of the element to access. + /// - Returns: optional element if it exists. + subscript(index: Int) -> Element? { + get { + var result: Element? + + queue.sync { + guard self.array.startIndex.. Bool { + var result = false + queue.sync { result = self.array.contains(element) } + return result + } +} + +// MARK: - Infix operators +public extension AttachmentArray { + + static func +=(left: inout AttachmentArray, right: Element) { + left.append(right) + } + + static func +=(left: inout AttachmentArray, right: [Element]) { + left.append(right) + } +} + +extension AttachmentArray { + public func findBy(type: String) -> Bool { + var found = false + + self.queue.sync { + found = self.array.contains {element in + let attachment = element as! AttachmentItem + return attachment.type == type + } + } + + return found + } + + public func hasAttachementLargerThan(fileSize: UInt64) -> Bool { + var exceed = false + + self.queue.sync { + exceed = self.array.contains { element in + let attachment = element as! AttachmentItem + return attachment.fileSize > fileSize + } + } + + return exceed + } +} diff --git a/ios/UploadAttachments/UploadAttachments/AttachmentItem.swift b/ios/UploadAttachments/UploadAttachments/AttachmentItem.swift new file mode 100644 index 000000000..7baeb946d --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/AttachmentItem.swift @@ -0,0 +1,11 @@ +import Foundation + +public class AttachmentItem: NSObject { + public var fileName: String? + public var fileURL: URL? + public var fileSize: UInt64 = 0 + public var type: String? + + public override init() {} +} + diff --git a/ios/UploadAttachments/UploadAttachments/Constants.h b/ios/UploadAttachments/UploadAttachments/Constants.h new file mode 100644 index 000000000..05a791f68 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/Constants.h @@ -0,0 +1,7 @@ +#import + + +@interface Constants : NSObject +extern NSString *APP_GROUP_ID; +extern UInt64 DEFAULT_SERVER_MAX_FILE_SIZE; +@end diff --git a/ios/UploadAttachments/UploadAttachments/Constants.m b/ios/UploadAttachments/UploadAttachments/Constants.m new file mode 100644 index 000000000..4c3f899a4 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/Constants.m @@ -0,0 +1,6 @@ +#import "Constants.h" + +@implementation Constants +NSString *APP_GROUP_ID = @"group.com.mattermost.rnbeta"; +UInt64 DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024; +@end diff --git a/ios/UploadAttachments/UploadAttachments/MattermostBucket.h b/ios/UploadAttachments/UploadAttachments/MattermostBucket.h new file mode 100644 index 000000000..7ffd7aef4 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/MattermostBucket.h @@ -0,0 +1,12 @@ +#import + +@interface MattermostBucket : NSObject +- (NSUserDefaults *)bucketByName:(NSString*)name; +-(id) getPreference:(NSString *)key; +-(NSString *)readFromFile:(NSString *)fileName; +-(NSDictionary *)readFromFileAsJSON:(NSString *)fileName; +-(void)removeFile:(NSString *)fileName; +-(void)removePreference:(NSString *)key; +-(void)setPreference:(NSString *)key value:(NSString *) value; +-(void)writeToFile:(NSString *)fileName content:(NSString *)content; +@end diff --git a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m new file mode 100644 index 000000000..1aa8c7e3e --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m @@ -0,0 +1,69 @@ +#import "Constants.h" +#import "MattermostBucket.h" + +@implementation MattermostBucket + +-(NSUserDefaults *)bucketByName:(NSString*)name { + return [[NSUserDefaults alloc] initWithSuiteName: name]; +} + +-(NSString *)fileUrl:(NSString *)fileName { + NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:APP_GROUP_ID]; + return [NSString stringWithFormat:@"%@/%@", fileManagerURL.path, fileName]; +} + +-(id) getPreference:(NSString *)key { + NSUserDefaults* bucket = [self bucketByName: APP_GROUP_ID]; + return [bucket objectForKey:key]; +} + +-(NSString *)readFromFile:(NSString *)fileName { + NSString *filePath = [self fileUrl:fileName]; + NSFileManager *fileManager= [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:filePath]) { + return nil; + } + return [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; +} + +-(NSDictionary *)readFromFileAsJSON:(NSString *)fileName { + NSString *filePath = [self fileUrl:fileName]; + NSFileManager *fileManager= [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:filePath]) { + return nil; + } + NSData *data = [NSData dataWithContentsOfFile:filePath]; + return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; +} + +-(void)removeFile:(NSString *)fileName { + NSString *filePath = [self fileUrl:fileName]; + NSFileManager *fileManager= [NSFileManager defaultManager]; + if([fileManager isDeletableFileAtPath:filePath]) { + [fileManager removeItemAtPath:filePath error:nil]; + } +} + +-(void) removePreference:(NSString *)key { + NSUserDefaults* bucket = [self bucketByName: APP_GROUP_ID]; + [bucket removeObjectForKey: key]; +} + +-(void) setPreference:(NSString *)key value:(NSString *) value { + NSUserDefaults* bucket = [self bucketByName: APP_GROUP_ID]; + if (bucket && [key length] > 0 && [value length] > 0) { + [bucket setObject:value forKey:key]; + } +} + +-(void)writeToFile:(NSString *)fileName content:(NSString *)content { + NSString *filePath = [self fileUrl:fileName]; + NSFileManager *fileManager= [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:filePath]) { + [fileManager createFileAtPath:filePath contents:nil attributes:nil]; + } + if ([content length] > 0) { + [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; + } +} +@end diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.h b/ios/UploadAttachments/UploadAttachments/StoreManager.h new file mode 100644 index 000000000..8a0d282c5 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/StoreManager.h @@ -0,0 +1,22 @@ +#import +#import "MattermostBucket.h" + +@interface StoreManager : NSObject +@property MattermostBucket *bucket; +@property (nonatomic, strong) NSDictionary *entities; + ++(instancetype)shared; +-(NSDictionary *)getChannelById:(NSString *)channelId; +-(NSDictionary *)getChannelsBySections:(NSString *)forTeamId; +-(NSDictionary *)getCurrentChannel; +-(NSString *)getCurrentChannelId; +-(NSString *)getCurrentTeamId; +-(NSString *)getCurrentUserId; +-(NSDictionary *)getDefaultChannel:(NSString *)forTeamId; +-(NSDictionary *)getEntities:(BOOL)loadFromFile; +-(UInt64)getMaxFileSize; +-(NSArray *)getMyTeams; +-(NSString *)getServerUrl; +-(NSString *)getToken; +-(void)updateEntities:(NSString *)content; +@end diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.m b/ios/UploadAttachments/UploadAttachments/StoreManager.m new file mode 100644 index 000000000..7aebbcd4f --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/StoreManager.m @@ -0,0 +1,303 @@ +#import "StoreManager.h" +#import "Constants.h" + +@implementation StoreManager ++(instancetype)shared { + static id manager = nil; + static dispatch_once_t once; + dispatch_once(&once, ^{ + manager = [[self alloc] init]; + }); + + return manager; +} + +-(instancetype)init { + self = [super init]; + if (self) { + self.bucket = [[MattermostBucket alloc] init]; + [self getEntities:true]; + } + return self; +} + +#pragma public methods + +-(NSDictionary *)getChannelById:(NSString *)channelId { + NSDictionary *channelsStore = [self.entities objectForKey:@"channels"]; + NSDictionary *channels = [channelsStore objectForKey:@"channels"]; + + for (NSString * key in channels) { + NSDictionary *channel = channels[key]; + NSString *channel_id = [channel objectForKey:@"id"]; + if ([channel_id isEqualToString:channelId]) { + return channel; + } + } + + return nil; +} + +-(NSDictionary *)getChannelsBySections:(NSString *)forTeamId { + NSDictionary *channelsStore = [self.entities objectForKey:@"channels"]; + NSString *currentUserId = [self getCurrentUserId]; + NSDictionary *channels = [channelsStore objectForKey:@"channels"]; + NSMutableDictionary *channelsInTeam = [[NSMutableDictionary alloc] init]; + NSMutableArray *publicChannels = [[NSMutableArray alloc] init]; + NSMutableArray *privateChannels = [[NSMutableArray alloc] init]; + NSMutableArray *directChannels = [[NSMutableArray alloc] init]; + + for (NSString * key in channels) { + NSMutableDictionary *channel = [[channels objectForKey:key] mutableCopy]; + NSString *team_id = [channel objectForKey:@"team_id"]; + NSString *channelType = [channel objectForKey:@"type"]; + BOOL isDM = [channelType isEqualToString:@"D"]; + BOOL isGM = [channelType isEqualToString:@"G"]; + BOOL isPublic = [channelType isEqualToString:@"O"]; + BOOL isPrivate = [channelType isEqualToString:@"P"]; + if ([team_id isEqualToString:forTeamId] || isDM || isGM) { + if (isPublic) { + // public channel + [publicChannels addObject:channel]; + } else if (isPrivate) { + // private channel + [privateChannels addObject:channel]; + } else if (isDM) { + // direct message + NSString *otherUserId = [self getOtherUserIdFromChannel:currentUserId withChannelName:[channel objectForKey:@"name"]]; + NSDictionary *otherUser = [self getUserById:otherUserId]; + if (otherUser) { + [channel setObject:[self displayUserName:otherUser] forKey:@"display_name"]; + [directChannels addObject:channel]; + } + } else { + [channel setObject:[self completeDirectGroupInfo:key] forKey:@"display_name"]; + [directChannels addObject:channel]; + } + } + } + + [channelsInTeam setObject:[self sortDictArrayByDisplayName:publicChannels] forKey:@"public"]; + [channelsInTeam setObject:[self sortDictArrayByDisplayName:privateChannels] forKey:@"private"]; + [channelsInTeam setObject:[self sortDictArrayByDisplayName:directChannels] forKey:@"direct"]; + + return channelsInTeam; +} + +-(NSDictionary *)getCurrentChannel { + NSString *currentChannelId = [self getCurrentChannelId]; + return [self getChannelById:currentChannelId]; +} + + + +-(NSString *)getCurrentChannelId { + return [[self.entities objectForKey:@"channels"] objectForKey:@"currentChannelId"]; +} + +-(NSString *)getCurrentTeamId { + return [[self.entities objectForKey:@"teams"] objectForKey:@"currentTeamId"]; +} + +-(NSString *)getCurrentUserId { + return [[self.entities objectForKey:@"users"] objectForKey:@"currentUserId"]; +} + +-(NSDictionary *)getDefaultChannel:(NSString *)forTeamId { + NSArray *channelsInTeam = [self getChannelsInTeam:forTeamId]; + NSPredicate *filter = [NSPredicate predicateWithFormat:@"name = %@", @"town-square"]; + NSArray *townSquare = [channelsInTeam filteredArrayUsingPredicate:filter]; + if (townSquare != nil && townSquare.count > 0) { + return townSquare.firstObject; + } + + return nil; +} + +-(NSDictionary *)getEntities:(BOOL)loadFromFile { + if (loadFromFile) { + self.entities = [self.bucket readFromFileAsJSON:@"entities"]; + } + return self.entities; +} + +-(NSArray *)getMyTeams { + NSDictionary *teamsStore = [self.entities objectForKey:@"teams"]; + NSDictionary *teams = [teamsStore objectForKey:@"teams"]; + NSDictionary *membership = [teamsStore objectForKey:@"myMembers"]; + NSMutableArray *myTeams = [[NSMutableArray alloc] init]; + + for (NSString* key in teams) { + NSDictionary *member = [membership objectForKey:key]; + NSDictionary *team = [teams objectForKey:key]; + if (member) { + [myTeams addObject:team]; + } + } + + + return [self sortDictArrayByDisplayName:myTeams]; +} + +-(NSString *)getServerUrl { + NSDictionary *general = [self.entities objectForKey:@"general"]; + NSDictionary *credentials = [general objectForKey:@"credentials"]; + + return [credentials objectForKey:@"url"]; +} + +-(NSString *)getToken { + NSDictionary *general = [self.entities objectForKey:@"general"]; + NSDictionary *credentials = [general objectForKey:@"credentials"]; + + return [credentials objectForKey:@"token"]; +} + +-(UInt64)getMaxFileSize { + NSDictionary *config = [self getConfig]; + if (config != nil && [config objectForKey:@"MaxFileSize"]) { + NSString *maxFileSize = [config objectForKey:@"MaxFileSize"]; + NSScanner *scanner = [NSScanner scannerWithString:maxFileSize]; + unsigned long long convertedValue = 0; + [scanner scanUnsignedLongLong:&convertedValue]; + return convertedValue; + } + + return DEFAULT_SERVER_MAX_FILE_SIZE; +} + +-(void)updateEntities:(NSString *)content { + [self.bucket writeToFile:@"entities" content:content]; +} + +#pragma utilities + +-(NSString *)completeDirectGroupInfo:(NSString *)channelId { + NSDictionary *usersStore = [self.entities objectForKey:@"users"]; + NSDictionary *profilesInChannels = [usersStore objectForKey:@"profilesInChannel"]; + NSDictionary *profileIds = [profilesInChannels objectForKey:channelId]; + NSDictionary *channel = [self getChannelById:channelId]; + NSString *currentUserId = [self getCurrentUserId]; + NSMutableArray *result = [[NSMutableArray alloc] init]; + + if (profileIds) { + for (NSString *key in profileIds) { + NSDictionary *user = [self getUserById:key]; + NSString *userId = [user objectForKey:@"id"]; + if (![userId isEqualToString:currentUserId]) { + NSString *fullName = [self getUserFullName:user]; + [result addObject:fullName]; + } + } + } + + if ([result count] != ([profileIds count] - 1)) { + return [channel objectForKey:@"display_name"]; + } + + NSArray *sorted = [result sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + return [sorted componentsJoinedByString:@", "]; +} + +-(NSString *)displayUserName:(NSDictionary *)user { + NSString *teammateNameDisplay = [self getTeammateNameDisplaySetting]; + NSString *username = [user objectForKey:@"username"]; + NSString *name; + + if ([teammateNameDisplay isEqualToString:@"nickname_full_name"]) { + name = [user objectForKey:@"nickname"] ?: [self getUserFullName:user]; + } else if ([teammateNameDisplay isEqualToString:@"full_name"]) { + name = [self getUserFullName:user]; + } else { + name = username; + } + + if (!name || [[name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) { + name = username; + } + + return name; +} + +-(NSArray *)getChannelsInTeam:(NSString *) teamId { + NSDictionary *channels = [[self.entities objectForKey:@"channels"] objectForKey:@"channels"]; + NSArray *arr = [channels allValues]; + NSPredicate *filter = [NSPredicate predicateWithFormat:@"team_id = %@", teamId]; + + return [arr filteredArrayUsingPredicate:filter]; +} + +-(NSDictionary *)getConfig { + return [[self.entities objectForKey:@"general"] objectForKey:@"config"]; +} + +-(NSDictionary *)getMyPreferences { + return [[self.entities objectForKey:@"preferences"] objectForKey:@"myPreferences"]; +} + +-(NSString *)getOtherUserIdFromChannel:(NSString *)currentUserId withChannelName:(NSString *)channelName { + NSArray *ids = [channelName componentsSeparatedByString:@"_"]; + if ([ids[0] isEqualToString:currentUserId]) { + return ids[2]; + } + + return ids[0]; +} + +-(NSDictionary *)getUserById:(NSString *)userId { + NSDictionary *usersStore = [self.entities objectForKey:@"users"]; + NSDictionary *users = [usersStore objectForKey:@"profiles"]; + + for (NSString* key in users) { + NSDictionary *user = [users objectForKey:key]; + NSString *user_id = [user objectForKey:@"id"]; + if ([user_id isEqualToString:userId]) { + return user; + } + } + + return nil; +} + +-(NSString *)getUserFullName:(NSDictionary*) user { + NSString *fistName = [user objectForKey:@"first_name"]; + NSString *lastName = [user objectForKey:@"last_name"]; + + if (fistName && lastName) { + return [fistName stringByAppendingFormat:@" %@", lastName]; + } else if (fistName) { + return fistName; + } else if (lastName) { + return lastName; + } + + return @""; +} + +-(NSString *)getTeammateNameDisplaySetting { + NSDictionary *config = [self getConfig]; + NSString *teammateNameDisplay = [config objectForKey:@"TeammateNameDisplay"]; + + NSDictionary *preferences = [self getMyPreferences]; + NSString *key = @"display_settings--name_format"; + NSDictionary *displayFormat = [preferences objectForKey:key]; + + if (displayFormat) { + return [displayFormat objectForKey:@"value"]; + } else if (teammateNameDisplay) { + return teammateNameDisplay; + } + + return @"username"; +} + +-(NSArray *)sortDictArrayByDisplayName:(NSArray *)array { + NSSortDescriptor *sd = [[NSSortDescriptor alloc] + initWithKey:@"display_name" + ascending:YES + selector: @selector(localizedCaseInsensitiveCompare:)]; + NSArray *sortDescriptor = [NSArray arrayWithObjects:sd, nil]; + return [array sortedArrayUsingDescriptors:sortDescriptor]; +} + +@end diff --git a/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h b/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h new file mode 100644 index 000000000..dae2745e4 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h @@ -0,0 +1,7 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "Constants.h" +#import "MattermostBucket.h" +#import "StoreManager.h" diff --git a/ios/UploadAttachments/UploadAttachments/UploadManager.swift b/ios/UploadAttachments/UploadAttachments/UploadManager.swift new file mode 100644 index 000000000..d86d5c14d --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/UploadManager.swift @@ -0,0 +1,41 @@ +import Foundation +import UIKit + +@objc @objcMembers public class UploadManager: NSObject { + public class var shared :UploadManager { + struct Singleton { + static let instance = UploadManager() + } + return Singleton.instance + } + + public func uploadFiles(baseURL: String, token: String, channelId: String, message: String?, attachments: AttachmentArray, callback: () -> Void) { + let identifier = "mattermost-share-upload-\(UUID().uuidString)" + UploadSessionManager.shared.createUploadSessionData( + identifier: identifier, + channelId: channelId, + message: message ?? "", + totalFiles: attachments.count + ) + + if attachments.count > 0 { + // if the share action has attachments we upload the files first + let uploadSession = UploadSession.shared.createURLSession(identifier: identifier) + + for index in 0.. Void)? + public var session: URLSession? + + public func createPost(identifier: String) { + let store = StoreManager.shared() as StoreManager + let _ = store.getEntities(true) + let serverURL = store.getServerUrl() + let sessionToken = store.getToken() + let urlString = "\(serverURL!)/api/v4/posts" + + guard let uploadSessionData = UploadSessionManager.shared.getUploadSessionData(identifier: identifier) else {return} + guard let url = URL(string: urlString) else {return} + + if uploadSessionData.message != "" || uploadSessionData.fileIds.count > 0 { + let jsonObject: [String: Any] = [ + "channel_id": uploadSessionData.channelId as Any, + "message": uploadSessionData.message as Any, + "file_ids": uploadSessionData.fileIds + ] + if !JSONSerialization.isValidJSONObject(jsonObject) {return} + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("Bearer \(sessionToken!)", forHTTPHeaderField: "Authorization") + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + request.httpBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) + URLSession(configuration: .ephemeral).dataTask(with: request).resume() + + UploadSessionManager.shared.removeUploadSessionData(identifier: identifier) + UploadSessionManager.shared.clearTempDirectory() + } + } + + public func attachSession(identifier: String, completionHandler: @escaping () -> Void) { + self.completionHandler = completionHandler + let sessionConfig = URLSessionConfiguration.background(withIdentifier: identifier) + sessionConfig.sharedContainerIdentifier = APP_GROUP_ID + if #available(iOS 11.0, *) { + sessionConfig.waitsForConnectivity = true + } + + session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: OperationQueue.main) + } + + public func createURLSession(identifier: String) -> URLSession { + let sessionConfig = URLSessionConfiguration.background(withIdentifier: identifier) + sessionConfig.sharedContainerIdentifier = APP_GROUP_ID + if #available(iOS 11.0, *) { + sessionConfig.waitsForConnectivity = true + } + + self.session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil) + return self.session! + } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + // here we should get the file Id and update it in the session + guard let identifier = session.configuration.identifier else {return} + do { + let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as! NSDictionary + let fileInfos = jsonObject.object(forKey: "file_infos") as! NSArray + if fileInfos.count > 0 { + let fileInfoData = fileInfos[0] as! NSDictionary + let fileId = fileInfoData.object(forKey: "id") as! String + UploadSessionManager.shared.appendCompletedUploadToSession(identifier: identifier, fileId: fileId) + } + } catch { + print("MMLOG: Failed to get the file upload response %@", error.localizedDescription) + } + + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if error == nil { + let identifier = session.configuration.identifier! + guard let sessionData = UploadSessionManager.shared.getUploadSessionData(identifier: identifier) else {return} + if sessionData.fileIds.count == sessionData.totalFiles { + ProcessInfo().performExpiringActivity(withReason: "Need to post the message") { (expires) in + self.createPost(identifier: identifier) + } + } + } + } + + public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + DispatchQueue.main.async { + + if self.completionHandler != nil { + self.completionHandler!() + } + } + } +} diff --git a/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift b/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift new file mode 100644 index 000000000..5406862a1 --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/UploadSessionManager.swift @@ -0,0 +1,79 @@ +import Foundation + +@objc public class UploadSessionData: NSObject { + public var channelId: String? + public var fileIds: [String] = [] + public var message: String = "" + public var totalFiles: Int = 0 +} + +@objc @objcMembers public class UploadSessionManager: NSObject { + private let bucket = MattermostBucket().bucket(byName: APP_GROUP_ID) + + public class var shared :UploadSessionManager { + struct Singleton { + static let instance = UploadSessionManager() + } + return Singleton.instance + } + + public func createUploadSessionData(identifier: String, channelId: String, message: String, totalFiles: Int) { + let fileIds: [String] = [] + let uploadSessionData: NSDictionary = [ + "channelId": channelId, + "fileIds": fileIds, + "message": message, + "totalFiles": totalFiles + ] + bucket?.set(uploadSessionData, forKey: identifier) + bucket?.synchronize() + } + + public func getUploadSessionData(identifier: String) -> UploadSessionData? { + let dictionary = bucket?.object(forKey: identifier) as? NSDictionary + let sessionData = UploadSessionData() + + sessionData.channelId = dictionary?.object(forKey: "channelId") as? String + sessionData.fileIds = dictionary?.object(forKey: "fileIds") as? [String] ?? [] + sessionData.message = dictionary?.object(forKey: "message") as! String + sessionData.totalFiles = dictionary?.object(forKey: "totalFiles") as! Int + + return sessionData + } + + public func removeUploadSessionData(identifier: String) { + bucket?.removeObject(forKey: identifier) + bucket?.synchronize() + } + + public func appendCompletedUploadToSession(identifier: String, fileId: String) { + let uploadSessionData = bucket?.object(forKey: identifier) as? NSDictionary + if (uploadSessionData != nil) { + let newData = uploadSessionData?.mutableCopy() as! NSMutableDictionary + var fileIds = newData.object(forKey: "fileIds") as! [String] + fileIds.append(fileId) + newData.setValue(fileIds, forKey: "fileIds") + bucket?.set(newData, forKey: identifier) + bucket?.synchronize() + } + } + + public func tempContainerURL() -> URL? { + let filemgr = FileManager.default + let containerURL = filemgr.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_ID) + guard let tempDirectoryURL = containerURL?.appendingPathComponent("shareTempItems") else {return nil} + var isDirectory = ObjCBool(false) + let exists = filemgr.fileExists(atPath: tempDirectoryURL.path, isDirectory: &isDirectory) + if !exists && !isDirectory.boolValue { + try? filemgr.createDirectory(atPath: tempDirectoryURL.path, withIntermediateDirectories: true, attributes: nil) + } + + return tempDirectoryURL + } + + public func clearTempDirectory() { + guard let tempURL = tempContainerURL() else {return} + let fileMgr = FileManager.default + try? fileMgr.removeItem(atPath: tempURL.path) + } +} diff --git a/package-lock.json b/package-lock.json index 8a3239d2a..801d3a543 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12098,14 +12098,6 @@ "prop-types": "^15.6.1" } }, - "react-native-tableview": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-native-tableview/-/react-native-tableview-2.4.1.tgz", - "integrity": "sha512-TrCUPYbWFBYGg0ZBZ6TDEwBA1+xksSk0ffpwMJNav9q6GQOD0t6eMXCkHMxA5vkBo3+i+fwWQd0sSVHYdZuPbA==", - "requires": { - "prop-types": "^15.6.2" - } - }, "react-native-vector-icons": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-6.3.0.tgz", diff --git a/package.json b/package.json index b24be8c01..deb967cac 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "react-native-slider": "0.11.0", "react-native-status-bar-size": "0.3.3", "react-native-svg": "9.2.4", - "react-native-tableview": "2.4.1", "react-native-vector-icons": "6.3.0", "react-native-video": "4.4.0", "react-native-webview": "5.2.1", diff --git a/share.ios.js b/share.ios.js deleted file mode 100644 index 6b18654bd..000000000 --- a/share.ios.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {AppRegistry} from 'react-native'; - -import ShareExtension from 'share_extension/ios'; - -AppRegistry.registerComponent('MattermostShare', () => ShareExtension); diff --git a/share_extension/ios/error_message.js b/share_extension/ios/error_message.js deleted file mode 100644 index bdadbb996..000000000 --- a/share_extension/ios/error_message.js +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import React from 'react'; -import PropTypes from 'prop-types'; -import {TouchableOpacity, View} from 'react-native'; - -import {Preferences} from 'mattermost-redux/constants'; - -import FormattedText from 'app/components/formatted_text'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - -export default function errorMessage(props) { - const {close} = props; - const theme = Preferences.THEMES.default; - const styles = getStyleSheet(theme); - - return ( - - - - - - - close()} - > - - - - - - ); -} - -errorMessage.propTypes = { - close: PropTypes.func.isRequired, -}; - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - errorButton: { - alignItems: 'center', - justifyContent: 'center', - borderTopWidth: 2, - borderTopColor: changeOpacity(theme.linkColor, 0.3), - paddingVertical: 15, - }, - errorButtonText: { - color: changeOpacity(theme.linkColor, 0.7), - fontSize: 18, - }, - errorContainer: { - borderRadius: 5, - backgroundColor: 'white', - marginHorizontal: 35, - }, - errorContent: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.05), - }, - errorMessage: { - alignItems: 'center', - justifyContent: 'center', - padding: 25, - }, - errorMessageText: { - color: theme.centerChannelColor, - fontSize: 16, - textAlign: 'center', - }, - errorWrapper: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - backgroundColor: changeOpacity(theme.centerChannelColor, 0.5), - }, - }; -}); diff --git a/share_extension/ios/extension.js b/share_extension/ios/extension.js deleted file mode 100644 index ae60ac76e..000000000 --- a/share_extension/ios/extension.js +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import {Animated, Dimensions, NavigatorIOS, StyleSheet, View} from 'react-native'; -import {intlShape} from 'react-intl'; - -import {Preferences} from 'mattermost-redux/constants'; - -import initialState from 'app/initial_state'; -import mattermostBucket from 'app/mattermost_bucket'; - -import ExtensionPost from './extension_post'; - -const {View: AnimatedView} = Animated; - -export default class SharedApp extends PureComponent { - static propTypes = { - appGroupId: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - constructor(props) { - super(props); - - const {height, width} = Dimensions.get('window'); - const isLandscape = width > height; - - this.state = { - backdropOpacity: new Animated.Value(0), - containerOpacity: new Animated.Value(0), - isLandscape, - }; - - mattermostBucket.readFromFile('entities', props.appGroupId).then((value) => { - this.entities = value || initialState.entities; - this.setState({init: true}); - }); - } - - componentDidMount() { - Animated.parallel([ - Animated.timing(this.state.backdropOpacity, { - toValue: 0.5, - duration: 100, - }), - Animated.timing(this.state.containerOpacity, { - toValue: 1, - duration: 250, - }), - ]).start(); - } - - onLayout = (e) => { - const {height, width} = e.nativeEvent.layout; - const isLandscape = width > height; - if (this.state.isLandscape !== isLandscape) { - this.setState({isLandscape}); - } - }; - - userIsLoggedIn = () => { - if ( - this.entities && - this.entities.general && - this.entities.general.credentials && - this.entities.general.credentials.token && - this.entities.general.credentials.url - ) { - return true; - } - - return false; - }; - - render() { - const {init, isLandscape} = this.state; - const {intl} = this.context; - const {formatMessage} = intl; - - if (!init) { - return null; - } - - const title = formatMessage({ - id: 'mobile.extension.title', - defaultMessage: 'Share in Mattermost', - }); - - const theme = Preferences.THEMES.default; - - const initialRoute = { - component: ExtensionPost, - title, - passProps: { - authenticated: this.userIsLoggedIn(), - entities: this.entities, - onClose: this.props.onClose, - isLandscape, - theme, - title, - }, - wrapperStyle: { - borderRadius: 10, - backgroundColor: theme.centerChannelBg, - }, - }; - - return ( - - - - - - - - - ); - } -} - -const styles = StyleSheet.create({ - flex: { - flex: 1, - }, - backdrop: { - position: 'absolute', - flex: 1, - backgroundColor: '#000', - height: '100%', - width: '100%', - }, - wrapper: { - flex: 1, - marginHorizontal: 20, - }, - container: { - backgroundColor: 'white', - borderRadius: 10, - position: 'relative', - width: '100%', - }, -}); diff --git a/share_extension/ios/extension_channels.js b/share_extension/ios/extension_channels.js deleted file mode 100644 index 46a5e5f07..000000000 --- a/share_extension/ios/extension_channels.js +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import { - ActivityIndicator, - Text, - View, -} from 'react-native'; -import DeviceInfo from 'react-native-device-info'; -import {intlShape} from 'react-intl'; -import TableView from 'react-native-tableview'; -import Icon from 'react-native-vector-icons/FontAwesome'; - -import {General} from 'mattermost-redux/constants'; - -import SearchBar from 'app/components/search_bar'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - -import { - getExtensionSortedDirectChannels, - getExtensionSortedPrivateChannels, - getExtensionSortedPublicChannels, -} from 'share_extension/common/selectors'; - -import ExtensionNavBar from './extension_nav_bar'; - -export default class ExtensionChannels extends PureComponent { - static propTypes = { - entities: PropTypes.object, - currentChannelId: PropTypes.string.isRequired, - navigator: PropTypes.object.isRequired, - onSelectChannel: PropTypes.func.isRequired, - teamId: PropTypes.string.isRequired, - theme: PropTypes.object.isRequired, - title: PropTypes.string.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - state = { - sections: null, - }; - - componentWillMount() { - this.loadChannels(); - } - - buildSections = (term = '') => { - const {publicChannels: pub, privateChannels: priv, directChannels: dms} = this.state; - const sections = []; - const publicChannels = this.filterChannels(pub, term); - const privateChannels = this.filterChannels(priv, term); - const directChannels = this.filterChannels(dms, term); - - if (publicChannels.length) { - sections.push({ - id: 'sidebar.channels', - defaultMessage: 'PUBLIC CHANNELS', - data: publicChannels.sort(this.sortDisplayName), - }); - } - - if (privateChannels.length) { - sections.push({ - id: 'sidebar.pg', - defaultMessage: 'PRIVATE CHANNELS', - data: privateChannels.sort(this.sortDisplayName), - }); - } - - if (directChannels.length) { - sections.push({ - id: 'sidebar.direct', - defaultMessage: 'DIRECT MESSAGES', - data: directChannels.sort(this.sortDisplayName), - }); - } - - this.setState({sections}); - }; - - cancelSearch = () => { - this.setState({term: ''}); - this.buildSections(); - }; - - filterChannels = (channels, term) => { - return channels.filter((c) => c.display_name.toLowerCase().includes(term.toLocaleLowerCase()) && c.delete_at === 0); - }; - - goBack = () => { - this.props.navigator.pop(); - }; - - loadChannels = async () => { - try { - const {entities, teamId} = this.props; - const views = { - extension: { - selectedTeamId: teamId, - }, - }; - const state = {entities, views}; - const publicChannels = getExtensionSortedPublicChannels(state); - const privateChannels = getExtensionSortedPrivateChannels(state); - const directChannels = getExtensionSortedDirectChannels(state); - - const icons = await Promise.all([ - Icon.getImageSource('globe', 16, this.props.theme.centerChannelColor), - Icon.getImageSource('lock', 16, this.props.theme.centerChannelColor), - Icon.getImageSource('user', 16, this.props.theme.centerChannelColor), - Icon.getImageSource('users', 16, this.props.theme.centerChannelColor), - ]); - - this.publicChannelIcon = icons[0]; - this.privateChannelIcon = icons[1]; - this.dmChannelIcon = icons[2]; - this.gmChannelIcon = icons[3]; - - this.setState({ - publicChannels, - privateChannels, - directChannels, - }, () => { - this.buildSections(); - }); - } catch (error) { - this.setState({error}); - } - }; - - handleSearch = (term) => { - this.setState({term}, () => { - if (this.throttleTimeout) { - clearTimeout(this.throttleTimeout); - } - - this.throttleTimeout = setTimeout(() => { - this.buildSections(term); - }, 300); - }); - }; - - handleSelectChannel = (selected) => { - const {sections} = this.state; - const section = sections.find((s) => s.id === selected.detail); - if (section) { - const channel = section.data.find((c) => c.id === selected.value); - if (channel) { - this.props.onSelectChannel(channel); - this.goBack(); - } - } - }; - - renderBody = (styles) => { - const {theme} = this.props; - const {error, sections} = this.state; - - if (error) { - return ( - - - {error.message} - - - ); - } - - if (!sections) { - return ( - - - - ); - } - - return ( - - - {this.renderSearchBar(styles)} - - {this.renderSections()} - - ); - }; - - renderSearchBar = (styles) => { - const {formatMessage} = this.context.intl; - const {theme} = this.props; - - return ( - - - - ); - }; - - renderSections = () => { - const {formatMessage} = this.context.intl; - const {currentChannelId} = this.props; - const {sections} = this.state; - - return sections.map((s) => { - const items = s.data.map((c) => { - let icon; - switch (c.type) { - case General.OPEN_CHANNEL: - icon = this.publicChannelIcon.uri; - break; - case General.PRIVATE_CHANNEL: - icon = this.privateChannelIcon.uri; - break; - case General.DM_CHANNEL: - icon = this.dmChannelIcon.uri; - break; - case General.GM_CHANNEL: - icon = this.gmChannelIcon.uri; - break; - } - - return ( - - {c.display_name} - - ); - }); - - return ( - - {items} - - ); - }); - }; - - sortDisplayName = (a, b) => { - const locale = DeviceInfo.getDeviceLocale().split('-')[0]; - return a.display_name.localeCompare(b.display_name, locale, {numeric: true}); - }; - - render() { - const {theme, title} = this.props; - const styles = getStyleSheet(theme); - - return ( - - - {this.renderBody(styles)} - - ); - } -} - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - flex: { - flex: 1, - }, - separatorContainer: { - paddingLeft: 35, - }, - separator: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), - height: 1, - }, - loadingContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - }, - searchContainer: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), - paddingBottom: 2, - paddingHorizontal: 5, - }, - searchBarInput: { - backgroundColor: '#fff', - color: theme.centerChannelColor, - fontSize: 15, - }, - titleContainer: { - height: 30, - }, - title: { - color: changeOpacity(theme.centerChannelColor, 0.6), - fontSize: 15, - lineHeight: 30, - paddingHorizontal: 15, - }, - errorContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - paddingHorizontal: 15, - }, - error: { - color: theme.errorTextColor, - fontSize: 14, - }, - }; -}); diff --git a/share_extension/ios/extension_nav_bar.js b/share_extension/ios/extension_nav_bar.js deleted file mode 100644 index 954697930..000000000 --- a/share_extension/ios/extension_nav_bar.js +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import {Text, TouchableOpacity, View} from 'react-native'; -import IonIcon from 'react-native-vector-icons/Ionicons'; - -import {emptyFunction} from 'app/utils/general'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - -export default class ExtensionNavBar extends PureComponent { - static propTypes = { - authenticated: PropTypes.bool, - backButton: PropTypes.bool, - leftButtonTitle: PropTypes.string, - onLeftButtonPress: PropTypes.func, - onRightButtonPress: PropTypes.func, - rightButtonTitle: PropTypes.string, - theme: PropTypes.object.isRequired, - title: PropTypes.string, - }; - - static defaultProps = { - backButton: false, - onLeftButtonPress: emptyFunction, - }; - - renderLeftButton = (styles) => { - const {backButton, leftButtonTitle, onLeftButtonPress} = this.props; - let backComponent; - if (backButton) { - backComponent = ( - - ); - } else if (leftButtonTitle) { - backComponent = ( - - {leftButtonTitle} - - ); - } - - if (backComponent) { - return ( - - {backComponent} - - ); - } - - return ; - }; - - renderRightButton = (styles) => { - const {authenticated, onRightButtonPress, rightButtonTitle} = this.props; - - if (rightButtonTitle && authenticated) { - return ( - - - {rightButtonTitle} - - - ); - } - - return ; - }; - - render() { - const {theme, title} = this.props; - const styles = getStyleSheet(theme); - - return ( - - {this.renderLeftButton(styles)} - - - {title} - - - {this.renderRightButton(styles)} - - ); - } -} - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - container: { - borderBottomColor: changeOpacity(theme.centerChannelColor, 0.2), - borderBottomWidth: 1, - flexDirection: 'row', - height: 45, - }, - backButtonContainer: { - justifyContent: 'center', - flex: 1, - paddingLeft: 15, - }, - titleContainer: { - alignItems: 'center', - flex: 3, - justifyContent: 'center', - }, - backButton: { - color: theme.linkColor, - fontSize: 34, - }, - leftButton: { - color: theme.linkColor, - fontSize: 16, - fontWeight: '600', - }, - title: { - fontSize: 17, - fontWeight: '600', - textAlign: 'center', - }, - rightButtonContainer: { - justifyContent: 'center', - flex: 1, - paddingRight: 15, - }, - rightButton: { - color: theme.linkColor, - fontSize: 16, - fontWeight: '600', - textAlign: 'right', - }, - }; -}); diff --git a/share_extension/ios/extension_post.js b/share_extension/ios/extension_post.js deleted file mode 100644 index 20c856c04..000000000 --- a/share_extension/ios/extension_post.js +++ /dev/null @@ -1,839 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import {intlShape} from 'react-intl'; -import { - ActivityIndicator, - DeviceEventEmitter, - Dimensions, - Image, - NativeModules, - ScrollView, - Text, - TextInput, - TouchableHighlight, - View, -} from 'react-native'; -import IonIcon from 'react-native-vector-icons/Ionicons'; -import Video from 'react-native-video'; -import LocalAuth from 'react-native-local-auth'; -import RNFetchBlob from 'rn-fetch-blob'; - -import {Client4} from 'mattermost-redux/client'; -import {General} from 'mattermost-redux/constants'; -import {getChannel, getDefaultChannel} from 'mattermost-redux/selectors/entities/channels'; -import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_utils'; -import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers'; - -import mattermostBucket from 'app/mattermost_bucket'; -import {generateId, getAllowedServerMaxFileSize} from 'app/utils/file'; -import {preventDoubleTap} from 'app/utils/tap'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; -import Config from 'assets/config'; - -import { - ExcelSvg, - GenericSvg, - PdfSvg, - PptSvg, - ZipSvg, -} from 'share_extension/common/icons'; - -import ExtensionChannels from './extension_channels'; -import ExtensionNavBar from './extension_nav_bar'; -import ExtensionTeams from './extension_teams'; - -const ShareExtension = NativeModules.MattermostShare; -const MAX_INPUT_HEIGHT = 95; -const MAX_MESSAGE_LENGTH = 4000; -const MAX_FILE_SIZE = 20 * 1024 * 1024; - -const extensionSvg = { - csv: ExcelSvg, - pdf: PdfSvg, - ppt: PptSvg, - pptx: PptSvg, - xls: ExcelSvg, - xlsx: ExcelSvg, - zip: ZipSvg, -}; - -export default class ExtensionPost extends PureComponent { - static propTypes = { - authenticated: PropTypes.bool.isRequired, - entities: PropTypes.object, - navigator: PropTypes.object.isRequired, - onClose: PropTypes.func.isRequired, - theme: PropTypes.object.isRequired, - title: PropTypes.string, - }; - - static contextTypes = { - intl: intlShape, - }; - - constructor(props, context) { - super(props, context); - - const {height, width} = Dimensions.get('window'); - const isLandscape = width > height; - const entities = props.entities; - - this.useBackgroundUpload = props.authenticated ? isMinimumServerVersion(entities.general.serverVersion, 4, 8) : false; - - this.state = { - entities, - error: null, - files: [], - isLandscape, - exceededSize: 0, - value: '', - sending: false, - }; - } - - componentWillMount() { - this.event = DeviceEventEmitter.addListener('extensionPostFailed', this.handlePostFailed); - this.loadData(); - } - - componentDidMount() { - this.focusInput(); - } - - componentWillUnmount() { - this.event.remove(); - } - - componentDidUpdate() { - this.focusInput(); - } - - emmAuthenticationIfNeeded = async () => { - try { - const emmSecured = await mattermostBucket.getPreference('emm', Config.AppGroupId); - if (emmSecured) { - const {intl} = this.context; - await LocalAuth.authenticate({ - reason: intl.formatMessage({ - id: 'mobile.managed.secured_by', - defaultMessage: 'Secured by {vendor}', - }, {emmSecured}), - fallbackToPasscode: true, - suppressEnterPassword: true, - }); - } - } catch (error) { - this.props.onClose(); - } - }; - - focusInput = () => { - if (this.input && !this.input.isFocused()) { - this.input.focus(); - } - }; - - getInputRef = (ref) => { - this.input = ref; - }; - - getScrollViewRef = (ref) => { - this.scrollView = ref; - }; - - goToChannels = preventDoubleTap(() => { - const {navigator, theme} = this.props; - const {channel, entities, team} = this.state; - - if (!entities || !team || !channel) { - return; - } - - navigator.push({ - component: ExtensionChannels, - wrapperStyle: { - borderRadius: 10, - backgroundColor: theme.centerChannelBg, - }, - passProps: { - currentChannelId: channel.id, - entities, - onSelectChannel: this.selectChannel, - teamId: team.id, - theme, - title: team.display_name, - }, - }); - }); - - goToTeams = preventDoubleTap(() => { - const {navigator, theme} = this.props; - const {formatMessage} = this.context.intl; - const {entities, team} = this.state; - - if (!entities || !team) { - return; - } - - navigator.push({ - component: ExtensionTeams, - title: formatMessage({id: 'quick_switch_modal.teams', defaultMessage: 'Teams'}), - wrapperStyle: { - borderRadius: 10, - backgroundColor: theme.centerChannelBg, - }, - passProps: { - entities, - currentTeamId: team.id, - onSelectTeam: this.selectTeam, - theme, - }, - }); - }); - - handleCancel = preventDoubleTap(() => { - this.props.onClose(); - }); - - handleTextChange = (value) => { - this.setState({value}); - }; - - handlePostFailed = () => { - const {formatMessage} = this.context.intl; - this.setState({ - error: { - message: formatMessage({ - id: 'mobile.share_extension.post_error', - defaultMessage: 'An error has occurred while posting the message. Please try again.', - }), - }, - sending: false, - }); - }; - - loadData = async () => { - const {entities} = this.state; - - if (this.props.authenticated) { - try { - const {config, credentials} = entities.general; - const {currentUserId} = entities.users; - const team = entities.teams.teams[entities.teams.currentTeamId]; - let channel = entities.channels.channels[entities.channels.currentChannelId]; - const items = await ShareExtension.data(Config.AppGroupId); - const serverMaxFileSize = getAllowedServerMaxFileSize(config); - const maxSize = Math.min(MAX_FILE_SIZE, serverMaxFileSize); - const text = []; - const urls = []; - const files = []; - let totalSize = 0; - let exceededSize = false; - - if (channel && (channel.type === General.GM_CHANNEL || channel.type === General.DM_CHANNEL)) { - channel = getChannel({entities}, channel.id); - } - - if (channel.delete_at !== 0) { - channel = getDefaultChannel({entities}); - } - - for (let i = 0; i < items.length; i++) { - const item = items[i]; - switch (item.type) { - case 'public.plain-text': - text.push(item.value); - break; - case 'public.url': - urls.push(item.value); - break; - default: { - const fullPath = item.value; - const filePath = decodeURIComponent(fullPath.replace('file://', '')); - const fileSize = await RNFetchBlob.fs.stat(filePath); // eslint-disable-line no-await-in-loop - const filename = decodeURIComponent(fullPath.replace(/^.*[\\/]/, '')); - const extension = filename.split('.').pop(); - - if (this.useBackgroundUpload) { - if (!exceededSize) { - exceededSize = fileSize.size >= maxSize; - } - } else { - totalSize += fileSize.size; - } - files.push({ - extension, - filename, - filePath, - fullPath, - mimeType: lookupMimeType(filename.toLowerCase()), - size: getFormattedFileSize(fileSize), - type: item.type, - }); - break; - } - } - } - - let value = text.join('\n'); - if (urls.length) { - value += text.length ? `\n${urls.join('\n')}` : urls.join('\n'); - } - - Client4.setUrl(credentials.url); - Client4.setToken(credentials.token); - Client4.setUserId(currentUserId); - - if (!this.useBackgroundUpload) { - exceededSize = totalSize >= maxSize; - } - - this.setState({channel, files, team, value, exceededSize}); - } catch (error) { - this.setState({error}); - } - } - }; - - onLayout = async () => { - const isLandscape = await ShareExtension.getOrientation() === 'LANDSCAPE'; - - if (this.state.isLandscape !== isLandscape) { - if (this.scrollView) { - setTimeout(() => { - this.scrollView.scrollTo({y: 0, animated: false}); - }, 250); - } - this.setState({isLandscape}); - } - }; - - renderBody = (styles) => { - const {formatMessage} = this.context.intl; - const {authenticated, theme} = this.props; - const {entities, error, sending, exceededSize, value} = this.state; - const {config} = entities.general; - const serverMaxFileSize = getAllowedServerMaxFileSize(config); - const maxSize = Math.min(MAX_FILE_SIZE, serverMaxFileSize); - - if (error) { - return ( - - - {error.message} - - - ); - } - - if (sending) { - return ( - - - - {formatMessage({ - id: 'mobile.extension.posting', - defaultMessage: 'Posting...', - })} - - - ); - } - - if (exceededSize) { - return ( - - - {formatMessage({ - id: 'mobile.extension.max_file_size', - defaultMessage: 'File attachments shared in Mattermost must be less than {size}.', - }, {size: getFormattedFileSize({size: maxSize})})} - - - ); - } - - if (authenticated && !error) { - return ( - - - {this.renderFiles(styles)} - - ); - } - - return ( - - - {formatMessage({ - id: 'mobile.extension.authentication_required', - defaultMessage: 'Authentication required: Please first login using the app.', - })} - - - ); - }; - - renderChannelButton = (styles) => { - const {formatMessage} = this.context.intl; - const {authenticated, theme} = this.props; - const {channel, sending} = this.state; - const channelName = channel ? channel.display_name : ''; - - if (sending) { - return null; - } - - if (!authenticated) { - return null; - } - - return ( - - - - - {formatMessage({id: 'mobile.share_extension.channel', defaultMessage: 'Channel'})} - - - - - {channelName} - - - - - - - - ); - }; - - renderFiles = (styles) => { - const {files} = this.state; - return files.map((file, index) => { - let component; - - switch (file.type) { - case 'public.image': - component = ( - - - - ); - break; - case 'public.movie': - component = ( - - - ); - break; - case 'public.file-url': { - let SvgIcon = extensionSvg[file.extension]; - if (!SvgIcon) { - SvgIcon = GenericSvg; - } - - component = ( - - - - - - - - ); - break; - } - } - - return ( - - {component} - - {`${file.size} - ${file.filename}`} - - - ); - }); - }; - - renderTeamButton = (styles) => { - const {formatMessage} = this.context.intl; - const {authenticated, theme} = this.props; - const {sending, team} = this.state; - const teamName = team ? team.display_name : ''; - - if (sending) { - return null; - } - - if (!authenticated) { - return null; - } - - return ( - - - - - {formatMessage({id: 'mobile.share_extension.team', defaultMessage: 'Team'})} - - - - - {teamName} - - - - - - - - ); - }; - - selectChannel = (channel) => { - this.setState({channel}); - }; - - selectTeam = (team, channel) => { - this.setState({channel, team, error: null}); - - // Update the channels for the team - Client4.getMyChannels(team.id).then((channels) => { - const defaultChannel = channels.find((c) => c.name === General.DEFAULT_CHANNEL && c.team_id === team.id); - this.updateChannelsInEntities(channels); - if (!channel) { - this.setState({channel: defaultChannel}); - } - }).catch((error) => { - const {entities} = this.props; - if (entities.channels.channelsInTeam[team.id]) { - const townSquare = Object.values(entities.channels.channels).find((c) => { - return c.name === General.DEFAULT_CHANNEL && c.team_id === team.id; - }); - - if (!channel) { - this.setState({channel: townSquare}); - } - } else { - this.setState({error, channel: null}); - } - }); - }; - - sendMessage = preventDoubleTap(async () => { - const {authenticated, onClose} = this.props; - const {channel, entities, files, value} = this.state; - const {currentUserId} = entities.users; - - // If no text and no files do nothing - if ((!value && !files.length) || !channel) { - return; - } - - if (currentUserId && authenticated) { - await this.emmAuthenticationIfNeeded(); - const certificate = await mattermostBucket.getPreference('cert', Config.AppGroupId); - - try { - // Check to see if the use still belongs to the channel - await Client4.getMyChannelMember(channel.id); - const post = { - user_id: currentUserId, - channel_id: channel.id, - message: value, - }; - - const data = { - files, - post, - requestId: generateId().replace(/-/g, ''), - useBackgroundUpload: this.useBackgroundUpload, - certificate: certificate || '', - }; - - this.setState({sending: true}); - onClose(data); - } catch (error) { - this.setState({error}); - setTimeout(() => { - onClose(); - }, 5000); - } - } - }); - - updateChannelsInEntities = (newChannels) => { - const {entities} = this.state; - const newEntities = { - ...entities, - channels: { - ...entities.channels, - channels: {...entities.channels.channels}, - channelsInTeam: {...entities.channels.channelsInTeam}, - }, - }; - const {channels, channelsInTeam} = newEntities.channels; - - newChannels.forEach((c) => { - channels[c.id] = c; - const channelIdsInTeam = channelsInTeam[c.team_id]; - if (channelIdsInTeam) { - if (!channelIdsInTeam.includes(c.id)) { - channelsInTeam[c.team_id].push(c.id); - } - } else { - channelsInTeam[c.team_id] = [c.id]; - } - }); - - this.setState({entities: newEntities}); - mattermostBucket.writeToFile('entities', JSON.stringify(newEntities), Config.AppGroupId); - }; - - render() { - const {authenticated, theme, title} = this.props; - const {channel, error, totalSize, sending} = this.state; - const {formatMessage} = this.context.intl; - const styles = getStyleSheet(theme); - - let postButtonText = formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'}); - if (totalSize >= MAX_FILE_SIZE || sending || error || !channel) { - postButtonText = null; - } - - let cancelButton = formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'}); - if (sending) { - cancelButton = null; - } else if (error) { - cancelButton = formatMessage({id: 'mobile.share_extension.error_close', defaultMessage: 'Close'}); - } - - return ( - - - {this.renderBody(styles)} - {!error && this.renderTeamButton(styles)} - {!error && this.renderChannelButton(styles)} - - ); - } -} - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - flex: { - flex: 1, - }, - container: { - flex: 1, - backgroundColor: changeOpacity(theme.centerChannelColor, 0.05), - }, - input: { - color: theme.centerChannelColor, - fontSize: 17, - marginBottom: 5, - width: '100%', - }, - divider: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), - height: 1, - marginVertical: 5, - width: '100%', - }, - scrollView: { - paddingHorizontal: 15, - }, - buttonContainer: { - borderTopColor: changeOpacity(theme.centerChannelColor, 0.2), - borderTopWidth: 1, - height: 45, - paddingHorizontal: 15, - }, - buttonWrapper: { - alignItems: 'center', - flex: 1, - flexDirection: 'row', - }, - buttonLabelContainer: { - flex: 1, - }, - buttonLabel: { - fontSize: 17, - lineHeight: 45, - }, - buttonValueContainer: { - justifyContent: 'flex-end', - flex: 1, - flexDirection: 'row', - }, - buttonValue: { - color: changeOpacity(theme.centerChannelColor, 0.4), - alignSelf: 'flex-end', - fontSize: 17, - lineHeight: 45, - }, - arrowContainer: { - height: 45, - justifyContent: 'center', - marginLeft: 15, - top: 2, - }, - unauthenticatedContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - paddingHorizontal: 15, - }, - unauthenticated: { - color: theme.errorTextColor, - fontSize: 14, - }, - fileContainer: { - alignItems: 'center', - backgroundColor: theme.centerChannelBg, - borderColor: changeOpacity(theme.centerChannelColor, 0.2), - borderRadius: 4, - borderWidth: 1, - flexDirection: 'row', - height: 48, - marginBottom: 10, - width: '100%', - }, - filename: { - color: changeOpacity(theme.centerChannelColor, 0.5), - fontSize: 13, - flex: 1, - }, - otherContainer: { - borderBottomLeftRadius: 4, - borderTopLeftRadius: 4, - height: 48, - marginRight: 10, - paddingVertical: 10, - width: 38, - }, - otherWrapper: { - borderRightWidth: 1, - borderRightColor: changeOpacity(theme.centerChannelColor, 0.2), - flex: 1, - }, - fileIcon: { - alignItems: 'center', - justifyContent: 'center', - flex: 1, - }, - imageContainer: { - borderBottomLeftRadius: 4, - borderTopLeftRadius: 4, - height: 48, - marginRight: 10, - width: 38, - }, - image: { - alignItems: 'center', - height: 48, - justifyContent: 'center', - overflow: 'hidden', - width: 38, - }, - video: { - backgroundColor: theme.centerChannelBg, - alignItems: 'center', - height: 48, - justifyContent: 'center', - overflow: 'hidden', - width: 38, - }, - sendingContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - paddingHorizontal: 15, - }, - sendingText: { - color: theme.centerChannelColor, - fontSize: 16, - paddingTop: 10, - }, - }; -}); diff --git a/share_extension/ios/extension_team_item.js b/share_extension/ios/extension_team_item.js deleted file mode 100644 index 1b7141ce0..000000000 --- a/share_extension/ios/extension_team_item.js +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import PropTypes from 'prop-types'; -import { - Text, - TouchableHighlight, - View, -} from 'react-native'; - -import {preventDoubleTap} from 'app/utils/tap'; -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - -import TeamIcon from 'app/components/team_icon/team_icon'; - -export default class TeamsListItem extends React.PureComponent { - static propTypes = { - currentTeamId: PropTypes.string.isRequired, - onSelectTeam: PropTypes.func.isRequired, - team: PropTypes.object.isRequired, - theme: PropTypes.object.isRequired, - }; - - onPress = preventDoubleTap(() => { - const {onSelectTeam, team} = this.props; - onSelectTeam(team); - }); - - render() { - const { - currentTeamId, - team, - theme, - } = this.props; - const styles = getStyleSheet(theme); - - const wrapperStyle = [styles.wrapper]; - if (team.id === currentTeamId) { - wrapperStyle.push({ - width: '90%', - }); - } - - return ( - - - - - - {team.display_name} - - - - - ); - } -} - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - wrapper: { - height: 45, - width: '100%', - }, - container: { - flex: 1, - flexDirection: 'row', - height: 45, - paddingHorizontal: 15, - }, - item: { - alignItems: 'center', - height: 45, - flex: 1, - flexDirection: 'row', - }, - text: { - color: theme.centerChannelColor, - flex: 1, - fontSize: 16, - fontWeight: '600', - lineHeight: 16, - paddingRight: 5, - }, - teamIconContainer: { - backgroundColor: theme.sidebarBg, - marginRight: 10, - }, - teamIconText: { - color: theme.sidebarText, - }, - imageContainer: { - backgroundColor: '#ffffff', - }, - checkmarkContainer: { - alignItems: 'flex-end', - }, - checkmark: { - color: theme.linkColor, - fontSize: 16, - }, - }; -}); diff --git a/share_extension/ios/extension_teams.js b/share_extension/ios/extension_teams.js deleted file mode 100644 index 946a1b842..000000000 --- a/share_extension/ios/extension_teams.js +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import {ActivityIndicator, Text, View} from 'react-native'; -import DeviceInfo from 'react-native-device-info'; -import {intlShape} from 'react-intl'; -import TableView from 'react-native-tableview'; - -import {General} from 'mattermost-redux/constants'; -import {getChannelsInTeam} from 'mattermost-redux/selectors/entities/channels'; - -import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; - -import ExtensionNavBar from './extension_nav_bar'; -import ExtensionTeamItem from './extension_team_item'; - -export default class ExtensionTeams extends PureComponent { - static propTypes = { - currentTeamId: PropTypes.string.isRequired, - entities: PropTypes.object, - navigator: PropTypes.object.isRequired, - onSelectTeam: PropTypes.func.isRequired, - theme: PropTypes.object.isRequired, - }; - - static contextTypes = { - intl: intlShape, - }; - - state = { - defaultChannels: null, - error: null, - myTeams: null, - }; - - componentWillMount() { - this.loadTeams(); - } - - goBack = () => { - this.props.navigator.pop(); - }; - - handleSelectTeam = (team) => { - const {defaultChannels} = this.state; - const townSquare = defaultChannels[team.id]; - this.props.onSelectTeam(team, townSquare); - this.goBack(); - }; - - loadTeams = async () => { - try { - const defaultChannels = {}; - const {teams, myMembers} = this.props.entities.teams; - const myTeams = []; - const channelsInTeam = getChannelsInTeam({entities: this.props.entities}); - - for (const key in teams) { - if (teams.hasOwnProperty(key)) { - const team = teams[key]; - const belong = myMembers[key]; - if (belong) { - const channelIds = channelsInTeam[key]; - let channels; - if (channelIds) { - channels = channelIds.map((id) => this.props.entities.channels.channels[id]); - defaultChannels[team.id] = channels.find((channel) => channel.name === General.DEFAULT_CHANNEL); - } - - myTeams.push(team); - } - } - } - - this.setState({ - defaultChannels, - myTeams: myTeams.sort(this.sortDisplayName), - }); - } catch (error) { - this.setState({error}); - } - }; - - renderBody = (styles) => { - const {theme} = this.props; - const {error, myTeams} = this.state; - - if (error) { - return ( - - - {error.message} - - - ); - } - - if (!myTeams) { - return ( - - - - ); - } - - return ( - - - {this.renderItems(myTeams)} - - - ); - }; - - renderItems = (myTeams) => { - const {currentTeamId, theme} = this.props; - - return myTeams.map((team) => { - return ( - - - - - - ); - }); - }; - - sortDisplayName = (a, b) => { - const locale = DeviceInfo.getDeviceLocale().split('-')[0]; - return a.display_name.localeCompare(b.display_name, locale, {numeric: true}); - }; - - render() { - const {formatMessage} = this.context.intl; - const {theme} = this.props; - const styles = getStyleSheet(theme); - - return ( - - - {this.renderBody(styles)} - - ); - } -} - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - flex: { - flex: 1, - }, - separatorContainer: { - paddingLeft: 60, - }, - separator: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), - height: 1, - }, - loadingContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - }, - searchContainer: { - backgroundColor: changeOpacity(theme.centerChannelColor, 0.2), - paddingBottom: 2, - }, - searchBarInput: { - backgroundColor: '#fff', - color: theme.centerChannelColor, - fontSize: 15, - }, - titleContainer: { - height: 30, - }, - title: { - color: changeOpacity(theme.centerChannelColor, 0.6), - fontSize: 15, - lineHeight: 30, - paddingHorizontal: 15, - }, - errorContainer: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - paddingHorizontal: 15, - }, - error: { - color: theme.errorTextColor, - fontSize: 14, - }, - }; -}); diff --git a/share_extension/ios/index.js b/share_extension/ios/index.js deleted file mode 100644 index 1d653eb4e..000000000 --- a/share_extension/ios/index.js +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import {NativeModules} from 'react-native'; -import {IntlProvider} from 'react-intl'; -import DeviceInfo from 'react-native-device-info'; - -import Config from 'assets/config'; -import {getTranslations} from 'app/i18n'; -import 'app/fetch_preconfig'; - -import {captureExceptionWithoutState, initializeSentry, LOGGER_EXTENSION} from 'app/utils/sentry'; - -import ErrorMessage from './error_message'; -import Extension from './extension'; - -const ShareExtension = NativeModules.MattermostShare; - -export class SharedApp extends PureComponent { - constructor(props) { - super(props); - - initializeSentry(); - - this.state = { - hasError: false, - }; - } - - componentDidCatch(error) { - this.setState({hasError: true}); - - captureExceptionWithoutState(error, LOGGER_EXTENSION); - } - - close = (data) => { - ShareExtension.close(data, Config.AppGroupId); - }; - - render() { - if (this.state.hasError) { - return ( - - ); - } - - return ( - - ); - } -} - -export default function ShareExtensionProvider() { - const locale = DeviceInfo.getDeviceLocale().split('-')[0]; - - return ( - - - - ); -}