MM-30164 fix safe area insets (#4979)
* MM-30164 fix safe area insets * Fix unit test setup mock for react-native-device-info * Add insets for edit profile screen * Fix about screen * Fix theme screen * Lock phone screen to portrait * fix unit tests * Fix autocomplete layout
This commit is contained in:
parent
05beb6c64b
commit
dcaaaee44c
316 changed files with 3800 additions and 5091 deletions
|
|
@ -9,9 +9,15 @@ import {Preferences} from '@mm-redux/constants';
|
|||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import EventEmmiter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import {DeviceTypes, NavigationTypes} from '@constants';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import Store from '@store/store';
|
||||
import {NavigationTypes} from '@constants';
|
||||
|
||||
Navigation.setDefaultOptions({
|
||||
layout: {
|
||||
orientation: [DeviceTypes.IS_TABLET ? undefined : 'portrait'],
|
||||
},
|
||||
});
|
||||
|
||||
function getThemeFromState() {
|
||||
const state = Store.redux?.getState() || {};
|
||||
|
|
|
|||
|
|
@ -1,121 +1,133 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AnnouncementBanner should match snapshot 1`] = `
|
||||
<ForwardRef(AnimatedComponentWrapper)
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"overflow": "hidden",
|
||||
"paddingHorizontal": 10,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
<Unknown
|
||||
bannerColor="#ddd"
|
||||
bannerDismissed={false}
|
||||
bannerEnabled={true}
|
||||
bannerText="Banner Text"
|
||||
bannerTextColor="#fff"
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ddd",
|
||||
"height": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
},
|
||||
null,
|
||||
]
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"marginRight": 5,
|
||||
},
|
||||
Object {
|
||||
"color": "#fff",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>
|
||||
<CompassIcon
|
||||
color="#fff"
|
||||
name="info-outline"
|
||||
size={16}
|
||||
/>
|
||||
</ForwardRef>
|
||||
</ForwardRef(AnimatedComponentWrapper)>
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`AnnouncementBanner should match snapshot 2`] = `
|
||||
<ForwardRef(AnimatedComponentWrapper)
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"overflow": "hidden",
|
||||
"paddingHorizontal": 10,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
<Unknown
|
||||
bannerColor="#ddd"
|
||||
bannerDismissed={false}
|
||||
bannerEnabled={false}
|
||||
bannerText="Banner Text"
|
||||
bannerTextColor="#fff"
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ddd",
|
||||
"height": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
},
|
||||
null,
|
||||
]
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"fontSize": 14,
|
||||
"marginRight": 5,
|
||||
},
|
||||
Object {
|
||||
"color": "#fff",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>
|
||||
<CompassIcon
|
||||
color="#fff"
|
||||
name="info-outline"
|
||||
size={16}
|
||||
/>
|
||||
</ForwardRef>
|
||||
</ForwardRef(AnimatedComponentWrapper)>
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"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",
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,62 +1,33 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Animated,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {injectIntl} from 'react-intl';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import RemoveMarkdown from '@components/remove_markdown';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import {ViewTypes} from '@constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class AnnouncementBanner extends PureComponent {
|
||||
static propTypes = {
|
||||
bannerColor: PropTypes.string,
|
||||
bannerDismissed: PropTypes.bool,
|
||||
bannerEnabled: PropTypes.bool,
|
||||
bannerText: PropTypes.string,
|
||||
bannerTextColor: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
state = {
|
||||
bannerHeight: new Animated.Value(0),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const {bannerDismissed, bannerEnabled, bannerText} = this.props;
|
||||
const showBanner = bannerEnabled && !bannerDismissed && Boolean(bannerText);
|
||||
this.toggleBanner(showBanner);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.bannerText !== prevProps.bannerText ||
|
||||
this.props.bannerEnabled !== prevProps.bannerEnabled ||
|
||||
this.props.bannerDismissed !== prevProps.bannerDismissed
|
||||
) {
|
||||
const showBanner = this.props.bannerEnabled && !this.props.bannerDismissed && Boolean(this.props.bannerText);
|
||||
this.toggleBanner(showBanner);
|
||||
}
|
||||
}
|
||||
|
||||
handlePress = () => {
|
||||
const {intl} = this.context;
|
||||
const AnnouncementBanner = injectIntl((props) => {
|
||||
const {bannerColor, bannerDismissed, bannerEnabled, bannerText, bannerTextColor, intl} = props;
|
||||
const insets = useSafeAreaInsets();
|
||||
const translateY = useRef(new Animated.Value(0)).current;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [navHeight, setNavHeight] = useState(0);
|
||||
|
||||
const handlePress = () => {
|
||||
const screen = 'ExpandedAnnouncementBanner';
|
||||
const title = intl.formatMessage({
|
||||
id: 'mobile.announcement_banner.title',
|
||||
|
|
@ -66,80 +37,88 @@ export default class AnnouncementBanner extends PureComponent {
|
|||
goToScreen(screen, title);
|
||||
};
|
||||
|
||||
toggleBanner = (show = true) => {
|
||||
const value = show ? 38 : 0;
|
||||
if (show && !this.state.visible) {
|
||||
this.setState({visible: show});
|
||||
}
|
||||
useEffect(() => {
|
||||
const handleNavbarHeight = (height) => {
|
||||
setNavHeight(height);
|
||||
};
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
Animated.timing(this.state.bannerHeight, {
|
||||
toValue: value,
|
||||
duration: 350,
|
||||
useNativeDriver: false,
|
||||
}).start(() => {
|
||||
if (this.state.visible !== show) {
|
||||
this.setState({visible: show});
|
||||
}
|
||||
});
|
||||
});
|
||||
EventEmitter.on(ViewTypes.CHANNEL_NAV_BAR_CHANGED, handleNavbarHeight);
|
||||
|
||||
return () => EventEmitter.off(ViewTypes.CHANNEL_NAV_BAR_CHANGED, handleNavbarHeight);
|
||||
}, [insets]);
|
||||
|
||||
useEffect(() => {
|
||||
const showBanner = bannerEnabled && !bannerDismissed && Boolean(bannerText);
|
||||
setVisible(showBanner);
|
||||
EventEmitter.emit(ViewTypes.INDICATOR_BAR_VISIBLE, showBanner);
|
||||
}, [bannerDismissed, bannerEnabled, bannerText]);
|
||||
|
||||
useEffect(() => {
|
||||
Animated.timing(translateY, {
|
||||
toValue: visible ? navHeight : insets.top,
|
||||
duration: 50,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}, [visible, navHeight]);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bannerStyle = {
|
||||
backgroundColor: bannerColor,
|
||||
height: ViewTypes.INDICATOR_BAR_HEIGHT,
|
||||
transform: [{translateY}],
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.visible) {
|
||||
return null;
|
||||
}
|
||||
const bannerTextStyle = {
|
||||
color: bannerTextColor,
|
||||
};
|
||||
|
||||
const {bannerHeight} = this.state;
|
||||
const {
|
||||
bannerColor,
|
||||
bannerText,
|
||||
bannerTextColor,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
const bannerStyle = {
|
||||
backgroundColor: bannerColor,
|
||||
height: bannerHeight,
|
||||
};
|
||||
|
||||
const bannerTextStyle = {
|
||||
color: bannerTextColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedView
|
||||
style={[style.bannerContainer, bannerStyle]}
|
||||
return (
|
||||
<AnimatedView
|
||||
style={[style.bannerContainer, bannerStyle]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={handlePress}
|
||||
style={[style.wrapper, {marginLeft: insets.left, marginRight: insets.right}]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={this.handlePress}
|
||||
style={[style.wrapper, padding(isLandscape)]}
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[style.bannerText, bannerTextStyle]}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[style.bannerText, bannerTextStyle]}
|
||||
>
|
||||
<RemoveMarkdown value={bannerText}/>
|
||||
</Text>
|
||||
<CompassIcon
|
||||
color={bannerTextColor}
|
||||
name='info-outline'
|
||||
size={16}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
<RemoveMarkdown value={bannerText}/>
|
||||
</Text>
|
||||
<CompassIcon
|
||||
color={bannerTextColor}
|
||||
name='information-outline'
|
||||
size={16}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</AnimatedView>
|
||||
);
|
||||
});
|
||||
|
||||
AnnouncementBanner.propTypes = {
|
||||
bannerColor: PropTypes.string,
|
||||
bannerDismissed: PropTypes.bool,
|
||||
bannerEnabled: PropTypes.bool,
|
||||
bannerText: PropTypes.string,
|
||||
bannerTextColor: PropTypes.string,
|
||||
};
|
||||
|
||||
export default AnnouncementBanner;
|
||||
|
||||
const style = StyleSheet.create({
|
||||
bannerContainer: {
|
||||
elevation: 2,
|
||||
paddingHorizontal: 10,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
zIndex: 2,
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
|
|
@ -18,11 +18,10 @@ describe('AnnouncementBanner', () => {
|
|||
bannerText: 'Banner Text',
|
||||
bannerTextColor: '#fff',
|
||||
theme: Preferences.THEMES.default,
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<AnnouncementBanner {...baseProps}/>,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConfig, getLicense} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
|
|
@ -21,7 +20,6 @@ function mapStateToProps(state) {
|
|||
bannerEnabled: config.EnableBanner === 'true' && license.IsLicensed === 'true',
|
||||
bannerText: config.BannerText,
|
||||
bannerTextColor: config.BannerTextColor || '#000',
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ export default class AtMention extends PureComponent {
|
|||
teamMembers: PropTypes.array,
|
||||
theme: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
nestedScrollEnabled: PropTypes.bool,
|
||||
useChannelMentions: PropTypes.bool.isRequired,
|
||||
groups: PropTypes.array,
|
||||
|
|
@ -213,7 +212,6 @@ export default class AtMention extends PureComponent {
|
|||
id={section.id}
|
||||
defaultMessage={section.defaultMessage}
|
||||
theme={this.props.theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
isFirstSection={isFirstSection}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {getLicense} from '@mm-redux/selectors/entities/general';
|
|||
import {getCurrentChannelId, getDefaultChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getAssociatedGroupsForReference, searchAssociatedGroupsForReferenceLocal} from '@mm-redux/selectors/entities/groups';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import {
|
||||
filterMembersInChannel,
|
||||
|
|
@ -76,7 +75,6 @@ function mapStateToProps(state, ownProps) {
|
|||
outChannel,
|
||||
requestStatus: state.requests.users.autocompleteUsers.status,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
useChannelMentions,
|
||||
groups,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,56 +1,18 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class GroupMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
completeHandle: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
const {onPress, completeHandle} = this.props;
|
||||
onPress(completeHandle);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
completeHandle,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeMention}
|
||||
style={style.row}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<CompassIcon
|
||||
name='account-group-outline'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<Text style={style.rowFullname}>{`${completeHandle}`}</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
|
|
@ -85,3 +47,40 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
const GroupMentionItem = (props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const {onPress, completeHandle, theme} = props;
|
||||
|
||||
const completeMention = () => {
|
||||
onPress(completeHandle);
|
||||
};
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={completeMention}
|
||||
style={[style.row, {marginLeft: insets.left, marginRight: insets.right}]}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<CompassIcon
|
||||
name='account-group-outline'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<Text style={style.rowFullname}>{`${completeHandle}`}</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
GroupMentionItem.propTypes = {
|
||||
completeHandle: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default GroupMentionItem;
|
||||
|
|
|
|||
|
|
@ -1,129 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {BotTag, GuestTag} from 'app/components/tag';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
export default class AtMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
firstName: PropTypes.string,
|
||||
lastName: PropTypes.string,
|
||||
nickname: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
username: PropTypes.string,
|
||||
isGuest: PropTypes.bool,
|
||||
isBot: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isCurrentUser: PropTypes.bool.isRequired,
|
||||
showFullName: PropTypes.string,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
const {onPress, username} = this.props;
|
||||
onPress(username);
|
||||
};
|
||||
|
||||
renderNameBlock = () => {
|
||||
let name = '';
|
||||
const {showFullName, firstName, lastName, nickname} = this.props;
|
||||
const hasNickname = nickname.length > 0;
|
||||
|
||||
if (showFullName === 'true') {
|
||||
name += `${firstName} ${lastName} `;
|
||||
}
|
||||
|
||||
if (hasNickname) {
|
||||
name += `(${nickname})`;
|
||||
}
|
||||
|
||||
return name.trim();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
userId,
|
||||
username,
|
||||
theme,
|
||||
isBot,
|
||||
isLandscape,
|
||||
isGuest,
|
||||
isCurrentUser,
|
||||
testID,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const name = this.renderNameBlock();
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
testID={testID}
|
||||
key={userId}
|
||||
onPress={this.completeMention}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
userId={userId}
|
||||
theme={theme}
|
||||
size={24}
|
||||
status={null}
|
||||
showStatus={false}
|
||||
/>
|
||||
</View>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
</Text>
|
||||
}
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{` @${username}`}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
|
|
@ -155,3 +42,115 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
const AtMentionItem = (props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const {
|
||||
firstName,
|
||||
isBot,
|
||||
isCurrentUser,
|
||||
isGuest,
|
||||
lastName,
|
||||
nickname,
|
||||
onPress,
|
||||
showFullName,
|
||||
testID,
|
||||
theme,
|
||||
userId,
|
||||
username,
|
||||
} = props;
|
||||
|
||||
const completeMention = () => {
|
||||
onPress(username);
|
||||
};
|
||||
|
||||
const renderNameBlock = () => {
|
||||
let name = '';
|
||||
const hasNickname = nickname.length > 0;
|
||||
|
||||
if (showFullName === 'true') {
|
||||
name += `${firstName} ${lastName} `;
|
||||
}
|
||||
|
||||
if (hasNickname) {
|
||||
name += `(${nickname})`;
|
||||
}
|
||||
|
||||
return name.trim();
|
||||
};
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const name = renderNameBlock();
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
testID={testID}
|
||||
key={userId}
|
||||
onPress={completeMention}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
style={{marginLeft: insets.left, marginRight: insets.right}}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
userId={userId}
|
||||
theme={theme}
|
||||
size={24}
|
||||
status={null}
|
||||
showStatus={false}
|
||||
/>
|
||||
</View>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
</Text>
|
||||
}
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{` @${username}`}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
AtMentionItem.propTypes = {
|
||||
firstName: PropTypes.string,
|
||||
lastName: PropTypes.string,
|
||||
nickname: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
username: PropTypes.string,
|
||||
isGuest: PropTypes.bool,
|
||||
isBot: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isCurrentUser: PropTypes.bool.isRequired,
|
||||
showFullName: PropTypes.string,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
AtMentionItem.defaultProps = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
};
|
||||
|
||||
export default AtMentionItem;
|
||||
|
|
|
|||
|
|
@ -3,16 +3,13 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {isGuest} from '@utils/users';
|
||||
|
||||
import AtMentionItem from './at_mention_item';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const user = getUser(state, ownProps.userId);
|
||||
const config = getConfig(state);
|
||||
|
|
@ -25,7 +22,6 @@ function mapStateToProps(state, ownProps) {
|
|||
isBot: Boolean(user.is_bot),
|
||||
isGuest: isGuest(user),
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
isCurrentUser: getCurrentUserId(state) === user.id,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ import {
|
|||
ViewPropTypes,
|
||||
} from 'react-native';
|
||||
|
||||
import {DeviceTypes} from '@constants';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {emptyFunction} from '@utils/general';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
import ChannelMention from './channel_mention';
|
||||
import EmojiSuggestion from './emoji_suggestion';
|
||||
|
|
@ -203,7 +202,10 @@ export default class Autocomplete extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={wrapperStyles}>
|
||||
<View
|
||||
style={wrapperStyles}
|
||||
edges={['left', 'right']}
|
||||
>
|
||||
<View
|
||||
testID='autocomplete'
|
||||
ref={this.containerRef}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,13 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {ActivityIndicator, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
export default class AutocompleteSectionHeader extends PureComponent {
|
||||
static propTypes = {
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isFirstSection: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {defaultMessage, id, loading, theme, isLandscape, isFirstSection} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
const sectionStyles = [style.section, padding(isLandscape)];
|
||||
|
||||
if (!isFirstSection) {
|
||||
sectionStyles.push(style.borderTop);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={sectionStyles}>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
style={style.sectionText}
|
||||
/>
|
||||
{loading &&
|
||||
<ActivityIndicator
|
||||
color={theme.centerChannelColor}
|
||||
size='small'
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
|
|
@ -79,3 +36,43 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
const AutocompleteSectionHeader = (props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const {defaultMessage, id, loading, theme, isFirstSection} = props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const sectionStyles = [style.section, {marginLeft: insets.left, marginRight: insets.right}];
|
||||
|
||||
if (!isFirstSection) {
|
||||
sectionStyles.push(style.borderTop);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={sectionStyles}>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
style={style.sectionText}
|
||||
/>
|
||||
{loading &&
|
||||
<ActivityIndicator
|
||||
color={theme.centerChannelColor}
|
||||
size='small'
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
AutocompleteSectionHeader.propTypes = {
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isFirstSection: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default AutocompleteSectionHeader;
|
||||
|
|
@ -36,7 +36,6 @@ export default class ChannelMention extends PureComponent {
|
|||
requestStatus: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
nestedScrollEnabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
|
@ -191,7 +190,6 @@ export default class ChannelMention extends PureComponent {
|
|||
defaultMessage={section.defaultMessage}
|
||||
loading={!section.hideLoadingIndicator && this.props.requestStatus === RequestStatus.STARTED}
|
||||
theme={this.props.theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
isFirstSection={isFirstSection}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {searchChannels, autocompleteChannelsForSearch} from '@mm-redux/actions/channels';
|
||||
import {getMyChannelMemberships} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import {
|
||||
filterMyChannels,
|
||||
filterOtherChannels,
|
||||
|
|
@ -16,8 +15,7 @@ import {
|
|||
filterPrivateChannels,
|
||||
filterDirectAndGroupMessages,
|
||||
getMatchTermForChannelMention,
|
||||
} from 'app/selectors/autocomplete';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
} from '@selectors/autocomplete';
|
||||
|
||||
import ChannelMention from './channel_mention';
|
||||
|
||||
|
|
@ -52,7 +50,6 @@ function mapStateToProps(state, ownProps) {
|
|||
matchTerm,
|
||||
requestStatus: state.requests.channels.getChannels.status,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,113 +1,17 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class ChannelMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
channelId: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isGuest: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
const {onPress, displayName, name, type} = this.props;
|
||||
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
|
||||
onPress('@' + displayName.replace(/ /g, ''));
|
||||
} else {
|
||||
onPress(name);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelId,
|
||||
displayName,
|
||||
name,
|
||||
theme,
|
||||
type,
|
||||
isBot,
|
||||
isLandscape,
|
||||
isGuest,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
let iconName = 'globe';
|
||||
let component;
|
||||
if (type === General.PRIVATE_CHANNEL) {
|
||||
iconName = 'lock';
|
||||
}
|
||||
|
||||
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
|
||||
if (!displayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
component = (
|
||||
<TouchableWithFeedback
|
||||
key={channelId}
|
||||
onPress={this.completeMention}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Text style={style.rowDisplayName}>{'@' + displayName}</Text>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
} else {
|
||||
component = (
|
||||
<TouchableWithFeedback
|
||||
key={channelId}
|
||||
onPress={this.completeMention}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.rowDisplayName}>{displayName}</Text>
|
||||
<Text style={style.rowName}>{` ~${name}`}</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{component}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
icon: {
|
||||
|
|
@ -133,3 +37,92 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ChannelMentionItem = (props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const {
|
||||
channelId,
|
||||
displayName,
|
||||
isBot,
|
||||
isGuest,
|
||||
name,
|
||||
onPress,
|
||||
theme,
|
||||
type,
|
||||
} = props;
|
||||
|
||||
const completeMention = () => {
|
||||
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
|
||||
onPress('@' + displayName.replace(/ /g, ''));
|
||||
} else {
|
||||
onPress(name);
|
||||
}
|
||||
};
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const margins = {marginLeft: insets.left, marginRight: insets.right};
|
||||
let iconName = 'globe';
|
||||
let component;
|
||||
if (type === General.PRIVATE_CHANNEL) {
|
||||
iconName = 'lock';
|
||||
}
|
||||
|
||||
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
|
||||
if (!displayName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
component = (
|
||||
<TouchableWithFeedback
|
||||
key={channelId}
|
||||
onPress={completeMention}
|
||||
style={[style.row, margins]}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Text style={style.rowDisplayName}>{'@' + displayName}</Text>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
} else {
|
||||
component = (
|
||||
<TouchableWithFeedback
|
||||
key={channelId}
|
||||
onPress={this.completeMention}
|
||||
style={margins}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.rowDisplayName}>{displayName}</Text>
|
||||
<Text style={style.rowName}>{` ~${name}`}</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
return component;
|
||||
};
|
||||
|
||||
ChannelMentionItem.propTypes = {
|
||||
channelId: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isGuest: PropTypes.bool.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default ChannelMentionItem;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import {General} from '@mm-redux/constants';
|
|||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getUser} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
import {getChannelNameForSearchAutocomplete} from 'app/selectors/channel';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {isGuest as isGuestUser} from 'app/utils/users';
|
||||
import {getChannelNameForSearchAutocomplete} from '@selectors/channel';
|
||||
import {isGuest as isGuestUser} from '@utils/users';
|
||||
|
||||
import ChannelMentionItem from './channel_mention_item';
|
||||
|
||||
|
|
@ -36,7 +34,6 @@ function mapStateToProps(state, ownProps) {
|
|||
isBot,
|
||||
isGuest,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {getAutocompleteCommands, getCommandAutocompleteSuggestions} from '@mm-re
|
|||
import {getAutocompleteCommandsList, getCommandAutocompleteSuggestionsList} from '@mm-redux/selectors/entities/integrations';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import SlashSuggestion from './slash_suggestion';
|
||||
|
||||
|
|
@ -31,7 +30,6 @@ function mapStateToProps(state) {
|
|||
commands: mobileCommandsSelector(state),
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
suggestions: getCommandAutocompleteSuggestionsList(state),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import {
|
|||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import SlashSuggestionItem from './slash_suggestion_item';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
import {analytics} from '@init/analytics.ts';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import SlashSuggestionItem from './slash_suggestion_item';
|
||||
|
||||
const TIME_BEFORE_NEXT_COMMAND_REQUEST = 1000 * 60 * 5;
|
||||
|
||||
|
|
@ -31,7 +31,6 @@ export default class SlashSuggestion extends PureComponent {
|
|||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
nestedScrollEnabled: PropTypes.bool,
|
||||
suggestions: PropTypes.array,
|
||||
rootId: PropTypes.string,
|
||||
|
|
@ -187,7 +186,6 @@ export default class SlashSuggestion extends PureComponent {
|
|||
theme={this.props.theme}
|
||||
suggestion={item.Suggestion}
|
||||
complete={item.Complete}
|
||||
isLandscape={this.props.isLandscape}
|
||||
/>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,79 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import slashIcon from '@assets/images/autocomplete/slash_command.png';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import slashIcon from '@assets/images/autocomplete/slash_command.png';
|
||||
|
||||
export default class SlashSuggestionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
description: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
suggestion: PropTypes.string,
|
||||
complete: PropTypes.string,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
completeSuggestion = () => {
|
||||
const {onPress, complete} = this.props;
|
||||
onPress(complete);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
description,
|
||||
hint,
|
||||
theme,
|
||||
suggestion,
|
||||
complete,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let suggestionText = suggestion;
|
||||
if (suggestionText[0] === '/' && complete.split(' ').length === 1) {
|
||||
suggestionText = suggestionText.substring(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeSuggestion}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.icon}>
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={slashIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.suggestionContainer}>
|
||||
<Text style={style.suggestionName}>{`${suggestionText} ${hint}`}</Text>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.suggestionDescription}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
|
|
@ -112,3 +47,67 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SlashSuggestionItem = (props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const {
|
||||
complete,
|
||||
description,
|
||||
hint,
|
||||
onPress,
|
||||
suggestion,
|
||||
theme,
|
||||
} = props;
|
||||
|
||||
const completeSuggestion = () => {
|
||||
onPress(complete);
|
||||
};
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let suggestionText = suggestion;
|
||||
if (suggestionText[0] === '/' && complete.split(' ').length === 1) {
|
||||
suggestionText = suggestionText.substring(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={completeSuggestion}
|
||||
style={{marginLeft: insets.left, marginRight: insets.right}}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.icon}>
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={slashIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.suggestionContainer}>
|
||||
<Text style={style.suggestionName}>{`${suggestionText} ${hint}`}</Text>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.suggestionDescription}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
SlashSuggestionItem.propTypes = {
|
||||
description: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
suggestion: PropTypes.string,
|
||||
complete: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SlashSuggestionItem;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {displayUsername} from '@mm-redux/utils/user_utils';
|
|||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
|
@ -35,7 +34,6 @@ export default class AutocompleteSelector extends PureComponent {
|
|||
helpText: PropTypes.node,
|
||||
errorText: PropTypes.node,
|
||||
roundedBorders: PropTypes.bool,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
|
@ -119,7 +117,6 @@ export default class AutocompleteSelector extends PureComponent {
|
|||
optional,
|
||||
showRequiredAsterisk,
|
||||
roundedBorders,
|
||||
isLandscape,
|
||||
disabled,
|
||||
} = this.props;
|
||||
const {selectedText} = this.state;
|
||||
|
|
@ -186,9 +183,7 @@ export default class AutocompleteSelector extends PureComponent {
|
|||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<View style={padding(isLandscape)}>
|
||||
{labelContent}
|
||||
</View>
|
||||
{labelContent}
|
||||
<TouchableWithFeedback
|
||||
style={disabled ? style.disabled : null}
|
||||
onPress={this.goToSelectorScreen}
|
||||
|
|
@ -197,7 +192,7 @@ export default class AutocompleteSelector extends PureComponent {
|
|||
>
|
||||
<View style={inputStyle}>
|
||||
<Text
|
||||
style={[selectedStyle, padding(isLandscape)]}
|
||||
style={selectedStyle}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{text}
|
||||
|
|
@ -205,14 +200,12 @@ export default class AutocompleteSelector extends PureComponent {
|
|||
<CompassIcon
|
||||
name='chevron-down'
|
||||
color={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
style={[style.icon, padding(isLandscape)]}
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
<View style={padding(isLandscape)}>
|
||||
{helpTextContent}
|
||||
{errorTextContent}
|
||||
</View>
|
||||
{helpTextContent}
|
||||
{errorTextContent}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entit
|
|||
import {setAutocompleteSelector} from 'app/actions/views/post';
|
||||
|
||||
import AutocompleteSelector from './autocomplete_selector';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {General} from '@mm-redux/constants';
|
|||
import {showModal} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
|
@ -30,7 +29,6 @@ class ChannelIntro extends PureComponent {
|
|||
currentChannelMembers: PropTypes.array.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
teammateNameDisplay: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
|
@ -329,7 +327,7 @@ class ChannelIntro extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {currentChannel, theme, isLandscape} = this.props;
|
||||
const {currentChannel, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
const channelType = currentChannel.type;
|
||||
|
||||
|
|
@ -337,10 +335,10 @@ class ChannelIntro extends PureComponent {
|
|||
if (channelType === General.DM_CHANNEL || channelType === General.GM_CHANNEL) {
|
||||
profiles = (
|
||||
<View>
|
||||
<View style={[style.profilesContainer, padding(isLandscape)]}>
|
||||
<View style={style.profilesContainer}>
|
||||
{this.buildProfiles()}
|
||||
</View>
|
||||
<View style={[style.namesContainer, padding(isLandscape)]}>
|
||||
<View style={style.namesContainer}>
|
||||
{this.buildNames()}
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -350,7 +348,7 @@ class ChannelIntro extends PureComponent {
|
|||
return (
|
||||
<View style={style.container}>
|
||||
{profiles}
|
||||
<View style={[style.contentContainer, padding(isLandscape)]}>
|
||||
<View style={style.contentContainer}>
|
||||
{this.buildContent()}
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {makeGetChannel} from '@mm-redux/selectors/entities/channels';
|
|||
import {getCurrentUserId, getUser, makeGetProfilesInChannel} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {getChannelMembersForDm} from 'app/selectors/channel';
|
||||
|
||||
import ChannelIntro from './channel_intro';
|
||||
|
|
@ -48,7 +47,6 @@ function makeMapStateToProps() {
|
|||
currentChannel,
|
||||
currentChannelMembers,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ exports[`ChannelLoader should match snapshot 1`] = `
|
|||
"overflow": "hidden",
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ import {
|
|||
} from 'react-native';
|
||||
import * as RNPlaceholder from 'rn-placeholder';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
function calculateMaxRows(height) {
|
||||
return Math.round(height / 100);
|
||||
|
|
@ -36,7 +35,6 @@ export default class ChannelLoader extends PureComponent {
|
|||
style: CustomPropTypes.Style,
|
||||
theme: PropTypes.object.isRequired,
|
||||
height: PropTypes.number,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -107,7 +105,6 @@ export default class ChannelLoader extends PureComponent {
|
|||
channelIsLoading,
|
||||
style: styleProp,
|
||||
theme,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
if (!channelIsLoading) {
|
||||
|
|
@ -119,7 +116,7 @@ export default class ChannelLoader extends PureComponent {
|
|||
|
||||
return (
|
||||
<View
|
||||
style={[style.container, styleProp, padding(isLandscape), {backgroundColor: bg}]}
|
||||
style={[style.container, styleProp, {backgroundColor: bg}]}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{Array(this.state.maxRows).fill().map((item, index) => this.buildSections({
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ describe('ChannelLoader', () => {
|
|||
const baseProps = {
|
||||
channelIsLoading: true,
|
||||
theme: Preferences.THEMES.default,
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import ChannelLoader from './channel_loader';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
|
|
@ -15,7 +13,6 @@ function mapStateToProps(state, ownProps) {
|
|||
return {
|
||||
channelIsLoading,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -117,13 +117,10 @@ exports[`CustomList should match snapshot, renderSectionHeader 1`] = `
|
|||
>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontWeight": "600",
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
>
|
||||
section_id
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ export default class ChannelListRow extends React.PureComponent {
|
|||
theme: PropTypes.object.isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
...CustomListRow.propTypes,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
|
|
@ -49,7 +48,6 @@ export default class ChannelListRow extends React.PureComponent {
|
|||
enabled={this.props.enabled}
|
||||
selectable={this.props.selectable}
|
||||
selected={this.props.selected}
|
||||
isLandscape={this.props.isLandscape}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.titleContainer}>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {connect} from 'react-redux';
|
|||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import {makeGetChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import ChannelListRow from './channel_list_row';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
|
|
@ -16,7 +15,6 @@ function makeMapStateToProps() {
|
|||
return {
|
||||
theme: getTheme(state),
|
||||
channel: getChannel(state, ownProps),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {paddingLeft as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import ConditionalTouchable from '@components/conditional_touchable';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
|
||||
|
|
@ -20,14 +19,11 @@ export default class CustomListRow extends React.PureComponent {
|
|||
selectable: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
children: CustomPropTypes.Children,
|
||||
item: PropTypes.object,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
enabled: true,
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -38,7 +34,7 @@ export default class CustomListRow extends React.PureComponent {
|
|||
style={style.touchable}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
<View style={[style.container, padding(this.props.isLandscape)]}>
|
||||
<View style={style.container}>
|
||||
{this.props.selectable &&
|
||||
<View style={style.selectorContainer}>
|
||||
<View style={[style.selector, (this.props.selected && style.selectorFilled), (!this.props.enabled && style.selectorDisabled)]}>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {FlatList, Keyboard, Platform, RefreshControl, SectionList, Text, View} f
|
|||
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
export const FLATLIST = 'flat';
|
||||
export const SECTIONLIST = 'section';
|
||||
|
|
@ -32,13 +31,11 @@ export default class CustomList extends PureComponent {
|
|||
selectable: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
shouldRenderSeparator: PropTypes.bool,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
canRefresh: true,
|
||||
isLandscape: false,
|
||||
listType: FLATLIST,
|
||||
showNoResults: true,
|
||||
shouldRenderSeparator: true,
|
||||
|
|
@ -165,13 +162,13 @@ export default class CustomList extends PureComponent {
|
|||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {theme, isLandscape} = this.props;
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={style.sectionContainer}>
|
||||
<Text style={[style.sectionText, padding(isLandscape)]}>{section.id}</Text>
|
||||
<Text style={style.sectionText}>{section.id}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import OptionListRow from './option_list_row';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,17 +17,12 @@ export default class OptionListRow extends React.PureComponent {
|
|||
id: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
...CustomListRow.propTypes,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
if (this.props.onPress) {
|
||||
this.props.onPress(this.props.id, this.props.item);
|
||||
|
|
@ -41,7 +36,6 @@ export default class OptionListRow extends React.PureComponent {
|
|||
selected,
|
||||
theme,
|
||||
item,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
const {text, value} = item;
|
||||
|
|
@ -54,7 +48,6 @@ export default class OptionListRow extends React.PureComponent {
|
|||
enabled={enabled}
|
||||
selectable={selectable}
|
||||
selected={selected}
|
||||
isLandscape={isLandscape}
|
||||
>
|
||||
<View style={style.textContainer}>
|
||||
<View>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ exports[`UserListRow should match snapshot 1`] = `
|
|||
<CustomListRow
|
||||
enabled={true}
|
||||
id="21345"
|
||||
isLandscape={false}
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
|
|
@ -147,7 +146,6 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
|||
<CustomListRow
|
||||
enabled={true}
|
||||
id="21345"
|
||||
isLandscape={false}
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
|
|
@ -278,7 +276,6 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
|||
<CustomListRow
|
||||
enabled={true}
|
||||
id="21345"
|
||||
isLandscape={false}
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
|
|
@ -422,7 +419,6 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
|
|||
<CustomListRow
|
||||
enabled={true}
|
||||
id="21345"
|
||||
isLandscape={false}
|
||||
onPress={[Function]}
|
||||
>
|
||||
<View
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import UserListRow from './user_list_row';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
|
|
@ -14,7 +13,6 @@ function mapStateToProps(state, ownProps) {
|
|||
theme: getTheme(state),
|
||||
user: getUser(state, ownProps.id),
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ export default class UserListRow extends React.PureComponent {
|
|||
teammateNameDisplay,
|
||||
theme,
|
||||
user,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
const {id, username} = user;
|
||||
|
|
@ -73,7 +72,6 @@ export default class UserListRow extends React.PureComponent {
|
|||
enabled={enabled}
|
||||
selectable={selectable}
|
||||
selected={selected}
|
||||
isLandscape={isLandscape}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
<View style={style.profileContainer}>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ describe('UserListRow', () => {
|
|||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
teammateNameDisplay: 'test',
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EditChannelInfo should match snapshot 1`] = `
|
||||
<React.Fragment>
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(StatusBar) />
|
||||
<KeyboardAwareScrollView
|
||||
enableAutomaticScroll={true}
|
||||
|
|
@ -23,16 +36,11 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.06)",
|
||||
"flex": 1,
|
||||
"paddingTop": 30,
|
||||
},
|
||||
Object {
|
||||
"height": 600,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.06)",
|
||||
"flex": 1,
|
||||
"paddingTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View>
|
||||
|
|
@ -41,30 +49,20 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
defaultMessage="Name"
|
||||
id="channel_modal.name"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 14,
|
||||
"marginLeft": 15,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 14,
|
||||
"marginLeft": 15,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginTop": 10,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
|
|
@ -98,15 +96,10 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
<View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginTop": 30,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginTop": 30,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedText
|
||||
|
|
@ -134,15 +127,10 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginTop": 10,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
|
|
@ -184,17 +172,12 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
defaultMessage="Describe how this channel should be used."
|
||||
id="channel_modal.descriptionHelp"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 14,
|
||||
"marginHorizontal": 15,
|
||||
"marginTop": 10,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 14,
|
||||
"marginHorizontal": 15,
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
|
@ -202,15 +185,10 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
<View
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginTop": 15,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"marginTop": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedText
|
||||
|
|
@ -238,15 +216,10 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginTop": 10,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
|
|
@ -295,17 +268,12 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
defaultMessage="Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com)."
|
||||
id="channel_modal.headerHelp"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 14,
|
||||
"marginHorizontal": 15,
|
||||
"marginTop": 10,
|
||||
},
|
||||
Object {
|
||||
"paddingHorizontal": 44,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"color": "rgba(61,60,64,0.5)",
|
||||
"fontSize": 14,
|
||||
"marginHorizontal": 15,
|
||||
"marginTop": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
|
@ -342,5 +310,5 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
|||
value="header"
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,453 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {popTopScreen, dismissModal} from 'app/actions/navigation';
|
||||
|
||||
export default class EditChannelInfo extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
enableRightButton: PropTypes.func,
|
||||
saving: PropTypes.bool.isRequired,
|
||||
editing: PropTypes.bool,
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
displayName: PropTypes.string,
|
||||
currentTeamUrl: PropTypes.string,
|
||||
channelURL: PropTypes.string,
|
||||
purpose: PropTypes.string,
|
||||
header: PropTypes.string,
|
||||
onDisplayNameChange: PropTypes.func,
|
||||
onChannelURLChange: PropTypes.func,
|
||||
onPurposeChange: PropTypes.func,
|
||||
onHeaderChange: PropTypes.func,
|
||||
oldDisplayName: PropTypes.string,
|
||||
oldChannelURL: PropTypes.string,
|
||||
oldHeader: PropTypes.string,
|
||||
oldPurpose: PropTypes.string,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
editing: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.nameInput = React.createRef();
|
||||
this.urlInput = React.createRef();
|
||||
this.purposeInput = React.createRef();
|
||||
this.headerInput = React.createRef();
|
||||
this.scroll = React.createRef();
|
||||
|
||||
this.state = {
|
||||
keyboardVisible: false,
|
||||
keyboardPosition: 0,
|
||||
};
|
||||
}
|
||||
|
||||
blur = () => {
|
||||
if (this.nameInput?.current) {
|
||||
this.nameInput.current.blur();
|
||||
}
|
||||
|
||||
// TODO: uncomment below once the channel URL field is added
|
||||
// if (this.urlInput?.current) {
|
||||
// this.urlInput.current.blur();
|
||||
// }
|
||||
|
||||
if (this.purposeInput?.current) {
|
||||
this.purposeInput.current.blur();
|
||||
}
|
||||
if (this.headerInput?.current) {
|
||||
this.headerInput.current.blur();
|
||||
}
|
||||
|
||||
if (this.scroll?.current) {
|
||||
this.scroll.current.scrollTo({x: 0, y: 0, animated: true});
|
||||
}
|
||||
};
|
||||
|
||||
close = (goBack = false) => {
|
||||
if (goBack) {
|
||||
popTopScreen();
|
||||
} else {
|
||||
dismissModal();
|
||||
}
|
||||
};
|
||||
|
||||
canUpdate = (displayName, channelURL, purpose, header) => {
|
||||
const {
|
||||
oldDisplayName,
|
||||
oldChannelURL,
|
||||
oldPurpose,
|
||||
oldHeader,
|
||||
} = this.props;
|
||||
|
||||
return displayName !== oldDisplayName || channelURL !== oldChannelURL ||
|
||||
purpose !== oldPurpose || header !== oldHeader;
|
||||
};
|
||||
|
||||
enableRightButton = (enable = false) => {
|
||||
this.props.enableRightButton(enable);
|
||||
};
|
||||
|
||||
onDisplayNameChangeText = (displayName) => {
|
||||
const {editing, onDisplayNameChange} = this.props;
|
||||
onDisplayNameChange(displayName);
|
||||
|
||||
if (editing) {
|
||||
const {channelURL, purpose, header} = this.props;
|
||||
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
|
||||
this.enableRightButton(canUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
const displayNameExists = displayName && displayName.length >= 2;
|
||||
this.props.enableRightButton(displayNameExists);
|
||||
};
|
||||
|
||||
onPurposeChangeText = (purpose) => {
|
||||
const {editing, onPurposeChange} = this.props;
|
||||
onPurposeChange(purpose);
|
||||
|
||||
if (editing) {
|
||||
const {displayName, channelURL, header} = this.props;
|
||||
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
|
||||
this.enableRightButton(canUpdate);
|
||||
}
|
||||
};
|
||||
|
||||
onHeaderChangeText = (header) => {
|
||||
const {editing, onHeaderChange} = this.props;
|
||||
onHeaderChange(header);
|
||||
|
||||
if (editing) {
|
||||
const {displayName, channelURL, purpose} = this.props;
|
||||
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
|
||||
this.enableRightButton(canUpdate);
|
||||
}
|
||||
};
|
||||
|
||||
onHeaderLayout = ({nativeEvent}) => {
|
||||
this.setState({headerPosition: nativeEvent.layout.y});
|
||||
}
|
||||
|
||||
onKeyboardDidShow = () => {
|
||||
this.setState({keyboardVisible: true});
|
||||
|
||||
if (this.state.headerHasFocus) {
|
||||
this.setState({headerHasFocus: false});
|
||||
this.scrollHeaderToTop();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardDidHide = () => {
|
||||
this.setState({keyboardVisible: false});
|
||||
}
|
||||
|
||||
onKeyboardOffsetChanged = (keyboardPosition) => {
|
||||
this.setState({keyboardPosition});
|
||||
}
|
||||
|
||||
onHeaderFocus = () => {
|
||||
if (this.state.keyboardVisible) {
|
||||
this.scrollHeaderToTop();
|
||||
} else {
|
||||
this.setState({headerHasFocus: true});
|
||||
}
|
||||
};
|
||||
|
||||
scrollHeaderToTop = () => {
|
||||
if (this.scroll.current) {
|
||||
this.scroll.current.scrollTo({x: 0, y: this.state.headerPosition});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
channelType,
|
||||
deviceWidth,
|
||||
deviceHeight,
|
||||
displayName,
|
||||
header,
|
||||
purpose,
|
||||
isLandscape,
|
||||
error,
|
||||
saving,
|
||||
testID,
|
||||
} = this.props;
|
||||
const {keyboardVisible, keyboardPosition} = this.state;
|
||||
const bottomStyle = {
|
||||
bottom: Platform.select({
|
||||
ios: keyboardPosition,
|
||||
android: 0,
|
||||
}),
|
||||
};
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
|
||||
channelType === General.GM_CHANNEL;
|
||||
|
||||
if (saving) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<Loading color={theme.centerChannelColor}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
let displayError;
|
||||
if (error) {
|
||||
displayError = (
|
||||
<View style={[style.errorContainer, {width: deviceWidth}]}>
|
||||
<View style={[style.errorWrapper, padding(isLandscape)]}>
|
||||
<ErrorText error={error}/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StatusBar/>
|
||||
<KeyboardAwareScrollView
|
||||
testID={testID}
|
||||
ref={this.scroll}
|
||||
style={style.container}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
onKeyboardDidShow={this.onKeyboardDidShow}
|
||||
onKeyboardDidHide={this.onKeyboardDidHide}
|
||||
enableAutomaticScroll={!keyboardVisible}
|
||||
>
|
||||
{displayError}
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
<View style={[style.scrollView, {height: deviceHeight + (Platform.OS === 'android' ? 200 : 0)}]}>
|
||||
{!displayHeaderOnly && (
|
||||
<View>
|
||||
<View>
|
||||
<FormattedText
|
||||
style={[style.title, padding(isLandscape)]}
|
||||
id='channel_modal.name'
|
||||
defaultMessage='Name'
|
||||
/>
|
||||
</View>
|
||||
<View style={[style.inputContainer, padding(isLandscape)]}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
testID='edit_channel.name.input'
|
||||
ref={this.nameInput}
|
||||
value={displayName}
|
||||
onChangeText={this.onDisplayNameChangeText}
|
||||
style={style.input}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: t('channel_modal.nameEx'), defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
maxLength={64}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{!displayHeaderOnly && (
|
||||
<View>
|
||||
<View style={[style.titleContainer30, padding(isLandscape)]}>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.purpose'
|
||||
defaultMessage='Purpose'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={[style.inputContainer, padding(isLandscape)]}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
testID='edit_channel.purpose.input'
|
||||
ref={this.purposeInput}
|
||||
value={purpose}
|
||||
onChangeText={this.onPurposeChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: t('channel_modal.purposeEx'), defaultMessage: 'E.g.: "A channel to file bugs and improvements"'}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
multiline={true}
|
||||
blurOnSubmit={false}
|
||||
textAlignVertical='top'
|
||||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<FormattedText
|
||||
style={[style.helpText, padding(isLandscape)]}
|
||||
id='channel_modal.descriptionHelp'
|
||||
defaultMessage='Describe how this channel should be used.'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<View
|
||||
onLayout={this.onHeaderLayout}
|
||||
style={[style.titleContainer15, padding(isLandscape)]}
|
||||
>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.header'
|
||||
defaultMessage='Header'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={[style.inputContainer, padding(isLandscape)]}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
testID={'edit_channel.header.input'}
|
||||
ref={this.headerInput}
|
||||
value={header}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: t('channel_modal.headerEx'), defaultMessage: 'E.g.: "[Link Title](http://example.com)"'}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
multiline={true}
|
||||
blurOnSubmit={false}
|
||||
onFocus={this.onHeaderFocus}
|
||||
textAlignVertical='top'
|
||||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.headerHelpText}>
|
||||
<FormattedText
|
||||
style={[style.helpText, padding(isLandscape)]}
|
||||
id='channel_modal.headerHelp'
|
||||
defaultMessage={'Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com).'}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
<View style={[style.autocompleteContainer, bottomStyle]}>
|
||||
<Autocomplete
|
||||
cursorPosition={header.length}
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
value={header}
|
||||
nestedScrollEnabled={true}
|
||||
onKeyboardOffsetChanged={this.onKeyboardOffsetChanged}
|
||||
offsetY={8}
|
||||
style={style.autocomplete}
|
||||
/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
autocomplete: {
|
||||
position: undefined,
|
||||
},
|
||||
autocompleteContainer: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
paddingTop: 30,
|
||||
},
|
||||
errorContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
},
|
||||
errorWrapper: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 10,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
input: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
titleContainer30: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 30,
|
||||
},
|
||||
titleContainer15: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 15,
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 15,
|
||||
},
|
||||
optional: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 14,
|
||||
marginLeft: 5,
|
||||
},
|
||||
helpText: {
|
||||
fontSize: 14,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
headerHelpText: {
|
||||
zIndex: -1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@ import {shallow} from 'enzyme';
|
|||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
import EditChannelInfo from './edit_channel_info';
|
||||
import EditChannelInfo from './index';
|
||||
|
||||
describe('EditChannelInfo', () => {
|
||||
const baseProps = {
|
||||
|
|
@ -32,7 +32,6 @@ describe('EditChannelInfo', () => {
|
|||
oldChannelURL: '/team_a/channels/channel_old',
|
||||
oldHeader: 'old_header',
|
||||
oldPurpose: 'old_purpose',
|
||||
isLandscape: true,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,452 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import EditChannelInfo from './edit_channel_info';
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isLandscape: isLandscape(state),
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {popTopScreen, dismissModal} from 'app/actions/navigation';
|
||||
|
||||
export default class EditChannelInfo extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
channelType: PropTypes.string,
|
||||
enableRightButton: PropTypes.func,
|
||||
saving: PropTypes.bool.isRequired,
|
||||
editing: PropTypes.bool,
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
displayName: PropTypes.string,
|
||||
channelURL: PropTypes.string,
|
||||
purpose: PropTypes.string,
|
||||
header: PropTypes.string,
|
||||
onDisplayNameChange: PropTypes.func,
|
||||
onPurposeChange: PropTypes.func,
|
||||
onHeaderChange: PropTypes.func,
|
||||
oldDisplayName: PropTypes.string,
|
||||
oldChannelURL: PropTypes.string,
|
||||
oldHeader: PropTypes.string,
|
||||
oldPurpose: PropTypes.string,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
editing: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.nameInput = React.createRef();
|
||||
this.urlInput = React.createRef();
|
||||
this.purposeInput = React.createRef();
|
||||
this.headerInput = React.createRef();
|
||||
this.scroll = React.createRef();
|
||||
|
||||
this.state = {
|
||||
keyboardVisible: false,
|
||||
keyboardPosition: 0,
|
||||
};
|
||||
}
|
||||
|
||||
blur = () => {
|
||||
if (this.nameInput?.current) {
|
||||
this.nameInput.current.blur();
|
||||
}
|
||||
|
||||
// TODO: uncomment below once the channel URL field is added
|
||||
// if (this.urlInput?.current) {
|
||||
// this.urlInput.current.blur();
|
||||
// }
|
||||
|
||||
if (this.purposeInput?.current) {
|
||||
this.purposeInput.current.blur();
|
||||
}
|
||||
if (this.headerInput?.current) {
|
||||
this.headerInput.current.blur();
|
||||
}
|
||||
|
||||
if (this.scroll?.current) {
|
||||
this.scroll.current.scrollTo({x: 0, y: 0, animated: true});
|
||||
}
|
||||
};
|
||||
|
||||
close = (goBack = false) => {
|
||||
if (goBack) {
|
||||
popTopScreen();
|
||||
} else {
|
||||
dismissModal();
|
||||
}
|
||||
};
|
||||
|
||||
canUpdate = (displayName, channelURL, purpose, header) => {
|
||||
const {
|
||||
oldDisplayName,
|
||||
oldChannelURL,
|
||||
oldPurpose,
|
||||
oldHeader,
|
||||
} = this.props;
|
||||
|
||||
return displayName !== oldDisplayName || channelURL !== oldChannelURL ||
|
||||
purpose !== oldPurpose || header !== oldHeader;
|
||||
};
|
||||
|
||||
enableRightButton = (enable = false) => {
|
||||
this.props.enableRightButton(enable);
|
||||
};
|
||||
|
||||
onDisplayNameChangeText = (displayName) => {
|
||||
const {editing, onDisplayNameChange} = this.props;
|
||||
onDisplayNameChange(displayName);
|
||||
|
||||
if (editing) {
|
||||
const {channelURL, purpose, header} = this.props;
|
||||
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
|
||||
this.enableRightButton(canUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
const displayNameExists = displayName && displayName.length >= 2;
|
||||
this.props.enableRightButton(displayNameExists);
|
||||
};
|
||||
|
||||
onPurposeChangeText = (purpose) => {
|
||||
const {editing, onPurposeChange} = this.props;
|
||||
onPurposeChange(purpose);
|
||||
|
||||
if (editing) {
|
||||
const {displayName, channelURL, header} = this.props;
|
||||
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
|
||||
this.enableRightButton(canUpdate);
|
||||
}
|
||||
};
|
||||
|
||||
onHeaderChangeText = (header) => {
|
||||
const {editing, onHeaderChange} = this.props;
|
||||
onHeaderChange(header);
|
||||
|
||||
if (editing) {
|
||||
const {displayName, channelURL, purpose} = this.props;
|
||||
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
|
||||
this.enableRightButton(canUpdate);
|
||||
}
|
||||
};
|
||||
|
||||
onHeaderLayout = ({nativeEvent}) => {
|
||||
this.setState({headerPosition: nativeEvent.layout.y});
|
||||
}
|
||||
|
||||
onKeyboardDidShow = () => {
|
||||
this.setState({keyboardVisible: true});
|
||||
|
||||
if (this.state.headerHasFocus) {
|
||||
this.setState({headerHasFocus: false});
|
||||
this.scrollHeaderToTop();
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardDidHide = () => {
|
||||
this.setState({keyboardVisible: false});
|
||||
}
|
||||
|
||||
onKeyboardOffsetChanged = (keyboardPosition) => {
|
||||
this.setState({keyboardPosition});
|
||||
}
|
||||
|
||||
onHeaderFocus = () => {
|
||||
if (this.state.keyboardVisible) {
|
||||
this.scrollHeaderToTop();
|
||||
} else {
|
||||
this.setState({headerHasFocus: true});
|
||||
}
|
||||
};
|
||||
|
||||
scrollHeaderToTop = () => {
|
||||
if (this.scroll.current) {
|
||||
this.scroll.current.scrollTo({x: 0, y: this.state.headerPosition});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
channelType,
|
||||
displayName,
|
||||
header,
|
||||
purpose,
|
||||
error,
|
||||
saving,
|
||||
testID,
|
||||
} = this.props;
|
||||
const {keyboardVisible, keyboardPosition} = this.state;
|
||||
const bottomStyle = {
|
||||
bottom: Platform.select({
|
||||
ios: keyboardPosition,
|
||||
android: 0,
|
||||
}),
|
||||
};
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
|
||||
channelType === General.GM_CHANNEL;
|
||||
|
||||
if (saving) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<StatusBar/>
|
||||
<Loading color={theme.centerChannelColor}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
let displayError;
|
||||
if (error) {
|
||||
displayError = (
|
||||
<SafeAreaView
|
||||
edges={['bottom', 'left', 'right']}
|
||||
style={style.errorContainer}
|
||||
>
|
||||
<View style={style.errorWrapper}>
|
||||
<ErrorText error={error}/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={['bottom', 'left', 'right']}
|
||||
style={style.container}
|
||||
>
|
||||
<StatusBar/>
|
||||
<KeyboardAwareScrollView
|
||||
testID={testID}
|
||||
ref={this.scroll}
|
||||
style={style.container}
|
||||
keyboardShouldPersistTaps={'always'}
|
||||
onKeyboardDidShow={this.onKeyboardDidShow}
|
||||
onKeyboardDidHide={this.onKeyboardDidHide}
|
||||
enableAutomaticScroll={!keyboardVisible}
|
||||
>
|
||||
{displayError}
|
||||
<TouchableWithoutFeedback onPress={this.blur}>
|
||||
<View style={style.scrollView}>
|
||||
{!displayHeaderOnly && (
|
||||
<View>
|
||||
<View>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.name'
|
||||
defaultMessage='Name'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
testID='edit_channel.name.input'
|
||||
ref={this.nameInput}
|
||||
value={displayName}
|
||||
onChangeText={this.onDisplayNameChangeText}
|
||||
style={style.input}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: t('channel_modal.nameEx'), defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
maxLength={64}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{!displayHeaderOnly && (
|
||||
<View>
|
||||
<View style={style.titleContainer30}>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.purpose'
|
||||
defaultMessage='Purpose'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
testID='edit_channel.purpose.input'
|
||||
ref={this.purposeInput}
|
||||
value={purpose}
|
||||
onChangeText={this.onPurposeChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: t('channel_modal.purposeEx'), defaultMessage: 'E.g.: "A channel to file bugs and improvements"'}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
multiline={true}
|
||||
blurOnSubmit={false}
|
||||
textAlignVertical='top'
|
||||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.descriptionHelp'
|
||||
defaultMessage='Describe how this channel should be used.'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<View
|
||||
onLayout={this.onHeaderLayout}
|
||||
style={style.titleContainer15}
|
||||
>
|
||||
<FormattedText
|
||||
style={style.title}
|
||||
id='channel_modal.header'
|
||||
defaultMessage='Header'
|
||||
/>
|
||||
<FormattedText
|
||||
style={style.optional}
|
||||
id='channel_modal.optional'
|
||||
defaultMessage='(optional)'
|
||||
/>
|
||||
</View>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
testID={'edit_channel.header.input'}
|
||||
ref={this.headerInput}
|
||||
value={header}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
style={[style.input, {height: 110}]}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
placeholder={{id: t('channel_modal.headerEx'), defaultMessage: 'E.g.: "[Link Title](http://example.com)"'}}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
multiline={true}
|
||||
blurOnSubmit={false}
|
||||
onFocus={this.onHeaderFocus}
|
||||
textAlignVertical='top'
|
||||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.headerHelpText}>
|
||||
<FormattedText
|
||||
style={style.helpText}
|
||||
id='channel_modal.headerHelp'
|
||||
defaultMessage={'Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com).'}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
<View style={[style.autocompleteContainer, bottomStyle]}>
|
||||
<Autocomplete
|
||||
cursorPosition={header.length}
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
value={header}
|
||||
nestedScrollEnabled={true}
|
||||
onKeyboardOffsetChanged={this.onKeyboardOffsetChanged}
|
||||
offsetY={8}
|
||||
style={style.autocomplete}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(EditChannelInfo);
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
autocomplete: {
|
||||
position: undefined,
|
||||
},
|
||||
autocompleteContainer: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
paddingTop: 30,
|
||||
},
|
||||
errorContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
width: '100%',
|
||||
},
|
||||
errorWrapper: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
inputContainer: {
|
||||
marginTop: 10,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
input: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 14,
|
||||
height: 40,
|
||||
paddingHorizontal: 15,
|
||||
},
|
||||
titleContainer30: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 30,
|
||||
},
|
||||
titleContainer15: {
|
||||
flexDirection: 'row',
|
||||
marginTop: 15,
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 15,
|
||||
},
|
||||
optional: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
fontSize: 14,
|
||||
marginLeft: 5,
|
||||
},
|
||||
helpText: {
|
||||
fontSize: 14,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15,
|
||||
},
|
||||
headerHelpText: {
|
||||
zIndex: -1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
excludeFooter={true}
|
||||
excludeHeader={true}
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior="padding"
|
||||
|
|
@ -25,47 +34,43 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
}
|
||||
<Search
|
||||
autoCapitalize="none"
|
||||
backArrowSize={24}
|
||||
backgroundColor="transparent"
|
||||
blurOnSubmit={false}
|
||||
cancelTitle="Cancel"
|
||||
containerHeight={40}
|
||||
deleteIconSize={20}
|
||||
editable={true}
|
||||
inputHeight={33}
|
||||
inputStyle={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 13,
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onAnimationComplete={[Function]}
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.8)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
keyboardAppearance="light"
|
||||
keyboardShouldPersist={false}
|
||||
keyboardType="default"
|
||||
onAnimationComplete={[Function]}
|
||||
onBlur={[Function]}
|
||||
onCancelButtonPress={[Function]}
|
||||
onChangeText={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
placeholder="Search"
|
||||
placeholderTextColor="rgba(61,60,64,0.5)"
|
||||
returnKeyType="search"
|
||||
searchBarRightMargin={0}
|
||||
searchIconSize={24}
|
||||
showArrow={false}
|
||||
showCancel={true}
|
||||
tintColorDelete="rgba(61,60,64,0.5)"
|
||||
tintColorSearch="rgba(61,60,64,0.8)"
|
||||
titleCancelColor="#3d3c40"
|
||||
value=""
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
|
|
@ -10220,5 +10225,5 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
|||
</KeyboardTrackingView>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Connect(SafeAreaIos)>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme} from 'app/utils/theme';
|
||||
import SearchBar from '@components/search_bar';
|
||||
import {DeviceTypes} from '@constants';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme';
|
||||
|
||||
import EmojiPickerBase, {getStyleSheetFromTheme, SCROLLVIEW_NATIVE_ID} from './emoji_picker_base';
|
||||
|
||||
|
|
@ -38,8 +37,8 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
|
||||
return (
|
||||
<SafeAreaView
|
||||
excludeHeader={true}
|
||||
excludeFooter={true}
|
||||
style={{flex: 1}}
|
||||
edges={['left', 'right']}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
|
|
@ -48,26 +47,24 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
style={styles.flex}
|
||||
>
|
||||
<View style={styles.searchBar}>
|
||||
<View style={padding(isLandscape)}>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.8)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.changeSearchTerm}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={searchTerm}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onAnimationComplete={this.setRebuiltEmojis}
|
||||
/>
|
||||
</View>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.centerChannelColor, 0.8)}
|
||||
tintColorDelete={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
titleCancelColor={theme.centerChannelColor}
|
||||
onChangeText={this.changeSearchTerm}
|
||||
onCancelButtonPress={this.cancelSearch}
|
||||
autoCapitalize='none'
|
||||
value={searchTerm}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onAnimationComplete={this.setRebuiltEmojis}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.container]}>
|
||||
{this.renderListComponent(shorten)}
|
||||
|
|
|
|||
|
|
@ -80,18 +80,6 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
|||
expect(result).toEqual(output);
|
||||
});
|
||||
|
||||
test('should set rebuildEmojis to true when deviceWidth changes', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
expect(instance.rebuildEmojis).toBe(undefined);
|
||||
|
||||
const newDeviceWidth = baseProps.deviceWidth * 2;
|
||||
wrapper.setProps({deviceWidth: newDeviceWidth});
|
||||
|
||||
expect(instance.rebuildEmojis).toBe(true);
|
||||
});
|
||||
|
||||
test('should rebuild emojis emojis when emojis change', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import sectionListGetItemLayout from 'react-native-section-list-get-item-layout'
|
|||
import CompassIcon from '@components/compass_icon';
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {DeviceTypes} from '@constants';
|
||||
import {emptyFunction} from '@utils/general';
|
||||
import {
|
||||
|
|
@ -101,8 +100,9 @@ export default class EmojiPicker extends PureComponent {
|
|||
|
||||
if (this.props.emojis !== prevProps.emojis) {
|
||||
this.rebuildEmojis = true;
|
||||
this.setRebuiltEmojis();
|
||||
}
|
||||
|
||||
this.setRebuiltEmojis();
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
|
|
@ -305,7 +305,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
onPress={() => this.props.onEmojiPress(item)}
|
||||
style={style.flatListRow}
|
||||
>
|
||||
<View style={[style.flatListEmoji, padding(this.props.isLandscape)]}>
|
||||
<View style={style.flatListEmoji}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {incrementEmojiPickerPage} from '@actions/views/emoji';
|
|||
import {getCustomEmojis} from '@mm-redux/actions/emojis';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getDimensions, isLandscape} from '@selectors/device';
|
||||
import {isLandscape} from '@selectors/device';
|
||||
import {selectEmojisByName, selectEmojisBySection} from '@selectors/emojis';
|
||||
|
||||
import EmojiPicker from './emoji_picker';
|
||||
|
|
@ -17,7 +17,6 @@ import EmojiPicker from './emoji_picker';
|
|||
function mapStateToProps(state) {
|
||||
const emojisBySection = selectEmojisBySection(state);
|
||||
const emojis = selectEmojisByName(state);
|
||||
const {deviceWidth} = getDimensions(state);
|
||||
const options = {
|
||||
shouldSort: false,
|
||||
threshold: 0.3,
|
||||
|
|
@ -34,7 +33,6 @@ function mapStateToProps(state) {
|
|||
fuse,
|
||||
emojis,
|
||||
emojisBySection,
|
||||
deviceWidth,
|
||||
isLandscape: isLandscape(state),
|
||||
theme: getTheme(state),
|
||||
customEmojisEnabled: getConfig(state).EnableCustomEmoji === 'true',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ exports[`AttachmentFooter matches snapshot 1`] = `
|
|||
}
|
||||
>
|
||||
<ForwardRef(AnimatedComponentWrapper)
|
||||
edges={
|
||||
Array [
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
|
|
|
|||
|
|
@ -12,13 +12,14 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {RequestStatus} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {DeviceTypes, ViewTypes} from '@constants';
|
||||
import {ViewTypes} from '@constants';
|
||||
import {INDICATOR_BAR_HEIGHT} from '@constants/view';
|
||||
import networkConnectionListener, {checkConnection} from '@utils/network';
|
||||
import {t} from '@utils/i18n';
|
||||
|
|
@ -33,10 +34,11 @@ const {
|
|||
ANDROID_TOP_LANDSCAPE,
|
||||
ANDROID_TOP_PORTRAIT,
|
||||
IOS_TOP_LANDSCAPE,
|
||||
IOS_TOP_PORTRAIT,
|
||||
IOS_INSETS_TOP_PORTRAIT,
|
||||
} = ViewTypes;
|
||||
|
||||
const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView);
|
||||
|
||||
export default class NetworkIndicator extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
|
|
@ -64,12 +66,17 @@ export default class NetworkIndicator extends PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const navBarHeight = Platform.select({
|
||||
android: props.isLandscape ? ANDROID_TOP_LANDSCAPE : ANDROID_TOP_PORTRAIT,
|
||||
ios: props.isLandscape ? IOS_TOP_LANDSCAPE : IOS_INSETS_TOP_PORTRAIT,
|
||||
});
|
||||
|
||||
this.state = {
|
||||
opacity: 0,
|
||||
navBarHeight,
|
||||
};
|
||||
|
||||
const navBar = this.getNavBarHeight(props.isLandscape);
|
||||
this.top = new Animated.Value(navBar - INDICATOR_BAR_HEIGHT);
|
||||
this.top = new Animated.Value(navBarHeight - INDICATOR_BAR_HEIGHT);
|
||||
this.clearNotificationTimeout = null;
|
||||
|
||||
this.backgroundColor = new Animated.Value(0);
|
||||
|
|
@ -83,29 +90,28 @@ export default class NetworkIndicator extends PureComponent {
|
|||
this.mounted = true;
|
||||
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
EventEmitter.on(ViewTypes.CHANNEL_NAV_BAR_CHANGED, this.getNavBarHeight);
|
||||
|
||||
// Attempt to connect when this component mounts
|
||||
// if the websocket is already connected it does not try and connect again
|
||||
this.connect(true);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
currentChannelId: prevChannelId,
|
||||
isLandscape: prevIsLandscape,
|
||||
websocketStatus: previousWebsocketStatus,
|
||||
} = prevProps;
|
||||
const {currentChannelId, isLandscape, websocketErrorCount, websocketStatus} = this.props;
|
||||
const {currentChannelId, websocketErrorCount, websocketStatus} = this.props;
|
||||
|
||||
if (currentChannelId !== prevChannelId && this.clearNotificationTimeout) {
|
||||
clearTimeout(this.clearNotificationTimeout);
|
||||
this.clearNotificationTimeout = null;
|
||||
}
|
||||
|
||||
if (isLandscape !== prevIsLandscape) {
|
||||
const navBar = this.getNavBarHeight(isLandscape);
|
||||
if (prevState.navBarHeight !== this.state.navBarHeight) {
|
||||
const initialTop = websocketErrorCount || previousWebsocketStatus === RequestStatus.FAILURE || previousWebsocketStatus === RequestStatus.NOT_STARTED ? 0 : INDICATOR_BAR_HEIGHT;
|
||||
this.top.setValue(navBar - initialTop);
|
||||
this.top.setValue(this.state.navBarHeight - initialTop);
|
||||
}
|
||||
|
||||
if (this.props.isOnline) {
|
||||
|
|
@ -132,6 +138,7 @@ export default class NetworkIndicator extends PureComponent {
|
|||
stopPeriodicStatusUpdates();
|
||||
this.networkListener.removeEventListener();
|
||||
AppState.removeEventListener('change', this.handleAppStateChange);
|
||||
EventEmitter.off(ViewTypes.CHANNEL_NAV_BAR_CHANGED, this.getNavBarHeight);
|
||||
|
||||
clearTimeout(this.connectionRetryTimeout);
|
||||
this.connectionRetryTimeout = null;
|
||||
|
|
@ -180,7 +187,7 @@ export default class NetworkIndicator extends PureComponent {
|
|||
),
|
||||
Animated.timing(
|
||||
this.top, {
|
||||
toValue: (this.getNavBarHeight() - INDICATOR_BAR_HEIGHT),
|
||||
toValue: (this.state.navBarHeight - INDICATOR_BAR_HEIGHT),
|
||||
duration: 300,
|
||||
delay: 500,
|
||||
useNativeDriver: false,
|
||||
|
|
@ -196,26 +203,8 @@ export default class NetworkIndicator extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
getNavBarHeight = (isLandscape = this.props.isLandscape) => {
|
||||
if (Platform.OS === 'android') {
|
||||
if (isLandscape) {
|
||||
return ANDROID_TOP_LANDSCAPE;
|
||||
}
|
||||
|
||||
return ANDROID_TOP_PORTRAIT;
|
||||
}
|
||||
|
||||
const iPhoneWithInsets = DeviceTypes.IS_IPHONE_WITH_INSETS;
|
||||
|
||||
if (iPhoneWithInsets && isLandscape) {
|
||||
return IOS_TOP_LANDSCAPE;
|
||||
} else if (iPhoneWithInsets) {
|
||||
return IOS_INSETS_TOP_PORTRAIT;
|
||||
} else if (isLandscape && !DeviceTypes.IS_TABLET) {
|
||||
return IOS_TOP_LANDSCAPE;
|
||||
}
|
||||
|
||||
return IOS_TOP_PORTRAIT;
|
||||
getNavBarHeight = (navBarHeight) => {
|
||||
this.setState({navBarHeight});
|
||||
};
|
||||
|
||||
handleWebSocket = (open) => {
|
||||
|
|
@ -329,7 +318,7 @@ export default class NetworkIndicator extends PureComponent {
|
|||
|
||||
Animated.timing(
|
||||
this.top, {
|
||||
toValue: this.getNavBarHeight(),
|
||||
toValue: this.state.navBarHeight,
|
||||
duration: 300,
|
||||
useNativeDriver: false,
|
||||
},
|
||||
|
|
@ -391,14 +380,17 @@ export default class NetworkIndicator extends PureComponent {
|
|||
pointerEvents='none'
|
||||
style={[styles.container, {top: this.top, backgroundColor: background, opacity: this.state.opacity}]}
|
||||
>
|
||||
<Animated.View style={styles.wrapper}>
|
||||
<AnimatedSafeAreaView
|
||||
edges={['left', 'right']}
|
||||
style={styles.wrapper}
|
||||
>
|
||||
<FormattedText
|
||||
defaultMessage={defaultMessage}
|
||||
id={i18nId}
|
||||
style={styles.message}
|
||||
/>
|
||||
{action}
|
||||
</Animated.View>
|
||||
</AnimatedSafeAreaView>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {isDateLine, isStartOfNewMessages} from '@mm-redux/utils/post_list';
|
|||
import {isPostFlagged, isSystemMessage} from '@mm-redux/utils/post_utils';
|
||||
|
||||
import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import Post from './post';
|
||||
|
||||
|
|
@ -86,7 +85,6 @@ function makeMapStateToProps() {
|
|||
theme: getTheme(state),
|
||||
isFlagged: isPostFlagged(post.id, myPreferences),
|
||||
isCommentMention,
|
||||
isLandscape: isLandscape(state),
|
||||
previousPostExists: Boolean(previousPost),
|
||||
beforePrevPostUserId: (beforePrevPost ? beforePrevPost.user_id : null),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import PostHeader from '@components/post_header';
|
|||
import PostProfilePicture from '@components/post_profile_picture';
|
||||
import PostPreHeader from '@components/post_header/post_pre_header';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
import {NavigationTypes} from '@constants';
|
||||
|
||||
|
|
@ -70,7 +69,6 @@ export default class Post extends PureComponent {
|
|||
isCommentMention: PropTypes.bool,
|
||||
location: PropTypes.string,
|
||||
isBot: PropTypes.bool,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
previousPostExists: PropTypes.bool,
|
||||
beforePrevPostUserId: PropTypes.string,
|
||||
};
|
||||
|
|
@ -266,7 +264,6 @@ export default class Post extends PureComponent {
|
|||
skipFlaggedHeader,
|
||||
skipPinnedHeader,
|
||||
location,
|
||||
isLandscape,
|
||||
previousPostExists,
|
||||
beforePrevPostUserId,
|
||||
} = this.props;
|
||||
|
|
@ -327,7 +324,7 @@ export default class Post extends PureComponent {
|
|||
return (
|
||||
<View
|
||||
testID={testID}
|
||||
style={[style.postStyle, highlighted, padding(isLandscape)]}
|
||||
style={[style.postStyle, highlighted]}
|
||||
>
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePress}
|
||||
|
|
|
|||
|
|
@ -217,56 +217,40 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
|||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
"marginLeft": 0,
|
||||
"marginRight": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 11,
|
||||
"marginBottom": 2,
|
||||
"paddingLeft": 10,
|
||||
"paddingTop": 3,
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 11,
|
||||
"marginBottom": 2,
|
||||
"paddingLeft": 10,
|
||||
"paddingTop": 3,
|
||||
"position": "absolute",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderTopColor": "rgba(61,60,64,0.2)",
|
||||
"borderTopWidth": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"paddingBottom": 2,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"alignItems": "flex-end",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderTopColor": "rgba(61,60,64,0.2)",
|
||||
"borderTopWidth": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "center",
|
||||
"paddingBottom": 2,
|
||||
}
|
||||
}
|
||||
testID="post_draft"
|
||||
>
|
||||
|
|
@ -566,13 +550,17 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
|||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
</View>
|
||||
</RNCSafeAreaView>
|
||||
</KeyboardTrackingView>
|
||||
`;
|
||||
|
||||
exports[`PostDraft Should render the ReadOnly for canPost 1`] = `
|
||||
<RCTSafeAreaView
|
||||
emulateUnlessSupported={true}
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.04)",
|
||||
|
|
@ -617,12 +605,16 @@ exports[`PostDraft Should render the ReadOnly for canPost 1`] = `
|
|||
This channel is read-only.
|
||||
</Text>
|
||||
</View>
|
||||
</RCTSafeAreaView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
||||
exports[`PostDraft Should render the ReadOnly for channelIsReadOnly 1`] = `
|
||||
<RCTSafeAreaView
|
||||
emulateUnlessSupported={true}
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.04)",
|
||||
|
|
@ -667,5 +659,5 @@ exports[`PostDraft Should render the ReadOnly for channelIsReadOnly 1`] = `
|
|||
This channel is read-only.
|
||||
</Text>
|
||||
</View>
|
||||
</RCTSafeAreaView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
|
|||
import {Platform, ScrollView, View} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import HWKeyboardEvent from 'react-native-hw-keyboard-event';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import Autocomplete from '@components/autocomplete';
|
||||
import PostInput from '@components/post_draft/post_input';
|
||||
|
|
@ -13,7 +14,6 @@ import QuickActions from '@components/post_draft/quick_actions';
|
|||
import SendAction from '@components/post_draft/send_action';
|
||||
import Typing from '@components/post_draft/typing';
|
||||
import Uploads from '@components/post_draft/uploads';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {CHANNEL_POST_TEXTBOX_CURSOR_CHANGE, CHANNEL_POST_TEXTBOX_VALUE_CHANGE, IS_REACTION_REGEX} from '@constants/post_draft';
|
||||
import {NOTIFY_ALL_MEMBERS} from '@constants/view';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
|
@ -400,8 +400,8 @@ export default class DraftInput extends PureComponent {
|
|||
channelDisplayName,
|
||||
channelId,
|
||||
cursorPositionEvent,
|
||||
isLandscape,
|
||||
files,
|
||||
isLandscape,
|
||||
maxMessageLength,
|
||||
screenId,
|
||||
valueEvent,
|
||||
|
|
@ -417,10 +417,11 @@ export default class DraftInput extends PureComponent {
|
|||
theme={theme}
|
||||
registerTypingAnimation={registerTypingAnimation}
|
||||
/>
|
||||
<View
|
||||
testID={testID}
|
||||
style={[style.inputWrapper, padding(isLandscape)]}
|
||||
<SafeAreaView
|
||||
edges={['left', 'right']}
|
||||
onLayout={this.handleLayout}
|
||||
style={style.inputWrapper}
|
||||
testID={testID}
|
||||
>
|
||||
<ScrollView
|
||||
style={style.inputContainer}
|
||||
|
|
@ -467,7 +468,7 @@ export default class DraftInput extends PureComponent {
|
|||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
{Platform.OS === 'android' &&
|
||||
<Autocomplete
|
||||
cursorPositionEvent={cursorPositionEvent}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ describe('PostInput', () => {
|
|||
handleCommentDraftChanged: jest.fn(),
|
||||
handlePostDraftChanged: jest.fn(),
|
||||
inputEventType: '',
|
||||
isLandscape: false,
|
||||
maxMessageLength: 4000,
|
||||
onPasteFiles: jest.fn(),
|
||||
onSend: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PostDraft ReadOnly Should match snapshot 1`] = `
|
||||
<ForwardRef(SafeAreaView)
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.04)",
|
||||
|
|
@ -46,5 +51,5 @@ exports[`PostDraft ReadOnly Should match snapshot 1`] = `
|
|||
}
|
||||
/>
|
||||
</View>
|
||||
</ForwardRef(SafeAreaView)>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ReactNode} from 'react';
|
||||
import {SafeAreaView, View} from 'react-native';
|
||||
import {View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
|
|
@ -16,10 +17,13 @@ interface ReadOnlyProps {
|
|||
const ReadOnlyChannnel = ({theme}: ReadOnlyProps): ReactNode => {
|
||||
const style = getStyle(theme);
|
||||
return (
|
||||
<SafeAreaView style={style.background}>
|
||||
<SafeAreaView
|
||||
edges={['bottom']}
|
||||
style={style.background}
|
||||
>
|
||||
<View
|
||||
testID='post_draft.read_only'
|
||||
style={style.container}
|
||||
testID='post_draft.read_only'
|
||||
>
|
||||
<CompassIcon
|
||||
name='glasses'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SafeAreaView from '@components/safe_area_view';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {TYPING_VISIBLE, TYPING_HEIGHT} from '@constants/post_draft';
|
||||
|
||||
|
|
@ -95,19 +94,13 @@ export default class Typing extends PureComponent {
|
|||
|
||||
return (
|
||||
<Animated.View style={{bottom: this.typingBottom}}>
|
||||
<SafeAreaView
|
||||
excludeHeader={true}
|
||||
excludeFooter={true}
|
||||
useLandscapeMargin={true}
|
||||
<Text
|
||||
style={style.typing}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
<Text
|
||||
style={style.typing}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{this.renderTyping()}
|
||||
</Text>
|
||||
</SafeAreaView>
|
||||
{this.renderTyping()}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ exports[`MoreMessagesButton should match snapshot 1`] = `
|
|||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"translateY": -138,
|
||||
"translateY": -438,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import ViewTypes, {INDICATOR_BAR_HEIGHT} from '@constants/view';
|
|||
import {makeStyleSheetFromTheme, hexToHue} from '@utils/theme';
|
||||
import {t} from '@utils/i18n';
|
||||
|
||||
const HIDDEN_TOP = -100;
|
||||
const HIDDEN_TOP = -400;
|
||||
const SHOWN_TOP = 0;
|
||||
export const INDICATOR_BAR_FACTOR = Math.abs(INDICATOR_BAR_HEIGHT / (HIDDEN_TOP - SHOWN_TOP));
|
||||
export const MIN_INPUT = 0;
|
||||
|
|
|
|||
|
|
@ -3,19 +3,14 @@
|
|||
exports[`Reactions Should match snapshot with default emojis 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 72,
|
||||
"justifyContent": "space-between",
|
||||
},
|
||||
Object {
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 12,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 72,
|
||||
"justifyContent": "space-between",
|
||||
"paddingHorizontal": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionButton
|
||||
|
|
@ -265,19 +260,14 @@ exports[`Reactions Should match snapshot with default emojis 1`] = `
|
|||
exports[`Reactions Should match snapshot with default emojis 2`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 72,
|
||||
"justifyContent": "space-between",
|
||||
},
|
||||
Object {
|
||||
"paddingLeft": 12,
|
||||
"paddingRight": 12,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 72,
|
||||
"justifyContent": "space-between",
|
||||
"paddingHorizontal": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ReactionButton
|
||||
|
|
|
|||
|
|
@ -3,16 +3,12 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getDimensions, isLandscape} from 'app/selectors/device';
|
||||
import ReactionPicker from './reaction_picker';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {deviceWidth} = getDimensions(state);
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
recentEmojis: state.views.recentEmojis,
|
||||
deviceWidth,
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View,
|
||||
TouchableWithoutFeedback,
|
||||
} from 'react-native';
|
||||
import {View, TouchableWithoutFeedback, useWindowDimensions} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {
|
||||
REACTION_PICKER_HEIGHT,
|
||||
DEFAULT_EMOJIS,
|
||||
|
|
@ -23,88 +19,73 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
|
||||
import ReactionButton from './reaction_button';
|
||||
|
||||
export default class ReactionPicker extends PureComponent {
|
||||
static propTypes = {
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
openReactionScreen: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
recentEmojis: PropTypes.array,
|
||||
deviceWidth: PropTypes.number,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
const ReactionPicker = (props) => {
|
||||
const {theme} = props;
|
||||
const style = getStyleSheet(theme);
|
||||
const {width} = useWindowDimensions();
|
||||
const isSmallDevice = width < SMALL_ICON_BREAKPOINT;
|
||||
|
||||
const handlePress = (emoji) => {
|
||||
props.addReaction(emoji);
|
||||
};
|
||||
|
||||
let containerSize = LARGE_CONTAINER_SIZE;
|
||||
let iconSize = LARGE_ICON_SIZE;
|
||||
|
||||
if (isSmallDevice) {
|
||||
containerSize = SMALL_CONTAINER_SIZE;
|
||||
iconSize = SMALL_ICON_SIZE;
|
||||
}
|
||||
|
||||
handlePress = (emoji) => {
|
||||
this.props.addReaction(emoji);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme,
|
||||
deviceWidth,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
const isSmallDevice = deviceWidth < SMALL_ICON_BREAKPOINT;
|
||||
|
||||
let containerSize = LARGE_CONTAINER_SIZE;
|
||||
let iconSize = LARGE_ICON_SIZE;
|
||||
|
||||
if (isSmallDevice) {
|
||||
containerSize = SMALL_CONTAINER_SIZE;
|
||||
iconSize = SMALL_ICON_SIZE;
|
||||
}
|
||||
|
||||
// Mixing recent emojis with default list and removing duplicates
|
||||
const emojis = Array.from(new Set(this.props.recentEmojis.concat(DEFAULT_EMOJIS))).splice(0, 6);
|
||||
|
||||
const list = emojis.map((emoji) => {
|
||||
return (
|
||||
<ReactionButton
|
||||
key={emoji}
|
||||
theme={theme}
|
||||
addReaction={this.handlePress}
|
||||
emoji={emoji}
|
||||
iconSize={iconSize}
|
||||
containerSize={containerSize}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
let paddingRes = padding(isLandscape, 12);
|
||||
if (!paddingRes) {
|
||||
paddingRes = {
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
};
|
||||
}
|
||||
|
||||
const emojis = Array.from(new Set(props.recentEmojis.concat(DEFAULT_EMOJIS))).splice(0, 6);
|
||||
const list = emojis.map((emoji) => {
|
||||
return (
|
||||
<View style={[style.reactionListContainer, paddingRes]}>
|
||||
{list}
|
||||
<TouchableWithoutFeedback
|
||||
onPress={this.props.openReactionScreen}
|
||||
testID='open.add_reaction.button'
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
style.reactionContainer,
|
||||
{
|
||||
width: containerSize,
|
||||
height: containerSize,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CompassIcon
|
||||
name='emoticon-plus-outline'
|
||||
size={31.2}
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
<ReactionButton
|
||||
key={emoji}
|
||||
theme={theme}
|
||||
addReaction={handlePress}
|
||||
emoji={emoji}
|
||||
iconSize={iconSize}
|
||||
containerSize={containerSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={style.reactionListContainer}>
|
||||
{list}
|
||||
<TouchableWithoutFeedback
|
||||
onPress={props.openReactionScreen}
|
||||
testID='open.add_reaction.button'
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
style.reactionContainer,
|
||||
{
|
||||
width: containerSize,
|
||||
height: containerSize,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CompassIcon
|
||||
name='emoticon-plus-outline'
|
||||
size={31.2}
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ReactionPicker.propTypes = {
|
||||
addReaction: PropTypes.func.isRequired,
|
||||
openReactionScreen: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
recentEmojis: PropTypes.array,
|
||||
};
|
||||
|
||||
export default ReactionPicker;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
|
|
@ -117,6 +98,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
alignItems: 'center',
|
||||
height: REACTION_PICKER_HEIGHT,
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
reactionContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.04),
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import Preferences from '@mm-redux/constants/preferences';
|
|||
describe('Reactions', () => {
|
||||
const baseProps = {
|
||||
addReaction: jest.fn(),
|
||||
deviceWidth: undefined,
|
||||
isLandscape: false,
|
||||
openReactionScreen: jest.fn(),
|
||||
recentEmojis: [],
|
||||
theme: Preferences.THEMES.default,
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SafeAreaIos should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
"marginLeft": 0,
|
||||
"marginRight": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef(AnimatedComponentWrapper)
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#1153ab",
|
||||
"paddingTop": 44,
|
||||
"zIndex": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
|
@ -5,7 +5,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import SafeAreaView from './safe_area_view';
|
||||
import SafeAreaView from './safe_area';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
|
||||
export const paddingHorizontal = (isLandscape, modifier = 0) => {
|
||||
return DeviceTypes.IS_IPHONE_WITH_INSETS && isLandscape ? {paddingHorizontal: ViewTypes.IOS_HORIZONTAL_LANDSCAPE + modifier} : null;
|
||||
};
|
||||
|
||||
export const paddingLeft = (isLandscape, modifier = 0) => {
|
||||
return DeviceTypes.IS_IPHONE_WITH_INSETS && isLandscape ? {paddingLeft: ViewTypes.IOS_HORIZONTAL_LANDSCAPE + modifier} : null;
|
||||
};
|
||||
|
||||
export const paddingRight = (isLandscape, modifier = 0) => {
|
||||
return DeviceTypes.IS_IPHONE_WITH_INSETS && isLandscape ? {paddingRight: ViewTypes.IOS_HORIZONTAL_LANDSCAPE + modifier} : null;
|
||||
};
|
||||
|
||||
export const marginHorizontal = (isLandscape, modifier = 0) => {
|
||||
return DeviceTypes.IS_IPHONE_WITH_INSETS && isLandscape ? {marginHorizontal: ViewTypes.IOS_HORIZONTAL_LANDSCAPE + modifier} : null;
|
||||
};
|
||||
84
app/components/safe_area_view/safe_area.tsx
Normal file
84
app/components/safe_area_view/safe_area.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import type {Theme} from '@mm-redux/types/preferences';
|
||||
|
||||
type SafeAreaProps = {
|
||||
backgroundColor?: string;
|
||||
children: Array<React.ReactChild> | React.ReactChild;
|
||||
excludeHeader?: boolean;
|
||||
excludeFooter?: boolean;
|
||||
excludeLeft?: boolean;
|
||||
excludeRight?: boolean;
|
||||
headerComponent?: React.ReactElement;
|
||||
footerColor?: string;
|
||||
footerComponent?: React.ReactElement;
|
||||
navBarBackgroundColor?: string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const SafeArea = (props: SafeAreaProps) => {
|
||||
const {
|
||||
backgroundColor,
|
||||
excludeFooter,
|
||||
excludeHeader,
|
||||
excludeLeft,
|
||||
excludeRight,
|
||||
footerColor,
|
||||
footerComponent,
|
||||
headerComponent,
|
||||
navBarBackgroundColor,
|
||||
theme,
|
||||
} = props;
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const renderTop = useCallback(() => {
|
||||
if (excludeHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let topColor = theme.sidebarHeaderBg;
|
||||
if (navBarBackgroundColor) {
|
||||
topColor = navBarBackgroundColor;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{backgroundColor: topColor, zIndex: 10, paddingTop: insets.top, paddingLeft: insets.left, paddingRight: insets.right}}>
|
||||
{headerComponent}
|
||||
</View>
|
||||
);
|
||||
}, [insets, props.theme]);
|
||||
|
||||
let bgColor = theme.centerChannelBg;
|
||||
if (backgroundColor) {
|
||||
bgColor = backgroundColor;
|
||||
}
|
||||
|
||||
let bottomColor = theme.centerChannelBg;
|
||||
if (footerColor) {
|
||||
bottomColor = footerColor;
|
||||
}
|
||||
|
||||
let bottomInset = insets.bottom;
|
||||
if (excludeFooter) {
|
||||
bottomInset = 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, backgroundColor: bgColor}}>
|
||||
{renderTop()}
|
||||
<View style={{flex: 1, marginLeft: excludeLeft ? 0 : insets.left, marginRight: excludeRight ? 0 : insets.right}}>
|
||||
{props.children}
|
||||
</View>
|
||||
<View style={{marginBottom: bottomInset, backgroundColor: bottomColor}}>
|
||||
{footerComponent}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SafeArea;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
const SafeAreaAndroid = (props) => {
|
||||
return props.children;
|
||||
};
|
||||
|
||||
export default SafeAreaAndroid;
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Animated, Dimensions, Keyboard, NativeModules, View} from 'react-native';
|
||||
import SafeArea from 'react-native-safe-area';
|
||||
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
const {StatusBarManager} = NativeModules;
|
||||
const {PORTRAIT, LANDSCAPE} = ViewTypes;
|
||||
|
||||
export default class SafeAreaIos extends PureComponent {
|
||||
static propTypes = {
|
||||
backgroundColor: PropTypes.string,
|
||||
children: PropTypes.node.isRequired,
|
||||
excludeHeader: PropTypes.bool,
|
||||
excludeFooter: PropTypes.bool,
|
||||
footerColor: PropTypes.string,
|
||||
footerComponent: PropTypes.node,
|
||||
keyboardOffset: PropTypes.number.isRequired,
|
||||
navBarBackgroundColor: PropTypes.string,
|
||||
headerComponent: PropTypes.node,
|
||||
theme: PropTypes.object.isRequired,
|
||||
useLandscapeMargin: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
keyboardOffset: 0,
|
||||
useLandscapeMargin: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const insetTop = DeviceTypes.IS_IPHONE_WITH_INSETS ? 44 : 20;
|
||||
let insetBottom = 0;
|
||||
if ((DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) && props.excludeFooter) {
|
||||
insetBottom = 20;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
keyboard: false,
|
||||
safeAreaInsets: {
|
||||
top: insetTop,
|
||||
left: 0,
|
||||
bottom: insetBottom,
|
||||
right: 0,
|
||||
},
|
||||
};
|
||||
|
||||
this.topBarHeight = new Animated.Value(insetTop);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
|
||||
Dimensions.addEventListener('change', this.getSafeAreaInsets);
|
||||
EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets);
|
||||
if (EphemeralStore.safeAreaInsets[PORTRAIT] === null || EphemeralStore.safeAreaInsets[LANDSCAPE] === null) {
|
||||
SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
}
|
||||
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
|
||||
|
||||
this.getSafeAreaInsets();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Dimensions.removeEventListener('change', this.getSafeAreaInsets);
|
||||
EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets);
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
getSafeAreaInsets = async (dimensions) => {
|
||||
this.getStatusBarHeight();
|
||||
|
||||
if (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) {
|
||||
const window = dimensions?.window || Dimensions.get('window');
|
||||
const orientation = window.width > window.height ? LANDSCAPE : PORTRAIT;
|
||||
const {safeAreaInsets} = await SafeArea.getSafeAreaInsetsForRootView();
|
||||
this.setSafeAreaInsets(safeAreaInsets, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
setSafeAreaInsets = (safeAreaInsets, orientation) => {
|
||||
if (EphemeralStore.safeAreaInsets[orientation] === null) {
|
||||
EphemeralStore.safeAreaInsets[orientation] = safeAreaInsets;
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
safeAreaInsets: EphemeralStore.safeAreaInsets[orientation],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBarHeight = () => {
|
||||
try {
|
||||
StatusBarManager.getHeight(
|
||||
(statusBarFrameData) => {
|
||||
if (this.mounted) {
|
||||
if (statusBarFrameData.height === 0) {
|
||||
this.hideTopBar();
|
||||
} else {
|
||||
this.showTopBar();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// not needed
|
||||
}
|
||||
};
|
||||
|
||||
onSafeAreaInsetsForRootViewChange = ({safeAreaInsets}) => {
|
||||
if (EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null) {
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) {
|
||||
this.getStatusBarHeight();
|
||||
|
||||
const {width, height} = Dimensions.get('window');
|
||||
const orientation = width > height ? LANDSCAPE : PORTRAIT;
|
||||
this.setSafeAreaInsets(safeAreaInsets, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
keyboardWillHide = () => {
|
||||
this.setState({keyboard: false});
|
||||
};
|
||||
|
||||
keyboardWillShow = () => {
|
||||
this.setState({keyboard: true});
|
||||
};
|
||||
|
||||
hideTopBar = () => {
|
||||
Animated.timing(this.topBarHeight, {
|
||||
toValue: 10,
|
||||
duration: 350,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
}
|
||||
|
||||
showTopBar = () => {
|
||||
Animated.timing(this.topBarHeight, {
|
||||
toValue: this.state.safeAreaInsets.top,
|
||||
duration: 350,
|
||||
useNativeDriver: false,
|
||||
}).start();
|
||||
}
|
||||
|
||||
renderTopBar = () => {
|
||||
const {headerComponent, excludeHeader, navBarBackgroundColor, theme} = this.props;
|
||||
|
||||
if (excludeHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let topColor = theme.sidebarHeaderBg;
|
||||
if (navBarBackgroundColor) {
|
||||
topColor = navBarBackgroundColor;
|
||||
}
|
||||
|
||||
if (headerComponent) {
|
||||
return (
|
||||
<Animated.View
|
||||
style={{
|
||||
backgroundColor: topColor,
|
||||
height: this.topBarHeight,
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
{headerComponent}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={{
|
||||
backgroundColor: topColor,
|
||||
paddingTop: this.topBarHeight,
|
||||
zIndex: 10,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {backgroundColor, children, excludeFooter, footerColor, footerComponent, keyboardOffset, theme, useLandscapeMargin} = this.props;
|
||||
const {keyboard, safeAreaInsets} = this.state;
|
||||
|
||||
let bgColor = theme.centerChannelBg;
|
||||
if (backgroundColor) {
|
||||
bgColor = backgroundColor;
|
||||
}
|
||||
|
||||
let bottomColor = theme.centerChannelBg;
|
||||
if (footerColor) {
|
||||
bottomColor = footerColor;
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
if (keyboardOffset && mattermostManaged.hasSafeAreaInsets) {
|
||||
offset = keyboardOffset;
|
||||
}
|
||||
|
||||
let bottomInset = safeAreaInsets.bottom;
|
||||
if (excludeFooter) {
|
||||
bottomInset = 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: bgColor,
|
||||
marginLeft: useLandscapeMargin ? safeAreaInsets.left : 0,
|
||||
marginRight: useLandscapeMargin ? safeAreaInsets.right : 0,
|
||||
}}
|
||||
>
|
||||
{this.renderTopBar()}
|
||||
{children}
|
||||
<View style={{height: keyboard ? offset : bottomInset, backgroundColor: bottomColor}}>
|
||||
{footerComponent}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,305 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import SafeArea from 'react-native-safe-area';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
import SafeAreaIos from './safe_area_view.ios';
|
||||
|
||||
const {PORTRAIT, LANDSCAPE} = ViewTypes;
|
||||
|
||||
describe('SafeAreaIos', () => {
|
||||
const baseProps = {
|
||||
children: [],
|
||||
keyboardOffset: 100,
|
||||
useLandscapeMargin: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
const TEST_INSETS_1 = {
|
||||
safeAreaInsets: {
|
||||
top: 123,
|
||||
left: 123,
|
||||
bottom: 123,
|
||||
right: 123,
|
||||
},
|
||||
};
|
||||
|
||||
const TEST_INSETS_2 = {
|
||||
safeAreaInsets: {
|
||||
top: 456,
|
||||
left: 456,
|
||||
bottom: 456,
|
||||
right: 456,
|
||||
},
|
||||
};
|
||||
|
||||
const PORTRAIT_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 111,
|
||||
left: 111,
|
||||
bottom: 111,
|
||||
right: 111,
|
||||
},
|
||||
};
|
||||
|
||||
const LANDSCAPE_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 222,
|
||||
left: 222,
|
||||
bottom: 222,
|
||||
right: 222,
|
||||
},
|
||||
};
|
||||
|
||||
const IGNORED_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 333,
|
||||
left: 333,
|
||||
bottom: 333,
|
||||
right: 333,
|
||||
},
|
||||
};
|
||||
|
||||
SafeArea.getSafeAreaInsetsForRootView = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve(TEST_INSETS_1);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
EphemeralStore.safeAreaInsets = {
|
||||
[PORTRAIT]: null,
|
||||
[LANDSCAPE]: null,
|
||||
};
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should get safe area insets on mount if DeviceTypes.IS_IPHONE_WITH_INSETS is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(SafeArea.getSafeAreaInsetsForRootView).toHaveBeenCalled();
|
||||
await SafeArea.getSafeAreaInsetsForRootView();
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should get safe area insets on mount if mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(SafeArea.getSafeAreaInsetsForRootView).toHaveBeenCalled();
|
||||
await SafeArea.getSafeAreaInsetsForRootView();
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should not get safe area insets on mount if neither DeviceTypes.IS_IPHONE_WITH_INSET nor mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(SafeArea.getSafeAreaInsetsForRootView).not.toHaveBeenCalled();
|
||||
await SafeArea.getSafeAreaInsetsForRootView();
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change if mounted and DeviceTypes.IS_IPHONE_WITH_INSETS is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change if mounted and mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should not set safe area insets on change if mounted and neither DeviceTypes.IS_IPHONE_WITH_INSETS nor mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change not mounted', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.mounted = false;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
const orientation = PORTRAIT;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(PORTRAIT_INSETS.safeAreaInsets, orientation);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets from EphemeralStore', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = PORTRAIT_INSETS.safeAreaInsets;
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
|
||||
const orientation = PORTRAIT;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, orientation);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
const orientation = LANDSCAPE;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(LANDSCAPE_INSETS.safeAreaInsets, orientation);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets from EphemeralStore', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = LANDSCAPE_INSETS.safeAreaInsets;
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
|
||||
const orientation = LANDSCAPE;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, orientation);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should add safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are not set', async () => {
|
||||
const addEventListener = jest.spyOn(SafeArea, 'addEventListener');
|
||||
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
let wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
let instance = wrapper.instance();
|
||||
expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
addEventListener.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
addEventListener.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
expect(addEventListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should remove safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are set', async () => {
|
||||
const removeEventListener = jest.spyOn(SafeArea, 'removeEventListener');
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).not.toHaveBeenCalled();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).not.toHaveBeenCalled();
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
});
|
||||
});
|
||||
|
|
@ -28,10 +28,6 @@ const DRAGGING = 'Dragging';
|
|||
const SETTLING = 'Settling';
|
||||
const emptyObject = {};
|
||||
|
||||
export const DRAWER_INITIAL_OFFSET = 40;
|
||||
export const TABLET_WIDTH = 250;
|
||||
|
||||
|
||||
export default class DrawerLayout extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
57
app/components/sidebars/drawer_layout/index.tsx
Normal file
57
app/components/sidebars/drawer_layout/index.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import DrawerLayout from './drawer_layout';
|
||||
|
||||
export const DRAWER_INITIAL_OFFSET = 40;
|
||||
export const TABLET_WIDTH = 250;
|
||||
|
||||
interface DrawerLayoutAdapterProps {
|
||||
children: any;
|
||||
drawerBackgroundColor?: string;
|
||||
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open';
|
||||
drawerPosition?: 'left' | 'right';
|
||||
drawerWidth: number;
|
||||
keyboardDismissMode?: 'none' | 'on-drag';
|
||||
forwardRef: any;
|
||||
isTablet: boolean;
|
||||
onDrawerClose?: () => void;
|
||||
onDrawerOpen?: () => void;
|
||||
onDrawerSlide?: (event: {nativeEvent: {offset: number}}) => void;
|
||||
onDrawerStateChanged?: () => 'Idle' | 'Dragging' | 'Settling';
|
||||
renderNavigationView: (drawerWidth: number) => any;
|
||||
statusBarBackgroundColor?: string;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const DrawerLayoutAdapter = (props: DrawerLayoutAdapterProps) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const horizontal = insets.left + insets.right;
|
||||
|
||||
return (
|
||||
<DrawerLayout
|
||||
drawerBackgroundColor={props.drawerBackgroundColor}
|
||||
drawerLockMode={props.drawerLockMode}
|
||||
drawerPosition={props.drawerPosition}
|
||||
drawerWidth={props.drawerWidth - horizontal}
|
||||
isTablet={props.isTablet}
|
||||
keyboardDismissMode={props.keyboardDismissMode}
|
||||
onDrawerClose={props.onDrawerClose}
|
||||
onDrawerOpen={props.onDrawerOpen}
|
||||
onDrawerSlide={props.onDrawerSlide}
|
||||
onDrawerStateChanged={props.onDrawerStateChanged}
|
||||
ref={props.forwardRef}
|
||||
renderNavigationView={props.renderNavigationView}
|
||||
statusBarBackgroundColor={props.statusBarBackgroundColor}
|
||||
testID={props.testID}
|
||||
useNativeAnimations={true}
|
||||
>
|
||||
{props.children}
|
||||
</DrawerLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawerLayoutAdapter;
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MainSidebar should match, full snapshot 1`] = `
|
||||
<DrawerLayout
|
||||
drawerPosition="left"
|
||||
<DrawerLayoutAdapter
|
||||
drawerWidth={710}
|
||||
forwardRef={
|
||||
Object {
|
||||
"current": null,
|
||||
}
|
||||
}
|
||||
isTablet={false}
|
||||
onDrawerClose={[Function]}
|
||||
onDrawerOpen={[Function]}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ exports[`ChannelItem should match snapshot 1`] = `
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -117,7 +116,6 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -230,7 +228,6 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -343,7 +340,6 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -456,7 +452,6 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -559,7 +554,6 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -662,7 +656,6 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -769,7 +762,6 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
|||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
@ -874,7 +866,6 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
Object {
|
||||
"opacity": 0.5,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import Badge from 'app/components/badge';
|
||||
import ChannelIcon from 'app/components/channel_icon';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
|
|
@ -37,7 +36,6 @@ export default class ChannelItem extends PureComponent {
|
|||
unreadMsgs: PropTypes.number.isRequired,
|
||||
isSearchResult: PropTypes.bool,
|
||||
isBot: PropTypes.bool.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -78,7 +76,6 @@ export default class ChannelItem extends PureComponent {
|
|||
isSearchResult,
|
||||
channel,
|
||||
isBot,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
// Only ever show an archived channel if it's the currently viewed channel.
|
||||
|
|
@ -173,7 +170,7 @@ export default class ChannelItem extends PureComponent {
|
|||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<View style={[style.container, mutedStyle, padding(isLandscape)]}>
|
||||
<View style={[style.container, mutedStyle]}>
|
||||
{extraBorder}
|
||||
<View style={[style.item, extraItemStyle]}>
|
||||
{icon}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ describe('ChannelItem', () => {
|
|||
unreadMsgs: 1,
|
||||
isSearchResult: false,
|
||||
isBot: false,
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ import {getTheme, getTeammateNameDisplaySetting} from '@mm-redux/selectors/entit
|
|||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserIdFromChannelName, isChannelMuted} from '@mm-redux/utils/channel_utils';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {getDraftForChannel} from 'app/selectors/views';
|
||||
import {isGuest as isGuestUser} from 'app/utils/users';
|
||||
import {getDraftForChannel} from '@selectors/views';
|
||||
import {isGuest as isGuestUser} from '@utils/users';
|
||||
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
|
|
@ -82,7 +81,6 @@ function makeMapStateToProps() {
|
|||
isBot,
|
||||
isChannelMuted: isChannelMuted(member),
|
||||
isGuest,
|
||||
isLandscape: isLandscape(state),
|
||||
isManualUnread: isManuallyUnread(state, ownProps.channelId),
|
||||
mentions: member ? member.mention_count : 0,
|
||||
shouldHideChannel,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import SearchBar from '@components/search_bar';
|
||||
import {paddingLeft as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {ViewTypes} from '@constants';
|
||||
import {
|
||||
changeOpacity,
|
||||
|
|
@ -32,7 +32,6 @@ export default class ChannelsList extends PureComponent {
|
|||
onSearchStart: PropTypes.func.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
onShowTeams: PropTypes.func,
|
||||
};
|
||||
|
||||
|
|
@ -93,11 +92,7 @@ export default class ChannelsList extends PureComponent {
|
|||
|
||||
render() {
|
||||
const {intl} = this.context;
|
||||
const {
|
||||
onShowTeams,
|
||||
theme,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const {onShowTeams, theme} = this.props;
|
||||
|
||||
const {searching, term} = this.state;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
|
@ -130,7 +125,7 @@ export default class ChannelsList extends PureComponent {
|
|||
|
||||
const title = (
|
||||
<View
|
||||
style={[styles.searchContainer, padding(isLandscape)]}
|
||||
style={styles.searchContainer}
|
||||
>
|
||||
<SearchBar
|
||||
ref={this.setSearchBarRef}
|
||||
|
|
@ -158,7 +153,8 @@ export default class ChannelsList extends PureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
<SafeAreaView
|
||||
edges={['left']}
|
||||
style={styles.container}
|
||||
testID='channels.list'
|
||||
>
|
||||
|
|
@ -166,7 +162,7 @@ export default class ChannelsList extends PureComponent {
|
|||
{title}
|
||||
</View>
|
||||
{list}
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ import {
|
|||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import ChannelItem from '@components/sidebars/main/channels_list/channel_item';
|
||||
import {ListTypes} from '@constants';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {sortChannelsByDisplayName} from '@mm-redux/utils/channel_utils';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {paddingLeft} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {t} from '@utils/i18n';
|
||||
|
||||
const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS;
|
||||
|
||||
|
|
@ -50,7 +49,6 @@ class FilteredList extends Component {
|
|||
styles: PropTypes.object.isRequired,
|
||||
term: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -354,23 +352,21 @@ class FilteredList extends Component {
|
|||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {intl, isLandscape, styles} = this.props;
|
||||
const {intl, styles} = this.props;
|
||||
const {
|
||||
defaultMessage,
|
||||
id,
|
||||
} = section;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={[styles.titleContainer, paddingLeft(isLandscape)]}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
<View style={styles.separatorContainer}>
|
||||
<View style={styles.separator}/>
|
||||
</View>
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
<View style={styles.separatorContainer}>
|
||||
<View style={styles.separator}/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import {getConfig} from '@mm-redux/selectors/entities/general';
|
|||
import {getCurrentTeam} from '@mm-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId, getProfilesInCurrentTeam, getUsers, getUserIdsInChannels, getUserStatuses} from '@mm-redux/selectors/entities/users';
|
||||
import {getDirectShowPreferences, getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from '@selectors/device';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
|
||||
|
|
@ -127,7 +126,6 @@ function mapStateToProps(state) {
|
|||
pastDirectMessages: pastDirectMessages(state),
|
||||
restrictDms,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import ChannelsList from './channels_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {DeviceTypes, ViewTypes} from '@constants';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {
|
||||
getSortedFavoriteChannelIds,
|
||||
|
|
@ -19,9 +20,6 @@ import {getConfig, getLicense, hasNewPermissions} from '@mm-redux/selectors/enti
|
|||
import {haveITeamPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import Permissions from '@mm-redux/constants/permissions';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
|
||||
import List from './list';
|
||||
|
||||
const filterZeroUnreads = memoizeResult((sections) => {
|
||||
|
|
@ -71,7 +69,6 @@ function mapStateToProps(state) {
|
|||
theme: getTheme(state),
|
||||
unreadChannelIds,
|
||||
orderedChannelIds,
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import {debounce} from '@mm-redux/actions/helpers';
|
|||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ChannelItem from '@components/sidebars/main/channels_list/channel_item';
|
||||
import {paddingLeft} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {DeviceTypes, ListTypes, NavigationTypes} from '@constants';
|
||||
import {SidebarSectionTypes} from '@constants/view';
|
||||
|
||||
|
|
@ -48,7 +47,6 @@ export default class List extends PureComponent {
|
|||
styles: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
orderedChannelIds: PropTypes.array.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -326,24 +324,22 @@ export default class List extends PureComponent {
|
|||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {styles, isLandscape} = this.props;
|
||||
const {styles} = this.props;
|
||||
const {intl} = this.context;
|
||||
const {action, defaultMessage, id} = section;
|
||||
|
||||
const anchor = (id === 'sidebar.types.recent' || id === 'mobile.channel_list.channels');
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={[styles.titleContainer, paddingLeft(isLandscape)]}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
<View style={styles.separatorContainer}>
|
||||
<View style={styles.separator}/>
|
||||
</View>
|
||||
{action && this.renderSectionAction(styles, action, anchor, id)}
|
||||
<View style={styles.titleContainer}>
|
||||
<Text style={styles.title}>
|
||||
{intl.formatMessage({id, defaultMessage}).toUpperCase()}
|
||||
</Text>
|
||||
<View style={styles.separatorContainer}>
|
||||
<View style={styles.separator}/>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
{action && this.renderSectionAction(styles, action, anchor, id)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ import {Dimensions, Keyboard} from 'react-native';
|
|||
import {intlShape} from 'react-intl';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
|
||||
import DrawerLayout, {DRAWER_INITIAL_OFFSET, TABLET_WIDTH} from '@components/sidebars/drawer_layout/index.tsx';
|
||||
import {DeviceTypes, NavigationTypes} from '@constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import DrawerLayout, {DRAWER_INITIAL_OFFSET, TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
|
||||
import {DeviceTypes, NavigationTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
import MainSidebarBase from './main_sidebar_base';
|
||||
|
|
@ -125,7 +124,7 @@ export default class MainSidebarIOS extends MainSidebarBase {
|
|||
return (
|
||||
<DrawerLayout
|
||||
testID={this.props.testID}
|
||||
ref={this.drawerRef}
|
||||
forwardRef={this.drawerRef}
|
||||
renderNavigationView={this.renderNavigationView}
|
||||
onDrawerClose={this.handleDrawerClose}
|
||||
onDrawerOpen={this.handleDrawerOpen}
|
||||
|
|
@ -137,4 +136,4 @@ export default class MainSidebarIOS extends MainSidebarBase {
|
|||
</DrawerLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@
|
|||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {DeviceTypes} from '@constants';
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
|
||||
import MainSidebar from './main_sidebar.ios';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
|
@ -31,27 +30,27 @@ describe('MainSidebar', () => {
|
|||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match, full snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<MainSidebar {...baseProps}/>,
|
||||
const loadShallow = (props) => {
|
||||
return shallow(
|
||||
<MainSidebar {...(props || baseProps)}/>,
|
||||
);
|
||||
};
|
||||
|
||||
test('should match, full snapshot', () => {
|
||||
const wrapper = loadShallow();
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not set the permanentSidebar state if not Tablet', () => {
|
||||
const wrapper = shallow(
|
||||
<MainSidebar {...baseProps}/>,
|
||||
);
|
||||
const wrapper = loadShallow();
|
||||
|
||||
wrapper.instance().handlePermanentSidebar();
|
||||
expect(wrapper.state('permanentSidebar')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should set the permanentSidebar state if Tablet', async () => {
|
||||
const wrapper = shallow(
|
||||
<MainSidebar {...baseProps}/>,
|
||||
);
|
||||
const wrapper = loadShallow();
|
||||
|
||||
DeviceTypes.IS_TABLET = true;
|
||||
|
||||
|
|
@ -71,9 +70,7 @@ describe('MainSidebar', () => {
|
|||
theme,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<MainSidebar {...props}/>,
|
||||
);
|
||||
const wrapper = loadShallow(props);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.render = jest.fn();
|
||||
|
|
@ -82,13 +79,4 @@ describe('MainSidebar', () => {
|
|||
wrapper.setProps({theme: newTheme});
|
||||
expect(instance.render).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should render main sidebar below PostList for iOS', () => {
|
||||
const wrapper = shallow(
|
||||
<MainSidebar {...baseProps}/>,
|
||||
);
|
||||
const drawer = wrapper.dive().childAt(1);
|
||||
const drawerStyle = drawer.props().style.reduce((acc, obj) => ({...acc, ...obj}));
|
||||
expect(drawerStyle).toHaveProperty('zIndex', 0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,20 +10,18 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import SafeAreaView from '@components/safe_area_view';
|
||||
import {NavigationTypes, WebsocketEvents} from '@constants';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import {NavigationTypes, WebsocketEvents} from 'app/constants';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {t} from '@utils/i18n';
|
||||
import tracker from '@utils/time_tracker';
|
||||
import telemetry from '@telemetry/';
|
||||
|
||||
import ChannelsList from './channels_list';
|
||||
import DrawerSwiper from './drawer_swiper';
|
||||
import TeamsList from './teams_list';
|
||||
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
export default class MainSidebarBase extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
|
|
@ -246,6 +244,8 @@ export default class MainSidebarBase extends Component {
|
|||
return (
|
||||
<SafeAreaView
|
||||
excludeFooter={true}
|
||||
excludeLeft={true}
|
||||
excludeRight={true}
|
||||
navBarBackgroundColor={theme.sidebarBg}
|
||||
backgroundColor={theme.sidebarHeaderBg}
|
||||
footerColor={theme.sidebarBg}
|
||||
|
|
|
|||
|
|
@ -13,19 +13,19 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {showModal} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {DeviceTypes, ListTypes, ViewTypes} from '@constants';
|
||||
import {getCurrentServerUrl} from '@init/credentials';
|
||||
import telemetry from '@telemetry';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {removeProtocol} from '@utils/url';
|
||||
import tracker from '@utils/time_tracker';
|
||||
|
||||
import {getCurrentServerUrl} from 'app/init/credentials';
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
import TeamsListItem from './teams_list_item';
|
||||
|
||||
const {ANDROID_TOP_PORTRAIT} = ViewTypes;
|
||||
|
|
@ -155,7 +155,10 @@ export default class TeamsList extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<SafeAreaView
|
||||
edges={['left']}
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.headerContainer}>
|
||||
<FormattedText
|
||||
id='mobile.drawer.teamsTitle'
|
||||
|
|
@ -172,7 +175,7 @@ export default class TeamsList extends PureComponent {
|
|||
keyExtractor={this.keyExtractor}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import {connect} from 'react-redux';
|
|||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId, getTeam, makeGetBadgeCountForTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import TeamsListItem from './teams_list_item.js';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
|
|
@ -22,7 +20,6 @@ function makeMapStateToProps() {
|
|||
mentionCount: getMentionCount(state, ownProps.teamId),
|
||||
name: team.name,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import {
|
|||
import Badge from '@components/badge';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TeamIcon from '@components/team_icon';
|
||||
import {paddingLeft as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
|
|
@ -26,7 +25,6 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
selectTeam: PropTypes.func.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
selectTeam = preventDoubleTap(() => {
|
||||
|
|
@ -42,7 +40,6 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
name,
|
||||
teamId,
|
||||
theme,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -79,7 +76,7 @@ export default class TeamsListItem extends React.PureComponent {
|
|||
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
|
||||
onPress={this.selectTeam}
|
||||
>
|
||||
<View style={[styles.teamContainer, padding(isLandscape)]}>
|
||||
<View style={styles.teamContainer}>
|
||||
<View>
|
||||
<TeamIcon
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SettingsSidebar should match snapshot 1`] = `
|
||||
<DrawerLayout
|
||||
<DrawerLayoutAdapter
|
||||
drawerPosition="right"
|
||||
drawerWidth={710}
|
||||
isTablet={false}
|
||||
forwardRef={
|
||||
Object {
|
||||
"current": null,
|
||||
}
|
||||
}
|
||||
onDrawerClose={[Function]}
|
||||
onDrawerOpen={[Function]}
|
||||
renderNavigationView={[Function]}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ export default class SettingsDrawerAndroid extends SettingsSidebarBase {
|
|||
this.goToEditProfileScreen(intl);
|
||||
});
|
||||
|
||||
goToFlagged = preventDoubleTap(() => {
|
||||
goToSaved = preventDoubleTap(() => {
|
||||
const {intl} = this.providerRef.getChildContext();
|
||||
this.goToFlaggedScreen(intl);
|
||||
this.goToSavedPostsScreen(intl);
|
||||
});
|
||||
|
||||
goToMentions = preventDoubleTap(() => {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import {Dimensions, Keyboard, View} from 'react-native';
|
|||
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import DrawerLayout, {DRAWER_INITIAL_OFFSET, TABLET_WIDTH} from 'app/components/sidebars/drawer_layout';
|
||||
import {DeviceTypes, NavigationTypes} from 'app/constants';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import SafeAreaView from '@components/safe_area_view';
|
||||
import DrawerLayout, {DRAWER_INITIAL_OFFSET, TABLET_WIDTH} from '@components/sidebars/drawer_layout/index.tsx';
|
||||
import {DeviceTypes, NavigationTypes} from '@constants';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import SettingsSidebarBase from './settings_sidebar_base';
|
||||
|
||||
|
|
@ -23,6 +23,8 @@ export default class SettingsDrawer extends SettingsSidebarBase {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.drawerRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
deviceWidth: Dimensions.get('window').width,
|
||||
openDrawerOffset: DRAWER_INITIAL_OFFSET,
|
||||
|
|
@ -42,26 +44,22 @@ export default class SettingsDrawer extends SettingsSidebarBase {
|
|||
Dimensions.removeEventListener('change', this.handleDimensions);
|
||||
}
|
||||
|
||||
setDrawerRef = (ref) => {
|
||||
this.drawerRef = ref;
|
||||
}
|
||||
|
||||
confirmReset = (status) => {
|
||||
const {intl} = this.context;
|
||||
this.confirmResetBase(status, intl);
|
||||
};
|
||||
|
||||
closeSettingsSidebar = () => {
|
||||
if (this.drawerRef && this.drawerOpened) {
|
||||
this.drawerRef.closeDrawer();
|
||||
if (this.drawerRef.current && this.drawerOpened) {
|
||||
this.drawerRef.current.closeDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
open = () => {
|
||||
EventEmitter.emit(NavigationTypes.BLUR_POST_DRAFT);
|
||||
|
||||
if (this.drawerRef && !this.drawerOpened) {
|
||||
this.drawerRef.openDrawer();
|
||||
if (this.drawerRef.current && !this.drawerOpened) {
|
||||
this.drawerRef.current.openDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -93,9 +91,9 @@ export default class SettingsDrawer extends SettingsSidebarBase {
|
|||
this.goToEditProfileScreen(intl);
|
||||
});
|
||||
|
||||
goToFlagged = preventDoubleTap(() => {
|
||||
goToSaved = preventDoubleTap(() => {
|
||||
const {intl} = this.context;
|
||||
this.goToFlaggedScreen(intl);
|
||||
this.goToSavedPostsScreen(intl);
|
||||
});
|
||||
|
||||
goToMentions = preventDoubleTap(() => {
|
||||
|
|
@ -120,6 +118,8 @@ export default class SettingsDrawer extends SettingsSidebarBase {
|
|||
return (
|
||||
<SafeAreaView
|
||||
backgroundColor={theme.centerChannelBg}
|
||||
excludeLeft={true}
|
||||
excludeRight={true}
|
||||
navBarBackgroundColor={theme.centerChannelBg}
|
||||
footerColor={theme.centerChannelBg}
|
||||
footerComponent={<View style={style.container}/>}
|
||||
|
|
@ -138,12 +138,12 @@ export default class SettingsDrawer extends SettingsSidebarBase {
|
|||
|
||||
return (
|
||||
<DrawerLayout
|
||||
ref={this.setDrawerRef}
|
||||
drawerPosition='right'
|
||||
drawerWidth={drawerWidth}
|
||||
forwardRef={this.drawerRef}
|
||||
renderNavigationView={this.renderNavigationView}
|
||||
onDrawerClose={this.handleDrawerClose}
|
||||
onDrawerOpen={this.handleDrawerOpen}
|
||||
drawerPosition='right'
|
||||
drawerWidth={drawerWidth}
|
||||
useNativeAnimations={true}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -92,9 +92,9 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
goToFlaggedScreen = (intl) => {
|
||||
goToSavedPostsScreen = (intl) => {
|
||||
this.openModal(
|
||||
'FlaggedPosts',
|
||||
'SavedPosts',
|
||||
intl.formatMessage({id: 'search_header.title3', defaultMessage: 'Saved Messages'}),
|
||||
);
|
||||
};
|
||||
|
|
@ -228,7 +228,7 @@ export default class SettingsSidebarBase extends PureComponent {
|
|||
defaultMessage='Saved Messages'
|
||||
i18nId='search_header.title3'
|
||||
iconName='bookmark-outline'
|
||||
onPress={this.goToFlagged}
|
||||
onPress={this.goToSaved}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ export default class SlideUpPanel extends PureComponent {
|
|||
const styles = getStyleSheet(theme);
|
||||
|
||||
const translateStyle = {
|
||||
alignItems: 'center',
|
||||
transform: [{translateY: this.translateY}],
|
||||
};
|
||||
const backdropStyle = {
|
||||
|
|
@ -289,32 +290,34 @@ export default class SlideUpPanel extends PureComponent {
|
|||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
{headerComponent}
|
||||
<PanGestureHandler
|
||||
ref={this.panRef}
|
||||
simultaneousHandlers={[this.scrollRef, this.masterRef]}
|
||||
waitFor={this.headerRef}
|
||||
shouldCancelWhenOutside={false}
|
||||
onGestureEvent={this.onGestureEvent}
|
||||
onHandlerStateChange={this.onHandlerStateChange}
|
||||
>
|
||||
<Animated.View style={[styles.container, !headerComponent && styles.border]}>
|
||||
<NativeViewGestureHandler
|
||||
ref={this.scrollRef}
|
||||
waitFor={this.masterRef}
|
||||
simultaneousHandlers={this.panRef}
|
||||
>
|
||||
<Animated.ScrollView
|
||||
ref={this.scrollViewRef}
|
||||
bounces={false}
|
||||
onScrollBeginDrag={this.onRegisterLastScroll}
|
||||
scrollEventThrottle={1}
|
||||
style={{marginBottom: (this.props.marginFromTop + BOTTOM_MARGIN)}}
|
||||
<View style={styles.wrapper}>
|
||||
<PanGestureHandler
|
||||
ref={this.panRef}
|
||||
simultaneousHandlers={[this.scrollRef, this.masterRef]}
|
||||
waitFor={this.headerRef}
|
||||
shouldCancelWhenOutside={false}
|
||||
onGestureEvent={this.onGestureEvent}
|
||||
onHandlerStateChange={this.onHandlerStateChange}
|
||||
>
|
||||
<Animated.View style={[styles.container, !headerComponent && styles.border]}>
|
||||
<NativeViewGestureHandler
|
||||
ref={this.scrollRef}
|
||||
waitFor={this.masterRef}
|
||||
simultaneousHandlers={this.panRef}
|
||||
>
|
||||
{children}
|
||||
</Animated.ScrollView>
|
||||
</NativeViewGestureHandler>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
<Animated.ScrollView
|
||||
ref={this.scrollViewRef}
|
||||
bounces={false}
|
||||
onScrollBeginDrag={this.onRegisterLastScroll}
|
||||
scrollEventThrottle={1}
|
||||
style={{marginBottom: (this.props.marginFromTop + BOTTOM_MARGIN)}}
|
||||
>
|
||||
{children}
|
||||
</Animated.ScrollView>
|
||||
</NativeViewGestureHandler>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</TapGestureHandler>
|
||||
|
|
@ -331,6 +334,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
maxWidth: 450,
|
||||
width: '100%',
|
||||
},
|
||||
border: {
|
||||
...Platform.select({
|
||||
android: {
|
||||
|
|
|
|||
|
|
@ -35,16 +35,13 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
|||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderBottomColor": "rgba(61,60,64,0.1)",
|
||||
"borderBottomWidth": 1,
|
||||
"borderTopColor": "rgba(61,60,64,0.1)",
|
||||
"borderTopWidth": 1,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderBottomColor": "rgba(61,60,64,0.1)",
|
||||
"borderBottomWidth": 1,
|
||||
"borderTopColor": "rgba(61,60,64,0.1)",
|
||||
"borderTopWidth": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
|
|
@ -52,14 +49,11 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingHorizontal": 15,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
|
|
@ -93,14 +87,11 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingHorizontal": 15,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
|
|
@ -134,14 +125,11 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
|||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingHorizontal": 15,
|
||||
},
|
||||
null,
|
||||
]
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flexDirection": "row",
|
||||
"paddingHorizontal": 15,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
|
|
@ -170,9 +158,7 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
|
|||
</View>
|
||||
</ForwardRef>
|
||||
</View>
|
||||
<View
|
||||
style={null}
|
||||
>
|
||||
<View>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ import {
|
|||
Switch,
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
} from '@utils/theme';
|
||||
|
||||
export default class BoolSetting extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -33,11 +32,6 @@ export default class BoolSetting extends PureComponent {
|
|||
optional: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
handleChange = (value) => {
|
||||
|
|
@ -53,7 +47,6 @@ export default class BoolSetting extends PureComponent {
|
|||
errorText,
|
||||
optional,
|
||||
theme,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -104,27 +97,27 @@ export default class BoolSetting extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={padding(isLandscape)}>
|
||||
<>
|
||||
<View>
|
||||
{labelContent}
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View style={style.inputContainer}>
|
||||
<Text style={[style.placeholderText, padding(isLandscape)]}>
|
||||
<Text style={style.placeholderText}>
|
||||
{placeholder}
|
||||
</Text>
|
||||
<Switch
|
||||
onValueChange={this.handleChange}
|
||||
value={value}
|
||||
style={[style.inputSwitch, padding(isLandscape)]}
|
||||
style={style.inputSwitch}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.separator}/>
|
||||
<View style={padding(isLandscape)}>
|
||||
<View>
|
||||
{helpTextContent}
|
||||
{errorTextContent}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ describe('components/widgets/settings/TextSetting', () => {
|
|||
optional={false}
|
||||
theme={theme}
|
||||
onChange={onChange}
|
||||
isLandscape={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
|
|||
import {Text, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class RadioSetting extends PureComponent {
|
||||
|
|
@ -17,15 +16,10 @@ export default class RadioSetting extends PureComponent {
|
|||
default: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
helpText: PropTypes.node,
|
||||
errorText: PropTypes.node,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
|
@ -66,7 +60,6 @@ export default class RadioSetting extends PureComponent {
|
|||
label,
|
||||
helpText,
|
||||
errorText,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -90,7 +83,7 @@ export default class RadioSetting extends PureComponent {
|
|||
let additionalTextContent;
|
||||
if (errorText || helpText) {
|
||||
additionalTextContent = (
|
||||
<View style={padding(isLandscape)}>
|
||||
<View >
|
||||
{helpTextContent}
|
||||
{errorTextContent}
|
||||
</View>
|
||||
|
|
@ -104,7 +97,7 @@ export default class RadioSetting extends PureComponent {
|
|||
onPress={() => this.handleChange(value)}
|
||||
key={value}
|
||||
>
|
||||
<View style={[style.container, padding(isLandscape)]}>
|
||||
<View style={style.container}>
|
||||
<View style={style.rowContainer}>
|
||||
<Text>{text}</Text>
|
||||
</View>
|
||||
|
|
@ -121,7 +114,7 @@ export default class RadioSetting extends PureComponent {
|
|||
<Text style={style.asterisk}>{' *'}</Text>
|
||||
</View>
|
||||
|
||||
<View style={[style.items, padding(isLandscape)]}>
|
||||
<View style={style.items}>
|
||||
{options}
|
||||
</View>
|
||||
{additionalTextContent}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,12 @@ import {
|
|||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
} from '@utils/theme';
|
||||
|
||||
export default class TextSetting extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -39,7 +38,6 @@ export default class TextSetting extends PureComponent {
|
|||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
multiline: PropTypes.bool,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
keyboardType: PropTypes.oneOf([
|
||||
'default',
|
||||
'number-pad',
|
||||
|
|
@ -57,7 +55,6 @@ export default class TextSetting extends PureComponent {
|
|||
disabled: false,
|
||||
multiline: false,
|
||||
keyboardType: 'default',
|
||||
isLandscape: false,
|
||||
secureTextEntry: false,
|
||||
};
|
||||
|
||||
|
|
@ -78,7 +75,6 @@ export default class TextSetting extends PureComponent {
|
|||
errorText,
|
||||
value,
|
||||
multiline,
|
||||
isLandscape,
|
||||
secureTextEntry,
|
||||
} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -151,12 +147,12 @@ export default class TextSetting extends PureComponent {
|
|||
|
||||
return (
|
||||
<View>
|
||||
<View style={[style.titleContainer, padding(isLandscape)]}>
|
||||
<View style={style.titleContainer}>
|
||||
{labelContent}
|
||||
{asterisk}
|
||||
{optionalContent}
|
||||
</View>
|
||||
<View style={[style.inputContainer, padding(isLandscape), noediting]}>
|
||||
<View style={[style.inputContainer, noediting]}>
|
||||
<View>
|
||||
<TextInput
|
||||
value={value}
|
||||
|
|
@ -177,7 +173,7 @@ export default class TextSetting extends PureComponent {
|
|||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={padding(isLandscape)}>
|
||||
<View>
|
||||
{disabledTextContent}
|
||||
{helpTextContent}
|
||||
{errorTextContent}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import RNFetchBlobFS from 'rn-fetch-blob/fs';
|
||||
import keyMirror from '@mm-redux/utils/key_mirror';
|
||||
|
||||
const DeviceModel = DeviceInfo.getModel();
|
||||
|
||||
const deviceTypes = keyMirror({
|
||||
CONNECTION_CHANGED: null,
|
||||
DEVICE_DIMENSIONS_CHANGED: null,
|
||||
|
|
@ -19,7 +18,7 @@ export default {
|
|||
...deviceTypes,
|
||||
DOCUMENTS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Documents`,
|
||||
IMAGES_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Images`,
|
||||
IS_IPHONE_WITH_INSETS: DeviceModel.includes('iPhone X') || DeviceModel.includes('iPhone 11'),
|
||||
IS_IPHONE_WITH_INSETS: Platform.OS === 'ios' && DeviceInfo.hasNotch(),
|
||||
IS_TABLET: DeviceInfo.isTablet(),
|
||||
VIDEOS_PATH: `${RNFetchBlobFS.dirs.CacheDir}/Videos`,
|
||||
PERMANENT_SIDEBAR_SETTINGS: '@PERMANENT_SIDEBAR_SETTINGS',
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue