iOS Share Extension (#1308)

* objective-C share extension

* MattermostBucket module to share data between the main app and the extension

* middleware that shares the data between the main app and the extension

* Fix setState when safe area in unmounted

* Share extension for iOS

* Fastlane changes to include iOS share extension

* Fix unit test

* Feedback review

* define proptypes for icons
This commit is contained in:
enahum
2018-01-15 16:11:42 -03:00
committed by GitHub
parent 7860478d54
commit 70c4e3e005
39 changed files with 3548 additions and 59 deletions

View File

@@ -35,7 +35,7 @@ export function handleTeamChange(teamId, selectChannel = true) {
markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState);
}
dispatch(batchActions(actions), getState);
dispatch(batchActions(actions, 'BATCH_SELECT_TEAM'), getState);
};
}

View File

@@ -48,6 +48,7 @@ export default class SafeAreaIos extends PureComponent {
}
componentWillMount() {
this.mounted = true;
this.getSafeAreaInsets();
this.mounted = true;
}
@@ -60,6 +61,7 @@ export default class SafeAreaIos extends PureComponent {
}
componentWillUnmount() {
this.mounted = false;
Orientation.removeOrientationListener(this.getSafeAreaInsets);
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
@@ -86,7 +88,9 @@ export default class SafeAreaIos extends PureComponent {
if (this.isX) {
SafeArea.getSafeAreaInsetsForRootView().then((result) => {
const {safeAreaInsets} = result;
this.setState({safeAreaInsets});
if (this.mounted) {
this.setState({safeAreaInsets});
}
});
}
};

View File

@@ -49,6 +49,7 @@ import initialState from 'app/initial_state';
import PushNotifications from 'app/push_notifications';
import {registerScreens} from 'app/screens';
import configureStore from 'app/store';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import {deleteFileCache} from 'app/utils/file';
import {init as initAnalytics} from 'app/utils/segment';
@@ -210,6 +211,7 @@ export default class Mattermost {
const intl = this.getIntl();
if (isSecured) {
try {
mattermostBucket.set('emm', vendor, Config.AppGroupId);
await mattermostManaged.authenticate({
reason: intl.formatMessage({
id: 'mobile.managed.secured_by',

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {NativeModules, Platform} from 'react-native';
// TODO: Remove platform specific once android is implemented
const MattermostBucket = Platform.OS === 'ios' ? NativeModules.MattermostBucket : null;
export default {
set: (key, value, groupName) => {
if (MattermostBucket) {
MattermostBucket.set(key, value, groupName);
}
},
get: async (key, groupName) => {
if (MattermostBucket) {
const value = await MattermostBucket.get(key, groupName);
if (value) {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
}
return null;
},
remove: (key, groupName) => {
if (MattermostBucket) {
MattermostBucket.remove(key, groupName);
}
}
};

View File

@@ -17,7 +17,7 @@ import networkConnectionListener from 'app/utils/network';
import {createSentryMiddleware} from 'app/utils/sentry/middleware';
import {promiseTimeout} from 'app/utils/promise_timeout';
import {messageRetention} from './middleware';
import {messageRetention, shareExtensionData} from './middleware';
import {transformSet} from './utils';
function getAppReducer() {
@@ -211,7 +211,7 @@ export default function configureAppStore(initialState) {
}
};
const additionalMiddleware = [createSentryMiddleware(), messageRetention];
const additionalMiddleware = [createSentryMiddleware(), messageRetention, shareExtensionData];
return configureStore(initialState, appReducer, offlineOptions, getAppReducer, {
additionalMiddleware
});

View File

@@ -3,8 +3,16 @@
import DeviceInfo from 'react-native-device-info';
import {ChannelTypes, GeneralTypes, TeamTypes, UserTypes} from 'mattermost-redux/action_types';
import {General} from 'mattermost-redux/constants';
import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getUserIdFromChannelName, getGroupDisplayNameFromUserIds} from 'mattermost-redux/utils/channel_utils';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import {ViewTypes} from 'app/constants';
import initialState from 'app/initial_state';
import mattermostBucket from 'app/mattermost_bucket';
import Config from 'assets/config';
import {
captureException,
@@ -305,3 +313,95 @@ function cleanupState(action, keepCurrent = false) {
error: action.error
};
}
export function shareExtensionData(store) {
return (next) => (action) => {
// allow other middleware to do their things
const nextAction = next(action);
switch (action.type) {
case 'persist/REHYDRATE': {
const {entities} = action.payload;
if (entities) {
if (entities.general && entities.general.credentials && entities.general.credentials.token) {
mattermostBucket.set('credentials', JSON.stringify(entities.general.credentials), Config.AppGroupId);
}
if (entities.teams) {
const {currentTeamId, teams} = entities.teams;
if (currentTeamId) {
const team = teams[currentTeamId];
const teamToSave = {
id: currentTeamId,
name: team.name,
display_name: team.display_name
};
mattermostBucket.set('selectedTeam', JSON.stringify(teamToSave), Config.AppGroupId);
}
}
if (entities.users) {
const {currentUserId} = entities.users;
if (currentUserId) {
mattermostBucket.set('currentUserId', currentUserId, Config.AppGroupId);
}
}
}
break;
}
case GeneralTypes.RECEIVED_APP_CREDENTIALS:
mattermostBucket.set('credentials', JSON.stringify(action.data), Config.AppGroupId);
break;
case ChannelTypes.SELECT_CHANNEL: {
const state = store.getState();
const {channels} = state.entities.channels;
const {currentUserId, profiles, profilesInChannel} = state.entities.users;
const channel = {...channels[action.data]};
if (channel.type === General.DM_CHANNEL) {
const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
channel.display_name = displayUsername(profiles[teammateId], getTeammateNameDisplaySetting(state));
} else if (channel.type === General.GM_CHANNEL) {
channel.display_name = getGroupDisplayNameFromUserIds(
profilesInChannel[channel.id],
profiles,
currentUserId,
getTeammateNameDisplaySetting(state)
);
}
const channelToSave = {
id: channel.id,
name: channel.name,
display_name: channel.display_name,
type: channel.type
};
mattermostBucket.set('selectedChannel', JSON.stringify(channelToSave), Config.AppGroupId);
break;
}
case 'BATCH_SELECT_TEAM': {
const teamData = action.payload.find((data) => data.type === TeamTypes.SELECT_TEAM);
if (teamData && teamData.data) {
const team = store.getState().entities.teams.teams[teamData.data];
const teamToSave = {
id: team.id,
name: team.name,
display_name: team.display_name
};
mattermostBucket.set('selectedTeam', JSON.stringify(teamToSave), Config.AppGroupId);
}
break;
}
case UserTypes.RECEIVED_ME:
mattermostBucket.set('currentUserId', action.data.id, Config.AppGroupId);
break;
case UserTypes.LOGOUT_SUCCESS:
mattermostBucket.remove('credentials', Config.AppGroupId);
mattermostBucket.remove('selectedChannel', Config.AppGroupId);
mattermostBucket.remove('selectedTeam', Config.AppGroupId);
mattermostBucket.remove('currentUserId', Config.AppGroupId);
mattermostBucket.remove('emm', Config.AppGroupId);
break;
}
return nextAction;
};
}

View File

@@ -37,5 +37,7 @@
"EnableMobileClientUpgradeUserSetting": true,
"EnableForceMobileClientUpgrade": true,
"MobileClientUpgradeAndroidApkLink": "https://about.mattermost.com/mattermost-android-app/",
"MobileClientUpgradeIosIpaLink": "https://about.mattermost.com/mattermost-ios-app/"
"MobileClientUpgradeIosIpaLink": "https://about.mattermost.com/mattermost-ios-app/",
"AppGroupId": "group.com.mattermost.rnbeta"
}

View File

@@ -2124,6 +2124,10 @@
"mobile.search.in_modifier_title": "channel-name",
"mobile.search.jump": "JUMP",
"mobile.search.no_results": "No Results Found",
"mobile.share_extension.cancel": "Cancel",
"mobile.share_extension.channel": "Channel",
"mobile.share_extension.send": "Send",
"mobile.share_extension.team": "Team",
"mobile.select_team.choose": "Your teams:",
"mobile.select_team.join_open": "Open teams you can join",
"mobile.select_team.no_teams": "There are no available teams for you to join.",

View File

@@ -44,6 +44,10 @@
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 */; };
7F3F660F1FE4280D0085CA0E /* libReactNativeExceptionHandler.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FA7950B1F61A1A500C02924 /* libReactNativeExceptionHandler.a */; };
7F3F66101FE4281A0085CA0E /* libRNSentry.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FA795061F61A1A500C02924 /* libRNSentry.a */; };
7F43D5A01F6BF882001FC614 /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37DD11281E79EBE1004111BA /* libRNDeviceInfo.a */; };
7F43D5D61F6BF8C2001FC614 /* libRNLocalAuth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49871F3DFC30003A22BA /* libRNLocalAuth.a */; };
7F43D5D71F6BF8D0001FC614 /* libRNPasscodeStatus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F8C49F81F3E0710003A22BA /* libRNPasscodeStatus.a */; };
@@ -59,12 +63,37 @@
7F43D63F1F6BFA19001FC614 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D8FEC21E80B5230091F3BD /* libBVLinearGradient.a */; };
7F43D6401F6BFA82001FC614 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */; };
7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6877B01E7835E50094B63F /* libToolTipMenu.a */; };
7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */; };
7FB6006B1FE3116800DB284F /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 375218411F4B9E320035444B /* libRNFetchBlob.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 */; };
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 */; };
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 */; };
7FF7BE711FE004A3005E55FE /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37DD11281E79EBE1004111BA /* libRNDeviceInfo.a */; };
7FF7BE721FE01FC7005E55FE /* libRCTOrientation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 374634671E848085005E1244 /* libRCTOrientation.a */; };
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 */; };
7FFE32EA1FD9CD050038C7A0 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.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 */; };
@@ -431,20 +460,6 @@
remoteGlobalIDString = 4681C02C1B05271A004D67D4;
remoteInfo = ToolTipMenuTests;
};
7F6CEE231FDAEA0D0010135A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNReactNativeDocViewer;
};
7F6CEE281FDAEA0D0010135A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 6DA7B8031F692C4C00FD1D50;
remoteInfo = RNSafeArea;
};
7F8AAB3B1F4E0FEB00F5A52C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 50ECB1D221E44F51B5690DF2 /* FastImage.xcodeproj */;
@@ -529,6 +544,27 @@
remoteGlobalIDString = 5DBEB1501B18CEA900B34395;
remoteInfo = RNVectorIcons;
};
7FFE328F1FD9CAF40038C7A0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNReactNativeDocViewer;
};
7FFE32941FD9CAF40038C7A0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */;
proxyType = 2;
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 */;
@@ -549,6 +585,17 @@
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
7FFE32A91FD9CB650038C7A0 /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
7FFE32A51FD9CB650038C7A0 /* MattermostShare.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@@ -613,11 +660,14 @@
7F292A701E8AB73400A450A3 /* SplashScreenResource */ = {isa = PBXFileReference; lastKnownFileType = folder; path = SplashScreenResource; sourceTree = "<group>"; };
7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = LaunchScreen.xib; path = SplashScreenResource/LaunchScreen.xib; sourceTree = "<group>"; };
7F292AA51E8ABB1100A450A3 /* splash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = splash.png; path = SplashScreenResource/splash.png; sourceTree = "<group>"; };
7F380D0A1FDB28160061AAD2 /* MattermostShare.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MattermostShare.entitlements; sourceTree = "<group>"; };
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 = "<group>"; };
7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-Mattermost.a"; path = "../../../../../../../Library/Developer/Xcode/DerivedData/Mattermost-czlinsdviifujheezzjvmisotjrm/Build/Products/Debug-iphonesimulator/libPods-Mattermost.a"; sourceTree = "<group>"; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ToolTipMenu.xcodeproj; path = "../node_modules/react-native-tooltip/ToolTipMenu.xcodeproj"; sourceTree = "<group>"; };
7F6C47A31FE87E8C00F5A912 /* PerformRequests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformRequests.h; sourceTree = "<group>"; };
7F6C47A41FE87E8C00F5A912 /* PerformRequests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerformRequests.m; sourceTree = "<group>"; };
7FC200BC1EBB65100099331B /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = "<group>"; };
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNImagePicker.xcodeproj; path = "../node_modules/react-native-image-picker/ios/RNImagePicker.xcodeproj"; sourceTree = "<group>"; };
7FEB10961F6101710039A015 /* BlurAppScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BlurAppScreen.h; path = Mattermost/BlurAppScreen.h; sourceTree = "<group>"; };
@@ -626,6 +676,24 @@
7FEB109A1F61019C0039A015 /* MattermostManaged.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MattermostManaged.m; path = Mattermost/MattermostManaged.m; sourceTree = "<group>"; };
7FEB109B1F61019C0039A015 /* UIImage+ImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ImageEffects.h"; path = "Mattermost/UIImage+ImageEffects.h"; sourceTree = "<group>"; };
7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ImageEffects.m"; path = "Mattermost/UIImage+ImageEffects.m"; sourceTree = "<group>"; };
7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MattermostBucket.h; sourceTree = "<group>"; };
7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = "<group>"; };
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 = "<group>"; };
7FFE329D1FD9CB650038C7A0 /* ShareViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = "<group>"; };
7FFE32A01FD9CB650038C7A0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
7FFE32A21FD9CB650038C7A0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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; };
7FFE32B81FD9CCAA0038C7A0 /* libKSCrashLib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libKSCrashLib.a; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32B91FD9CCAA0038C7A0 /* libSDWebImage iOS static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libSDWebImage iOS static.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32BA1FD9CCAA0038C7A0 /* libSentryStatic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libSentryStatic.a; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32BB1FD9CCAA0038C7A0 /* libXCDYouTubeKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libXCDYouTubeKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32BC1FD9CCAA0038C7A0 /* PerformanceBezier.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PerformanceBezier.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32BD1FD9CCAA0038C7A0 /* QuartzBookPack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = QuartzBookPack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32BE1FD9CCAA0038C7A0 /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7FFE32BF1FD9CCAA0038C7A0 /* Sentry.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Sentry.framework; sourceTree = BUILT_PRODUCTS_DIR; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSafeArea.xcodeproj; path = "../node_modules/react-native-safe-area/ios/RNSafeArea.xcodeproj"; sourceTree = "<group>"; };
8606EB1EB7E349EF8248933E /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
@@ -706,6 +774,31 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
7FFE32971FD9CB640038C7A0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7FFE32E71FD9CCD00038C7A0 /* libART.a in Frameworks */,
7F3F66101FE4281A0085CA0E /* libRNSentry.a in Frameworks */,
7F3F660F1FE4280D0085CA0E /* libReactNativeExceptionHandler.a in Frameworks */,
7FB6006B1FE3116800DB284F /* libRNFetchBlob.a in Frameworks */,
7FF7BE721FE01FC7005E55FE /* libRCTOrientation.a in Frameworks */,
7FF7BE711FE004A3005E55FE /* 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 */,
7FFE32EC1FD9CD360038C7A0 /* libRCTText.a in Frameworks */,
7FFE32EB1FD9CD170038C7A0 /* libRCTImage.a in Frameworks */,
7FFE32EA1FD9CD050038C7A0 /* libReact.a in Frameworks */,
7FFE32E91FD9CCF40038C7A0 /* libRCTAnimation.a in Frameworks */,
7FFE32E81FD9CCDE0038C7A0 /* libRCTCameraRoll.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -828,6 +921,8 @@
7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */,
7FF7BE6B1FDEE5E8005E55FE /* MattermostBucket.h */,
7FF7BE6C1FDEE5E8005E55FE /* MattermostBucket.m */,
7FEB10991F61019C0039A015 /* MattermostManaged.h */,
7FEB109A1F61019C0039A015 /* MattermostManaged.m */,
7F292AA51E8ABB1100A450A3 /* splash.png */,
@@ -941,6 +1036,17 @@
4B992D7BAAEDF8759DB525B5 /* Frameworks */ = {
isa = PBXGroup;
children = (
7FFE32B51FD9CCAA0038C7A0 /* FLAnimatedImage.framework */,
7FFE32B61FD9CCAA0038C7A0 /* KSCrash.framework */,
7FFE32B71FD9CCAA0038C7A0 /* KSCrash.framework */,
7FFE32B81FD9CCAA0038C7A0 /* libKSCrashLib.a */,
7FFE32B91FD9CCAA0038C7A0 /* libSDWebImage iOS static.a */,
7FFE32BA1FD9CCAA0038C7A0 /* libSentryStatic.a */,
7FFE32BB1FD9CCAA0038C7A0 /* libXCDYouTubeKit.a */,
7FFE32BC1FD9CCAA0038C7A0 /* PerformanceBezier.framework */,
7FFE32BD1FD9CCAA0038C7A0 /* QuartzBookPack.framework */,
7FFE32BE1FD9CCAA0038C7A0 /* SDWebImage.framework */,
7FFE32BF1FD9CCAA0038C7A0 /* Sentry.framework */,
7F43D6051F6BF9EB001FC614 /* libPods-Mattermost.a */,
7F43D5DF1F6BF994001FC614 /* libRNSVG.a */,
65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */,
@@ -1012,22 +1118,6 @@
name = Products;
sourceTree = "<group>";
};
7F6CEE201FDAEA0D0010135A /* Products */ = {
isa = PBXGroup;
children = (
7F6CEE241FDAEA0D0010135A /* libRNReactNativeDocViewer.a */,
);
name = Products;
sourceTree = "<group>";
};
7F6CEE251FDAEA0D0010135A /* Products */ = {
isa = PBXGroup;
children = (
7F6CEE291FDAEA0D0010135A /* libRNSafeArea.a */,
);
name = Products;
sourceTree = "<group>";
};
7F8AAB371F4E0FEB00F5A52C /* Products */ = {
isa = PBXGroup;
children = (
@@ -1118,6 +1208,36 @@
name = Products;
sourceTree = "<group>";
};
7FFE328C1FD9CAF40038C7A0 /* Products */ = {
isa = PBXGroup;
children = (
7FFE32901FD9CAF40038C7A0 /* libRNReactNativeDocViewer.a */,
);
name = Products;
sourceTree = "<group>";
};
7FFE32911FD9CAF40038C7A0 /* Products */ = {
isa = PBXGroup;
children = (
7FFE32951FD9CAF40038C7A0 /* libRNSafeArea.a */,
);
name = Products;
sourceTree = "<group>";
};
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 */,
);
path = MattermostShare;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
@@ -1176,6 +1296,7 @@
13B07FAE1A68108700A75B9A /* Mattermost */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* MattermostTests */,
7FFE329B1FD9CB650038C7A0 /* MattermostShare */,
83CBBA001A601CBA00E9B192 /* Products */,
0156F464626F49C2977D7982 /* Resources */,
37DF8AC51F5F0D410079BF89 /* Recovered References */,
@@ -1191,6 +1312,7 @@
children = (
13B07F961A680F5B00A75B9A /* Mattermost.app */,
00E356EE1AD99517003FC87E /* MattermostTests.xctest */,
7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -1232,16 +1354,36 @@
AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */,
BC5024E82B42A23CE05E72ED /* [CP] Embed Pods Frameworks */,
509B02AF14835503C5C6029D /* [CP] Copy Pods Resources */,
7FFE32A91FD9CB650038C7A0 /* Embed App Extensions */,
);
buildRules = (
);
dependencies = (
7FFE32A41FD9CB650038C7A0 /* PBXTargetDependency */,
);
name = Mattermost;
productName = "Hello World";
productReference = 13B07F961A680F5B00A75B9A /* Mattermost.app */;
productType = "com.apple.product-type.application";
};
7FFE32991FD9CB640038C7A0 /* MattermostShare */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7FFE32A61FD9CB650038C7A0 /* Build configuration list for PBXNativeTarget "MattermostShare" */;
buildPhases = (
7FFE32961FD9CB640038C7A0 /* Sources */,
7FFE32971FD9CB640038C7A0 /* Frameworks */,
7FFE32981FD9CB640038C7A0 /* Resources */,
7FFE32F01FD9D1F00038C7A0 /* Bundle React Native code and images */,
);
buildRules = (
);
dependencies = (
);
name = MattermostShare;
productName = MattermostShare;
productReference = 7FFE329A1FD9CB640038C7A0 /* MattermostShare.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -1259,11 +1401,28 @@
DevelopmentTeam = UQ8HT4Q2XM;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.ApplicationGroups.iOS = {
enabled = 1;
};
com.apple.BackgroundModes = {
enabled = 1;
};
com.apple.Push = {
enabled = 1;
};
};
};
7FFE32991FD9CB640038C7A0 = {
CreatedOnToolsVersion = 9.1;
DevelopmentTeam = UQ8HT4Q2XM;
LastSwiftMigration = 0920;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.ApplicationGroups.iOS = {
enabled = 1;
};
};
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Mattermost" */;
@@ -1395,11 +1554,11 @@
ProjectRef = EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */;
},
{
ProductGroup = 7F6CEE201FDAEA0D0010135A /* Products */;
ProductGroup = 7FFE328C1FD9CAF40038C7A0 /* Products */;
ProjectRef = 41898656FAE24E0BB390D0E4 /* RNReactNativeDocViewer.xcodeproj */;
},
{
ProductGroup = 7F6CEE251FDAEA0D0010135A /* Products */;
ProductGroup = 7FFE32911FD9CAF40038C7A0 /* Products */;
ProjectRef = 849D881A0372465294DE7315 /* RNSafeArea.xcodeproj */;
},
{
@@ -1423,6 +1582,7 @@
targets = (
13B07F861A680F5B00A75B9A /* Mattermost */,
00E356ED1AD99517003FC87E /* MattermostTests */,
7FFE32991FD9CB640038C7A0 /* MattermostShare */,
);
};
/* End PBXProject section */
@@ -1771,20 +1931,6 @@
remoteRef = 7F6877B11E7835E50094B63F /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F6CEE241FDAEA0D0010135A /* libRNReactNativeDocViewer.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNReactNativeDocViewer.a;
remoteRef = 7F6CEE231FDAEA0D0010135A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F6CEE291FDAEA0D0010135A /* libRNSafeArea.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNSafeArea.a;
remoteRef = 7F6CEE281FDAEA0D0010135A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F8AAB3C1F4E0FEB00F5A52C /* libFastImage.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -1869,6 +2015,20 @@
remoteRef = 7FDF290B1E1F4B4E00DBBE56 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7FFE32901FD9CAF40038C7A0 /* libRNReactNativeDocViewer.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNReactNativeDocViewer.a;
remoteRef = 7FFE328F1FD9CAF40038C7A0 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7FFE32951FD9CAF40038C7A0 /* libRNSafeArea.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNSafeArea.a;
remoteRef = 7FFE32941FD9CAF40038C7A0 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -1918,6 +2078,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
7FFE32981FD9CB640038C7A0 /* 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 */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -1998,6 +2170,20 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\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;
@@ -2063,11 +2249,23 @@
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
7FEB109E1F61019C0039A015 /* UIImage+ImageEffects.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
7FF7BE6D1FDEE5E8005E55FE /* MattermostBucket.m in Sources */,
7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */,
7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
7FFE32961FD9CB640038C7A0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7FF7BE6E1FDEE5E8005E55FE /* MattermostBucket.m in Sources */,
7FF7BE2C1FDEE4AE005E55FE /* MattermostManaged.m in Sources */,
7FFE329E1FD9CB650038C7A0 /* ShareViewController.m in Sources */,
7F6C47A51FE87E8C00F5A912 /* PerformRequests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -2076,8 +2274,24 @@
target = 13B07F861A680F5B00A75B9A /* Mattermost */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
7FFE32A41FD9CB650038C7A0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 7FFE32991FD9CB640038C7A0 /* MattermostShare */;
targetProxy = 7FFE32A31FD9CB650038C7A0 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
7FFE329F1FD9CB650038C7A0 /* MainInterface.storyboard */ = {
isa = PBXVariantGroup;
children = (
7FFE32A01FD9CB650038C7A0 /* Base */,
);
name = MainInterface.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
@@ -2152,9 +2366,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 634A8F047C73D24A87850EC0 /* Pods-Mattermost.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 78;
DEAD_CODE_STRIPPING = NO;
@@ -2163,7 +2377,6 @@
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**",
"$(SRCROOT)/../node_modules/react-native-orientation/iOS/RCTOrientation/**",
"$(SRCROOT)/../node_modules/react-native-smart-splash-screen/ios/RCTSplashScreen/RCTSplashScreen/**",
"$(SRCROOT)/../node_modules/react-native-navigation/ios/**",
"$(SRCROOT)/../node_modules/react-native-notifications/RNNotifications/**",
"$(SRCROOT)/../node_modules/react-native-local-auth",
@@ -2202,9 +2415,9 @@
isa = XCBuildConfiguration;
baseConfigurationReference = BFB7025EA936C1B5DC9725C2 /* Pods-Mattermost.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 78;
DEAD_CODE_STRIPPING = NO;
@@ -2213,7 +2426,6 @@
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**",
"$(SRCROOT)/../node_modules/react-native-orientation/iOS/RCTOrientation/**",
"$(SRCROOT)/../node_modules/react-native-smart-splash-screen/ios/RCTSplashScreen/RCTSplashScreen/**",
"$(SRCROOT)/../node_modules/react-native-navigation/ios/**",
"$(SRCROOT)/../node_modules/react-native-notifications/RNNotifications/**",
"$(SRCROOT)/../node_modules/react-native-local-auth",
@@ -2248,6 +2460,122 @@
};
name = Release;
};
7FFE32A71FD9CB650038C7A0 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = 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_STYLE = Automatic;
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-orientation/iOS/RCTOrientation/**",
"$(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-fast-image/ios/FastImage/**",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
"$(SRCROOT)/../node_modules/react-native-exception-handler/ios",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS",
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
"$(SRCROOT)/../node_modules/react-native-youtube/**",
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
);
INFOPLIST_FILE = MattermostShare/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.mattermost.rnbeta.MattermostShare;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MattermostShare/MattermostShare-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
7FFE32A81FD9CB650038C7A0 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = 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_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-orientation/iOS/RCTOrientation/**",
"$(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-fast-image/ios/FastImage/**",
"$(SRCROOT)/../node_modules/react-native-fetch-blob/ios/**",
"$(SRCROOT)/../node_modules/react-native-exception-handler/ios",
"$(SRCROOT)/../node_modules/react-native-sentry/ios/**",
"$(SRCROOT)/../node_modules/react-native-cookies/ios/RNCookieManagerIOS",
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
"$(SRCROOT)/../node_modules/react-native-youtube/**",
"$(SRCROOT)/../node_modules/react-native-video/ios",
"$(SRCROOT)/../node_modules/react-native-doc-viewer/ios/**",
"$(SRCROOT)/../node_modules/react-native-safe-area/ios/RNSafeArea",
);
INFOPLIST_FILE = MattermostShare/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
);
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;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -2353,6 +2681,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7FFE32A61FD9CB650038C7A0 /* Build configuration list for PBXNativeTarget "MattermostShare" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7FFE32A71FD9CB650038C7A0 /* Debug */,
7FFE32A81FD9CB650038C7A0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Mattermost" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -36,6 +36,36 @@
return YES;
}
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)(void))completionHandler {
NSUserDefaults *bucket = [[NSUserDefaults alloc] initWithSuiteName: @"group.com.mattermost"];
NSString *credentialsString = [bucket objectForKey:@"credentials"];
NSData *credentialsData = [credentialsString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *credentials = [NSJSONSerialization JSONObjectWithData:credentialsData options:NSJSONReadingMutableContainers error:nil];
NSString *server = [credentials objectForKey:@"url"];
NSString *token = [credentials objectForKey:@"token"];
NSDictionary *post = [NSDictionary dictionaryWithObjectsAndKeys:@"user_id", [bucket objectForKey:@"currentUserId"], @"message", @"Shit fuck", @"channel_id", @"zw43c5ttrjyu9dg7jnudwuz6bw"];
NSData *postData = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:nil];
NSString* postAsString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding];
NSURL *createUrl = [NSURL URLWithString:[server stringByAppendingString:@"/api/v4/posts"]];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundSession-post"];
config.sharedContainerIdentifier = @"group.com.mattermost";
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]];
NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request];
[createTask resume];
completionHandler();
}
// Required for orientation
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return [Orientation getOrientation];

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BundleEntryFilename</key>
<string>index.js</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
@@ -9,7 +11,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>com.mattermost.rnbeta</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@@ -81,6 +83,10 @@
<string>OpenSans-Semibold.ttf</string>
<string>OpenSans-SemiboldItalic.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>

View File

@@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.mattermost.rnbeta</string>
</array>
</dict>
</plist>

View File

@@ -10,7 +10,7 @@
@interface MattermostManaged : NSObject <RCTBridgeModule>
- (NSUserDefaults *)bucketByName:(NSString*)name;
+ (void)sendConfigChangedEvent;
@end

6
ios/MattermostBucket.h Normal file
View File

@@ -0,0 +1,6 @@
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
@interface MattermostBucket : NSObject <RCTBridgeModule>
- (NSUserDefaults *)bucketByName:(NSString*)name;
@end

54
ios/MattermostBucket.m Normal file
View File

@@ -0,0 +1,54 @@
//
// MattermostBucket.m
// Mattermost
//
// Created by Elias Nahum on 12/11/17.
// Copyright © 2017 Facebook. All rights reserved.
//
#import "MattermostBucket.h"
@implementation MattermostBucket
- (NSUserDefaults *)bucketByName:(NSString*)name {
return [[NSUserDefaults alloc] initWithSuiteName: name];
}
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(set:(NSString *) key
value:(NSString *) value
bucketName:(NSString*) bucketName)
{
NSUserDefaults* bucket = [self bucketByName: bucketName];
[bucket setObject:value forKey:key];
}
RCT_EXPORT_METHOD(get:(NSString *) key
bucketName:(NSString*) bucketName
getWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSUserDefaults* bucket = [self bucketByName: bucketName];
id value = [bucket objectForKey:key];
if (value == nil) {
value = [NSNull null];
}
resolve(value);
}
RCT_EXPORT_METHOD(remove:(NSString *) key
bucketName:(NSString*) bucketName)
{
NSUserDefaults* bucket = [self bucketByName: bucketName];
[bucket removeObjectForKey: key];
}
@end

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A278a" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BundleEntryFilename</key>
<string>share.ios.js</string>
<key>BundleForced</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Mattermost</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.mattermost.rnbeta.MattermostShare</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.5.1</string>
<key>CFBundleVersion</key>
<string>74</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
<string>5</string>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
<key>UIAppFonts</key>
<array>
<string>Ionicons.ttf</string>
<string>FontAwesome.ttf</string>
<string>EvilIcons.ttf</string>
<string>OpenSans-Bold.ttf</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.mattermost.rnbeta</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,28 @@
//
// PerformRequests.h
// MattermostShare
//
// Created by Elias Nahum on 12/18/17.
// Copyright © 2017 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface PerformRequests : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate>
@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 NSUserDefaults *bucket;
- (id) initWithPost:(NSDictionary *) post
withFiles:(NSArray *) files
forRequestId:(NSString *)requestId
inAppGroupId:(NSString *) appGroupId;
-(void)createPost;
@end

