[IMPROVEMENT] Unified header UX (#2234)

* Change drawer icon

* Removed iOS variation

* Patch to react-navigation-header-buttons... easier to patch then to overwrite its behaviour :(

* Correctly position title

* Header subtitle

* Layout

* Alignment

* RoomView header

* Renamed RoomHeaderLeft to LeftButtons

* RoomView back button

* Search icon on RoomView

* Refactor

* Fix header on tablet

* Fix search messages close button on tablet

* Search key command

* Network status on RoomView header subtitle

* Update tests

* Scale content

* SearchBox cancel color
This commit is contained in:
Diego Mello 2020-07-06 17:56:28 -03:00 committed by GitHub
parent 2ec2a52f45
commit 5834ab5e22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 234 additions and 322 deletions

View File

@ -1,5 +1,3 @@
import { isIOS, isAndroid } from '../utils/deviceInfo';
export const STATUS_COLORS = { export const STATUS_COLORS = {
online: '#2de0a5', online: '#2de0a5',
busy: '#f5455c', busy: '#f5455c',
@ -8,7 +6,7 @@ export const STATUS_COLORS = {
}; };
export const SWITCH_TRACK_COLOR = { export const SWITCH_TRACK_COLOR = {
false: isAndroid ? '#f5455c' : null, false: '#f5455c',
true: '#2de0a5' true: '#2de0a5'
}; };
@ -34,11 +32,11 @@ export const themes = {
separatorColor: '#cbcbcc', separatorColor: '#cbcbcc',
navbarBackground: '#ffffff', navbarBackground: '#ffffff',
headerBorder: '#B2B2B2', headerBorder: '#B2B2B2',
headerBackground: isIOS ? '#f8f8f8' : '#2f343d', headerBackground: '#EEEFF1',
headerSecondaryBackground: '#ffffff', headerSecondaryBackground: '#ffffff',
headerTintColor: isAndroid ? '#ffffff' : '#1d74f5', headerTintColor: '#6C727A',
headerTitleColor: isAndroid ? '#ffffff' : '#0d0e12', headerTitleColor: '#0C0D0F',
headerSecondaryText: isAndroid ? '#9ca2a8' : '#1d74f5', headerSecondaryText: '#1d74f5',
toastBackground: '#0C0D0F', toastBackground: '#0C0D0F',
videoBackground: '#1f2329', videoBackground: '#1f2329',
favoriteBackground: '#ffbb00', favoriteBackground: '#ffbb00',
@ -63,7 +61,7 @@ export const themes = {
chatComponentBackground: '#192132', chatComponentBackground: '#192132',
auxiliaryBackground: '#07101e', auxiliaryBackground: '#07101e',
bannerBackground: '#0e1f38', bannerBackground: '#0e1f38',
titleText: '#FFFFFF', titleText: '#f9f9f9',
bodyText: '#e8ebed', bodyText: '#e8ebed',
backdropColor: '#000000', backdropColor: '#000000',
dangerColor: '#f5455c', dangerColor: '#f5455c',
@ -80,9 +78,9 @@ export const themes = {
headerBorder: '#2F3A4B', headerBorder: '#2F3A4B',
headerBackground: '#0b182c', headerBackground: '#0b182c',
headerSecondaryBackground: '#0b182c', headerSecondaryBackground: '#0b182c',
headerTintColor: isAndroid ? '#ffffff' : '#1d74f5', headerTintColor: '#f9f9f9',
headerTitleColor: '#FFFFFF', headerTitleColor: '#f9f9f9',
headerSecondaryText: isAndroid ? '#9297a2' : '#1d74f5', headerSecondaryText: '#9297a2',
toastBackground: '#0C0D0F', toastBackground: '#0C0D0F',
videoBackground: '#1f2329', videoBackground: '#1f2329',
favoriteBackground: '#ffbb00', favoriteBackground: '#ffbb00',
@ -124,9 +122,9 @@ export const themes = {
headerBorder: '#323232', headerBorder: '#323232',
headerBackground: '#0d0d0d', headerBackground: '#0d0d0d',
headerSecondaryBackground: '#0d0d0d', headerSecondaryBackground: '#0d0d0d',
headerTintColor: isAndroid ? '#ffffff' : '#1e9bfe', headerTintColor: '#f9f9f9',
headerTitleColor: '#f9f9f9', headerTitleColor: '#f9f9f9',
headerSecondaryText: isAndroid ? '#b2b8c6' : '#1e9bfe', headerSecondaryText: '#b2b8c6',
toastBackground: '#0C0D0F', toastBackground: '#0C0D0F',
videoBackground: '#1f2329', videoBackground: '#1f2329',
favoriteBackground: '#ffbb00', favoriteBackground: '#ffbb00',

View File

@ -17,7 +17,7 @@ const styles = StyleSheet.create({
export const DisclosureImage = React.memo(({ theme }) => ( export const DisclosureImage = React.memo(({ theme }) => (
<CustomIcon <CustomIcon
name='chevron-right' name='chevron-right'
color={themes[theme].auxiliaryTintColor} color={themes[theme].auxiliaryText}
size={20} size={20}
/> />
)); ));

View File

@ -20,6 +20,11 @@ export const getHeaderHeight = (isLandscape) => {
return 56; return 56;
}; };
export const getHeaderTitlePosition = insets => ({
left: 60 + insets.left,
right: 80 + insets.right
});
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
height: headerHeight, height: headerHeight,

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { HeaderButtons, HeaderButton, Item } from 'react-navigation-header-buttons'; import { HeaderButtons, HeaderButton, Item } from 'react-navigation-header-buttons';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { isIOS, isAndroid } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
@ -15,11 +15,7 @@ const CustomHeaderButton = React.memo(withTheme(({ theme, ...props }) => (
{...props} {...props}
IconComponent={CustomIcon} IconComponent={CustomIcon}
iconSize={headerIconSize} iconSize={headerIconSize}
color={ color={themes[theme].headerTintColor}
isAndroid
? themes[theme].headerTitleColor
: themes[theme].headerTintColor
}
/> />
))); )));
@ -32,7 +28,7 @@ export const CustomHeaderButtons = React.memo(props => (
export const DrawerButton = React.memo(({ navigation, testID, ...otherProps }) => ( export const DrawerButton = React.memo(({ navigation, testID, ...otherProps }) => (
<CustomHeaderButtons left> <CustomHeaderButtons left>
<Item title='drawer' iconName='customize' onPress={navigation.toggleDrawer} testID={testID} {...otherProps} /> <Item title='drawer' iconName='menu_hamburguer' onPress={navigation.toggleDrawer} testID={testID} {...otherProps} />
</CustomHeaderButtons> </CustomHeaderButtons>
)); ));

View File

@ -47,7 +47,7 @@ const styles = StyleSheet.create({
const CancelButton = (onCancelPress, theme) => ( const CancelButton = (onCancelPress, theme) => (
<Touchable onPress={onCancelPress} style={styles.cancel}> <Touchable onPress={onCancelPress} style={styles.cancel}>
<Text style={[styles.cancelText, { color: themes[theme].tintColor }]}>{I18n.t('Cancel')}</Text> <Text style={[styles.cancelText, { color: themes[theme].headerTintColor }]}>{I18n.t('Cancel')}</Text>
</Touchable> </Touchable>
); );

View File

@ -2,13 +2,12 @@ import React from 'react';
import { StatusBar as StatusBarRN } from 'react-native'; import { StatusBar as StatusBarRN } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isIOS } from '../utils/deviceInfo';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
const StatusBar = React.memo(({ theme, barStyle, backgroundColor }) => { const StatusBar = React.memo(({ theme, barStyle, backgroundColor }) => {
if (!barStyle) { if (!barStyle) {
barStyle = 'light-content'; barStyle = 'light-content';
if (theme === 'light' && isIOS) { if (theme === 'light') {
barStyle = 'dark-content'; barStyle = 'dark-content';
} }
} }

View File

@ -543,6 +543,7 @@ export default {
Video_call: 'Video call', Video_call: 'Video call',
View_Original: 'View Original', View_Original: 'View Original',
Voice_call: 'Voice call', Voice_call: 'Voice call',
Waiting_for_network: 'Waiting for network...',
Websocket_disabled: 'Websocket is disabled for this server.\n{{contact}}', Websocket_disabled: 'Websocket is disabled for this server.\n{{contact}}',
Welcome: 'Welcome', Welcome: 'Welcome',
What_are_you_doing_right_now: 'What are you doing right now?', What_are_you_doing_right_now: 'What are you doing right now?',

View File

@ -479,6 +479,7 @@ export default {
Verify_your_email_for_the_code_we_sent: 'Verifique em seu e-mail o código que enviamos', Verify_your_email_for_the_code_we_sent: 'Verifique em seu e-mail o código que enviamos',
Video_call: 'Chamada de vídeo', Video_call: 'Chamada de vídeo',
Voice_call: 'Chamada de voz', Voice_call: 'Chamada de voz',
Waiting_for_network: 'Aguardando rede...',
Websocket_disabled: 'Websocket está desativado para esse servidor.\n{{contact}}', Websocket_disabled: 'Websocket está desativado para esse servidor.\n{{contact}}',
Welcome: 'Bem vindo', Welcome: 'Bem vindo',
Whats_your_2fa: 'Qual seu código de autenticação?', Whats_your_2fa: 'Qual seu código de autenticação?',

View File

@ -6,28 +6,20 @@ import {
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import { isAndroid, isTablet } from '../../../utils/deviceInfo';
import Icon from './Icon'; import Icon from './Icon';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import Markdown from '../../../containers/markdown'; import Markdown from '../../../containers/markdown';
const androidMarginLeft = isTablet ? 0 : 4;
const TITLE_SIZE = 16; const TITLE_SIZE = 16;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
marginRight: isAndroid ? 15 : 5,
marginLeft: isAndroid ? androidMarginLeft : -10,
justifyContent: 'center' justifyContent: 'center'
}, },
titleContainer: { titleContainer: {
alignItems: 'center', alignItems: 'center',
flexDirection: 'row' flexDirection: 'row'
}, },
threadContainer: {
marginRight: isAndroid ? 20 : undefined
},
title: { title: {
...sharedStyles.textSemibold, ...sharedStyles.textSemibold,
fontSize: TITLE_SIZE fontSize: TITLE_SIZE
@ -36,7 +28,6 @@ const styles = StyleSheet.create({
alignItems: 'center' alignItems: 'center'
}, },
subtitle: { subtitle: {
marginRight: -16,
...sharedStyles.textRegular, ...sharedStyles.textRegular,
fontSize: 12 fontSize: 12
}, },
@ -87,12 +78,8 @@ SubTitle.propTypes = {
}; };
const HeaderTitle = React.memo(({ const HeaderTitle = React.memo(({
title, tmid, prid, scale, connecting, theme title, tmid, prid, scale, theme
}) => { }) => {
if (connecting) {
title = I18n.t('Connecting');
}
if (!tmid && !prid) { if (!tmid && !prid) {
return ( return (
<Text <Text
@ -122,7 +109,6 @@ HeaderTitle.propTypes = {
tmid: PropTypes.string, tmid: PropTypes.string,
prid: PropTypes.string, prid: PropTypes.string,
scale: PropTypes.number, scale: PropTypes.number,
connecting: PropTypes.bool,
theme: PropTypes.string theme: PropTypes.string
}; };
@ -147,7 +133,7 @@ const Header = React.memo(({
style={styles.container} style={styles.container}
disabled={tmid} disabled={tmid}
> >
<View style={[styles.titleContainer, tmid && styles.threadContainer]}> <View style={styles.titleContainer}>
<Icon type={prid ? 'discussion' : type} status={status} roomUserId={roomUserId} theme={theme} /> <Icon type={prid ? 'discussion' : type} status={status} roomUserId={roomUserId} theme={theme} />
<HeaderTitle <HeaderTitle
title={title} title={title}

View File

@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
import { STATUS_COLORS, themes } from '../../../constants/colors'; import { STATUS_COLORS, themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import Status from '../../../containers/Status/Status'; import Status from '../../../containers/Status/Status';
import { isAndroid } from '../../../utils/deviceInfo';
const ICON_SIZE = 15; const ICON_SIZE = 15;
@ -32,7 +31,7 @@ const Icon = React.memo(({
if (type === 'l') { if (type === 'l') {
colorStyle = { color: STATUS_COLORS[status] }; colorStyle = { color: STATUS_COLORS[status] };
} else { } else {
colorStyle = { color: isAndroid && theme === 'light' ? themes[theme].buttonText : themes[theme].auxiliaryText }; colorStyle = { color: themes[theme].auxiliaryText };
} }
let icon; let icon;

View File

@ -13,16 +13,21 @@ const styles = StyleSheet.create({
} }
}); });
const RoomHeaderLeft = React.memo(({ const LeftButtons = React.memo(({
tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail
}) => { }) => {
if (!isMasterDetail || tmid) { if (!isMasterDetail || tmid) {
const onPress = useCallback(() => navigation.goBack()); const onPress = useCallback(() => navigation.goBack());
const label = unreadsCount > 99 ? '+99' : unreadsCount || ' ';
const labelLength = label.length ? label.length : 1;
const marginLeft = -2 * labelLength;
const fontSize = labelLength > 1 ? 14 : 17;
return ( return (
<HeaderBackButton <HeaderBackButton
label={unreadsCount > 999 ? '+999' : unreadsCount || ' '} label={label}
onPress={onPress} onPress={onPress}
tintColor={themes[theme].headerTintColor} tintColor={themes[theme].headerTintColor}
labelStyle={{ fontSize, marginLeft }}
/> />
); );
} }
@ -44,7 +49,7 @@ const RoomHeaderLeft = React.memo(({
return null; return null;
}); });
RoomHeaderLeft.propTypes = { LeftButtons.propTypes = {
tmid: PropTypes.string, tmid: PropTypes.string,
unreadsCount: PropTypes.number, unreadsCount: PropTypes.number,
navigation: PropTypes.object, navigation: PropTypes.object,
@ -58,4 +63,4 @@ RoomHeaderLeft.propTypes = {
isMasterDetail: PropTypes.bool isMasterDetail: PropTypes.bool
}; };
export default RoomHeaderLeft; export default LeftButtons;

View File

@ -68,6 +68,17 @@ class RightButtonsContainer extends React.PureComponent {
} }
} }
goSearchView = () => {
const {
rid, navigation, isMasterDetail
} = this.props;
if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { screen: 'SearchMessagesView', params: { rid, showCloseModal: true } });
} else {
navigation.navigate('SearchMessagesView', { rid });
}
}
toggleFollowThread = () => { toggleFollowThread = () => {
const { isFollowingThread } = this.state; const { isFollowingThread } = this.state;
const { toggleFollowThread } = this.props; const { toggleFollowThread } = this.props;
@ -104,6 +115,12 @@ class RightButtonsContainer extends React.PureComponent {
testID='room-view-header-threads' testID='room-view-header-threads'
/> />
) : null} ) : null}
<Item
title='search'
iconName='magnifier'
onPress={this.goSearchView}
testID='room-view-search'
/>
</CustomHeaderButtons> </CustomHeaderButtons>
); );
} }

View File

@ -4,10 +4,11 @@ import { connect } from 'react-redux';
import equal from 'deep-equal'; import equal from 'deep-equal';
import Header from './Header'; import Header from './Header';
import LeftButtons from './LeftButtons';
import RightButtons from './RightButtons'; import RightButtons from './RightButtons';
import { withTheme } from '../../../theme'; import { withTheme } from '../../../theme';
import RoomHeaderLeft from './RoomHeaderLeft';
import { withDimensions } from '../../../dimensions'; import { withDimensions } from '../../../dimensions';
import I18n from '../../../i18n';
class RoomHeaderView extends Component { class RoomHeaderView extends Component {
static propTypes = { static propTypes = {
@ -20,6 +21,7 @@ class RoomHeaderView extends Component {
status: PropTypes.string, status: PropTypes.string,
statusText: PropTypes.string, statusText: PropTypes.string,
connecting: PropTypes.bool, connecting: PropTypes.bool,
connected: PropTypes.bool,
theme: PropTypes.string, theme: PropTypes.string,
roomUserId: PropTypes.string, roomUserId: PropTypes.string,
widthOffset: PropTypes.number, widthOffset: PropTypes.number,
@ -30,7 +32,7 @@ class RoomHeaderView extends Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const { const {
type, title, subtitle, status, statusText, connecting, goRoomActionsView, usersTyping, theme, width, height type, title, subtitle, status, statusText, connecting, connected, goRoomActionsView, usersTyping, theme, width, height
} = this.props; } = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
@ -53,6 +55,9 @@ class RoomHeaderView extends Component {
if (nextProps.connecting !== connecting) { if (nextProps.connecting !== connecting) {
return true; return true;
} }
if (nextProps.connected !== connected) {
return true;
}
if (nextProps.width !== width) { if (nextProps.width !== width) {
return true; return true;
} }
@ -70,9 +75,18 @@ class RoomHeaderView extends Component {
render() { render() {
const { const {
title, subtitle, type, prid, tmid, widthOffset, status = 'offline', statusText, connecting, usersTyping, goRoomActionsView, roomUserId, theme, width, height title, subtitle: subtitleProp, type, prid, tmid, widthOffset, status = 'offline', statusText, connecting, connected, usersTyping, goRoomActionsView, roomUserId, theme, width, height
} = this.props; } = this.props;
let subtitle;
if (connecting) {
subtitle = I18n.t('Connecting');
} else if (!connected) {
subtitle = I18n.t('Waiting_for_network');
} else {
subtitle = subtitleProp;
}
return ( return (
<Header <Header
prid={prid} prid={prid}
@ -108,7 +122,8 @@ const mapStateToProps = (state, ownProps) => {
} }
return { return {
connecting: state.meteor.connecting, connecting: state.meteor.connecting || state.server.loading,
connected: state.meteor.connected,
usersTyping: state.usersTyping, usersTyping: state.usersTyping,
status, status,
statusText statusText
@ -117,4 +132,4 @@ const mapStateToProps = (state, ownProps) => {
export default connect(mapStateToProps)(withDimensions(withTheme(RoomHeaderView))); export default connect(mapStateToProps)(withDimensions(withTheme(RoomHeaderView)));
export { RightButtons, RoomHeaderLeft }; export { RightButtons, LeftButtons };

View File

@ -8,6 +8,7 @@ import moment from 'moment';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { import {
@ -26,7 +27,7 @@ import styles from './styles';
import log from '../../utils/log'; import log from '../../utils/log';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import I18n from '../../i18n'; import I18n from '../../i18n';
import RoomHeaderView, { RightButtons, RoomHeaderLeft } from './Header'; import RoomHeaderView, { RightButtons, LeftButtons } from './Header';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import Separator from './Separator'; import Separator from './Separator';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
@ -53,6 +54,7 @@ import Banner from './Banner';
import Navigation from '../../lib/Navigation'; import Navigation from '../../lib/Navigation';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
import { getHeaderTitlePosition } from '../../containers/Header';
const stateAttrsUpdate = [ const stateAttrsUpdate = [
'joined', 'joined',
@ -91,7 +93,8 @@ class RoomView extends React.Component {
theme: PropTypes.string, theme: PropTypes.string,
replyBroadcast: PropTypes.func, replyBroadcast: PropTypes.func,
width: PropTypes.number, width: PropTypes.number,
height: PropTypes.number height: PropTypes.number,
insets: PropTypes.object
}; };
constructor(props) { constructor(props) {
@ -178,7 +181,7 @@ class RoomView extends React.Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { state } = this; const { state } = this;
const { roomUpdate, member } = state; const { roomUpdate, member } = state;
const { appState, theme } = this.props; const { appState, theme, insets } = this.props;
if (theme !== nextProps.theme) { if (theme !== nextProps.theme) {
return true; return true;
} }
@ -192,12 +195,15 @@ class RoomView extends React.Component {
if (stateUpdated) { if (stateUpdated) {
return true; return true;
} }
if (!isEqual(nextProps.insets, insets)) {
return true;
}
return roomAttrsUpdate.some(key => !isEqual(nextState.roomUpdate[key], roomUpdate[key])); return roomAttrsUpdate.some(key => !isEqual(nextState.roomUpdate[key], roomUpdate[key]));
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { roomUpdate } = this.state; const { roomUpdate } = this.state;
const { appState } = this.props; const { appState, insets } = this.props;
if (appState === 'foreground' && appState !== prevProps.appState && this.rid) { if (appState === 'foreground' && appState !== prevProps.appState && this.rid) {
this.onForegroundInteraction = InteractionManager.runAfterInteractions(() => { this.onForegroundInteraction = InteractionManager.runAfterInteractions(() => {
@ -222,6 +228,9 @@ class RoomView extends React.Component {
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) { if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) {
this.setHeader(); this.setHeader();
} }
if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) {
this.setHeader();
}
this.setReadOnly(); this.setReadOnly();
} }
@ -281,7 +290,7 @@ class RoomView extends React.Component {
setHeader = () => { setHeader = () => {
const { room, unreadsCount, roomUserId: stateRoomUserId } = this.state; const { room, unreadsCount, roomUserId: stateRoomUserId } = this.state;
const { const {
navigation, route, isMasterDetail, theme, baseUrl, user navigation, route, isMasterDetail, theme, baseUrl, user, insets
} = this.props; } = this.props;
const rid = route.params?.rid; const rid = route.params?.rid;
const prid = route.params?.prid; const prid = route.params?.prid;
@ -299,9 +308,29 @@ class RoomView extends React.Component {
if (!rid) { if (!rid) {
return; return;
} }
const headerTitlePosition = getHeaderTitlePosition(insets);
navigation.setOptions({ navigation.setOptions({
headerShown: true, headerShown: true,
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerLeft: () => (
<LeftButtons
tmid={tmid}
unreadsCount={unreadsCount}
navigation={navigation}
baseUrl={baseUrl}
userId={userId}
token={token}
title={avatar}
theme={theme}
t={t}
goRoomActionsView={this.goRoomActionsView}
isMasterDetail={isMasterDetail}
/>
),
headerTitle: () => ( headerTitle: () => (
<RoomHeaderView <RoomHeaderView
rid={rid} rid={rid}
@ -323,21 +352,6 @@ class RoomView extends React.Component {
navigation={navigation} navigation={navigation}
toggleFollowThread={this.toggleFollowThread} toggleFollowThread={this.toggleFollowThread}
/> />
),
headerLeft: () => (
<RoomHeaderLeft
tmid={tmid}
unreadsCount={unreadsCount}
navigation={navigation}
baseUrl={baseUrl}
userId={userId}
token={token}
title={avatar}
theme={theme}
t={t}
goRoomActionsView={this.goRoomActionsView}
isMasterDetail={isMasterDetail}
/>
) )
}); });
} }
@ -1040,4 +1054,4 @@ const mapDispatchToProps = dispatch => ({
replyBroadcast: message => dispatch(replyBroadcastAction(message)) replyBroadcast: message => dispatch(replyBroadcastAction(message))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(RoomView))); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomView))));

View File

@ -1,95 +0,0 @@
import React from 'react';
import {
Text, View, TouchableOpacity, StyleSheet
} from 'react-native';
import PropTypes from 'prop-types';
import I18n from '../../../i18n';
import sharedStyles from '../../Styles';
import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
button: {
flexDirection: 'row',
alignItems: 'center'
},
title: {
fontSize: 14,
...sharedStyles.textRegular
},
server: {
fontSize: 12,
...sharedStyles.textRegular
},
disclosure: {
marginLeft: 3,
marginTop: 1,
width: 12,
height: 9
},
upsideDown: {
transform: [{ scaleY: -1 }]
}
});
const HeaderTitle = React.memo(({ connecting, isFetching, theme }) => {
let title = I18n.t('Messages');
if (connecting) {
title = I18n.t('Connecting');
}
if (isFetching) {
title = I18n.t('Updating');
}
return <Text style={[styles.title, { color: themes[theme].headerTitleColor }]}>{title}</Text>;
});
const Header = React.memo(({
connecting, isFetching, serverName, showServerDropdown, onPress, theme
}) => (
<View style={styles.container}>
<TouchableOpacity
onPress={onPress}
testID='rooms-list-header-server-dropdown-button'
style={styles.container}
// disabled={connecting || isFetching}
>
<HeaderTitle connecting={connecting} isFetching={isFetching} theme={theme} />
<View style={styles.button}>
<Text style={[styles.server, { color: themes[theme].headerTintColor }]} numberOfLines={1}>{serverName}</Text>
<CustomIcon
name='chevron-down'
color={themes[theme].headerTintColor}
style={[showServerDropdown && styles.upsideDown]}
size={18}
/>
</View>
</TouchableOpacity>
</View>
));
Header.propTypes = {
connecting: PropTypes.bool,
isFetching: PropTypes.bool,
serverName: PropTypes.string,
theme: PropTypes.string,
showServerDropdown: PropTypes.bool.isRequired,
onPress: PropTypes.func.isRequired
};
Header.defaultProps = {
serverName: 'Rocket.Chat'
};
HeaderTitle.propTypes = {
connecting: PropTypes.bool,
isFetching: PropTypes.bool,
theme: PropTypes.string
};
export default Header;

View File

@ -9,26 +9,23 @@ import I18n from '../../../i18n';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import { isTablet, isIOS } from '../../../utils/deviceInfo';
import { useOrientation } from '../../../dimensions';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center' justifyContent: 'center',
marginLeft: isTablet ? 10 : 0
}, },
button: { button: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center'
marginRight: 64
}, },
server: { title: {
fontSize: 20, ...sharedStyles.textSemibold
...sharedStyles.textRegular
}, },
serverSmall: { subtitle: {
fontSize: 16
},
updating: {
fontSize: 14,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
upsideDown: { upsideDown: {
@ -37,41 +34,55 @@ const styles = StyleSheet.create({
}); });
const Header = React.memo(({ const Header = React.memo(({
connecting, isFetching, serverName, showServerDropdown, showSearchHeader, theme, onSearchChangeText, onPress connecting, connected, isFetching, serverName, server, showServerDropdown, showSearchHeader, theme, onSearchChangeText, onPress
}) => { }) => {
const titleColorStyle = { color: themes[theme].headerTitleColor }; const titleColorStyle = { color: themes[theme].headerTitleColor };
const isLight = theme === 'light'; const isLight = theme === 'light';
const { isLandscape } = useOrientation();
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
const titleFontSize = 16 * scale;
const subTitleFontSize = 12 * scale;
if (showSearchHeader) { if (showSearchHeader) {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<TextInput <TextInput
autoFocus autoFocus
style={[styles.server, isLight && titleColorStyle]} style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]}
placeholder='Search' placeholder='Search'
onChangeText={onSearchChangeText} onChangeText={onSearchChangeText}
theme={theme} theme={theme}
testID='rooms-list-view-search-input'
/> />
</View> </View>
); );
} }
let subtitle;
if (connecting) {
subtitle = I18n.t('Connecting');
} else if (isFetching) {
subtitle = I18n.t('Updating');
} else if (!connected) {
subtitle = I18n.t('Waiting_for_network');
} else {
subtitle = server?.replace(/(^\w+:|^)\/\//, '');
}
return ( return (
<View style={styles.container}> <View style={styles.container}>
<TouchableOpacity <TouchableOpacity
onPress={onPress} onPress={onPress}
testID='rooms-list-header-server-dropdown-button' testID='rooms-list-header-server-dropdown-button'
disabled={connecting || isFetching}
> >
{connecting ? <Text style={[styles.updating, titleColorStyle]}>{I18n.t('Connecting')}</Text> : null}
{isFetching ? <Text style={[styles.updating, titleColorStyle]}>{I18n.t('Updating')}</Text> : null}
<View style={styles.button}> <View style={styles.button}>
<Text style={[styles.server, isFetching && styles.serverSmall, titleColorStyle]} numberOfLines={1}>{serverName}</Text> <Text style={[styles.title, isFetching && styles.serverSmall, titleColorStyle, { fontSize: titleFontSize }]} numberOfLines={1}>{serverName}</Text>
<CustomIcon <CustomIcon
name='chevron-down' name='chevron-down'
color={themes[theme].headerTintColor} color={themes[theme].headerTintColor}
style={[showServerDropdown && styles.upsideDown]} style={[showServerDropdown && styles.upsideDown, { fontSize: subTitleFontSize }]}
size={18} size={18}
/> />
</View> </View>
{subtitle ? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{subtitle}</Text> : null}
</TouchableOpacity> </TouchableOpacity>
</View> </View>
); );
@ -83,8 +94,10 @@ Header.propTypes = {
onPress: PropTypes.func.isRequired, onPress: PropTypes.func.isRequired,
onSearchChangeText: PropTypes.func.isRequired, onSearchChangeText: PropTypes.func.isRequired,
connecting: PropTypes.bool, connecting: PropTypes.bool,
connected: PropTypes.bool,
isFetching: PropTypes.bool, isFetching: PropTypes.bool,
serverName: PropTypes.string, serverName: PropTypes.string,
server: PropTypes.string,
theme: PropTypes.string theme: PropTypes.string
}; };

View File

@ -18,8 +18,10 @@ class RoomsListHeaderView extends PureComponent {
showSearchHeader: PropTypes.bool, showSearchHeader: PropTypes.bool,
serverName: PropTypes.string, serverName: PropTypes.string,
connecting: PropTypes.bool, connecting: PropTypes.bool,
connected: PropTypes.bool,
isFetching: PropTypes.bool, isFetching: PropTypes.bool,
theme: PropTypes.string, theme: PropTypes.string,
server: PropTypes.string,
open: PropTypes.func, open: PropTypes.func,
close: PropTypes.func, close: PropTypes.func,
closeSort: PropTypes.func, closeSort: PropTypes.func,
@ -68,16 +70,18 @@ class RoomsListHeaderView extends PureComponent {
render() { render() {
const { const {
serverName, showServerDropdown, showSearchHeader, connecting, isFetching, theme serverName, showServerDropdown, showSearchHeader, connecting, connected, isFetching, theme, server
} = this.props; } = this.props;
return ( return (
<Header <Header
theme={theme} theme={theme}
serverName={serverName} serverName={serverName}
server={server}
showServerDropdown={showServerDropdown} showServerDropdown={showServerDropdown}
showSearchHeader={showSearchHeader} showSearchHeader={showSearchHeader}
connecting={connecting} connecting={connecting}
connected={connected}
isFetching={isFetching} isFetching={isFetching}
onPress={this.onPress} onPress={this.onPress}
onSearchChangeText={this.onSearchChangeText} onSearchChangeText={this.onSearchChangeText}
@ -91,8 +95,10 @@ const mapStateToProps = state => ({
showSortDropdown: state.rooms.showSortDropdown, showSortDropdown: state.rooms.showSortDropdown,
showSearchHeader: state.rooms.showSearchHeader, showSearchHeader: state.rooms.showSearchHeader,
connecting: state.meteor.connecting || state.server.loading, connecting: state.meteor.connecting || state.server.loading,
connected: state.meteor.connected,
isFetching: state.rooms.isFetching, isFetching: state.rooms.isFetching,
serverName: state.settings.Site_Name serverName: state.settings.Site_Name,
server: state.server.server
}); });
const mapDispatchtoProps = dispatch => ({ const mapDispatchtoProps = dispatch => ({

View File

@ -1,36 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import SearchBox from '../../../containers/SearchBox';
import { isIOS } from '../../../utils/deviceInfo';
import { withTheme } from '../../../theme';
const SearchBar = React.memo(({
theme, onChangeSearchText, inputRef, searching, onCancelSearchPress, onSearchFocus
}) => {
if (isIOS) {
return (
<SearchBox
onChangeText={onChangeSearchText}
testID='rooms-list-view-search'
inputRef={inputRef}
theme={theme}
hasCancel={searching}
onCancelPress={onCancelSearchPress}
onFocus={onSearchFocus}
/>
);
}
return null;
});
SearchBar.propTypes = {
theme: PropTypes.string,
searching: PropTypes.bool,
inputRef: PropTypes.func,
onChangeSearchText: PropTypes.func,
onCancelSearchPress: PropTypes.func,
onSearchFocus: PropTypes.func
};
export default withTheme(SearchBar);

View File

@ -1,28 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import SearchBar from './SearchBar';
import Directory from './Directory'; import Directory from './Directory';
import Sort from './Sort'; import Sort from './Sort';
const ListHeader = React.memo(({ const ListHeader = React.memo(({
searching, searching,
sortBy, sortBy,
onChangeSearchText,
toggleSort, toggleSort,
goDirectory, goDirectory
inputRef,
onCancelSearchPress,
onSearchFocus
}) => ( }) => (
<> <>
<SearchBar
inputRef={inputRef}
searching={searching}
onChangeSearchText={onChangeSearchText}
onCancelSearchPress={onCancelSearchPress}
onSearchFocus={onSearchFocus}
/>
<Directory searching={searching} goDirectory={goDirectory} /> <Directory searching={searching} goDirectory={goDirectory} />
<Sort searching={searching} sortBy={sortBy} toggleSort={toggleSort} /> <Sort searching={searching} sortBy={sortBy} toggleSort={toggleSort} />
</> </>
@ -31,12 +19,8 @@ const ListHeader = React.memo(({
ListHeader.propTypes = { ListHeader.propTypes = {
searching: PropTypes.bool, searching: PropTypes.bool,
sortBy: PropTypes.string, sortBy: PropTypes.string,
onChangeSearchText: PropTypes.func,
toggleSort: PropTypes.func, toggleSort: PropTypes.func,
goDirectory: PropTypes.func, goDirectory: PropTypes.func
inputRef: PropTypes.func,
onCancelSearchPress: PropTypes.func,
onSearchFocus: PropTypes.func
}; };
export default ListHeader; export default ListHeader;

View File

@ -12,6 +12,7 @@ import { connect } from 'react-redux';
import { isEqual, orderBy } from 'lodash'; import { isEqual, orderBy } from 'lodash';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import database from '../../lib/database'; import database from '../../lib/database';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
@ -30,7 +31,7 @@ import {
} from '../../actions/rooms'; } from '../../actions/rooms';
import { appStart as appStartAction, ROOT_BACKGROUND } from '../../actions/app'; import { appStart as appStartAction, ROOT_BACKGROUND } from '../../actions/app';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import { isIOS, isAndroid, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import RoomsListHeaderView from './Header'; import RoomsListHeaderView from './Header';
import { import {
DrawerButton, DrawerButton,
@ -59,10 +60,9 @@ import { MAX_SIDEBAR_WIDTH } from '../../constants/tablet';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import Header from '../../containers/Header'; import Header, { getHeaderTitlePosition } from '../../containers/Header';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
const SCROLL_OFFSET = 56;
const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12;
const CHATS_HEADER = 'Chats'; const CHATS_HEADER = 'Chats';
const UNREAD_HEADER = 'Unread'; const UNREAD_HEADER = 'Unread';
@ -129,7 +129,8 @@ class RoomsListView extends React.Component {
connected: PropTypes.bool, connected: PropTypes.bool,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
rooms: PropTypes.array, rooms: PropTypes.array,
width: PropTypes.number width: PropTypes.number,
insets: PropTypes.object
}; };
constructor(props) { constructor(props) {
@ -242,7 +243,7 @@ class RoomsListView extends React.Component {
loading, loading,
search search
} = this.state; } = this.state;
const { rooms, width } = this.props; const { rooms, width, insets } = this.props;
if (nextState.loading !== loading) { if (nextState.loading !== loading) {
return true; return true;
} }
@ -255,6 +256,9 @@ class RoomsListView extends React.Component {
if (!isEqual(nextProps.rooms, rooms)) { if (!isEqual(nextProps.rooms, rooms)) {
return true; return true;
} }
if (!isEqual(nextProps.insets, insets)) {
return true;
}
// If it's focused and there are changes, update // If it's focused and there are changes, update
if (chatsNotEqual) { if (chatsNotEqual) {
this.shouldUpdate = false; this.shouldUpdate = false;
@ -273,7 +277,8 @@ class RoomsListView extends React.Component {
connected, connected,
roomsRequest, roomsRequest,
rooms, rooms,
isMasterDetail isMasterDetail,
insets
} = this.props; } = this.props;
const { item } = this.state; const { item } = this.state;
@ -298,6 +303,9 @@ class RoomsListView extends React.Component {
// eslint-disable-next-line react/no-did-update-set-state // eslint-disable-next-line react/no-did-update-set-state
this.setState({ item: { rid: rooms[0] } }); this.setState({ item: { rid: rooms[0] } });
} }
if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) {
this.setHeader();
}
} }
componentWillUnmount() { componentWillUnmount() {
@ -318,9 +326,11 @@ class RoomsListView extends React.Component {
getHeader = () => { getHeader = () => {
const { searching } = this.state; const { searching } = this.state;
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail, insets } = this.props;
const headerTitlePosition = getHeaderTitlePosition(insets);
return { return {
headerLeft: () => (searching && isAndroid ? ( headerTitleAlign: 'left',
headerLeft: () => (searching ? (
<CustomHeaderButtons left> <CustomHeaderButtons left>
<Item <Item
title='cancel' title='cancel'
@ -332,24 +342,31 @@ class RoomsListView extends React.Component {
<DrawerButton <DrawerButton
navigation={navigation} navigation={navigation}
testID='rooms-list-view-sidebar' testID='rooms-list-view-sidebar'
onPress={isMasterDetail ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) : () => navigation.toggleDrawer()} onPress={isMasterDetail
? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' })
: () => navigation.toggleDrawer()}
/> />
)), )),
headerTitle: () => <RoomsListHeaderView />, headerTitle: () => <RoomsListHeaderView />,
headerRight: () => (searching && isAndroid ? null : ( headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => (searching ? null : (
<CustomHeaderButtons> <CustomHeaderButtons>
{isAndroid ? ( <Item
title='new'
iconName='new-chat'
onPress={isMasterDetail
? () => navigation.navigate('ModalStackNavigator', { screen: 'NewMessageView' })
: () => navigation.navigate('NewMessageStackNavigator')}
testID='rooms-list-view-create-channel'
/>
<Item <Item
title='search' title='search'
iconName='magnifier' iconName='magnifier'
onPress={this.initSearching} onPress={this.initSearching}
/> testID='rooms-list-view-search'
) : null}
<Item
title='new'
iconName='new-chat'
onPress={isMasterDetail ? () => navigation.navigate('ModalStackNavigator', { screen: 'NewMessageView' }) : () => navigation.navigate('NewMessageStackNavigator')}
testID='rooms-list-view-create-channel'
/> />
</CustomHeaderButtons> </CustomHeaderButtons>
)) ))
@ -476,10 +493,8 @@ class RoomsListView extends React.Component {
initSearching = () => { initSearching = () => {
const { openSearchHeader } = this.props; const { openSearchHeader } = this.props;
this.internalSetState({ searching: true }, () => { this.internalSetState({ searching: true }, () => {
if (isAndroid) {
openSearchHeader(); openSearchHeader();
this.setHeader(); this.setHeader();
}
}); });
}; };
@ -493,18 +508,11 @@ class RoomsListView extends React.Component {
Keyboard.dismiss(); Keyboard.dismiss();
if (isIOS && this.inputRef) {
this.inputRef.blur();
this.inputRef.clear();
}
this.setState({ searching: false, search: [] }, () => { this.setState({ searching: false, search: [] }, () => {
if (isAndroid) {
this.setHeader(); this.setHeader();
closeSearchHeader(); closeSearchHeader();
}
setTimeout(() => { setTimeout(() => {
const offset = isAndroid ? 0 : SCROLL_OFFSET; const offset = 0;
if (this.scroll.scrollTo) { if (this.scroll.scrollTo) {
this.scroll.scrollTo({ x: 0, y: offset, animated: true }); this.scroll.scrollTo({ x: 0, y: offset, animated: true });
} else if (this.scroll.scrollToOffset) { } else if (this.scroll.scrollToOffset) {
@ -564,7 +572,7 @@ class RoomsListView extends React.Component {
toggleSort = () => { toggleSort = () => {
const { toggleSortDropdown } = this.props; const { toggleSortDropdown } = this.props;
const offset = isAndroid ? 0 : SCROLL_OFFSET; const offset = 0;
if (this.scroll.scrollTo) { if (this.scroll.scrollTo) {
this.scroll.scrollTo({ x: 0, y: offset, animated: true }); this.scroll.scrollTo({ x: 0, y: offset, animated: true });
} else if (this.scroll.scrollToOffset) { } else if (this.scroll.scrollToOffset) {
@ -714,8 +722,7 @@ class RoomsListView extends React.Component {
if (handleCommandShowPreferences(event)) { if (handleCommandShowPreferences(event)) {
navigation.navigate('SettingsView'); navigation.navigate('SettingsView');
} else if (handleCommandSearching(event)) { } else if (handleCommandSearching(event)) {
this.scroll.scrollToOffset({ animated: true, offset: 0 }); this.initSearching();
this.inputRef.focus();
} else if (handleCommandSelectRoom(event)) { } else if (handleCommandSelectRoom(event)) {
this.goRoomByIndex(input); this.goRoomByIndex(input);
} else if (handleCommandPreviousRoom(event)) { } else if (handleCommandPreviousRoom(event)) {
@ -744,19 +751,13 @@ class RoomsListView extends React.Component {
getScrollRef = ref => (this.scroll = ref); getScrollRef = ref => (this.scroll = ref);
getInputRef = ref => (this.inputRef = ref);
renderListHeader = () => { renderListHeader = () => {
const { searching } = this.state; const { searching } = this.state;
const { sortBy } = this.props; const { sortBy } = this.props;
return ( return (
<ListHeader <ListHeader
inputRef={this.getInputRef}
searching={searching} searching={searching}
sortBy={sortBy} sortBy={sortBy}
onChangeSearchText={this.search}
onCancelSearchPress={this.cancelSearch}
onSearchFocus={this.initSearching}
toggleSort={this.toggleSort} toggleSort={this.toggleSort}
goDirectory={this.goDirectory} goDirectory={this.goDirectory}
/> />
@ -869,7 +870,6 @@ class RoomsListView extends React.Component {
ref={this.getScrollRef} ref={this.getScrollRef}
data={searching ? search : chats} data={searching ? search : chats}
extraData={searching ? search : chats} extraData={searching ? search : chats}
contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
renderItem={this.renderItem} renderItem={this.renderItem}
@ -953,4 +953,4 @@ const mapDispatchToProps = dispatch => ({
closeServerDropdown: () => dispatch(closeServerDropdownAction()) closeServerDropdown: () => dispatch(closeServerDropdownAction())
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(RoomsListView))); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView))));

View File

@ -23,7 +23,7 @@ export default StyleSheet.create({
sortToggleText: { sortToggleText: {
fontSize: 16, fontSize: 16,
flex: 1, flex: 1,
marginLeft: 15, marginLeft: 12,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
dropdownContainer: { dropdownContainer: {
@ -50,16 +50,16 @@ export default StyleSheet.create({
}, },
sortSeparator: { sortSeparator: {
height: StyleSheet.hairlineWidth, height: StyleSheet.hairlineWidth,
marginHorizontal: 15, marginHorizontal: 12,
flex: 1 flex: 1
}, },
sortIcon: { sortIcon: {
width: 22, width: 22,
height: 22, height: 22,
marginHorizontal: 15 marginHorizontal: 12
}, },
groupTitleContainer: { groupTitleContainer: {
paddingHorizontal: 15, paddingHorizontal: 12,
paddingTop: 17, paddingTop: 17,
paddingBottom: 10 paddingBottom: 10
}, },
@ -75,12 +75,12 @@ export default StyleSheet.create({
}, },
serverHeaderText: { serverHeaderText: {
fontSize: 16, fontSize: 16,
marginLeft: 15, marginLeft: 12,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
serverHeaderAdd: { serverHeaderAdd: {
fontSize: 16, fontSize: 16,
marginRight: 15, marginRight: 12,
paddingVertical: 10, paddingVertical: 10,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
@ -95,7 +95,7 @@ export default StyleSheet.create({
serverIcon: { serverIcon: {
width: 42, width: 42,
height: 42, height: 42,
marginHorizontal: 15, marginHorizontal: 12,
marginVertical: 13, marginVertical: 13,
borderRadius: 4, borderRadius: 4,
resizeMode: 'contain' resizeMode: 'contain'
@ -120,7 +120,7 @@ export default StyleSheet.create({
directoryIcon: { directoryIcon: {
width: 22, width: 22,
height: 22, height: 22,
marginHorizontal: 15 marginHorizontal: 12
}, },
directoryText: { directoryText: {
fontSize: 16, fontSize: 16,

View File

@ -72,6 +72,14 @@ async function sleep(ms) {
return new Promise(res => setTimeout(res, ms)); return new Promise(res => setTimeout(res, ms));
} }
async function searchRoom(room) {
await element(by.id('rooms-list-view-search')).tap();
await expect(element(by.id('rooms-list-view-search-input'))).toExist();
await waitFor(element(by.id('rooms-list-view-search-input'))).toExist().withTimeout(5000);
await element(by.id('rooms-list-view-search-input')).typeText(room);
await sleep(2000);
}
module.exports = { module.exports = {
navigateToWorkspace, navigateToWorkspace,
navigateToLogin, navigateToLogin,
@ -80,5 +88,6 @@ module.exports = {
logout, logout,
createUser, createUser,
tapBack, tapBack,
sleep sleep,
searchRoom
}; };

View File

@ -3,7 +3,7 @@ const {
} = require('detox'); } = require('detox');
const OTP = require('otp.js'); const OTP = require('otp.js');
const GA = OTP.googleAuthenticator; const GA = OTP.googleAuthenticator;
const { navigateToLogin, login, tapBack, sleep, createUser } = require('../../helpers/app'); const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app');
const data = require('../../data'); const data = require('../../data');
describe('Broadcast room', () => { describe('Broadcast room', () => {
@ -73,9 +73,7 @@ describe('Broadcast room', () => {
await sleep(1000); await sleep(1000);
await element(by.id('two-factor-send')).tap(); await element(by.id('two-factor-send')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top'); await searchRoom(`broadcast${ data.random }`);
await element(by.id('rooms-list-view-search')).typeText(`broadcast${ data.random }`);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist(); await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap(); await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();

View File

@ -2,7 +2,7 @@ const {
device, expect, element, by, waitFor device, expect, element, by, waitFor
} = require('detox'); } = require('detox');
const data = require('../../data'); const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app'); const { tapBack, sleep, searchRoom } = require('../../helpers/app');
const room = 'detox-public'; const room = 'detox-public';
@ -16,9 +16,7 @@ async function mockMessage(message) {
async function navigateToRoom() { async function navigateToRoom() {
await sleep(2000); await sleep(2000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top'); await searchRoom(room);
await element(by.id('rooms-list-view-search')).typeText(room);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toBeVisible().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toBeVisible().withTimeout(60000);
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap(); await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);

View File

@ -1,7 +1,7 @@
const { const {
device, expect, element, by, waitFor device, expect, element, by, waitFor
} = require('detox'); } = require('detox');
const { logout, tapBack, sleep } = require('../../helpers/app'); const { logout, tapBack, sleep, searchRoom } = require('../../helpers/app');
describe('Rooms list screen', () => { describe('Rooms list screen', () => {
describe('Render', () => { describe('Render', () => {
@ -27,10 +27,7 @@ describe('Rooms list screen', () => {
describe('Usage', () => { describe('Usage', () => {
it('should search room and navigate', async() => { it('should search room and navigate', async() => {
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top'); await searchRoom('rocket.cat');
await waitFor(element(by.id('rooms-list-view-search'))).toExist().withTimeout(2000);
await element(by.id('rooms-list-view-search')).typeText('rocket.cat');
await sleep(2000);
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000); await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible(); await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
await element(by.id('rooms-list-view-item-rocket.cat')).tap(); await element(by.id('rooms-list-view-item-rocket.cat')).tap();
@ -41,7 +38,6 @@ describe('Rooms list screen', () => {
await tapBack(); await tapBack();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('rooms-list-view'))).toBeVisible(); await expect(element(by.id('rooms-list-view'))).toBeVisible();
// await element(by.id('rooms-list-view-search')).typeText('');
await sleep(2000); await sleep(2000);
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toExist().withTimeout(60000); await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toExist().withTimeout(60000);
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toExist(); await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toExist();

View File

@ -2,7 +2,7 @@ const {
device, expect, element, by, waitFor device, expect, element, by, waitFor
} = require('detox'); } = require('detox');
const data = require('../../data'); const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app'); const { tapBack, sleep, searchRoom } = require('../../helpers/app');
async function mockMessage(message) { async function mockMessage(message) {
await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).tap();
@ -13,9 +13,7 @@ async function mockMessage(message) {
}; };
async function navigateToRoom() { async function navigateToRoom() {
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top'); await searchRoom(`private${ data.random }`);
await element(by.id('rooms-list-view-search')).typeText(`private${ data.random }`);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toExist().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toExist().withTimeout(60000);
await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap(); await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);

View File

@ -2,7 +2,7 @@ const {
device, expect, element, by, waitFor device, expect, element, by, waitFor
} = require('detox'); } = require('detox');
const data = require('../../data'); const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app'); const { tapBack, sleep, searchRoom } = require('../../helpers/app');
const scrollDown = 200; const scrollDown = 200;
@ -13,10 +13,7 @@ async function navigateToRoomActions(type) {
} else { } else {
room = `private${ data.random }`; room = `private${ data.random }`;
} }
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); await searchRoom(room);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
await element(by.id('rooms-list-view-search')).typeText(room);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
await element(by.id(`rooms-list-view-item-${ room }`)).tap(); await element(by.id(`rooms-list-view-item-${ room }`)).tap();
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);

View File

@ -2,7 +2,7 @@ const {
device, expect, element, by, waitFor device, expect, element, by, waitFor
} = require('detox'); } = require('detox');
const data = require('../../data'); const data = require('../../data');
const { tapBack, sleep } = require('../../helpers/app'); const { tapBack, sleep, searchRoom } = require('../../helpers/app');
async function navigateToRoomInfo(type) { async function navigateToRoomInfo(type) {
let room; let room;
@ -11,10 +11,7 @@ async function navigateToRoomInfo(type) {
} else { } else {
room = `private${ data.random }`; room = `private${ data.random }`;
} }
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); await searchRoom(room);
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
await element(by.id('rooms-list-view-search')).typeText(room);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
await element(by.id(`rooms-list-view-item-${ room }`)).tap(); await element(by.id(`rooms-list-view-item-${ room }`)).tap();
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
@ -311,7 +308,6 @@ describe('Room info screen', () => {
await expect(element(by.text('Yes, delete it!'))).toExist(); await expect(element(by.text('Yes, delete it!'))).toExist();
await element(by.text('Yes, delete it!')).tap(); await element(by.text('Yes, delete it!')).tap();
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000);
// await element(by.id('rooms-list-view-search')).typeText('');
await sleep(2000); await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible(); await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();

View File

@ -0,0 +1,12 @@
diff --git a/node_modules/react-navigation-header-buttons/src/HeaderButtons.js b/node_modules/react-navigation-header-buttons/src/HeaderButtons.js
index 70ff376..01fba5e 100644
--- a/node_modules/react-navigation-header-buttons/src/HeaderButtons.js
+++ b/node_modules/react-navigation-header-buttons/src/HeaderButtons.js
@@ -144,6 +144,6 @@ const styles = StyleSheet.create({
}),
},
button: {
- marginHorizontal: 11,
+ marginHorizontal: 6
},
});