diff --git a/app/components/autocomplete/autocomplete.js b/app/components/autocomplete/autocomplete.js
index d67b5b383..83e8f1a37 100644
--- a/app/components/autocomplete/autocomplete.js
+++ b/app/components/autocomplete/autocomplete.js
@@ -8,8 +8,8 @@ import {
Platform,
View,
} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
+import {DeviceTypes} from 'app/constants';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import AtMention from './at_mention';
@@ -92,7 +92,7 @@ export default class Autocomplete extends PureComponent {
} else {
// List is expanding downwards, likely from the search box
let offset = Platform.select({ios: 65, android: 75});
- if (DeviceInfo.getModel().includes('iPhone X')) {
+ if (DeviceTypes.IS_IPHONE_X) {
offset = 90;
}
diff --git a/app/components/client_upgrade_listener/client_upgrade_listener.js b/app/components/client_upgrade_listener/client_upgrade_listener.js
index fe05b18b1..88dcf1d1f 100644
--- a/app/components/client_upgrade_listener/client_upgrade_listener.js
+++ b/app/components/client_upgrade_listener/client_upgrade_listener.js
@@ -11,11 +11,10 @@ import {
View,
} from 'react-native';
import {intlShape} from 'react-intl';
-import DeviceInfo from 'react-native-device-info';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FormattedText from 'app/components/formatted_text';
-import {UpgradeTypes} from 'app/constants/view';
+import {DeviceTypes, UpgradeTypes} from 'app/constants';
import checkUpgradeType from 'app/utils/client_upgrade';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -47,8 +46,6 @@ export default class ClientUpgradeListener extends PureComponent {
constructor(props) {
super(props);
- this.isX = DeviceInfo.getModel().includes('iPhone X');
-
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
this.closeButton = source;
});
@@ -73,7 +70,7 @@ export default class ClientUpgradeListener extends PureComponent {
if (versionMismatch && (forceUpgrade || Date.now() - lastUpgradeCheck > UPDATE_TIMEOUT)) {
this.checkUpgrade(minVersion, latestVersion, nextProps.isLandscape);
} else if (this.props.isLandscape !== nextProps.isLandscape &&
- this.state.upgradeType !== UpgradeTypes.NO_UPGRADE && this.isX) {
+ this.state.upgradeType !== UpgradeTypes.NO_UPGRADE && DeviceTypes.IS_IPHONE_X) {
const newTop = nextProps.isLandscape ? 45 : 100;
this.setState({top: new Animated.Value(newTop)});
}
@@ -100,10 +97,10 @@ export default class ClientUpgradeListener extends PureComponent {
toggleUpgradeMessage = (show = true, isLandscape) => {
let toValue = -100;
if (show) {
- if (this.isX && isLandscape) {
+ if (DeviceTypes.IS_IPHONE_X && isLandscape) {
toValue = 45;
} else {
- toValue = this.isX ? 100 : 75;
+ toValue = DeviceTypes.IS_IPHONE_X ? 100 : 75;
}
}
Animated.timing(this.state.top, {
diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js
index 94aa3b407..c3565b04c 100644
--- a/app/components/emoji_picker/emoji_picker.js
+++ b/app/components/emoji_picker/emoji_picker.js
@@ -14,7 +14,6 @@ import {
TouchableOpacity,
View,
} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
import sectionListGetItemLayout from 'react-native-section-list-get-item-layout';
@@ -24,6 +23,7 @@ import Emoji from 'app/components/emoji';
import FormattedText from 'app/components/formatted_text';
import SafeAreaView from 'app/components/safe_area_view';
import SearchBar from 'app/components/search_bar';
+import {DeviceTypes} from 'app/constants';
import {emptyFunction} from 'app/utils/general';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
@@ -75,7 +75,6 @@ export default class EmojiPicker extends PureComponent {
const emojis = this.renderableEmojis(props.emojisBySection, props.deviceWidth);
const emojiSectionIndexByOffset = this.measureEmojiSections(emojis);
- this.isX = DeviceInfo.getModel().includes('iPhone X');
this.scrollToSectionTries = 0;
this.state = {
emojis,
@@ -436,7 +435,7 @@ export default class EmojiPicker extends PureComponent {
let keyboardOffset = 64;
if (Platform.OS === 'android') {
keyboardOffset = -200;
- } else if (this.isX) {
+ } else if (DeviceTypes.IS_IPHONE_X) {
keyboardOffset = isLandscape ? 35 : 107;
} else if (isLandscape) {
keyboardOffset = 52;
diff --git a/app/components/offline_indicator/offline_indicator.js b/app/components/offline_indicator/offline_indicator.js
index a7f42f137..27590438b 100644
--- a/app/components/offline_indicator/offline_indicator.js
+++ b/app/components/offline_indicator/offline_indicator.js
@@ -11,11 +11,10 @@ import {
TouchableOpacity,
View,
} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
import IonIcon from 'react-native-vector-icons/Ionicons';
import FormattedText from 'app/components/formatted_text';
-import {ViewTypes} from 'app/constants';
+import {DeviceTypes, ViewTypes} from 'app/constants';
import mattermostBucket from 'app/mattermost_bucket';
import checkNetwork from 'app/utils/network';
import {t} from 'app/utils/i18n';
@@ -55,7 +54,6 @@ export default class OfflineIndicator extends Component {
constructor(props) {
super(props);
- this.isX = DeviceInfo.getModel().includes('iPhone X');
const navBar = this.getNavBarHeight(props.isLandscape);
this.state = {
@@ -154,9 +152,9 @@ export default class OfflineIndicator extends Component {
return ANDROID_TOP_PORTRAIT;
}
- if (this.isX && isLandscape) {
+ if (DeviceTypes.IS_IPHONE_X && isLandscape) {
return IOS_TOP_LANDSCAPE;
- } else if (this.isX) {
+ } else if (DeviceTypes.IS_IPHONE_X) {
return IOSX_TOP_PORTRAIT;
} else if (isLandscape) {
return IOS_TOP_LANDSCAPE + STATUS_BAR_HEIGHT;
diff --git a/app/components/reactions/reactions.js b/app/components/reactions/reactions.js
index e0fd31f5d..602767870 100644
--- a/app/components/reactions/reactions.js
+++ b/app/components/reactions/reactions.js
@@ -9,9 +9,6 @@ import {
TouchableOpacity,
View,
} from 'react-native';
-import {intlShape} from 'react-intl';
-
-import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import addReactionIcon from 'assets/images/icons/reaction.png';
@@ -34,24 +31,12 @@ export default class Reactions extends PureComponent {
theme: PropTypes.object.isRequired,
canAddReaction: PropTypes.bool,
canRemoveReaction: PropTypes.bool.isRequired,
- }
+ };
static defaultProps = {
position: 'right',
};
- static contextTypes = {
- intl: intlShape,
- };
-
- constructor(props) {
- super(props);
-
- MaterialIcon.getImageSource('close', 20, props.theme.sidebarHeaderTextColor).then((source) => {
- this.closeButton = source;
- });
- }
-
componentDidMount() {
const {actions, postId} = this.props;
actions.getReactionsForPost(postId);
@@ -67,25 +52,16 @@ export default class Reactions extends PureComponent {
};
showReactionList = () => {
- const {navigator, postId, theme} = this.props;
- const {formatMessage} = this.context.intl;
+ const {navigator, postId} = this.props;
+
const options = {
screen: 'ReactionList',
- title: formatMessage({id: 'mobile.reaction_list.title', defaultMessage: 'Reactions'}),
- animationType: 'slide-up',
- animated: true,
+ animationType: 'none',
backButtonTitle: '',
navigatorStyle: {
- navBarTextColor: theme.sidebarHeaderTextColor,
- navBarBackgroundColor: theme.sidebarHeaderBg,
- navBarButtonColor: theme.sidebarHeaderTextColor,
- screenBackgroundColor: theme.centerChannelBg,
- },
- navigatorButtons: {
- leftButtons: [{
- id: 'close-reaction-list',
- icon: this.closeButton,
- }],
+ navBarHidden: true,
+ screenBackgroundColor: 'transparent',
+ modalPresentationStyle: 'overCurrentContext',
},
passProps: {
postId,
diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js
index 754be4d9f..05a4ccd1a 100644
--- a/app/components/safe_area_view/safe_area_view.ios.js
+++ b/app/components/safe_area_view/safe_area_view.ios.js
@@ -4,9 +4,10 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Dimensions, Keyboard, NativeModules, View} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
import SafeArea from 'react-native-safe-area';
+import {DeviceTypes} from 'app/constants';
+
const {StatusBarManager} = NativeModules;
export default class SafeAreaIos extends PureComponent {
@@ -31,8 +32,6 @@ export default class SafeAreaIos extends PureComponent {
constructor(props) {
super(props);
- this.isX = DeviceInfo.getModel().includes('iPhone X');
-
if (props.navigator) {
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
}
@@ -40,9 +39,9 @@ export default class SafeAreaIos extends PureComponent {
this.state = {
keyboard: false,
safeAreaInsets: {
- top: this.isX ? 44 : 20,
+ top: DeviceTypes.IS_IPHONE_X ? 44 : 20,
left: 0,
- bottom: this.isX ? 34 : 15,
+ bottom: DeviceTypes.IS_IPHONE_X ? 34 : 15,
right: 0,
},
statusBarHeight: 20,
@@ -87,7 +86,7 @@ export default class SafeAreaIos extends PureComponent {
getSafeAreaInsets = () => {
this.getStatusBarHeight();
- if (this.isX) {
+ if (DeviceTypes.IS_IPHONE_X) {
SafeArea.getSafeAreaInsetsForRootView().then((result) => {
const {safeAreaInsets} = result;
@@ -130,7 +129,7 @@ export default class SafeAreaIos extends PureComponent {
}
let top = safeAreaInsets.top;
- if (forceTop && this.isX && !hideTopBar) {
+ if (forceTop && DeviceTypes.IS_IPHONE_X && !hideTopBar) {
top = forceTop;
}
@@ -174,7 +173,7 @@ export default class SafeAreaIos extends PureComponent {
}
let offset = 0;
- if (keyboardOffset && this.isX) {
+ if (keyboardOffset && DeviceTypes.IS_IPHONE_X) {
offset = keyboardOffset;
}
diff --git a/app/components/slide_up_panel/index.js b/app/components/slide_up_panel/index.js
new file mode 100644
index 000000000..1bcce4cc7
--- /dev/null
+++ b/app/components/slide_up_panel/index.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {connect} from 'react-redux';
+
+import {getDimensions} from 'app/selectors/device';
+
+import SlideUpPanel from './slide_up_panel';
+
+function mapStateToProps(state, ownProps) {
+ const dimensions = getDimensions(state);
+ return {
+ containerHeight: ownProps.containerHeight || dimensions.deviceHeight,
+ };
+}
+
+export default connect(mapStateToProps, null, null, {withRef: true})(SlideUpPanel);
diff --git a/app/components/slide_up_panel/slide_up_panel.js b/app/components/slide_up_panel/slide_up_panel.js
new file mode 100644
index 000000000..efd9da28b
--- /dev/null
+++ b/app/components/slide_up_panel/slide_up_panel.js
@@ -0,0 +1,246 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {
+ Animated,
+ PanResponder,
+ Platform,
+ StyleSheet,
+ View,
+} from 'react-native';
+
+import {DeviceTypes} from 'app/constants';
+
+import SlideUpPanelIndicator from './slide_up_panel_indicator';
+
+const TOP_IOS_MARGIN = DeviceTypes.IS_IPHONE_X ? 84 : 64;
+const TOP_ANDROID_MARGIN = 44;
+const TOP_MARGIN = Platform.OS === 'ios' ? TOP_IOS_MARGIN : TOP_ANDROID_MARGIN;
+const BOTTOM_MARGIN = DeviceTypes.IS_IPHONE_X ? 24 : 0;
+const CONTAINER_MARGIN = TOP_MARGIN - 10;
+
+export default class SlideUpPanel extends PureComponent {
+ static propTypes = {
+ containerHeight: PropTypes.number,
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
+ headerHeight: PropTypes.number,
+ initialPosition: PropTypes.number,
+ marginFromTop: PropTypes.number,
+ onRequestClose: PropTypes.func,
+ };
+
+ static defaultProps = {
+ headerHeight: 0,
+ initialPosition: 0.5,
+ marginFromTop: TOP_MARGIN,
+ onRequestClose: () => true,
+ };
+
+ constructor(props) {
+ super(props);
+
+ const initialUsedSpace = Math.abs(props.initialPosition);
+ const initialPosition = ((props.containerHeight - (props.headerHeight + BOTTOM_MARGIN)) * (1 - initialUsedSpace));
+
+ this.mainPanGesture = PanResponder.create({
+ onMoveShouldSetPanResponder: (evt, gestureState) => {
+ const isGoingDown = gestureState.y0 < gestureState.dy;
+ return this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown);
+ },
+ onPanResponderMove: (evt, gestureState) => {
+ this.moveStart(gestureState);
+ },
+ onPanResponderRelease: (evt, gestureState) => {
+ this.moveFinished(gestureState);
+ },
+ });
+
+ this.secondaryPanGesture = PanResponder.create({
+ onMoveShouldSetPanResponder: (evt, gestureState) => {
+ const isGoingDown = gestureState.y0 < gestureState.dy;
+ return this.isAValidMovement(gestureState.dx, gestureState.dy, isGoingDown, true);
+ },
+ onPanResponderMove: (evt, gestureState) => {
+ this.moveStart(gestureState);
+ },
+ onPanResponderRelease: (evt, gestureState) => {
+ this.moveFinished(gestureState);
+ },
+ });
+
+ this.previousTop = initialPosition;
+ this.canDrag = true;
+
+ this.state = {
+ position: new Animated.Value(props.containerHeight),
+ initialPosition,
+ finalPosition: props.marginFromTop,
+ endPosition: 0,
+ };
+ }
+
+ componentDidMount() {
+ this.startAnimation(this.props.containerHeight, this.state.initialPosition, false, true);
+ }
+
+ handleTouchEnd = () => {
+ if (!this.isDragging) {
+ this.startAnimation(this.state.endPosition, this.props.containerHeight, false, true);
+ }
+ };
+
+ isAValidMovement = (distanceX, distanceY, isGoingDown, forceCheck = false) => {
+ const {endPosition, finalPosition} = this.state;
+
+ if (finalPosition !== endPosition || forceCheck || (isGoingDown && this.canDrag)) {
+ const moveTravelledFarEnough = Math.abs(distanceY) > Math.abs(distanceX) && Math.abs(distanceY) > 2;
+ return moveTravelledFarEnough;
+ }
+
+ return false;
+ };
+
+ moveStart = (gestureState) => {
+ if (this.viewRef && this.backdrop) {
+ const {endPosition} = this.state;
+ const position = endPosition - (gestureState.y0 - gestureState.moveY);
+ this.isDragging = true;
+
+ this.backdrop.setNativeProps({pointerEvents: 'none'});
+ this.updatePosition(position);
+ }
+ };
+
+ moveFinished = (gestureState) => {
+ if (this.viewRef) {
+ const isGoingDown = gestureState.y0 < gestureState.moveY;
+ let position = gestureState.moveY;
+ if (this.previousTop !== position) {
+ position = this.previousTop;
+ }
+
+ this.startAnimation(gestureState.y0, position, isGoingDown);
+ }
+ };
+
+ setBackdropRef = (ref) => {
+ this.backdrop = ref;
+ };
+
+ setDrag = (val) => {
+ this.canDrag = val;
+ };
+
+ setViewRef = (ref) => {
+ this.viewRef = ref;
+ };
+
+ startAnimation = (initialY, positionY, isGoingDown, initial = false) => {
+ const {containerHeight, onRequestClose} = this.props;
+ const {finalPosition, initialPosition} = this.state;
+ const position = new Animated.Value(initial ? initialY : positionY);
+ let endPosition = (!isGoingDown && !initial ? finalPosition : positionY);
+
+ position.removeAllListeners();
+ if (isGoingDown) {
+ if (positionY <= this.state.initialPosition) {
+ endPosition = initialPosition;
+ } else {
+ endPosition = containerHeight;
+ }
+ }
+
+ Animated.timing(position, {
+ toValue: endPosition,
+ duration: initial ? 200 : 100,
+ useNativeDriver: true,
+ }).start(() => {
+ if (this.viewRef && this.backdrop) {
+ this.setState({endPosition});
+ this.backdrop.setNativeProps({pointerEvents: 'box-only'});
+ this.isDragging = false;
+
+ if (endPosition === containerHeight) {
+ onRequestClose();
+ }
+ }
+ });
+
+ position.addListener((pos) => {
+ if (this.viewRef) {
+ this.updatePosition(pos.value);
+ }
+ });
+ };
+
+ updatePosition = (newPosition) => {
+ const {position} = this.state;
+ this.previousTop = newPosition;
+ position.setValue(newPosition);
+ };
+
+ render() {
+ const {children} = this.props;
+ const containerPosition = {
+ top: this.state.position,
+ };
+
+ return (
+
+
+
+
+
+ {children}
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ viewport: {
+ flex: 1,
+ },
+ container: {
+ flex: 1,
+ backgroundColor: 'white',
+ ...Platform.select({
+ android: {
+ borderTopRightRadius: 2,
+ borderTopLeftRadius: 2,
+ },
+ ios: {
+ borderTopRightRadius: 10,
+ borderTopLeftRadius: 10,
+ },
+ }),
+ },
+ backdrop: {
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
+ position: 'absolute',
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0,
+ },
+});
diff --git a/app/components/slide_up_panel/slide_up_panel_indicator.js b/app/components/slide_up_panel/slide_up_panel_indicator.js
new file mode 100644
index 000000000..53b3cb425
--- /dev/null
+++ b/app/components/slide_up_panel/slide_up_panel_indicator.js
@@ -0,0 +1,45 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React, {PureComponent} from 'react';
+import PropTypes from 'prop-types';
+import {Animated, Platform, StyleSheet, View} from 'react-native';
+
+export default class SlideUpPanelIndicator extends PureComponent {
+ static propTypes = {
+ containerPosition: PropTypes.object.isRequired,
+ panHandlers: PropTypes.object.isRequired,
+ };
+
+ render() {
+ const {panHandlers, containerPosition} = this.props;
+
+ if (Platform.OS === 'android') {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+ }
+}
+
+const styles = StyleSheet.create({
+ dragIndicatorContainer: {
+ marginVertical: 10,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ dragIndicator: {
+ backgroundColor: 'white',
+ height: 5,
+ width: 62.5,
+ opacity: 0.9,
+ borderRadius: 25,
+ },
+});
diff --git a/app/components/start/empty_toolbar.js b/app/components/start/empty_toolbar.js
index a825ba980..8db278b75 100644
--- a/app/components/start/empty_toolbar.js
+++ b/app/components/start/empty_toolbar.js
@@ -4,9 +4,8 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Platform, View} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
-import {ViewTypes} from 'app/constants';
+import {DeviceTypes, ViewTypes} from 'app/constants';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import Icon from 'react-native-vector-icons/Ionicons';
@@ -25,12 +24,6 @@ export default class EmptyToolbar extends PureComponent {
theme: PropTypes.object.isRequired,
};
- constructor(props) {
- super(props);
-
- this.isX = DeviceInfo.getModel().includes('iPhone X');
- }
-
render() {
const {isLandscape, theme} = this.props;
@@ -51,7 +44,7 @@ export default class EmptyToolbar extends PureComponent {
height = IOS_TOP_LANDSCAPE;
}
- if (this.isX && isLandscape) {
+ if (DeviceTypes.IS_IPHONE_X && isLandscape) {
padding.paddingHorizontal = 10;
}
break;
diff --git a/app/constants/device.js b/app/constants/device.js
index 97223e3ce..43d1986b6 100644
--- a/app/constants/device.js
+++ b/app/constants/device.js
@@ -1,8 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import keyMirror from 'mattermost-redux/utils/key_mirror';
+import DeviceInfo from 'react-native-device-info';
import RNFetchBlobFS from 'rn-fetch-blob/fs';
+import keyMirror from 'mattermost-redux/utils/key_mirror';
const deviceTypes = keyMirror({
CONNECTION_CHANGED: null,
@@ -16,5 +17,6 @@ export default {
...deviceTypes,
DOCUMENTS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Documents`,
IMAGES_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Images`,
+ IS_IPHONE_X: DeviceInfo.getModel().includes('iPhone X'),
VIDEOS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Videos`,
};
diff --git a/app/constants/index.js b/app/constants/index.js
index 28c0c18bd..575e5f161 100644
--- a/app/constants/index.js
+++ b/app/constants/index.js
@@ -5,12 +5,13 @@ import DeviceTypes from './device';
import ListTypes from './list';
import NavigationTypes from './navigation';
import PermissionTypes from './permissions';
-import ViewTypes from './view';
+import ViewTypes, {UpgradeTypes} from './view';
export {
DeviceTypes,
ListTypes,
NavigationTypes,
PermissionTypes,
+ UpgradeTypes,
ViewTypes,
};
diff --git a/app/screens/channel/channel.js b/app/screens/channel/channel.js
index 32989b8a0..53ac32ce2 100644
--- a/app/screens/channel/channel.js
+++ b/app/screens/channel/channel.js
@@ -12,7 +12,6 @@ import {
StyleSheet,
View,
} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
@@ -24,7 +23,7 @@ import KeyboardLayout from 'app/components/layout/keyboard_layout';
import OfflineIndicator from 'app/components/offline_indicator';
import SafeAreaView from 'app/components/safe_area_view';
import StatusBar from 'app/components/status_bar';
-import {ViewTypes} from 'app/constants';
+import {DeviceTypes, ViewTypes} from 'app/constants';
import mattermostBucket from 'app/mattermost_bucket';
import {preventDoubleTap} from 'app/utils/tap';
import PostTextbox from 'app/components/post_textbox';
@@ -75,8 +74,6 @@ export default class Channel extends PureComponent {
constructor(props) {
super(props);
- this.isX = DeviceInfo.getModel().includes('iPhone X');
-
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
}
@@ -170,8 +167,8 @@ export default class Channel extends PureComponent {
if (isLandscape) {
top = IOS_TOP_LANDSCAPE;
} else {
- height = this.isX ? (height - IOSX_TOP_PORTRAIT) : (height - IOS_TOP_PORTRAIT);
- top = this.isX ? IOSX_TOP_PORTRAIT : IOS_TOP_PORTRAIT;
+ height = DeviceTypes.IS_IPHONE_X ? (height - IOSX_TOP_PORTRAIT) : (height - IOS_TOP_PORTRAIT);
+ top = DeviceTypes.IS_IPHONE_X ? IOSX_TOP_PORTRAIT : IOS_TOP_PORTRAIT;
}
break;
}
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 5202e5ce1..d3fb51ec0 100644
--- a/app/screens/channel/channel_nav_bar/channel_nav_bar.js
+++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js
@@ -4,9 +4,8 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Platform, View} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
-import {ViewTypes} from 'app/constants';
+import {DeviceTypes, ViewTypes} from 'app/constants';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import ChannelDrawerButton from './channel_drawer_button';
@@ -32,12 +31,6 @@ export default class ChannelNavBar extends PureComponent {
theme: PropTypes.object.isRequired,
};
- constructor(props) {
- super(props);
-
- this.isX = DeviceInfo.getModel().includes('iPhone X');
- }
-
render() {
const {isLandscape, navigator, onPress, theme} = this.props;
const {openChannelDrawer, openSettingsDrawer} = this.props;
@@ -58,7 +51,7 @@ export default class ChannelNavBar extends PureComponent {
height = IOS_TOP_LANDSCAPE;
}
- if (this.isX && isLandscape) {
+ if (DeviceTypes.IS_IPHONE_X && isLandscape) {
padding.paddingHorizontal = 10;
}
break;
diff --git a/app/screens/client_upgrade/client_upgrade.js b/app/screens/client_upgrade/client_upgrade.js
index 3990caff1..3a8daea98 100644
--- a/app/screens/client_upgrade/client_upgrade.js
+++ b/app/screens/client_upgrade/client_upgrade.js
@@ -15,7 +15,7 @@ import {intlShape} from 'react-intl';
import FormattedText from 'app/components/formatted_text';
import StatusBar from 'app/components/status_bar';
-import {UpgradeTypes} from 'app/constants/view';
+import {UpgradeTypes} from 'app/constants';
import logo from 'assets/images/logo.png';
import checkUpgradeType from 'app/utils/client_upgrade';
import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
diff --git a/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap b/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap
index 14689ca7b..ef8ce8798 100644
--- a/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap
+++ b/app/screens/reaction_list/__snapshots__/reaction_header.test.js.snap
@@ -4,8 +4,10 @@ exports[`ReactionHeader should match snapshot 1`] = `
-
+ >
+
+
+
+
+
+
`;
diff --git a/app/screens/reaction_list/__snapshots__/reaction_row.test.js.snap b/app/screens/reaction_list/__snapshots__/reaction_row.test.js.snap
index 118578377..c3947b514 100644
--- a/app/screens/reaction_list/__snapshots__/reaction_row.test.js.snap
+++ b/app/screens/reaction_list/__snapshots__/reaction_row.test.js.snap
@@ -5,7 +5,6 @@ exports[`ReactionRow should match snapshot, renderContent 1`] = `
style={
Object {
"alignItems": "center",
- "backgroundColor": "#ffffff",
"flexDirection": "row",
"height": 44,
"justifyContent": "flex-start",
@@ -53,7 +52,6 @@ exports[`ReactionRow should match snapshot, renderContent 1`] = `
{
this.props.onSelectReaction(emoji);
@@ -37,12 +37,9 @@ export default class ReactionHeader extends PureComponent {
theme={theme}
/>
));
- }
+ };
render() {
- const {theme} = this.props;
- const styles = getStyleSheet(theme);
-
return (
{
- return {
- container: {
- backgroundColor: theme.centerChannelBg,
- height: 37,
- paddingHorizontal: 0,
- },
- };
+const styles = StyleSheet.create({
+ container: {
+ backgroundColor: '#FFFFFF',
+ height: 36.5,
+ paddingHorizontal: 0,
+ ...Platform.select({
+ android: {
+ borderTopRightRadius: 2,
+ borderTopLeftRadius: 2,
+ },
+ ios: {
+ borderTopRightRadius: 10,
+ borderTopLeftRadius: 10,
+ },
+ }),
+ },
});
diff --git a/app/screens/reaction_list/reaction_header_item.js b/app/screens/reaction_list/reaction_header_item.js
index 887d4a745..770bb855f 100644
--- a/app/screens/reaction_list/reaction_header_item.js
+++ b/app/screens/reaction_list/reaction_header_item.js
@@ -22,12 +22,12 @@ export default class ReactionHeaderItem extends PureComponent {
highlight: PropTypes.bool.isRequired,
onPress: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
- }
+ };
handleOnPress = () => {
const {emojiName, highlight, onPress} = this.props;
onPress(emojiName, highlight);
- }
+ };
renderContent = () => {
const {count, emojiName, theme} = this.props;
@@ -56,7 +56,7 @@ export default class ReactionHeaderItem extends PureComponent {
{count}
);
- }
+ };
render() {
const {emojiName, highlight, theme} = this.props;
@@ -79,16 +79,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
marginLeft: 7,
},
text: {
- color: theme.linkColor,
+ color: theme.buttonBg,
marginLeft: 4,
fontSize: 16,
},
highlight: {
- borderColor: changeOpacity(theme.linkColor, 1),
+ borderColor: theme.buttonBg,
borderBottomWidth: 2,
},
regular: {
- borderColor: theme.centerChannelBg,
+ borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderBottomWidth: 2,
},
reaction: {
diff --git a/app/screens/reaction_list/reaction_list.js b/app/screens/reaction_list/reaction_list.js
index 936d3bfdc..64ead7543 100644
--- a/app/screens/reaction_list/reaction_list.js
+++ b/app/screens/reaction_list/reaction_list.js
@@ -3,10 +3,10 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
-import {View} from 'react-native';
+import {ScrollView, View} from 'react-native';
import {intlShape} from 'react-intl';
-import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
+import SlideUpPanel from 'app/components/slide_up_panel';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {
generateUserProfilesById,
@@ -44,7 +44,9 @@ export default class ReactionList extends PureComponent {
const reactionsByName = getReactionsByName(reactions);
+ this.contentOffsetY = -1;
this.state = {
+ canDrag: true,
allUserIds: getUniqueUserIds(reactions),
reactions,
reactionsByName,
@@ -98,13 +100,17 @@ export default class ReactionList extends PureComponent {
onNavigatorEvent = (event) => {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'close-reaction-list') {
- this.props.navigator.dismissModal({
- animationType: 'slide-down',
- });
+ this.close();
}
}
};
+ close = () => {
+ this.props.navigator.dismissModal({
+ animationType: 'none',
+ });
+ };
+
getMissingProfiles = () => {
const {allUserIds, userProfiles, userProfilesById} = this.state;
if (userProfiles.length !== allUserIds.length) {
@@ -116,13 +122,37 @@ export default class ReactionList extends PureComponent {
}
}
- scrollViewRef = (ref) => {
- this.scrollView = ref;
- };
-
handleOnSelectReaction = (emoji) => {
this.setState({selected: emoji});
- }
+ const slide = this.slideUpPanel?.getWrappedInstance();
+
+ if (slide) {
+ slide.setDrag(true);
+ }
+
+ if (this.scrollView) {
+ this.scrollView.scrollTo({x: 0, y: 0, animated: false});
+ }
+ };
+
+ handleScroll = (e) => {
+ const pageOffsetY = e.nativeEvent.contentOffset.y;
+ const canDrag = pageOffsetY <= 0;
+ const slide = this.slideUpPanel?.getWrappedInstance();
+
+ this.contentOffsetY = pageOffsetY;
+ if (slide) {
+ slide.setDrag(canDrag);
+ }
+ };
+
+ refSlideUpPanel = (r) => {
+ this.slideUpPanel = r;
+ };
+
+ refScrollView = (ref) => {
+ this.scrollView = ref;
+ };
renderReactionRows = () => {
const {
@@ -154,7 +184,7 @@ export default class ReactionList extends PureComponent {
));
- }
+ };
render() {
const {
@@ -168,20 +198,30 @@ export default class ReactionList extends PureComponent {
return (
-
-
-
-
- {this.renderReactionRows()}
-
+
+
+
+
+
+ {this.renderReactionRows()}
+
+
+
);
}
@@ -190,11 +230,10 @@ export default class ReactionList extends PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
flex: {
- backgroundColor: theme.centerChannelBg,
flex: 1,
},
headerContainer: {
- height: 38,
+ height: 37.5,
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
borderBottomWidth: 1,
},
diff --git a/app/screens/reaction_list/reaction_list.test.js b/app/screens/reaction_list/reaction_list.test.js
index a8db6e20b..695c713ab 100644
--- a/app/screens/reaction_list/reaction_list.test.js
+++ b/app/screens/reaction_list/reaction_list.test.js
@@ -2,10 +2,11 @@
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
-import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import Preferences from 'mattermost-redux/constants/preferences';
+import SlideUpPanel from 'app/components/slide_up_panel';
+
import ReactionList from './reaction_list';
jest.mock('react-intl');
@@ -30,7 +31,7 @@ describe('ReactionList', () => {
);
expect(wrapper.getElement()).toMatchSnapshot();
- expect(wrapper.find(KeyboardAwareScrollView).exists()).toEqual(true);
+ expect(wrapper.find(SlideUpPanel).exists()).toEqual(true);
});
test('should match snapshot, renderReactionRows', () => {
diff --git a/app/screens/reaction_list/reaction_row.js b/app/screens/reaction_list/reaction_row.js
index f8c238947..da33b73ff 100644
--- a/app/screens/reaction_list/reaction_row.js
+++ b/app/screens/reaction_list/reaction_row.js
@@ -7,6 +7,7 @@ import {intlShape} from 'react-intl';
import {
Text,
TouchableOpacity,
+ StyleSheet,
View,
} from 'react-native';
@@ -14,7 +15,7 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils';
import ProfilePicture from 'app/components/profile_picture';
import {preventDoubleTap} from 'app/utils/tap';
-import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
+import {changeOpacity} from 'app/utils/theme';
import Emoji from 'app/components/emoji';
@@ -29,7 +30,7 @@ export default class ReactionRow extends React.PureComponent {
static defaultProps = {
user: {},
- }
+ };
static contextTypes = {
intl: intlShape,
@@ -62,7 +63,6 @@ export default class ReactionRow extends React.PureComponent {
const {
emojiName,
teammateNameDisplay,
- theme,
user,
} = this.props;
@@ -71,7 +71,6 @@ export default class ReactionRow extends React.PureComponent {
}
const {id, username} = user;
- const style = getStyleFromTheme(theme);
const usernameDisplay = '@' + username;
return (
@@ -114,40 +113,36 @@ export default class ReactionRow extends React.PureComponent {
}
}
-const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
- return {
- container: {
- backgroundColor: theme.centerChannelBg,
- flexDirection: 'row',
- justifyContent: 'flex-start',
- height: 44,
- width: '100%',
- alignItems: 'center',
- },
- profileContainer: {
- alignItems: 'center',
- width: '13%',
- },
- profile: {
- paddingTop: 3,
- },
- textContainer: {
- width: '74%',
- flexDirection: 'row',
- },
- username: {
- fontSize: 14,
- color: theme.centerChannelColor,
- paddingRight: 5,
- },
- displayName: {
- fontSize: 14,
- color: changeOpacity(theme.centerChannelColor, 0.5),
- },
- emoji: {
- alignItems: 'center',
- width: '13%',
- justifyContent: 'center',
- },
- };
+const style = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'flex-start',
+ height: 44,
+ width: '100%',
+ alignItems: 'center',
+ },
+ profileContainer: {
+ alignItems: 'center',
+ width: '13%',
+ },
+ profile: {
+ paddingTop: 3,
+ },
+ textContainer: {
+ width: '74%',
+ flexDirection: 'row',
+ },
+ username: {
+ fontSize: 14,
+ paddingRight: 5,
+ },
+ displayName: {
+ fontSize: 14,
+ color: changeOpacity('#000', 0.5),
+ },
+ emoji: {
+ alignItems: 'center',
+ width: '13%',
+ justifyContent: 'center',
+ },
});
diff --git a/app/screens/search/search.js b/app/screens/search/search.js
index e8e5b92bb..145252f33 100644
--- a/app/screens/search/search.js
+++ b/app/screens/search/search.js
@@ -13,7 +13,6 @@ import {
TouchableOpacity,
View,
} from 'react-native';
-import DeviceInfo from 'react-native-device-info';
import IonIcon from 'react-native-vector-icons/Ionicons';
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
@@ -32,7 +31,7 @@ import PostSeparator from 'app/components/post_separator';
import SafeAreaView from 'app/components/safe_area_view';
import SearchBar from 'app/components/search_bar';
import StatusBar from 'app/components/status_bar';
-import {ListTypes} from 'app/constants';
+import {DeviceTypes, ListTypes} from 'app/constants';
import mattermostManaged from 'app/mattermost_managed';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -91,7 +90,7 @@ export default class Search extends PureComponent {
super(props);
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
- this.isX = DeviceInfo.getModel().includes('iPhone X');
+
this.contentOffsetY = 0;
this.state = {
channelName: '',
@@ -736,7 +735,7 @@ export default class Search extends PureComponent {
return (
diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js
index ebee7407a..3a29af689 100644
--- a/app/screens/select_server/select_server.js
+++ b/app/screens/select_server/select_server.js
@@ -25,7 +25,7 @@ import {Client4} from 'mattermost-redux/client';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
-import {UpgradeTypes} from 'app/constants/view';
+import {UpgradeTypes} from 'app/constants';
import fetchConfig from 'app/fetch_preconfig';
import mattermostBucket from 'app/mattermost_bucket';
import PushNotifications from 'app/push_notifications';
diff --git a/app/store/middleware.test.js b/app/store/middleware.test.js
index b4d4de8cf..eec073285 100644
--- a/app/store/middleware.test.js
+++ b/app/store/middleware.test.js
@@ -8,13 +8,6 @@ import assert from 'assert';
import {ViewTypes} from 'app/constants';
import {messageRetention} from 'app/store/middleware';
-jest.mock('react-native-device-info', () => {
- return {
- getVersion: () => '0.0.0',
- getBuildNumber: () => '0',
- };
-});
-
describe('store/middleware', () => {
describe('messageRetention', () => {
describe('should chain the same incoming action type', () => {
diff --git a/app/utils/client_upgrade.js b/app/utils/client_upgrade.js
index 31208591b..8a3ad0183 100644
--- a/app/utils/client_upgrade.js
+++ b/app/utils/client_upgrade.js
@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import semver from 'semver';
-import {UpgradeTypes} from 'app/constants/view';
+import {UpgradeTypes} from 'app/constants';
import LocalConfig from 'assets/config';
diff --git a/test/setup.js b/test/setup.js
index c5543cfa2..b316378dd 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -17,6 +17,14 @@ jest.mock('NativeModules', () => {
});
jest.mock('NativeEventEmitter');
+jest.mock('react-native-device-info', () => {
+ return {
+ getVersion: () => '0.0.0',
+ getBuildNumber: () => '0',
+ getModel: () => 'iPhone X',
+ };
+});
+
let logs;
let warns;
let errors;