View File

@@ -0,0 +1,156 @@
//
// PerformRequests.m
// MattermostShare
//
// Created by Elias Nahum on 12/18/17.
// Copyright © 2017 Facebook. All rights reserved.
//
#import "PerformRequests.h"
#import "MattermostBucket.h"
#import "ShareViewController.h"
@implementation PerformRequests
MattermostBucket *mattermostBucket;
- (id) initWithPost:(NSDictionary *) post
withFiles:(NSArray *) files
forRequestId:(NSString *)requestId
inAppGroupId:(NSString *) appGroupId {
self = [super init];
if (self) {
self.post = post;
self.files = files;
self.appGroupId = appGroupId;
self.requestId = requestId;
mattermostBucket = [[MattermostBucket alloc] init];
self.bucket = [mattermostBucket bucketByName: appGroupId];
[self setCredentials];
}
return self;
}
-(void)setCredentials {
NSString *credentialsString = [self.bucket objectForKey:@"credentials"];
NSData *credentialsData = [credentialsString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *credentials = [NSJSONSerialization JSONObjectWithData:credentialsData options:NSJSONReadingMutableContainers error:nil];
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]);
}
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:nil];
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"]];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.requestId stringByAppendingString:@"-post"]];
config.sharedContainerIdentifier = self.appGroupId;
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]];
NSURLSession *createSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURLSessionDataTask *createTask = [createSession dataTaskWithRequest:request];
NSLog(@"Executing post request");
[createTask resume];
}
- (void) cleanUpTempFiles {
NSURL *tmpDirectoryURL = [ShareViewController 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

View File

@@ -0,0 +1,6 @@
#import <UIKit/UIKit.h>
#import "React/RCTBridgeModule.h"
@interface ShareViewController : UIViewController<RCTBridgeModule>
+ (NSURL*) tempContainerURL: (NSString*)appGroupId;
@end

View File

@@ -0,0 +1,274 @@
#import "ShareViewController.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "MattermostBucket.h"
#import "PerformRequests.h"
NSExtensionContext* extensionContext;
@implementation ShareViewController
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
- (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) {
NSDictionary *post = [data objectForKey:@"post"];
NSArray *files = [data objectForKey:@"files"];
NSString *requestId = [data objectForKey:@"requestId"];
NSLog(@"Call createPost");
PerformRequests *request = [[PerformRequests alloc] initWithPost:post withFiles:files forRequestId:requestId inAppGroupId:appGroupId];
[request createPost];
}
[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<NSSecureCoding, NSObject> 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<NSSecureCoding, NSObject> 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 = [ShareViewController tempContainerURL:appGroupId];
if (tempContainerURL == nil){
return callback(nil, nil, NO, nil);
}
NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName];
BOOL created = [UIImageJPEGRepresentation(image, 1) 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 = [ShareViewController tempContainerURL:appGroupId];
if (tempContainerURL == nil){
return callback(nil, nil, NO, nil);
}
NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName];
BOOL created = [UIImageJPEGRepresentation(image, 0.95) 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<NSSecureCoding, NSObject> 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<NSSecureCoding, NSObject> 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<NSSecureCoding, NSObject> 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);
}
+ (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;
}
@end

View File

@@ -8,8 +8,8 @@ if [[ "${SENTRY_ENABLED}" = "true" ]]; then
./makeSentryProperties.sh
export SENTRY_PROPERTIES=sentry.properties
../node_modules/sentry-cli-binary/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh
../node_modules/sentry-cli-binary/bin/sentry-cli react-native xcode ./react-native-xcode.sh
else
echo "Sentry native integration is not enabled"
../node_modules/react-native/scripts/react-native-xcode.sh
./react-native-xcode.sh
fi

189
ios/react-native-xcode.sh Executable file
View File

@@ -0,0 +1,189 @@
#!/bin/bash
# Copyright (c) 2015-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# Bundle React Native app's code and image assets.
# This script is supposed to be invoked as part of Xcode build process
# and relies on environment variables (including PWD) set by Xcode
# This scripts allows the app and app extension bundles to be shared or separated.
# Separating bundles allows for a minimal footprint for both app and app extension.
# The original script provided by RN does not bundle app extensions.
# This way we can set the BundleEntryFilename to index.js for the main app and
# the BundleEntryFilename to share.ios.js for the extension
DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
MAIN_BUNDLE="main.jsbundle"
BUNDLE_FILE="$DEST/$MAIN_BUNDLE"
TMP_PATH="/tmp"
PLISTBUDDY='/usr/libexec/PlistBuddy'
PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH
[ -z "$SKIP_BUNDLING" ] && SKIP_BUNDLING=$($PLISTBUDDY -c "Print :BundleSkipped" "${PLIST}")
[ -z "$CP_BUNDLING" ] && CP_BUNDLING=$($PLISTBUDDY -c "Print :BundleCopied" "${PLIST}")
if [[ "$SKIP_BUNDLING" && $SKIP_BUNDLING == "true" ]]; then
echo "SKIP_BUNDLING enabled; skipping."
if [[ "$CP_BUNDLING" && $CP_BUNDLING == "true" ]]; then
TMP_BUNDLE="$TMP_PATH/$MAIN_BUNDLE"
echo "CP_BUNDLING enabled; copying $TMP_BUNDLE to $DEST/"
if [ -f "$TMP_BUNDLE" ]; then
cp "$TMP_PATH/$MAIN_BUNDLE"* "$DEST/"
else
echo "CP_BUNDLING $TMP_BUNDLE does not exist!"
fi
fi
exit 0;
fi
[ -z "$IS_DEV" ] && IS_DEV=$($PLISTBUDDY -c "Print :BundleDev" "${PLIST}")
[ -z "$FORCE_BUNDLING" ] && FORCE_BUNDLING=$($PLISTBUDDY -c "Print :BundleForced" "${PLIST}")
if [ -z "$IS_DEV" ]; then
case "$CONFIGURATION" in
*Debug*)
if [[ "$PLATFORM_NAME" == *simulator ]]; then
if [[ "$FORCE_BUNDLING" && $FORCE_BUNDLING == "true" ]]; then
echo "FORCE_BUNDLING enabled; continuing to bundle."
else
echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING env flag or BundleForced plist key to change this behavior."
exit 0;
fi
else
echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior."
fi
DEV=true
;;
"")
echo "$0 must be invoked by Xcode"
exit 1
;;
*)
DEV=false
;;
esac
else
if [[ "$PLATFORM_NAME" == *simulator ]]; then
if [[ "$FORCE_BUNDLING" && $FORCE_BUNDLING == "true" ]]; then
echo "FORCE_BUNDLING enabled; continuing to bundle."
else
echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING flag to change this behavior."
exit 0;
fi
else
echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior."
fi
DEV=$IS_DEV
fi
# Path to react-native folder inside node_modules
# REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Path to react-native folder inside src/native/utils/bin
REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../node_modules/react-native" && pwd)"
echo "REACT_NATIVE_DIR: $REACT_NATIVE_DIR"
# Xcode project file for React Native apps is located in ios/ subfolder
cd "${REACT_NATIVE_DIR}"/../..
# Define NVM_DIR and source the nvm.sh setup script
[ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm"
# Define default ENTRY_FILENAME
[ -z "$ENTRY_FILENAME" ] && ENTRY_FILENAME=$($PLISTBUDDY -c "Print :BundleEntryFilename" "${PLIST}")
[ -z "$ENTRY_FILENAME" ] && ENTRY_FILENAME="index.js"
echo "ENTRY_FILENAME: $ENTRY_FILENAME"
js_file_type=.js
ios_file_type=.ios.js
ios_file_name="${ENTRY_FILENAME/$js_file_type/$ios_file_type}"
# Define entry file
if [[ -s $ios_file_name ]]; then
ENTRY_FILE=${1:-$ios_file_name}
else
ENTRY_FILE=${1:-$ENTRY_FILENAME}
fi
if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
. "$HOME/.nvm/nvm.sh"
elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then
. "$(brew --prefix nvm)/nvm.sh"
fi
# Set up the nodenv node version manager if present
if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then
eval "$("$HOME/.nodenv/bin/nodenv" init -)"
fi
[ -z "$NODE_BINARY" ] && export NODE_BINARY="node"
[ -z "$CLI_PATH" ] && export CLI_PATH="$REACT_NATIVE_DIR/local-cli/cli.js"
nodejs_not_found()
{
echo "error: Can't find '$NODE_BINARY' binary to build React Native bundle" >&2
echo "If you have non-standard nodejs installation, select your project in Xcode," >&2
echo "find 'Build Phases' - 'Bundle React Native code and images'" >&2
echo "and change NODE_BINARY to absolute path to your node executable" >&2
echo "(you can find it by invoking 'which node' in the terminal)" >&2
exit 2
}
type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found
# Print commands before executing them (useful for troubleshooting)
set -x
# DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
if [[ "$CONFIGURATION" = "Debug" && ! "$PLATFORM_NAME" == *simulator ]]; then
BUNDLE_SERVER=$($PLISTBUDDY -c "Print :BundleServer" "${PLIST}")
echo "BUNDLE_SERVER: ${BUNDLE_SERVER}"
if [ -z "$BUNDLE_SERVER" ]; then
IP=$(ipconfig getifaddr en0)
if [ -z "$IP" ]; then
IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | cut -d\ -f2 | awk 'NR==1{print $1}')
fi
else
IP=$BUNDLE_SERVER
fi
if [ -z ${DISABLE_XIP+x} ]; then
IP="$IP.xip.io"
fi
$PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
$PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
echo "$IP" > "$DEST/ip.txt"
fi
$NODE_BINARY "$CLI_PATH" bundle \
--entry-file "$ENTRY_FILE" \
--platform ios \
--dev $DEV \
--reset-cache \
--bundle-output "$BUNDLE_FILE" \
--assets-dest "$DEST"
if [[ $DEV != true && ! -f "$BUNDLE_FILE" ]]; then
echo "error: File $BUNDLE_FILE does not exist. This must be a bug with" >&2
echo "React Native, please report it here: https://github.com/facebook/react-native/issues"
exit 2
else
cp "$BUNDLE_FILE"* $TMP_PATH
if [[ $DEV == "true" ]]; then
if nc -w 5 -z localhost 8081 ; then
if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running"; then
echo "Port 8081 already in use, packager is either not running or not running correctly"
exit 0
fi
else
open "$REACT_NATIVE_DIR/scripts/launchPackager.command" || echo "Can't start packager automatically"
fi
fi
fi

