Slide up panel for reaction list (#2285)
* Slide up panel for reaction list * Feedback review * UI Feedback review * Feedback review * Improve slide speed and fix end position * Scroll to top when changing reaction view * dismiss modal without animation so backdrop does not scroll down * Smooth animation to show/hide * Change animation speed
This commit is contained in:
parent
7fa9326ec4
commit
9a5122c19c
29 changed files with 716 additions and 413 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
17
app/components/slide_up_panel/index.js
Normal file
17
app/components/slide_up_panel/index.js
Normal file
|
|
@ -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);
|
||||
246
app/components/slide_up_panel/slide_up_panel.js
Normal file
246
app/components/slide_up_panel/slide_up_panel.js
Normal file
|
|
@ -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 (
|
||||
<View style={styles.viewport}>
|
||||
<View
|
||||
ref={this.setBackdropRef}
|
||||
style={styles.backdrop}
|
||||
pointerEvents='box-only'
|
||||
onTouchEnd={this.handleTouchEnd}
|
||||
{...this.secondaryPanGesture.panHandlers}
|
||||
/>
|
||||
<SlideUpPanelIndicator
|
||||
containerPosition={containerPosition}
|
||||
panHandlers={this.secondaryPanGesture.panHandlers}
|
||||
/>
|
||||
<Animated.View
|
||||
ref={this.setViewRef}
|
||||
style={[containerPosition, styles.container]}
|
||||
{...this.mainPanGesture.panHandlers}
|
||||
>
|
||||
<View style={{maxHeight: (this.props.containerHeight - this.props.headerHeight - CONTAINER_MARGIN)}}>
|
||||
{children}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
45
app/components/slide_up_panel/slide_up_panel_indicator.js
Normal file
45
app/components/slide_up_panel/slide_up_panel_indicator.js
Normal file
|
|
@ -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 (
|
||||
<Animated.View
|
||||
style={[containerPosition, styles.dragIndicatorContainer]}
|
||||
{...panHandlers}
|
||||
>
|
||||
<View style={styles.dragIndicator}/>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dragIndicatorContainer: {
|
||||
marginVertical: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
dragIndicator: {
|
||||
backgroundColor: 'white',
|
||||
height: 5,
|
||||
width: 62.5,
|
||||
opacity: 0.9,
|
||||
borderRadius: 25,
|
||||
},
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ exports[`ReactionHeader should match snapshot 1`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 37,
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"borderTopLeftRadius": 10,
|
||||
"borderTopRightRadius": 10,
|
||||
"height": 36.5,
|
||||
"paddingHorizontal": 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ exports[`ReactionHeaderItem should match snapshot 1`] = `
|
|||
},
|
||||
Object {
|
||||
"borderBottomWidth": 2,
|
||||
"borderColor": "#ffffff",
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
},
|
||||
false,
|
||||
]
|
||||
|
|
@ -33,7 +33,7 @@ exports[`ReactionHeaderItem should match snapshot 1`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"color": "#166de0",
|
||||
"fontSize": 16,
|
||||
"marginLeft": 4,
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ exports[`ReactionHeaderItem should match snapshot, renderContent 1`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"color": "#166de0",
|
||||
"fontSize": 16,
|
||||
"marginLeft": 4,
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ exports[`ReactionHeaderItem should match snapshot, renderContent 2`] = `
|
|||
id="mobile.reaction_header.all_emojis"
|
||||
style={
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"color": "#166de0",
|
||||
"fontSize": 16,
|
||||
"marginLeft": 4,
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ exports[`ReactionHeaderItem should match snapshot, renderContent 2`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"color": "#166de0",
|
||||
"fontSize": 16,
|
||||
"marginLeft": 4,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,237 +4,237 @@ exports[`ReactionList should match snapshot 1`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"borderBottomWidth": 1,
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"height": 38,
|
||||
}
|
||||
}
|
||||
<Connect(SlideUpPanel)
|
||||
headerHeight={37.5}
|
||||
initialPosition={0.55}
|
||||
onRequestClose={[Function]}
|
||||
>
|
||||
<ReactionHeader
|
||||
onSelectReaction={[Function]}
|
||||
reactions={
|
||||
Array [
|
||||
Object {
|
||||
"count": 2,
|
||||
"name": "all_emojis",
|
||||
},
|
||||
Object {
|
||||
"count": 1,
|
||||
"name": "+1",
|
||||
"reactions": Array [
|
||||
Object {
|
||||
"emoji_name": "+1",
|
||||
"user_id": "user_id_2",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"count": 1,
|
||||
"name": "smile",
|
||||
"reactions": Array [
|
||||
Object {
|
||||
"emoji_name": "smile",
|
||||
"user_id": "user_id_1",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
selected="all_emojis"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<KeyboardAwareScrollView
|
||||
bounces={true}
|
||||
enableAutomaticScroll={true}
|
||||
enableOnAndroid={false}
|
||||
enableResetScrollToCoords={true}
|
||||
extraHeight={75}
|
||||
extraScrollHeight={0}
|
||||
innerRef={[Function]}
|
||||
keyboardOpeningTime={250}
|
||||
viewIsInsideTabBar={false}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="+1"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
teammateNameDisplay="username"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
user={
|
||||
Object {
|
||||
"id": "user_id_2",
|
||||
"username": "username_2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
"borderBottomWidth": 1,
|
||||
"borderColor": "rgba(61,60,64,0.2)",
|
||||
"height": 37.5,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="smile"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
>
|
||||
<ReactionHeader
|
||||
onSelectReaction={[Function]}
|
||||
reactions={
|
||||
Array [
|
||||
Object {
|
||||
"count": 2,
|
||||
"name": "all_emojis",
|
||||
},
|
||||
Object {
|
||||
"count": 1,
|
||||
"name": "+1",
|
||||
"reactions": Array [
|
||||
Object {
|
||||
"emoji_name": "+1",
|
||||
"user_id": "user_id_2",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"count": 1,
|
||||
"name": "smile",
|
||||
"reactions": Array [
|
||||
Object {
|
||||
"emoji_name": "smile",
|
||||
"user_id": "user_id_1",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
selected="all_emojis"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<ScrollView
|
||||
bounce={false}
|
||||
onScroll={[Function]}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="+1"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
teammateNameDisplay="username"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
user={
|
||||
Object {
|
||||
"id": "user_id_2",
|
||||
"username": "username_2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"height": 45,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
}
|
||||
teammateNameDisplay="username"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
user={
|
||||
Object {
|
||||
"id": "user_id_1",
|
||||
"username": "username_1",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
</KeyboardAwareScrollView>
|
||||
>
|
||||
<ReactionRow
|
||||
emojiName="smile"
|
||||
navigator={
|
||||
Object {
|
||||
"setOnNavigatorEvent": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"isThrow": false,
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
teammateNameDisplay="username"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
user={
|
||||
Object {
|
||||
"id": "user_id_1",
|
||||
"username": "username_1",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
</ScrollView>
|
||||
</React.Fragment>
|
||||
</Connect(SlideUpPanel)>
|
||||
</Component>
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 14,
|
||||
"paddingRight": 5,
|
||||
}
|
||||
|
|
@ -67,7 +65,7 @@ exports[`ReactionRow should match snapshot, renderContent 1`] = `
|
|||
<Component
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"color": "rgba(0,0,0,0.5)",
|
||||
"fontSize": 14,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import ReactionHeaderItem from './reaction_header_item';
|
||||
|
||||
export default class ReactionHeader extends PureComponent {
|
||||
|
|
@ -18,7 +18,7 @@ export default class ReactionHeader extends PureComponent {
|
|||
onSelectReaction: PropTypes.func.isRequired,
|
||||
reactions: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
handleOnPress = (emoji) => {
|
||||
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 (
|
||||
<View style={styles.container}>
|
||||
<ScrollView
|
||||
|
|
@ -57,12 +54,20 @@ export default class ReactionHeader extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
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,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<Text style={styles.text}>{count}</Text>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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: {
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<View style={style.separator}/>
|
||||
</View>
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
|
@ -168,20 +198,30 @@ export default class ReactionList extends PureComponent {
|
|||
|
||||
return (
|
||||
<View style={style.flex}>
|
||||
<View style={style.headerContainer}>
|
||||
<ReactionHeader
|
||||
selected={selected}
|
||||
onSelectReaction={this.handleOnSelectReaction}
|
||||
reactions={sortedReactionsForHeader}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<KeyboardAwareScrollView
|
||||
bounces={true}
|
||||
innerRef={this.scrollViewRef}
|
||||
<SlideUpPanel
|
||||
ref={this.refSlideUpPanel}
|
||||
onRequestClose={this.close}
|
||||
initialPosition={0.55}
|
||||
headerHeight={37.5}
|
||||
>
|
||||
{this.renderReactionRows()}
|
||||
</KeyboardAwareScrollView>
|
||||
<React.Fragment>
|
||||
<View style={style.headerContainer}>
|
||||
<ReactionHeader
|
||||
selected={selected}
|
||||
onSelectReaction={this.handleOnSelectReaction}
|
||||
reactions={sortedReactionsForHeader}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<ScrollView
|
||||
ref={this.refScrollView}
|
||||
bounce={false}
|
||||
onScroll={this.handleScroll}
|
||||
>
|
||||
{this.renderReactionRows()}
|
||||
</ScrollView>
|
||||
</React.Fragment>
|
||||
</SlideUpPanel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<SafeAreaView
|
||||
excludeHeader={isLandscape && this.isX}
|
||||
excludeHeader={isLandscape && DeviceTypes.IS_IPHONE_X}
|
||||
forceTop={44}
|
||||
>
|
||||
<KeyboardLayout>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue