diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e3d966fce..4fff02aa5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -44,7 +44,8 @@ android:exported="false" /> + android:configChanges="keyboard|keyboardHidden|orientation|screenSize" + android:resizeableActivity="true"/> { @@ -124,6 +133,15 @@ export default class ChannelSidebar extends Component { return false; }; + handleDimensions = () => { + if (DeviceTypes.IS_TABLET && this.mounted) { + mattermostManaged.isRunningInSplitView().then((result) => { + const isSplitView = Boolean(result.isSplitView); + this.setState({isSplitView}); + }); + } + }; + handleShowDrawerContent = () => { this.setState({show: true}); }; @@ -380,6 +398,7 @@ export default class ChannelSidebar extends Component { render() { const {children, deviceWidth} = this.props; const {openDrawerOffset} = this.state; + const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView; const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset); return ( @@ -390,7 +409,7 @@ export default class ChannelSidebar extends Component { onDrawerOpen={this.handleDrawerOpen} drawerWidth={drawerWidth} useNativeAnimations={true} - isTablet={DeviceTypes.IS_TABLET} + isTablet={isTablet} > {children} diff --git a/app/mattermost.js b/app/mattermost.js index 0d11b7154..5f82d5d70 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -405,7 +405,7 @@ const launchSelectServer = () => { }); }; -const launchChannel = () => { +const launchChannel = (skipMetrics = false) => { Navigation.startSingleScreenApp({ screen: { screen: 'Channel', @@ -415,6 +415,9 @@ const launchChannel = () => { statusBarHideWithNavBar: false, screenBackgroundColor: 'transparent', }, + passProps: { + skipMetrics, + }, }, appStyle: { orientation: 'auto', @@ -513,14 +516,19 @@ const fromPushNotification = Platform.OS === 'android' && Initialization.replyFr if (startedSharedExtension || fromPushNotification) { // Hold on launching Entry screen app.setAppStarted(true); - - // Listen for when the user opens the app - new NativeEventsReceiver().appLaunched(() => { - app.setAppStarted(false); - launchEntry(); - }); } if (!app.appStarted) { launchEntry(); } + +new NativeEventsReceiver().appLaunched(() => { + if (startedSharedExtension || fromPushNotification) { + app.setAppStarted(false); + launchEntry(); + } else if (app.token && app.url) { + launchChannel(true); + } else { + launchSelectServer(); + } +}); diff --git a/app/mattermost_managed/mattermost-managed.android.js b/app/mattermost_managed/mattermost-managed.android.js index da192cb58..3cdd00095 100644 --- a/app/mattermost_managed/mattermost-managed.android.js +++ b/app/mattermost_managed/mattermost-managed.android.js @@ -36,6 +36,7 @@ export default { }, authenticate: LocalAuth.auth, blurAppScreen: MattermostManaged.blurAppScreen, + isRunningInSplitView: MattermostManaged.isRunningInSplitView, getConfig: async () => { try { cachedConfig = await MattermostManaged.getConfig(); diff --git a/app/mattermost_managed/mattermost-managed.ios.js b/app/mattermost_managed/mattermost-managed.ios.js index 7b6d81525..9c2415da2 100644 --- a/app/mattermost_managed/mattermost-managed.ios.js +++ b/app/mattermost_managed/mattermost-managed.ios.js @@ -52,6 +52,7 @@ export default { return cachedConfig; }, hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets, + isRunningInSplitView: MattermostManaged.isRunningInSplitView, isDeviceSecure: async () => { try { return await LocalAuth.isDeviceSecure(); diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index da40eacda..7dd030714 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -48,6 +48,7 @@ export default class ChannelBase extends PureComponent { theme: PropTypes.object.isRequired, showTermsOfService: PropTypes.bool, disableTermsModal: PropTypes.bool, + skipMetrics: PropTypes.bool, }; static contextTypes = { @@ -88,7 +89,7 @@ export default class ChannelBase extends PureComponent { } componentDidMount() { - if (tracker.initialLoad) { + if (tracker.initialLoad && !this.props.skipMetrics) { this.props.actions.recordLoadTime('Start time', 'initialLoad'); } @@ -98,7 +99,9 @@ export default class ChannelBase extends PureComponent { EventEmitter.emit('renderDrawer'); - telemetry.end(['start:channel_screen']); + if (!this.props.skipMetrics) { + telemetry.end(['start:channel_screen']); + } } componentWillReceiveProps(nextProps) { diff --git a/app/screens/channel/channel_nav_bar/channel_nav_bar.js b/app/screens/channel/channel_nav_bar/channel_nav_bar.js index d84a2cd6f..6e0a84f40 100644 --- a/app/screens/channel/channel_nav_bar/channel_nav_bar.js +++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js @@ -3,9 +3,10 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {Platform, View} from 'react-native'; +import {Dimensions, Platform, View} from 'react-native'; import {DeviceTypes, ViewTypes} from 'app/constants'; +import mattermostManaged from 'app/mattermost_managed'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; import ChannelDrawerButton from './channel_drawer_button'; @@ -31,6 +32,30 @@ export default class ChannelNavBar extends PureComponent { theme: PropTypes.object.isRequired, }; + state = { + isSplitView: false, + }; + + componentDidMount() { + this.mounted = true; + this.handleDimensions(); + Dimensions.addEventListener('change', this.handleDimensions); + } + + componentWillUnmount() { + this.mounted = false; + Dimensions.removeEventListener('change', this.handleDimensions); + } + + handleDimensions = () => { + if (DeviceTypes.IS_TABLET && this.mounted) { + mattermostManaged.isRunningInSplitView().then((result) => { + const isSplitView = Boolean(result.isSplitView); + this.setState({isSplitView}); + }); + } + }; + render() { const {isLandscape, navigator, onPress, theme} = this.props; const {openChannelDrawer, openSettingsDrawer} = this.props; @@ -60,7 +85,7 @@ export default class ChannelNavBar extends PureComponent { } let drawerButtonVisible = false; - if (!DeviceTypes.IS_TABLET) { + if (!DeviceTypes.IS_TABLET || this.state.isSplitView) { drawerButtonVisible = true; } diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 33de3701c..78b742f59 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; }; 7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; }; + 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */; }; 7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; }; 7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; }; 7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; }; @@ -819,6 +820,8 @@ 7F581D34221ED5C60099E66B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; + 7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Mattermost+RCTUITextView.h"; path = "Mattermost/Mattermost+RCTUITextView.h"; sourceTree = ""; }; + 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Mattermost+RCTUITextView.m"; path = "Mattermost/Mattermost+RCTUITextView.m"; sourceTree = ""; }; 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = ""; }; 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = ""; }; 7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = ""; }; @@ -1112,6 +1115,8 @@ 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */, 7F151D40221B069200FAD8F3 /* 0155-keys.png */, 7F292AA51E8ABB1100A450A3 /* splash.png */, + 7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */, + 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */, ); name = Mattermost; sourceTree = ""; @@ -2618,6 +2623,7 @@ 7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */, 7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */, 7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */, + 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Mattermost/Mattermost+RCTUITextView.h b/ios/Mattermost/Mattermost+RCTUITextView.h new file mode 100644 index 000000000..67057d05e --- /dev/null +++ b/ios/Mattermost/Mattermost+RCTUITextView.h @@ -0,0 +1,17 @@ +// +// Mattermost+RCTUITextView.h +// Mattermost +// +// Created by Elias Nahum on 6/18/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Mattermost_RCTUITextView : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Mattermost/Mattermost+RCTUITextView.m b/ios/Mattermost/Mattermost+RCTUITextView.m new file mode 100644 index 000000000..505798bbc --- /dev/null +++ b/ios/Mattermost/Mattermost+RCTUITextView.m @@ -0,0 +1,36 @@ +// +// Mattermost+RCTUITextView.m +// Mattermost +// +// Created by Elias Nahum on 6/18/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +#import "Mattermost+RCTUITextView.h" +#import "RCTUITextView.h" + +@implementation Mattermost_RCTUITextView + +@end + +@implementation RCTUITextView (DisableCopyPaste) + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"com.apple.configuration.managed"]; + if(response) { + NSString *copyPasteProtection = response[@"copyAndPasteProtection"]; + BOOL prevent = action == @selector(paste:) || + action == @selector(copy:) || + action == @selector(cut:) || + action == @selector(_share:); + + if ([copyPasteProtection isEqual: @"true"] && prevent) { + return NO; + } + } + + return [super canPerformAction:action withSender:sender]; +} + +@end diff --git a/ios/Mattermost/MattermostManaged.m b/ios/Mattermost/MattermostManaged.m index 4000f5902..b2f5c5ce1 100644 --- a/ios/Mattermost/MattermostManaged.m +++ b/ios/Mattermost/MattermostManaged.m @@ -6,7 +6,6 @@ // See License.txt for license information. // -#import "RCTUITextView.h" #import "MattermostManaged.h" #import @@ -152,31 +151,19 @@ RCT_EXPORT_METHOD(getConfig:(RCTPromiseResolveBlock)resolve } } +RCT_EXPORT_METHOD(isRunningInSplitView:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + BOOL isRunningInFullScreen = CGRectEqualToRect( + [UIApplication sharedApplication].delegate.window.frame, + [UIApplication sharedApplication].delegate.window.screen.bounds); + resolve(@{ + @"isSplitView": @(!isRunningInFullScreen) + }); +} + RCT_EXPORT_METHOD(quitApp) { exit(0); } @end - -@implementation RCTUITextView (DisableCopyPaste) - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey]; - if(response) { - NSString *copyPasteProtection = response[@"copyAndPasteProtection"]; - BOOL prevent = action == @selector(paste:) || - action == @selector(copy:) || - action == @selector(cut:) || - action == @selector(_share:); - - if ([copyPasteProtection isEqual: @"true"] && prevent) { - return NO; - } - } - - return [super canPerformAction:action withSender:sender]; -} - -@end