8
share.ios.js Normal file
View File

@@ -0,0 +1,8 @@
// Copyright (c) 2017-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);

View File

@@ -0,0 +1,73 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import {Preferences} from 'mattermost-redux/constants';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
const defaultTheme = Preferences.THEMES.default;
export function PublicChannel() {
return (
<View style={style.container}>
<Icon
name='globe'
style={style.icon}
/>
</View>
);
}
export function PrivateChannel() {
return (
<View style={style.container}>
<Icon
name='lock'
style={style.icon}
/>
</View>
);
}
export function DirectChannel() {
return (
<View style={style.container}>
<Icon
name='user'
style={style.icon}
/>
</View>
);
}
export function GroupChannel() {
return (
<View style={style.container}>
<Icon
name='users'
style={style.icon}
/>
</View>
);
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
height: 16,
marginRight: 5,
width: 16
},
icon: {
color: theme.centerChannelColor,
fontSize: 16
}
};
});
const style = getStyleSheet(defaultTheme);

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import Svg, {
G,
Path
} from 'react-native-svg';
function ExcelSvg({height, width}) {
return (
<View style={{height, width, alignItems: 'flex-start'}}>
<Svg
width={width}
height={height}
viewBox='0 0 24 32'
>
<G
stroke='none'
strokeWidth={1}
fillRule='evenodd'
>
<G
transform='translate(-918.000000, -1991.000000)'
fillRule='nonzero'
fill='#1875F0'
>
<G transform='translate(78.000000, 1991.000000)'>
<G transform='translate(840.000000, 0.000000)'>
<G>
<G>
<Path d='M23.3333333,32 L0.666666667,32 C0.298666667,32 0,31.7013333 0,31.3333333 L0,8.66666667 C0,8.48933333 0.0706666667,8.32 0.194666667,8.19466667 L8.19466667,0.194666667 C8.32,0.0706666667 8.48933333,0 8.66666667,0 L23.3333333,0 C23.7013333,0 24,0.298666667 24,0.666666667 L24,31.3333333 C24,31.7013333 23.7013333,32 23.3333333,32 Z M1.33333333,30.6666667 L22.6666667,30.6666667 L22.6666667,1.33333333 L8.94266667,1.33333333 L1.33333333,8.94266667 L1.33333333,30.6666667 Z'/>
<Path d='M8.66666667,9.33333333 L0.666666667,9.33333333 C0.298666667,9.33333333 0,9.03466667 0,8.66666667 C0,8.29866667 0.298666667,8 0.666666667,8 L8,8 L8,0.666666667 C8,0.298666667 8.29866667,0 8.66666667,0 C9.03466667,0 9.33333333,0.298666667 9.33333333,0.666666667 L9.33333333,8.66666667 C9.33333333,9.03466667 9.03466667,9.33333333 8.66666667,9.33333333 Z'/>
</G>
</G>
<G transform='translate(5.000000, 14.000000)'>
<Path d='M13.3636364,0 L0.636363636,0 C0.285090909,0 0,0.285090909 0,0.636363636 L0,12.0909091 C0,12.4421818 0.285090909,12.7272727 0.636363636,12.7272727 L13.3636364,12.7272727 C13.7149091,12.7272727 14,12.4421818 14,12.0909091 L14,0.636363636 C14,0.285090909 13.7149091,0 13.3636364,0 Z M1.27272727,5.09090909 L3.81818182,5.09090909 L3.81818182,7.63636364 L1.27272727,7.63636364 L1.27272727,5.09090909 Z M5.09090909,5.09090909 L12.7272727,5.09090909 L12.7272727,7.63636364 L5.09090909,7.63636364 L5.09090909,5.09090909 Z M12.7272727,3.81818182 L5.09090909,3.81818182 L5.09090909,1.27272727 L12.7272727,1.27272727 L12.7272727,3.81818182 Z M3.81818182,1.27272727 L3.81818182,3.81818182 L1.27272727,3.81818182 L1.27272727,1.27272727 L3.81818182,1.27272727 Z M1.27272727,8.90909091 L3.81818182,8.90909091 L3.81818182,11.4545455 L1.27272727,11.4545455 L1.27272727,8.90909091 Z M5.09090909,11.4545455 L5.09090909,8.90909091 L12.7272727,8.90909091 L12.7272727,11.4545455 L5.09090909,11.4545455 Z'/>
</G>
</G>
</G>
</G>
</G>
</Svg>
</View>
);
}
ExcelSvg.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired
};
export default ExcelSvg;

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import Svg, {
G,
Path
} from 'react-native-svg';
function GenericSvg({height, width}) {
return (
<View style={{height, width, alignItems: 'flex-start'}}>
<Svg
width={width}
height={height}
viewBox='0 0 24 32'
>
<G
stroke='none'
strokeWidth={1}
fillRule='evenodd'
>
<G
transform='translate(-393.000000, -1991.000000)'
fillRule='nonzero'
fill='#1875F0'
>
<G transform='translate(78.000000, 1991.000000)'>
<G transform='translate(315.000000, 0.000000)'>
<G>
<G>
<Path d='M23.3333333,32 L0.666666667,32 C0.298666667,32 0,31.7013333 0,31.3333333 L0,8.66666667 C0,8.48933333 0.0706666667,8.32 0.194666667,8.19466667 L8.19466667,0.194666667 C8.32,0.0706666667 8.48933333,0 8.66666667,0 L23.3333333,0 C23.7013333,0 24,0.298666667 24,0.666666667 L24,31.3333333 C24,31.7013333 23.7013333,32 23.3333333,32 Z M1.33333333,30.6666667 L22.6666667,30.6666667 L22.6666667,1.33333333 L8.94266667,1.33333333 L1.33333333,8.94266667 L1.33333333,30.6666667 Z'/>
<Path d='M8.66666667,9.33333333 L0.666666667,9.33333333 C0.298666667,9.33333333 0,9.03466667 0,8.66666667 C0,8.29866667 0.298666667,8 0.666666667,8 L8,8 L8,0.666666667 C8,0.298666667 8.29866667,0 8.66666667,0 C9.03466667,0 9.33333333,0.298666667 9.33333333,0.666666667 L9.33333333,8.66666667 C9.33333333,9.03466667 9.03466667,9.33333333 8.66666667,9.33333333 Z'/>
</G>
<Path d='M19.3333333,12 L4.66666667,12 C4.29866667,12 4,11.7013333 4,11.3333333 C4,10.9653333 4.29866667,10.6666667 4.66666667,10.6666667 L19.3333333,10.6666667 C19.7013333,10.6666667 20,10.9653333 20,11.3333333 C20,11.7013333 19.7013333,12 19.3333333,12 Z'/>
<Path d='M19.3333333,8 L12.6666667,8 C12.2986667,8 12,7.70133333 12,7.33333333 C12,6.96533333 12.2986667,6.66666667 12.6666667,6.66666667 L19.3333333,6.66666667 C19.7013333,6.66666667 20,6.96533333 20,7.33333333 C20,7.70133333 19.7013333,8 19.3333333,8 Z'/>
<Path d='M19.3333333,16 L4.66666667,16 C4.29866667,16 4,15.7013333 4,15.3333333 C4,14.9653333 4.29866667,14.6666667 4.66666667,14.6666667 L19.3333333,14.6666667 C19.7013333,14.6666667 20,14.9653333 20,15.3333333 C20,15.7013333 19.7013333,16 19.3333333,16 Z'/>
<Path d='M19.3333333,20 L4.66666667,20 C4.29866667,20 4,19.7013333 4,19.3333333 C4,18.9653333 4.29866667,18.6666667 4.66666667,18.6666667 L19.3333333,18.6666667 C19.7013333,18.6666667 20,18.9653333 20,19.3333333 C20,19.7013333 19.7013333,20 19.3333333,20 Z'/>
<Path d='M19.3333333,24 L4.66666667,24 C4.29866667,24 4,23.7013333 4,23.3333333 C4,22.9653333 4.29866667,22.6666667 4.66666667,22.6666667 L19.3333333,22.6666667 C19.7013333,22.6666667 20,22.9653333 20,23.3333333 C20,23.7013333 19.7013333,24 19.3333333,24 Z'/>
<Path d='M19.3333333,28 L4.66666667,28 C4.29866667,28 4,27.7013333 4,27.3333333 C4,26.9653333 4.29866667,26.6666667 4.66666667,26.6666667 L19.3333333,26.6666667 C19.7013333,26.6666667 20,26.9653333 20,27.3333333 C20,27.7013333 19.7013333,28 19.3333333,28 Z'/>
</G>
</G>
</G>
</G>
</G>
</Svg>
</View>
);
}
GenericSvg.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired
};
export default GenericSvg;

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import Svg, {
G,
Path
} from 'react-native-svg';
function PdfSvg({height, width}) {
return (
<View style={{height, width, alignItems: 'flex-start'}}>
<Svg
width={width}
height={height}
viewBox='0 0 24 32'
>
<G
stroke='none'
strokeWidth={1}
fillRule='evenodd'
>
<G
transform='translate(-182.000000, -1991.000000)'
fillRule='nonzero'
fill='#1875F0'
>
<G transform='translate(78.000000, 1991.000000)'>
<G transform='translate(104.000000, 0.000000)'>
<G>
<G>
<Path d='M23.3333333,32 L0.666666667,32 C0.298666667,32 0,31.7013333 0,31.3333333 L0,8.66666667 C0,8.48933333 0.0706666667,8.32 0.194666667,8.19466667 L8.19466667,0.194666667 C8.32,0.0706666667 8.48933333,0 8.66666667,0 L23.3333333,0 C23.7013333,0 24,0.298666667 24,0.666666667 L24,31.3333333 C24,31.7013333 23.7013333,32 23.3333333,32 Z M1.33333333,30.6666667 L22.6666667,30.6666667 L22.6666667,1.33333333 L8.94266667,1.33333333 L1.33333333,8.94266667 L1.33333333,30.6666667 Z'/>
<Path d='M8.66666667,9.33333333 L0.666666667,9.33333333 C0.298666667,9.33333333 0,9.03466667 0,8.66666667 C0,8.29866667 0.298666667,8 0.666666667,8 L8,8 L8,0.666666667 C8,0.298666667 8.29866667,0 8.66666667,0 C9.03466667,0 9.33333333,0.298666667 9.33333333,0.666666667 L9.33333333,8.66666667 C9.33333333,9.03466667 9.03466667,9.33333333 8.66666667,9.33333333 Z'/>
</G>
<Path d='M4.604,29.4213333 C4.348,29.4213333 4.084,29.3853333 3.816,29.308 C2.87733333,29.0373333 2.70266667,28.472 2.68133333,28.16 C2.60666667,27.048 4.62133333,25.476 8.67733333,23.476 C9.144,22.5266667 9.53333333,21.6173333 9.816,20.96 L10.056,20.4013333 C10.3306667,19.7706667 10.6093333,18.9986667 10.8586667,18.1786667 C9.32133333,15.8746667 9.02,13.408 9.624,11.9506667 C9.96,11.1346667 10.584,10.6666667 11.3333333,10.6666667 C11.3373333,10.6666667 11.3413333,10.6666667 11.3453333,10.6666667 C11.896,10.6773333 12.364,10.94 12.6653333,11.408 C13.4853333,12.6853333 12.988,15.5653333 12.3173333,17.9266667 C12.56,18.2426667 12.832,18.556 13.1373333,18.8613333 C13.9533333,19.676 14.7773333,20.3573333 15.5613333,20.9106667 C17.5773333,20.5546667 19.5146667,20.5866667 20.528,21.2466667 C21.048,21.5853333 21.3333333,22.0893333 21.3333333,22.6666667 C21.3333333,23.2866667 21.0253333,23.7706667 20.4866667,23.9933333 C19.36,24.46 17.304,23.6546667 15.288,22.3226667 C14.2893333,22.5306667 13.2453333,22.8466667 12.268,23.2773333 C11.3346667,23.688 10.4653333,24.092 9.66933333,24.4826667 C8.42,26.944 6.71866667,29.4213333 4.604,29.4213333 Z M4.06533333,27.9866667 C4.09733333,27.9986667 4.136,28.0133333 4.184,28.0266667 C5.34266667,28.3626667 6.504,27.168 7.49866667,25.604 C5.564,26.6653333 4.35066667,27.528 4.06533333,27.9866667 Z M17.4026667,22.0373333 C18.744,22.728 19.7453333,22.9426667 19.9973333,22.7506667 C20,22.584 19.984,22.484 19.8,22.364 C19.38,22.0893333 18.4973333,21.976 17.4026667,22.0373333 Z M11.8453333,19.4386667 C11.6373333,20.048 11.4373333,20.5653333 11.2773333,20.9333333 L11.04,21.4853333 C10.896,21.8226667 10.7373333,22.192 10.5653333,22.58 C10.94,22.408 11.3293333,22.2346667 11.7306667,22.0573333 C12.392,21.7666667 13.1213333,21.508 13.8746667,21.2986667 C13.28,20.8266667 12.7106667,20.3226667 12.1946667,19.8053333 C12.0746667,19.684 11.9586667,19.5613333 11.8453333,19.4386667 Z M11.328,12 C11.1386667,12.0026667 10.98,12.1573333 10.8546667,12.46 C10.5346667,13.232 10.5933333,14.728 11.3493333,16.3386667 C11.7706667,14.4946667 11.9466667,12.7546667 11.544,12.1293333 C11.476,12.024 11.4186667,12.0026667 11.328,12 Z'/>
</G>
</G>
</G>
</G>
</G>
</Svg>
</View>
);
}
PdfSvg.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired
};
export default PdfSvg;

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import Svg, {
G,
Path
} from 'react-native-svg';
function PptSvg({height, width}) {
return (
<View style={{height, width, alignItems: 'flex-start'}}>
<Svg
width={width}
height={height}
viewBox='0 0 24 32'
>
<G
stroke='none'
strokeWidth={1}
fillRule='evenodd'
>
<G
transform='translate(-1023.000000, -1991.000000)'
fillRule='nonzero'
fill='#1875F0'
>
<G transform='translate(78.000000, 1991.000000)'>
<G transform='translate(945.000000, 0.000000)'>
<G>
<G>
<Path d='M23.3333333,32 L0.666666667,32 C0.298666667,32 0,31.7013333 0,31.3333333 L0,8.66666667 C0,8.48933333 0.0706666667,8.32 0.194666667,8.19466667 L8.19466667,0.194666667 C8.32,0.0706666667 8.48933333,0 8.66666667,0 L23.3333333,0 C23.7013333,0 24,0.298666667 24,0.666666667 L24,31.3333333 C24,31.7013333 23.7013333,32 23.3333333,32 Z M1.33333333,30.6666667 L22.6666667,30.6666667 L22.6666667,1.33333333 L8.94266667,1.33333333 L1.33333333,8.94266667 L1.33333333,30.6666667 Z'/>
<Path d='M8.66666667,9.33333333 L0.666666667,9.33333333 C0.298666667,9.33333333 0,9.03466667 0,8.66666667 C0,8.29866667 0.298666667,8 0.666666667,8 L8,8 L8,0.666666667 C8,0.298666667 8.29866667,0 8.66666667,0 C9.03466667,0 9.33333333,0.298666667 9.33333333,0.666666667 L9.33333333,8.66666667 C9.33333333,9.03466667 9.03466667,9.33333333 8.66666667,9.33333333 Z'/>
</G>
</G>
<G transform='translate(6.000000, 13.000000)'>
<Path d='M11.8181818,4.72727273 L10.0454545,4.72727273 L10.0454545,2.95454545 C10.0454545,2.62836364 9.78072727,2.36363636 9.45454545,2.36363636 L7.68181818,2.36363636 L7.68181818,0.590909091 C7.68181818,0.264727273 7.41709091,0 7.09090909,0 L1.18181818,0 C0.855636364,0 0.590909091,0.264727273 0.590909091,0.590909091 L0.590909091,7.68181818 C0.590909091,8.008 0.855636364,8.27272727 1.18181818,8.27272727 L2.95454545,8.27272727 L2.95454545,10.0454545 C2.95454545,10.3716364 3.21927273,10.6363636 3.54545455,10.6363636 L5.31818182,10.6363636 L5.31818182,12.4090909 C5.31818182,12.7352727 5.58290909,13 5.90909091,13 L11.8181818,13 C12.1443636,13 12.4090909,12.7352727 12.4090909,12.4090909 L12.4090909,5.31818182 C12.4090909,4.992 12.1443636,4.72727273 11.8181818,4.72727273 Z M1.77272727,7.09090909 L1.77272727,1.18181818 L6.5,1.18181818 L6.5,2.36363636 L3.54545455,2.36363636 C3.21927273,2.36363636 2.95454545,2.62836364 2.95454545,2.95454545 L2.95454545,7.09090909 L1.77272727,7.09090909 Z M4.13636364,9.45454545 L4.13636364,3.54545455 L8.86363636,3.54545455 L8.86363636,4.72727273 L5.90909091,4.72727273 C5.58290909,4.72727273 5.31818182,4.992 5.31818182,5.31818182 L5.31818182,9.45454545 L4.13636364,9.45454545 Z M11.2272727,11.8181818 L6.5,11.8181818 L6.5,5.90909091 L11.2272727,5.90909091 L11.2272727,11.8181818 Z'/>
</G>
</G>
</G>
</G>
</G>
</Svg>
</View>
);
}
PptSvg.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired
};
export default PptSvg;

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import Svg, {
G,
Path
} from 'react-native-svg';
function ZipSvg({height, width}) {
return (
<View style={{height, width, alignItems: 'flex-start'}}>
<Svg
width={width}
height={height}
viewBox='0 0 24 33'
>
<G
stroke='none'
strokeWidth={1}
fillRule='evenodd'
>
<G
transform='translate(-287.000000, -1991.000000)'
fillRule='nonzero'
fill='#1875F0'
>
<G transform='translate(78.000000, 1991.000000)'>
<G transform='translate(209.000000, 0.000000)'>
<G>
<G transform='translate(0.000000, 0.320000)'>
<Path d='M23.3333333,32 L0.666666667,32 C0.298666667,32 0,31.7026667 0,31.3333333 L0,8.66666667 C0,8.48933333 0.0706666667,8.32 0.194666667,8.19466667 L8.19466667,0.194666667 C8.32,0.0706666667 8.48933333,0 8.66666667,0 L23.3333333,0 C23.7013333,0 24,0.298666667 24,0.666666667 L24,31.3333333 C24,31.7026667 23.7013333,32 23.3333333,32 Z M1.33333333,30.6666667 L22.6666667,30.6666667 L22.6666667,1.33333333 L8.94266667,1.33333333 L1.33333333,8.94266667 L1.33333333,30.6666667 Z'/>
<Path d='M8.66666667,9.33333333 L0.666666667,9.33333333 C0.298666667,9.33333333 0,9.036 0,8.66666667 C0,8.29733333 0.298666667,8 0.666666667,8 L8,8 L8,0.666666667 C8,0.298666667 8.29866667,0 8.66666667,0 C9.03466667,0 9.33333333,0.298666667 9.33333333,0.666666667 L9.33333333,8.66666667 C9.33333333,9.036 9.03466667,9.33333333 8.66666667,9.33333333 Z'/>
</G>
<Path d='M14,29.6533333 L12.6666667,29.6533333 C12.2986667,29.6533333 12,29.3546667 12,28.9866667 C12,28.6186667 12.2986667,28.32 12.6666667,28.32 L14,28.32 C14.368,28.32 14.6666667,28.6186667 14.6666667,28.9866667 C14.6666667,29.3546667 14.368,29.6533333 14,29.6533333 Z'/>
<Path d='M14,26.9866667 L12.6666667,26.9866667 C12.2986667,26.9866667 12,26.688 12,26.32 C12,25.952 12.2986667,25.6533333 12.6666667,25.6533333 L14,25.6533333 C14.368,25.6533333 14.6666667,25.952 14.6666667,26.32 C14.6666667,26.688 14.368,26.9866667 14,26.9866667 Z'/>
<Path d='M14,24.32 L12.6666667,24.32 C12.2986667,24.32 12,24.0213333 12,23.6533333 C12,23.2853333 12.2986667,22.9866667 12.6666667,22.9866667 L14,22.9866667 C14.368,22.9866667 14.6666667,23.2853333 14.6666667,23.6533333 C14.6666667,24.0213333 14.368,24.32 14,24.32 Z'/>
<Path d='M14,21.6533333 L12.6666667,21.6533333 C12.2986667,21.6533333 12,21.3546667 12,20.9866667 C12,20.6186667 12.2986667,20.32 12.6666667,20.32 L14,20.32 C14.368,20.32 14.6666667,20.6186667 14.6666667,20.9866667 C14.6666667,21.3546667 14.368,21.6533333 14,21.6533333 Z'/>
<Path d='M14,18.9866667 L12.6666667,18.9866667 C12.2986667,18.9866667 12,18.688 12,18.32 C12,17.952 12.2986667,17.6533333 12.6666667,17.6533333 L14,17.6533333 C14.368,17.6533333 14.6666667,17.952 14.6666667,18.32 C14.6666667,18.688 14.368,18.9866667 14,18.9866667 Z'/>
<Path d='M14,16.32 L12.6666667,16.32 C12.2986667,16.32 12,16.0213333 12,15.6533333 C12,15.2853333 12.2986667,14.9866667 12.6666667,14.9866667 L14,14.9866667 C14.368,14.9866667 14.6666667,15.2853333 14.6666667,15.6533333 C14.6666667,16.0213333 14.368,16.32 14,16.32 Z'/>
<Path d='M14,13.6533333 L12.6666667,13.6533333 C12.2986667,13.6533333 12,13.356 12,12.9866667 C12,12.6173333 12.2986667,12.32 12.6666667,12.32 L14,12.32 C14.368,12.32 14.6666667,12.6186667 14.6666667,12.9866667 C14.6666667,13.3546667 14.368,13.6533333 14,13.6533333 Z'/>
<Path d='M16.6666667,28.32 L15.3333333,28.32 C14.9653333,28.32 14.6666667,28.0213333 14.6666667,27.6533333 C14.6666667,27.2853333 14.9653333,26.9866667 15.3333333,26.9866667 L16.6666667,26.9866667 C17.0346667,26.9866667 17.3333333,27.2853333 17.3333333,27.6533333 C17.3333333,28.0213333 17.0346667,28.32 16.6666667,28.32 Z'/>
<Path d='M16.6666667,25.6533333 L15.3333333,25.6533333 C14.9653333,25.6533333 14.6666667,25.3546667 14.6666667,24.9866667 C14.6666667,24.6186667 14.9653333,24.32 15.3333333,24.32 L16.6666667,24.32 C17.0346667,24.32 17.3333333,24.6186667 17.3333333,24.9866667 C17.3333333,25.3546667 17.0346667,25.6533333 16.6666667,25.6533333 Z'/>
<Path d='M16.6666667,22.9866667 L15.3333333,22.9866667 C14.9653333,22.9866667 14.6666667,22.688 14.6666667,22.32 C14.6666667,21.952 14.9653333,21.6533333 15.3333333,21.6533333 L16.6666667,21.6533333 C17.0346667,21.6533333 17.3333333,21.952 17.3333333,22.32 C17.3333333,22.688 17.0346667,22.9866667 16.6666667,22.9866667 Z'/>
<Path d='M16.6666667,20.32 L15.3333333,20.32 C14.9653333,20.32 14.6666667,20.0213333 14.6666667,19.6533333 C14.6666667,19.2853333 14.9653333,18.9866667 15.3333333,18.9866667 L16.6666667,18.9866667 C17.0346667,18.9866667 17.3333333,19.2853333 17.3333333,19.6533333 C17.3333333,20.0213333 17.0346667,20.32 16.6666667,20.32 Z'/>
<Path d='M16.6666667,17.6533333 L15.3333333,17.6533333 C14.9653333,17.6533333 14.6666667,17.3546667 14.6666667,16.9866667 C14.6666667,16.6186667 14.9653333,16.32 15.3333333,16.32 L16.6666667,16.32 C17.0346667,16.32 17.3333333,16.6186667 17.3333333,16.9866667 C17.3333333,17.3546667 17.0346667,17.6533333 16.6666667,17.6533333 Z'/>
<Path d='M16.6666667,14.9866667 L15.3333333,14.9866667 C14.9653333,14.9866667 14.6666667,14.688 14.6666667,14.32 C14.6666667,13.952 14.9653333,13.6533333 15.3333333,13.6533333 L16.6666667,13.6533333 C17.0346667,13.6533333 17.3333333,13.952 17.3333333,14.32 C17.3333333,14.688 17.0346667,14.9866667 16.6666667,14.9866667 Z'/>
<Path d='M12.6666667,6.98666667 C12.4213333,6.98666667 12.1866667,6.852 12.0693333,6.61866667 L10.736,3.952 C10.6426667,3.764 10.6426667,3.54266667 10.736,3.356 L12.0693333,0.689333333 C12.2346667,0.358666667 12.6346667,0.226666667 12.964,0.390666667 C13.2933333,0.554666667 13.4266667,0.956 13.2626667,1.28533333 L12.0786667,3.65333333 L13.2626667,6.02133333 C13.428,6.35066667 13.2933333,6.75066667 12.964,6.916 C12.8693333,6.964 12.768,6.98666667 12.6666667,6.98666667 Z'/>
<Path d='M16.6666667,6.98666667 C16.5666667,6.98666667 16.4653333,6.964 16.3693333,6.916 C16.04,6.75066667 15.9066667,6.35066667 16.0706667,6.02133333 L17.2546667,3.65333333 L16.0706667,1.28533333 C15.9053333,0.956 16.04,0.556 16.3693333,0.390666667 C16.6986667,0.225333333 17.0973333,0.358666667 17.264,0.689333333 L18.5973333,3.356 C18.6906667,3.544 18.6906667,3.76533333 18.5973333,3.952 L17.264,6.61866667 C17.1453333,6.852 16.9106667,6.98666667 16.6666667,6.98666667 Z'/>
<Path d='M14.6666667,10.9386667 C14.056,10.9386667 13.444,10.7066667 12.9786667,10.2413333 C12.3186667,9.58133333 12.104,8.58933333 12.432,7.71466667 L13.3493333,3.50933333 C13.416,3.20533333 13.6866667,2.98666667 14,2.98666667 L15.3333333,2.98666667 C15.6466667,2.98666667 15.9173333,3.20533333 15.984,3.51066667 L16.928,7.80666667 C17.2293333,8.58933333 17.0146667,9.58133333 16.3546667,10.2413333 C15.8893333,10.7066667 15.2773333,10.9386667 14.6666667,10.9386667 Z M14.536,4.32 L13.7066667,8.09333333 C13.5346667,8.56933333 13.6293333,9.008 13.9213333,9.29866667 C14.3333333,9.71066667 15.0013333,9.71066667 15.412,9.29866667 C15.7026667,9.008 15.7986667,8.56933333 15.6533333,8.184 L14.7986667,4.32 L14.536,4.32 Z'/>
</G>
</G>
</G>
</G>
</G>
</Svg>
</View>
);
}
ZipSvg.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired
};
export default ZipSvg;

View File

@@ -0,0 +1,121 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
TouchableHighlight,
Text,
View
} from 'react-native';
import IonIcon from 'react-native-vector-icons/Ionicons';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {DirectChannel, GroupChannel, PublicChannel, PrivateChannel} from 'share_extension/icons/channel_type';
const channelTypes = {
D: DirectChannel,
G: GroupChannel,
O: PublicChannel,
P: PrivateChannel
};
export default class ExtensionChannelItem extends PureComponent {
static propTypes = {
channel: PropTypes.object.isRequired,
currentChannelId: PropTypes.string.isRequired,
onSelectChannel: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired
};
onPress = wrapWithPreventDoubleTap(() => {
const {channel, onSelectChannel} = this.props;
requestAnimationFrame(() => {
onSelectChannel(channel);
});
});
render() {
const {
channel,
currentChannelId,
theme
} = this.props;
const style = getStyleSheet(theme);
const isCurrent = channel.id === currentChannelId;
let current;
if (isCurrent) {
current = (
<View style={style.checkmarkContainer}>
<IonIcon
name='md-checkmark'
style={style.checkmark}
/>
</View>
);
}
const Type = channelTypes[channel.type] || PublicChannel;
const icon = <Type/>;
return (
<TouchableHighlight
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
onPress={this.onPress}
>
<View style={style.container}>
<View style={style.item}>
{icon}
<Text
style={[style.text]}
ellipsizeMode='tail'
numberOfLines={1}
>
{channel.display_name}
</Text>
{current}
</View>
</View>
</TouchableHighlight>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
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
},
iconContainer: {
marginRight: 5
},
checkmarkContainer: {
alignItems: 'flex-end'
},
checkmark: {
color: theme.linkColor,
fontSize: 16
}
};
});

View File

@@ -0,0 +1,363 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
ActivityIndicator,
SectionList,
Text,
View
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {intlShape} from 'react-intl';
import {Client4} from 'mattermost-redux/client';
import {General, Preferences} from 'mattermost-redux/constants';
import {getUserIdFromChannelName} from 'mattermost-redux/utils/channel_utils';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import SearchBar from 'app/components/search_bar';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import ExtensionChannelItem from './extension_channel_item';
import ExtensionNavBar from './extension_nav_bar';
export default class ExtensionChannels extends PureComponent {
static propTypes = {
currentChannelId: PropTypes.string.isRequired,
currentUserId: 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 {channels} = this.state;
const sections = [];
const publicChannels = [];
const privateChannels = [];
const directChannels = [];
channels.forEach((channel) => {
const include = term ? channel.display_name.toLowerCase().includes(term.toLowerCase()) : true;
if (channel.display_name && include) {
switch (channel.type) {
case General.OPEN_CHANNEL:
publicChannels.push(channel);
break;
case General.PRIVATE_CHANNEL:
privateChannels.push(channel);
break;
default:
directChannels.push(channel);
break;
}
}
});
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});
};
getGroupDisplayNameFromUserIds = (userIds, profiles) => {
const names = [];
userIds.forEach((id) => {
const profile = profiles.find((p) => p.id === id);
names.push(displayUsername(profile, Preferences.DISPLAY_PREFER_FULL_NAME));
});
return names.sort(this.sort).join(', ');
};
goBack = () => {
this.props.navigator.pop();
};
keyExtractor = (item) => item.id;
loadChannels = async () => {
try {
const {currentUserId, teamId} = this.props;
const channelsMap = {};
const myChannels = await Client4.getMyChannels(teamId);
const myPreferences = await Client4.getMyPreferences();
const usersInDms = myPreferences.filter((pref) => pref.category === Preferences.CATEGORY_DIRECT_CHANNEL_SHOW).map((pref) => pref.name);
const dms = myChannels.filter((channel) => {
const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
return (channel.type === General.DM_CHANNEL && usersInDms.includes(teammateId)) || channel.type === General.GM_CHANNEL;
});
const dmProfiles = await Client4.getProfilesByIds(usersInDms);
for (let i = 0; i < dms.length; i++) {
const channel = dms[i];
if (channel.type === General.DM_CHANNEL) {
const teammateId = getUserIdFromChannelName(currentUserId, channel.name);
const profile = dmProfiles.find((p) => p.id === teammateId);
channelsMap[channel.id] = displayUsername(profile, Preferences.DISPLAY_PREFER_FULL_NAME);
} else if (channel.type === General.GM_CHANNEL) {
const members = await Client4.getChannelMembers(channel.id, 0, General.MAX_USERS_IN_GM);
const userIds = members.filter((m) => m.user_id !== currentUserId).map((m) => m.user_id);
const gmProfiles = await Client4.getProfilesByIds(userIds);
channelsMap[channel.id] = this.getGroupDisplayNameFromUserIds(userIds, gmProfiles);
}
}
const channels = myChannels.map((channel) => {
return {
id: channel.id,
display_name: channelsMap[channel.id] || channel.display_name,
type: channel.type
};
});
this.setState({
channels
}, () => {
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 = (channel) => {
this.props.onSelectChannel(channel);
this.goBack();
};
renderBody = (styles) => {
const {error, sections} = this.state;
if (error) {
return (
<View style={styles.errorContainer}>
<Text style={styles.error}>
{error.message}
</Text>
</View>
);
}
if (!sections) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator/>
</View>
);
}
return (
<SectionList
sections={sections}
ListHeaderComponent={this.renderSearchBar(styles)}
ItemSeparatorComponent={this.renderItemSeparator}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyExtractor={this.keyExtractor}
keyboardShouldPersistTaps='always'
keyboardDismissMode='on-drag'
initialNumToRender={10}
maxToRenderPerBatch={10}
stickySectionHeadersEnabled={true}
scrollEventThrottle={100}
windowSize={5}
/>
);
};
renderItem = ({item}) => {
const {currentChannelId, theme} = this.props;
return (
<ExtensionChannelItem
channel={item}
currentChannelId={currentChannelId}
onSelectChannel={this.handleSelectChannel}
theme={theme}
/>
);
};
renderItemSeparator = () => {
const {theme} = this.props;
const styles = getStyleSheet(theme);
return (
<View style={styles.separatorContainer}>
<View style={styles.separator}/>
</View>
);
};
renderSearchBar = (styles) => {
const {formatMessage} = this.context.intl;
const {theme} = this.props;
return (
<View style={styles.searchContainer}>
<SearchBar
ref='search_bar'
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
backgroundColor='transparent'
inputHeight={33}
inputStyle={styles.searchBarInput}
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.5)}
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.3)}
titleCancelColor={theme.linkColor}
onChangeText={this.handleSearch}
value={this.state.term}
/>
</View>
);
};
renderSectionHeader = ({section}) => {
const {intl} = this.context;
const {theme} = this.props;
const styles = getStyleSheet(theme);
const {
defaultMessage,
id
} = section;
return (
<View style={[styles.titleContainer, {backgroundColor: theme.centerChannelBg}]}>
<View style={{backgroundColor: changeOpacity(theme.centerChannelColor, 0.1), justifyContent: 'center'}}>
<Text style={styles.title}>
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
</Text>
</View>
</View>
);
};
sort = (a, b) => {
const locale = DeviceInfo.getDeviceLocale().split('-')[0];
return a.localeCompare(b, locale, {numeric: true});
};
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 (
<View style={styles.flex}>
<ExtensionNavBar
backButton={true}
onLeftButtonPress={this.goBack}
title={title}
theme={theme}
/>
{this.renderBody(styles)}
</View>
);
}
}
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
},
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
}
};
});

View File

@@ -0,0 +1,149 @@
// Copyright (c) 2017-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,
title: 'Mattermost'
};
renderLeftButton = (styles) => {
const {backButton, leftButtonTitle, onLeftButtonPress} = this.props;
let backComponent;
if (backButton) {
backComponent = (
<IonIcon
name='ios-arrow-back'
style={styles.backButton}
/>
);
} else if (leftButtonTitle) {
backComponent = (
<Text
ellipsisMode='tail'
numberOfLines={1}
style={styles.leftButton}
>
{leftButtonTitle}
</Text>
);
}
if (backComponent) {
return (
<TouchableOpacity
onPress={onLeftButtonPress}
style={styles.backButtonContainer}
>
{backComponent}
</TouchableOpacity>
);
}
return <View style={styles.backButtonContainer}/>;
};
renderRightButton = (styles) => {
const {authenticated, onRightButtonPress, rightButtonTitle} = this.props;
if (rightButtonTitle && authenticated) {
return (
<TouchableOpacity
onPress={onRightButtonPress}
style={styles.rightButtonContainer}
>
<Text
ellipsisMode='tail'
numberOfLines={1}
style={styles.rightButton}
>
{rightButtonTitle}
</Text>
</TouchableOpacity>
);
}
return <View style={styles.rightButtonContainer}/>;
};
render() {
const {theme, title} = this.props;
const styles = getStyleSheet(theme);
return (
<View style={styles.container}>
{this.renderLeftButton(styles)}
<View style={styles.titleContainer}>
<Text style={styles.title}>
{title}
</Text>
</View>
{this.renderRightButton(styles)}
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.2),
borderBottomWidth: 1,
flexDirection: 'row',
height: 45
},
backButtonContainer: {
justifyContent: 'center',
paddingHorizontal: 15,
width: '30%'
},
titleContainer: {
alignItems: 'center',
flex: 1,
justifyContent: 'center'
},
backButton: {
color: theme.linkColor,
fontSize: 34
},
leftButton: {
color: theme.linkColor,
fontSize: 16
},
title: {
fontSize: 17,
fontWeight: '600'
},
rightButtonContainer: {
alignItems: 'flex-end',
justifyContent: 'center',
paddingHorizontal: 15,
width: '30%'
},
rightButton: {
color: theme.linkColor,
fontSize: 16,
fontWeight: '600'
}
};
});

View File

@@ -0,0 +1,644 @@
// Copyright (c) 2017-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 {
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 {Client4} from 'mattermost-redux/client';
import {lookupMimeType} from 'mattermost-redux/utils/file_utils';
import mattermostBucket from 'app/mattermost_bucket';
import {generateId} from 'app/utils/file';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import Config from 'assets/config';
import ExcelSvg from 'share_extension/icons/excel';
import GenericSvg from 'share_extension/icons/generic';
import PdfSvg from 'share_extension/icons/pdf';
import PptSvg from 'share_extension/icons/ppt';
import ZipSvg from 'share_extension/icons/zip';
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 extensionSvg = {
csv: ExcelSvg,
pdf: PdfSvg,
ppt: PptSvg,
pptx: PptSvg,
xls: ExcelSvg,
xlsx: ExcelSvg,
zip: ZipSvg
};
export default class ExtensionPost extends PureComponent {
static propTypes = {
credentials: PropTypes.object,
navigator: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired
};
static contextTypes = {
intl: intlShape
};
constructor(props, context) {
super(props, context);
const {height, width} = Dimensions.get('window');
const isLandscape = width > height;
this.state = {
currentUserId: null,
error: null,
files: [],
isLandscape,
value: ''
};
}
componentWillMount() {
this.loadData();
}
componentDidMount() {
this.focusInput();
}
componentDidUpdate() {
this.focusInput();
}
auth = async () => {
try {
const emmSecured = await mattermostBucket.get('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 = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
const {channel, currentUserId, team} = this.state;
navigator.push({
component: ExtensionChannels,
wrapperStyle: {
borderRadius: 10,
backgroundColor: theme.centerChannelBg
},
passProps: {
currentChannelId: channel.id,
currentUserId,
onSelectChannel: this.selectChannel,
teamId: team.id,
theme,
title: team.display_name
}
});
});
goToTeams = wrapWithPreventDoubleTap(() => {
const {navigator, theme} = this.props;
const {formatMessage} = this.context.intl;
const {team} = this.state;
navigator.push({
component: ExtensionTeams,
title: formatMessage({id: 'quick_switch_modal.teams', defaultMessage: 'Teams'}),
wrapperStyle: {
borderRadius: 10,
backgroundColor: theme.centerChannelBg
},
passProps: {
currentTeamId: team.id,
onSelectTeam: this.selectTeam,
theme
}
});
});
handleCancel = wrapWithPreventDoubleTap(() => {
this.props.onClose();
});
handleTextChange = (value) => {
this.setState({value});
};
loadData = async () => {
const {credentials} = this.props;
if (credentials) {
try {
const currentUserId = await mattermostBucket.get('currentUserId', Config.AppGroupId);
const channel = await mattermostBucket.get('selectedChannel', Config.AppGroupId);
const team = await mattermostBucket.get('selectedTeam', Config.AppGroupId);
const items = await ShareExtension.data(Config.AppGroupId);
const text = [];
const urls = [];
const files = [];
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 = fullPath.replace('file://', '');
const filename = fullPath.replace(/^.*[\\/]/, '');
const extension = filename.split('.').pop();
files.push({
extension,
filename,
filePath,
fullPath,
mimeType: lookupMimeType(filename.toLowerCase()),
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);
this.setState({channel, currentUserId, files, team, value});
} 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 {credentials, theme} = this.props;
const {error, value} = this.state;
if (credentials && !error) {
return (
<ScrollView
ref={this.getScrollViewRef}
contentContainerStyle={styles.scrollView}
style={styles.flex}
>
<TextInput
ref={this.getInputRef}
maxLength={MAX_MESSAGE_LENGTH}
multiline={true}
onChangeText={this.handleTextChange}
placeholder={formatMessage({id: 'create_post.write', defaultMessage: 'Write a message...'})}
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
style={[styles.input, {maxHeight: MAX_INPUT_HEIGHT}]}
value={value}
/>
{this.renderFiles(styles)}
</ScrollView>
);
}
if (error) {
return (
<View style={styles.unauthenticatedContainer}>
<Text style={styles.unauthenticated}>
{error.message}
</Text>
</View>
);
}
return (
<View style={styles.unauthenticatedContainer}>
<Text style={styles.unauthenticated}>
{'Authentication required: Please first login using the app.'}
</Text>
</View>
);
};
renderChannelButton = (styles) => {
const {formatMessage} = this.context.intl;
const {credentials, theme} = this.props;
const {channel} = this.state;
const channelName = channel ? channel.display_name : '';
if (!credentials) {
return null;
}
return (
<TouchableHighlight
onPress={this.goToChannels}
style={styles.buttonContainer}
underlayColor={changeOpacity(theme.centerChannelColor, 0.2)}
>
<View style={styles.buttonWrapper}>
<View style={styles.buttonLabelContainer}>
<Text style={styles.buttonLabel}>
{formatMessage({id: 'mobile.share_extension.channel', defaultMessage: 'Channel'})}
</Text>
</View>
<View style={styles.buttonValueContainer}>
<Text
ellipsizeMode='tail'
numberOfLines={1}
style={styles.buttonValue}
>
{channelName}
</Text>
<View style={styles.arrowContainer}>
<IonIcon
color={changeOpacity(theme.centerChannelColor, 0.4)}
name='ios-arrow-forward'
size={25}
/>
</View>
</View>
</View>
</TouchableHighlight>
);
};
renderFiles = (styles) => {
const {files} = this.state;
return files.map((file, index) => {
let component;
switch (file.type) {
case 'public.image':
component = (
<View
key={`item-${index}`}
style={styles.imageContainer}
>
<Image
source={{uri: file.fullPath}}
resizeMode='cover'
style={styles.image}
/>
</View>
);
break;
case 'public.movie':
component = (
<View
key={`item-${index}`}
style={styles.imageContainer}
>
<Video
style={styles.video}
resizeMode='cover'
source={{uri: file.fullPath}}
volume={0}
paused={true}
/>
</View>
);
break;
case 'public.file-url': {
let SvgIcon = extensionSvg[file.extension];
if (!SvgIcon) {
SvgIcon = GenericSvg;
}
component = (
<View
key={`item-${index}`}
style={styles.otherContainer}
>
<View style={styles.otherWrapper}>
<View style={styles.fileIcon}>
<SvgIcon
width={19}
height={48}
/>
</View>
</View>
</View>
);
break;
}
}
return (
<View
style={styles.fileContainer}
key={`item-${index}`}
>
{component}
<Text
ellipsisMode='tail'
numberOfLines={1}
style={styles.filename}
>
{file.filename}
</Text>
</View>
);
});
};
renderTeamButton = (styles) => {
const {formatMessage} = this.context.intl;
const {credentials, theme} = this.props;
const {team} = this.state;
const teamName = team ? team.display_name : '';
if (!credentials) {
return null;
}
return (
<TouchableHighlight
onPress={this.goToTeams}
style={styles.buttonContainer}
underlayColor={changeOpacity(theme.centerChannelColor, 0.2)}
>
<View style={styles.buttonWrapper}>
<View style={styles.flex}>
<Text style={styles.buttonLabel}>
{formatMessage({id: 'mobile.share_extension.team', defaultMessage: 'Team'})}
</Text>
</View>
<View style={styles.buttonValueContainer}>
<Text style={styles.buttonValue}>
{teamName}
</Text>
<View style={styles.arrowContainer}>
<IonIcon
color={changeOpacity(theme.centerChannelColor, 0.4)}
name='ios-arrow-forward'
size={25}
/>
</View>
</View>
</View>
</TouchableHighlight>
);
};
selectChannel = (channel) => {
this.setState({channel});
};
selectTeam = (team, channel) => {
this.setState({channel, team});
};
sendMessage = wrapWithPreventDoubleTap(async () => {
const {credentials, onClose} = this.props;
const {channel, currentUserId, files, value} = this.state;
// If no text and no files do nothing
if (!value && !files.length) {
return;
}
if (currentUserId && credentials) {
await this.auth();
try {
const post = {
user_id: currentUserId,
channel_id: channel.id,
message: value
};
const data = {
files,
post,
requestId: generateId()
};
onClose(data);
} catch (error) {
this.setState({error});
setTimeout(() => {
onClose();
}, 5000);
}
}
});
render() {
const {credentials, theme} = this.props;
const {formatMessage} = this.context.intl;
const styles = getStyleSheet(theme);
return (
<View
onLayout={this.onLayout}
style={styles.container}
>
<ExtensionNavBar
authenticated={Boolean(credentials)}
leftButtonTitle={formatMessage({id: 'mobile.share_extension.cancel', defaultMessage: 'Cancel'})}
onLeftButtonPress={this.handleCancel}
onRightButtonPress={this.sendMessage}
rightButtonTitle={formatMessage({id: 'mobile.share_extension.send', defaultMessage: 'Send'})}
theme={theme}
/>
{this.renderBody(styles)}
{this.renderTeamButton(styles)}
{this.renderChannelButton(styles)}
</View>
);
}
}
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
}
};
});

View File

@@ -0,0 +1,125 @@
// Copyright (c) 2017-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 IonIcon from 'react-native-vector-icons/Ionicons';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
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 = wrapWithPreventDoubleTap(() => {
const {onSelectTeam, team} = this.props;
onSelectTeam(team);
});
render() {
const {
currentTeamId,
team,
theme
} = this.props;
const styles = getStyleSheet(theme);
let current;
if (team.id === currentTeamId) {
current = (
<View style={styles.checkmarkContainer}>
<IonIcon
name='md-checkmark'
style={styles.checkmark}
/>
</View>
);
}
const icon = (
<View style={styles.iconContainer}>
<Text style={styles.icon}>
{team.display_name.substr(0, 2).toUpperCase()}
</Text>
</View>
);
return (
<TouchableHighlight
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
onPress={this.onPress}
>
<View style={styles.container}>
<View style={styles.item}>
{icon}
<Text
style={[styles.text]}
ellipsizeMode='tail'
numberOfLines={1}
>
{team.display_name}
</Text>
{current}
</View>
</View>
</TouchableHighlight>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
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
},
iconContainer: {
alignItems: 'center',
backgroundColor: theme.linkColor,
borderRadius: 2,
height: 30,
justifyContent: 'center',
width: 30,
marginRight: 10
},
icon: {
color: theme.sidebarText,
fontFamily: 'OpenSans',
fontSize: 15,
fontWeight: '600'
},
checkmarkContainer: {
alignItems: 'flex-end'
},
checkmark: {
color: theme.linkColor,
fontSize: 16
}
};
});

View File

@@ -0,0 +1,209 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {ActivityIndicator, FlatList, Text, View} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import {intlShape} from 'react-intl';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
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,
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();
};
keyExtractor = (item) => item.id;
loadTeams = async () => {
try {
const defaultChannels = {};
const teams = await Client4.getMyTeams();
const myMembers = await Client4.getMyTeamMembers();
const myTeams = [];
teams.forEach(async (team) => {
const belong = myMembers.find((member) => member.team_id === team.id);
if (belong) {
const channels = await Client4.getMyChannels(team.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 {error, myTeams} = this.state;
if (error) {
return (
<View style={styles.errorContainer}>
<Text style={styles.error}>
{error.message}
</Text>
</View>
);
}
if (!myTeams) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator/>
</View>
);
}
return (
<FlatList
data={myTeams}
ItemSeparatorComponent={this.renderItemSeparator}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
keyboardShouldPersistTaps='always'
keyboardDismissMode='on-drag'
initialNumToRender={10}
maxToRenderPerBatch={10}
scrollEventThrottle={100}
windowSize={5}
/>
);
};
renderItem = ({item}) => {
const {currentTeamId, theme} = this.props;
return (
<ExtensionTeamItem
currentTeamId={currentTeamId}
onSelectTeam={this.handleSelectTeam}
team={item}
theme={theme}
/>
);
};
renderItemSeparator = () => {
const {theme} = this.props;
const styles = getStyleSheet(theme);
return (
<View style={styles.separatorContainer}>
<View style={styles.separator}/>
</View>
);
};
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 (
<View style={styles.flex}>
<ExtensionNavBar
backButton={true}
onLeftButtonPress={this.goBack}
title={formatMessage({id: 'mobile.drawer.teamsTitle', defaultMessage: 'Teams'})}
theme={theme}
/>
{this.renderBody(styles)}
</View>
);
}
}
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
}
};
});

View File

@@ -0,0 +1,170 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import {IntlProvider} from 'react-intl';
import DeviceInfo from 'react-native-device-info';
import {
Animated,
Dimensions,
NativeModules,
NavigatorIOS,
StyleSheet,
View
} from 'react-native';
import {Preferences} from 'mattermost-redux/constants';
import {getTranslations} from 'app/i18n';
import mattermostBucket from 'app/mattermost_bucket';
import Config from 'assets/config';
import ExtensionPost from './extension_post';
const {View: AnimatedView} = Animated;
const ShareExtension = NativeModules.MattermostShare;
export default class SharedApp extends PureComponent {
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.get('credentials', Config.AppGroupId).then((value) => {
this.credentials = value;
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();
}
onClose = (data) => {
Animated.parallel([
Animated.timing(
this.state.backdropOpacity,
{
toValue: 0,
duration: 250
}),
Animated.timing(
this.state.containerOpacity,
{
toValue: 0,
duration: 250
})
]).start(() => {
ShareExtension.close(data, Config.AppGroupId);
});
};
onLayout = (e) => {
const {height, width} = e.nativeEvent.layout;
const isLandscape = width > height;
if (this.state.isLandscape !== isLandscape) {
this.setState({isLandscape});
}
};
render() {
const {init, isLandscape} = this.state;
if (!init) {
return null;
}
const theme = Preferences.THEMES.default;
const locale = DeviceInfo.getDeviceLocale().split('-')[0];
const initialRoute = {
component: ExtensionPost,
title: 'Mattermost',
passProps: {
credentials: this.credentials,
onClose: this.onClose,
isLandscape,
theme
},
wrapperStyle: {
borderRadius: 10,
backgroundColor: theme.centerChannelBg
}
};
return (
<IntlProvider
locale={locale}
messages={getTranslations(locale)}
>
<View
style={styles.flex}
onLayout={this.onLayout}
>
<AnimatedView style={[styles.backdrop, {opacity: this.state.backdropOpacity}]}/>
<View style={styles.wrapper}>
<AnimatedView
style={[
styles.container,
{
opacity: this.state.containerOpacity,
height: this.credentials ? 250 : 130,
top: isLandscape ? 20 : 65
}
]}
>
<NavigatorIOS
initialRoute={initialRoute}
style={styles.flex}
navigationBarHidden={true}
/>
</AnimatedView>
</View>
</View>
</IntlProvider>
);
}
}
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%'
}
});

View File

@@ -29,6 +29,9 @@ mockery.registerMock('react-native', {
addEventListener: () => true,
fetch: () => Promise.resolve(true)
}
},
Platform: {
OS: 'ios'
}
});
mockery.registerMock('react-native-device-info', {