[IMPROVE] Use UI Elements from react-navigation (#4314)

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Alex Junior 2022-07-06 10:23:02 -03:00 committed by GitHub
parent 2e8b7d7755
commit 1027b6c9e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 289 additions and 350 deletions

View File

@ -97,7 +97,7 @@ const ActionSheetContentWithInputAndSubmit = ({
const { hideActionSheet } = useActionSheet(); const { hideActionSheet } = useActionSheet();
return ( return (
<View style={sharedStyles.containerScrollView}> <View style={sharedStyles.containerScrollView} testID='action-sheet-content-with-input-and-submit'>
<> <>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
{iconName ? <CustomIcon name={iconName} size={32} color={iconColor || colors.dangerColor} /> : null} {iconName ? <CustomIcon name={iconName} size={32} color={iconColor || colors.dangerColor} /> : null}

View File

@ -10,7 +10,7 @@ export const IconSet = createIconSetFromIcoMoon(icoMoonConfig, 'custom', 'custom
export type TIconsName = keyof typeof mappedIcons; export type TIconsName = keyof typeof mappedIcons;
interface ICustomIcon extends TextProps { export interface ICustomIcon extends TextProps {
name: TIconsName; name: TIconsName;
size: number; size: number;
color: string; color: string;

View File

@ -1,69 +0,0 @@
import React from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StyleSheet, View } from 'react-native';
import { themes } from '../../lib/constants';
import { themedHeader } from '../../lib/methods/helpers/navigation';
import { isIOS, isTablet } from '../../lib/methods/helpers';
import { useTheme } from '../../theme';
export const headerHeight = isIOS ? 50 : 56;
export const getHeaderHeight = (isLandscape: boolean): number => {
if (isIOS) {
if (isLandscape && !isTablet) {
return 32;
}
return 44;
}
return 56;
};
interface IHeaderTitlePosition {
insets: {
left: number;
right: number;
};
numIconsRight: number;
}
export const getHeaderTitlePosition = ({
insets,
numIconsRight
}: IHeaderTitlePosition): {
left: number;
right: number;
} => ({
left: insets.left,
right: insets.right + Math.max(45 * numIconsRight, 15)
});
const styles = StyleSheet.create({
container: {
height: headerHeight,
flexDirection: 'row',
justifyContent: 'center',
elevation: 4
}
});
interface IHeader {
headerLeft: () => React.ReactElement | null;
headerTitle: () => React.ReactElement;
headerRight: () => React.ReactElement | null;
}
const Header = ({ headerLeft, headerTitle, headerRight }: IHeader): React.ReactElement => {
const { theme } = useTheme();
return (
<SafeAreaView style={{ backgroundColor: themes[theme].headerBackground }} edges={['top', 'left', 'right']}>
<View style={[styles.container, { ...themedHeader(theme).headerStyle }]}>
{headerLeft ? headerLeft() : null}
{headerTitle ? headerTitle() : null}
{headerRight ? headerRight() : null}
</View>
</SafeAreaView>
);
};
export default Header;

View File

@ -3,12 +3,10 @@ import React from 'react';
import { isIOS } from '../../lib/methods/helpers'; import { isIOS } from '../../lib/methods/helpers';
import I18n from '../../i18n'; import I18n from '../../i18n';
import Container from './HeaderButtonContainer'; import Container from './HeaderButtonContainer';
import Item from './HeaderButtonItem'; import Item, { IHeaderButtonItem } from './HeaderButtonItem';
interface IHeaderButtonCommon { interface IHeaderButtonCommon extends IHeaderButtonItem {
navigation?: any; // TODO: Evaluate proper type navigation?: any; // TODO: Evaluate proper type
onPress?: () => void;
testID?: string;
} }
// Left // Left
@ -28,20 +26,20 @@ export const CloseModal = React.memo(
) )
); );
export const CancelModal = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => ( export const CancelModal = React.memo(({ onPress, testID, ...props }: IHeaderButtonCommon) => (
<Container left> <Container left>
{isIOS ? ( {isIOS ? (
<Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} /> <Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} {...props} />
) : ( ) : (
<Item iconName='close' onPress={onPress} testID={testID} /> <Item iconName='close' onPress={onPress} testID={testID} {...props} />
)} )}
</Container> </Container>
)); ));
// Right // Right
export const More = React.memo(({ onPress, testID }: Partial<IHeaderButtonCommon>) => ( export const More = React.memo(({ onPress, testID, ...props }: IHeaderButtonCommon) => (
<Container> <Container>
<Item iconName='kebab' onPress={onPress} testID={testID} /> <Item iconName='kebab' onPress={onPress} testID={testID} {...props} />
</Container> </Container>
)); ));
@ -58,7 +56,7 @@ export const Preferences = React.memo(({ onPress, testID, ...props }: IHeaderBut
)); ));
export const Legal = React.memo( export const Legal = React.memo(
({ navigation, testID, onPress = () => navigation?.navigate('LegalView') }: IHeaderButtonCommon) => ( ({ navigation, testID, onPress = () => navigation?.navigate('LegalView'), ...props }: IHeaderButtonCommon) => (
<More onPress={onPress} testID={testID} /> <More onPress={onPress} testID={testID} {...props} />
) )
); );

View File

@ -1,18 +1,18 @@
import React from 'react'; import React from 'react';
import { Platform, StyleSheet, Text } from 'react-native'; import { Platform, StyleSheet, Text } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import { PlatformPressable } from '@react-navigation/elements';
import { CustomIcon, TIconsName } from '../CustomIcon'; import { CustomIcon, ICustomIcon, TIconsName } from '../CustomIcon';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
interface IHeaderButtonItem { export interface IHeaderButtonItem extends Omit<ICustomIcon, 'name' | 'size' | 'color'> {
title?: string; title?: string;
iconName?: TIconsName; iconName?: TIconsName;
onPress?: <T>(arg: T) => void; onPress?: <T>(arg: T) => void;
testID?: string; testID?: string;
badge?(): void; badge?(): void;
color?: string;
} }
export const BUTTON_HIT_SLOP = { export const BUTTON_HIT_SLOP = {
@ -24,7 +24,7 @@ export const BUTTON_HIT_SLOP = {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
marginHorizontal: 6 padding: 6
}, },
title: { title: {
...Platform.select({ ...Platform.select({
@ -39,19 +39,21 @@ const styles = StyleSheet.create({
} }
}); });
const Item = ({ title, iconName, onPress, testID, badge }: IHeaderButtonItem): React.ReactElement => { const Item = ({ title, iconName, onPress, testID, badge, color, ...props }: IHeaderButtonItem): React.ReactElement => {
const { theme } = useTheme(); const { colors } = useTheme();
return ( return (
<Touchable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}> <PlatformPressable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} style={styles.container}>
<> <>
{iconName ? ( {iconName ? (
<CustomIcon name={iconName} size={24} color={themes[theme].headerTintColor} /> <CustomIcon name={iconName} size={24} color={color || colors.headerTintColor} {...props} />
) : ( ) : (
<Text style={[styles.title, { color: themes[theme].headerTintColor }]}>{title}</Text> <Text style={[styles.title, { color: color || colors.headerTintColor }]} {...props}>
{title}
</Text>
)} )}
{badge ? badge() : null} {badge ? badge() : null}
</> </>
</Touchable> </PlatformPressable>
); );
}; };

View File

@ -7,8 +7,8 @@ const styles = StyleSheet.create({
badgeContainer: { badgeContainer: {
padding: 2, padding: 2,
position: 'absolute', position: 'absolute',
right: -3, right: 2,
top: -3, top: 2,
borderRadius: 10, borderRadius: 10,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'

View File

@ -1,25 +1,33 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types, react/destructuring-assignment */
import React from 'react'; import React from 'react';
import { Dimensions, View } from 'react-native'; import { Dimensions, SafeAreaView } from 'react-native';
import { storiesOf } from '@storybook/react-native'; import { storiesOf } from '@storybook/react-native';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Header, HeaderBackground } from '@react-navigation/elements';
import Header from '../Header';
import { longText } from '../../../storybook/utils'; import { longText } from '../../../storybook/utils';
import { ThemeContext } from '../../theme'; import { ThemeContext } from '../../theme';
import { store } from '../../../storybook/stories'; import { store } from '../../../storybook/stories';
import { colors } from '../../lib/constants'; import { colors, themes } from '../../lib/constants';
import RoomHeaderComponent from './RoomHeader'; import RoomHeaderComponent from './RoomHeader';
const stories = storiesOf('RoomHeader', module).addDecorator(story => <Provider store={store}>{story()}</Provider>); const stories = storiesOf('RoomHeader', module)
.addDecorator(story => <Provider store={store}>{story()}</Provider>)
// TODO: refactor after react-navigation v6 .addDecorator(story => <SafeAreaProvider>{story()}</SafeAreaProvider>);
const HeaderExample = ({ title }) => (
<Header headerTitle={() => <View style={{ flex: 1, paddingHorizontal: 12 }}>{title()}</View>} />
);
const { width, height } = Dimensions.get('window'); const { width, height } = Dimensions.get('window');
const HeaderExample = ({ title, theme = 'light' }) => (
<SafeAreaView>
<Header
title=''
headerTitle={title}
headerTitleAlign='left'
headerBackground={() => <HeaderBackground style={{ backgroundColor: themes[theme].headerBackground }} />}
/>
</SafeAreaView>
);
const RoomHeader = ({ ...props }) => ( const RoomHeader = ({ ...props }) => (
<RoomHeaderComponent <RoomHeaderComponent
width={width} width={width}
@ -28,6 +36,8 @@ const RoomHeader = ({ ...props }) => (
type='p' type='p'
testID={props.title} testID={props.title}
onPress={() => alert('header pressed!')} onPress={() => alert('header pressed!')}
status={props.status}
usersTyping={props.usersTyping}
{...props} {...props}
/> />
); );
@ -84,7 +94,7 @@ stories.add('thread', () => (
const ThemeStory = ({ theme }) => ( const ThemeStory = ({ theme }) => (
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}> <ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
<HeaderExample title={() => <RoomHeader subtitle='subtitle' />} /> <HeaderExample title={() => <RoomHeader subtitle='subtitle' />} theme={theme} />
</ThemeContext.Provider> </ThemeContext.Provider>
); );

File diff suppressed because one or more lines are too long

View File

@ -64,7 +64,7 @@ export const colors = {
passcodeDotEmpty: '#CBCED1', passcodeDotEmpty: '#CBCED1',
passcodeDotFull: '#6C727A', passcodeDotFull: '#6C727A',
previewBackground: '#1F2329', previewBackground: '#1F2329',
previewTintColor: '#ffffff', previewTintColor: '#f9f9f9',
backdropOpacity: 0.3, backdropOpacity: 0.3,
attachmentLoadingOpacity: 0.7, attachmentLoadingOpacity: 0.7,
collapsibleQuoteBorder: '#CBCED1', collapsibleQuoteBorder: '#CBCED1',
@ -116,7 +116,7 @@ export const colors = {
passcodeDotEmpty: '#CBCED1', passcodeDotEmpty: '#CBCED1',
passcodeDotFull: '#6C727A', passcodeDotFull: '#6C727A',
previewBackground: '#030b1b', previewBackground: '#030b1b',
previewTintColor: '#ffffff', previewTintColor: '#f9f9f9',
backdropOpacity: 0.9, backdropOpacity: 0.9,
attachmentLoadingOpacity: 0.3, attachmentLoadingOpacity: 0.3,
collapsibleQuoteBorder: '#CBCED1', collapsibleQuoteBorder: '#CBCED1',
@ -168,7 +168,7 @@ export const colors = {
passcodeDotEmpty: '#CBCED1', passcodeDotEmpty: '#CBCED1',
passcodeDotFull: '#6C727A', passcodeDotFull: '#6C727A',
previewBackground: '#000000', previewBackground: '#000000',
previewTintColor: '#ffffff', previewTintColor: '#f9f9f9',
backdropOpacity: 0.9, backdropOpacity: 0.9,
attachmentLoadingOpacity: 0.3, attachmentLoadingOpacity: 0.3,
collapsibleQuoteBorder: '#CBCED1', collapsibleQuoteBorder: '#CBCED1',

View File

@ -3,6 +3,7 @@ import { DarkTheme, DefaultTheme } from '@react-navigation/native';
import { themes } from '../../../constants'; import { themes } from '../../../constants';
import { TSupportedThemes } from '../../../../theme'; import { TSupportedThemes } from '../../../../theme';
import { isIOS } from '../deviceInfo';
export * from './animations'; export * from './animations';
@ -26,6 +27,9 @@ export const drawerStyle = {
width: 320 width: 320
}; };
// TODO: Remove it once we migrate dropdowns to action sheet
export const headerHeight = isIOS ? 50 : 56;
export const themedHeader = (theme: TSupportedThemes) => ({ export const themedHeader = (theme: TSupportedThemes) => ({
headerStyle: { headerStyle: {
...borderBottom(theme), ...borderBottom(theme),

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { PermissionsAndroid, StyleSheet, View } from 'react-native'; import { PermissionsAndroid, StyleSheet, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import CameraRoll from '@react-native-community/cameraroll'; import CameraRoll from '@react-native-community/cameraroll';
import * as mime from 'react-native-mime-types'; import * as mime from 'react-native-mime-types';
@ -9,6 +9,7 @@ import RNFetchBlob from 'rn-fetch-blob';
import { Video } from 'expo-av'; import { Video } from 'expo-av';
import { sha256 } from 'js-sha256'; import { sha256 } from 'js-sha256';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { HeaderBackground, HeaderHeightContext } from '@react-navigation/elements';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import EventEmitter from '../lib/methods/helpers/events'; import EventEmitter from '../lib/methods/helpers/events';
@ -21,7 +22,6 @@ import * as HeaderButton from '../containers/HeaderButton';
import { isAndroid, formatAttachmentUrl } from '../lib/methods/helpers'; import { isAndroid, formatAttachmentUrl } from '../lib/methods/helpers';
import { getUserSelector } from '../selectors/login'; import { getUserSelector } from '../selectors/login';
import { withDimensions } from '../dimensions'; import { withDimensions } from '../dimensions';
import { getHeaderHeight } from '../containers/Header';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { InsideStackParamList } from '../stacks/types'; import { InsideStackParamList } from '../stacks/types';
import { IApplicationState, IUser, IAttachment } from '../definitions'; import { IApplicationState, IUser, IAttachment } from '../definitions';
@ -86,18 +86,25 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
} catch { } catch {
// Do nothing // Do nothing
} }
const options = { const options: StackNavigationOptions = {
title, title: title || '',
headerLeft: () => <HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} />, headerTitleAlign: 'center',
headerRight: () => headerTitleStyle: { color: themes[theme].previewTintColor },
Allow_Save_Media_to_Gallery ? <HeaderButton.Download testID='save-image' onPress={this.handleSave} /> : null,
headerBackground: () => <View style={{ flex: 1, backgroundColor: themes[theme].previewBackground }} />,
headerTintColor: themes[theme].previewTintColor, headerTintColor: themes[theme].previewTintColor,
headerTitleStyle: { color: themes[theme].previewTintColor, paddingHorizontal: 20 }, headerTitleContainerStyle: { flex: 1, maxWidth: undefined },
headerTitleContainerStyle: { marginHorizontal: -20 }, headerLeftContainerStyle: { flexGrow: undefined, flexBasis: undefined },
headerTitleAlign: 'center' headerRightContainerStyle: { flexGrow: undefined, flexBasis: undefined },
headerLeft: () => (
<HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} color={themes[theme].previewTintColor} />
),
headerRight: () =>
Allow_Save_Media_to_Gallery ? (
<HeaderButton.Download testID='save-image' onPress={this.handleSave} color={themes[theme].previewTintColor} />
) : null,
headerBackground: () => (
<HeaderBackground style={{ backgroundColor: themes[theme].previewBackground, shadowOpacity: 0, elevation: 0 }} />
)
}; };
// @ts-ignore
navigation.setOptions(options); navigation.setOptions(options);
}; };
@ -147,14 +154,17 @@ class AttachmentView extends React.Component<IAttachmentViewProps, IAttachmentVi
renderImage = (uri: string) => { renderImage = (uri: string) => {
const { width, height, insets } = this.props; const { width, height, insets } = this.props;
const headerHeight = getHeaderHeight(width > height);
return ( return (
<ImageViewer <HeaderHeightContext.Consumer>
uri={uri} {headerHeight => (
onLoadEnd={() => this.setState({ loading: false })} <ImageViewer
width={width} uri={uri}
height={height - insets.top - insets.bottom - headerHeight} onLoadEnd={() => this.setState({ loading: false })}
/> width={width}
height={height - insets.top - insets.bottom - (headerHeight || 0)}
/>
)}
</HeaderHeightContext.Consumer>
); );
}; };

View File

@ -1,6 +1,5 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { FlatList } from 'react-native'; import { FlatList } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { HeaderBackButton } from '@react-navigation/elements'; import { HeaderBackButton } from '@react-navigation/elements';
@ -12,7 +11,6 @@ import StatusBar from '../../containers/StatusBar';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import SearchHeader from '../../containers/SearchHeader'; import SearchHeader from '../../containers/SearchHeader';
import BackgroundContainer from '../../containers/BackgroundContainer'; import BackgroundContainer from '../../containers/BackgroundContainer';
import { getHeaderTitlePosition } from '../../containers/Header';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import Navigation from '../../lib/navigation/appNavigation'; import Navigation from '../../lib/navigation/appNavigation';
import { goRoom } from '../../lib/methods/helpers/goRoom'; import { goRoom } from '../../lib/methods/helpers/goRoom';
@ -73,7 +71,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const insets = useSafeAreaInsets();
const { theme } = useTheme(); const { theme } = useTheme();
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail); const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const rooms = useAppSelector(state => state.room.rooms); const rooms = useAppSelector(state => state.room.rooms);
@ -248,9 +245,10 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
const getHeader = (): StackNavigationOptions => { const getHeader = (): StackNavigationOptions => {
if (isSearching) { if (isSearching) {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
return { return {
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { flex: 1, marginHorizontal: 0, marginRight: 15, maxWidth: undefined },
headerRightContainerStyle: { flexGrow: 0 },
headerLeft: () => ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Item <HeaderButton.Item
@ -263,35 +261,29 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView
</HeaderButton.Container> </HeaderButton.Container>
), ),
headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} testID='team-channels-view-search-header' />, headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} testID='team-channels-view-search-header' />,
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => null headerRight: () => null
}; };
} }
const options: StackNavigationOptions = { const options: StackNavigationOptions = {
headerTitleAlign: undefined,
headerTitle: I18n.t('Canned_Responses'),
headerTitleContainerStyle: { maxWidth: undefined },
headerRightContainerStyle: { flexGrow: 1 },
headerLeft: () => ( headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
), ),
headerTitleAlign: 'center', headerRight: () => (
headerTitle: I18n.t('Canned_Responses'), <HeaderButton.Container>
headerTitleContainerStyle: { <HeaderButton.Item iconName='search' onPress={() => setIsSearching(true)} />
left: 0, </HeaderButton.Container>
right: 0 )
}
}; };
if (isMasterDetail) { if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} }
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={() => setIsSearching(true)} />
</HeaderButton.Container>
);
return options; return options;
}; };

View File

@ -1,11 +1,10 @@
import React, { useEffect, useLayoutEffect, useState } from 'react'; import React, { useEffect, useLayoutEffect, useState } from 'react';
import { FlatList, StyleSheet } from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { HeaderBackButton } from '@react-navigation/elements'; import { HeaderBackButton } from '@react-navigation/elements';
import { RouteProp } from '@react-navigation/core'; import { RouteProp } from '@react-navigation/core';
import { IMessageFromServer } from '../../definitions'; import { IMessageFromServer, TThreadModel } from '../../definitions';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -16,10 +15,8 @@ import SafeAreaView from '../../containers/SafeAreaView';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import BackgroundContainer from '../../containers/BackgroundContainer'; import BackgroundContainer from '../../containers/BackgroundContainer';
import { getHeaderTitlePosition } from '../../containers/Header';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import SearchHeader from '../../containers/SearchHeader'; import SearchHeader from '../../containers/SearchHeader';
import { TThreadModel } from '../../definitions/IThread';
import Item from './Item'; import Item from './Item';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
import { useAppSelector } from '../../lib/hooks'; import { useAppSelector } from '../../lib/hooks';
@ -53,7 +50,6 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
const [searchTotal, setSearchTotal] = useState(0); const [searchTotal, setSearchTotal] = useState(0);
const { colors } = useTheme(); const { colors } = useTheme();
const insets = useSafeAreaInsets();
const load = async (text = '') => { const load = async (text = '') => {
if (loading) { if (loading) {
@ -103,9 +99,10 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
const setHeader = () => { const setHeader = () => {
let options: Partial<StackNavigationOptions>; let options: Partial<StackNavigationOptions>;
if (isSearching) { if (isSearching) {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
options = { options = {
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { flex: 1, marginHorizontal: 0, marginRight: 15, maxWidth: undefined },
headerRightContainerStyle: { flexGrow: 0 },
headerLeft: () => ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Item iconName='close' onPress={onCancelSearchPress} /> <HeaderButton.Item iconName='close' onPress={onCancelSearchPress} />
@ -114,25 +111,18 @@ const DiscussionsView = ({ navigation, route }: IDiscussionsViewProps): React.Re
headerTitle: () => ( headerTitle: () => (
<SearchHeader onSearchChangeText={onSearchChangeText} testID='discussion-messages-view-search-header' /> <SearchHeader onSearchChangeText={onSearchChangeText} testID='discussion-messages-view-search-header' />
), ),
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => null headerRight: () => null
}; };
return options; return options;
} }
options = { options = {
headerTitleAlign: 'center',
headerTitle: I18n.t('Discussions'),
headerRightContainerStyle: { flexGrow: 1 },
headerLeft: () => ( headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={colors.headerTintColor} /> <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={colors.headerTintColor} />
), ),
headerTitleAlign: 'center',
headerTitle: I18n.t('Discussions'),
headerTitleContainerStyle: {
left: 0,
right: 0
},
headerRight: () => ( headerRight: () => (
<HeaderButton.Container> <HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={onSearchPress} /> <HeaderButton.Item iconName='search' onPress={onSearchPress} />

View File

@ -63,7 +63,7 @@ export function DeleteAccountActionSheetContent(): React.ReactElement {
onCancel={hideActionSheet} onCancel={hideActionSheet}
onSubmit={password => handleDeleteAccount(password)} onSubmit={password => handleDeleteAccount(password)}
placeholder={i18n.t('Password')} placeholder={i18n.t('Password')}
testID='room-info-edit-view-name' testID='profile-view-delete-account-sheet'
iconName='warning' iconName='warning'
confirmTitle={i18n.t('Delete_Account')} confirmTitle={i18n.t('Delete_Account')}
confirmBackgroundColor={colors.dangerColor} confirmBackgroundColor={colors.dangerColor}

View File

@ -1,5 +1,5 @@
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet, Platform } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import { HeaderBackButton } from '@react-navigation/elements'; import { HeaderBackButton } from '@react-navigation/elements';
@ -7,11 +7,19 @@ import { themes } from '../../lib/constants';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import { ChatsStackParamList } from '../../stacks/types'; import { ChatsStackParamList } from '../../stacks/types';
import { TSupportedThemes } from '../../theme'; import { TSupportedThemes } from '../../theme';
import { isIOS } from '../../lib/methods/helpers';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: {
...Platform.select({
ios: {
minWidth: 60
}
})
},
avatar: { avatar: {
borderRadius: 10, borderRadius: 10,
marginHorizontal: 16 marginHorizontal: 15
} }
}); });
@ -59,9 +67,11 @@ const LeftButtons = ({
return ( return (
<HeaderBackButton <HeaderBackButton
label={label} label={label}
labelVisible={isIOS}
onPress={onPress} onPress={onPress}
tintColor={themes[theme].headerTintColor} tintColor={themes[theme].headerTintColor}
labelStyle={{ fontSize, marginLeft }} labelStyle={{ fontSize, marginLeft }}
style={styles.container}
/> />
); );
} }

View File

@ -24,7 +24,7 @@ import RoomHeader from '../../containers/RoomHeader';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ReactionsModal from '../../containers/ReactionsModal'; import ReactionsModal from '../../containers/ReactionsModal';
import { LISTENER } from '../../containers/Toast'; import { LISTENER } from '../../containers/Toast';
import { getBadgeColor, isBlocked, isTeamRoom, makeThreadName } from '../../lib/methods/helpers/room'; import { getBadgeColor, isBlocked, makeThreadName } from '../../lib/methods/helpers/room';
import { isReadOnly } from '../../lib/methods/helpers/isReadOnly'; import { isReadOnly } from '../../lib/methods/helpers/isReadOnly';
import { showErrorAlert } from '../../lib/methods/helpers/info'; import { showErrorAlert } from '../../lib/methods/helpers/info';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
@ -574,7 +574,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
let token: string | undefined; let token: string | undefined;
let avatar: string | undefined; let avatar: string | undefined;
let visitor: IVisitor | undefined; let visitor: IVisitor | undefined;
let status: string | undefined;
let sourceType: IOmnichannelSource | undefined; let sourceType: IOmnichannelSource | undefined;
if ('id' in room) { if ('id' in room) {
subtitle = room.topic; subtitle = room.topic;
@ -585,7 +584,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
({ id: userId, token } = user); ({ id: userId, token } = user);
avatar = room.name; avatar = room.name;
visitor = room.visitor; visitor = room.visitor;
status = room.status;
} }
if ('source' in room) { if ('source' in room) {
@ -594,19 +592,18 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
visitor = room.visitor; visitor = room.visitor;
} }
let numIconsRight = 2;
if (tmid || (status && joined)) {
numIconsRight = 1;
} else if (teamId && isTeamRoom({ teamId, joined })) {
numIconsRight = 3;
}
const omnichannelPermissions = { canForwardGuest, canReturnQueue, canPlaceLivechatOnHold }; const omnichannelPermissions = { canForwardGuest, canReturnQueue, canPlaceLivechatOnHold };
const paddingRight = this.getPaddingLeft(numIconsRight, isMasterDetail);
navigation.setOptions({ navigation.setOptions({
headerShown: true, headerShown: true,
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { paddingRight }, headerTitleContainerStyle: {
flex: 1,
marginLeft: 0,
marginRight: 4,
maxWidth: undefined
},
headerRightContainerStyle: { flexGrow: undefined, flexBasis: undefined },
headerLeft: () => ( headerLeft: () => (
<LeftButtons <LeftButtons
tmid={tmid} tmid={tmid}
@ -656,13 +653,6 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
}); });
}; };
getPaddingLeft = (numIcons: number, isMasterDetail: boolean) => {
if (numIcons === 3) {
return isMasterDetail ? 40 : 35;
}
return isMasterDetail ? 20 : 0;
};
goRoomActionsView = (screen?: keyof ModalStackParamList) => { goRoomActionsView = (screen?: keyof ModalStackParamList) => {
logEvent(events.ROOM_GO_RA); logEvent(events.ROOM_GO_RA);
const { room, member, joined, canForwardGuest, canReturnQueue, canViewCannedResponse, canPlaceLivechatOnHold } = this.state; const { room, member, joined, canForwardGuest, canReturnQueue, canViewCannedResponse, canPlaceLivechatOnHold } = this.state;

View File

@ -1,20 +1,18 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, TextInputProps, TouchableOpacity, TouchableOpacityProps, View } from 'react-native'; import { StyleSheet, Text, TextInputProps, TouchableOpacity, TouchableOpacityProps, View } from 'react-native';
import { TextInput } from '../../../containers/TextInput';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import { themes } from '../../../lib/constants';
import { CustomIcon } from '../../../containers/CustomIcon'; import { CustomIcon } from '../../../containers/CustomIcon';
import { isIOS, isTablet } from '../../../lib/methods/helpers'; import { isIOS, isTablet } from '../../../lib/methods/helpers';
import { useOrientation } from '../../../dimensions'; import { useOrientation } from '../../../dimensions';
import { useTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import SearchHeader from '../../../containers/SearchHeader';
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',
@ -56,26 +54,14 @@ const Header = React.memo(
onSearchChangeText, onSearchChangeText,
onPress onPress
}: IRoomHeader) => { }: IRoomHeader) => {
const { theme } = useTheme(); const { colors } = useTheme();
const titleColorStyle = { color: themes[theme].headerTitleColor };
const isLight = theme === 'light';
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1; const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
const titleFontSize = 16 * scale; const titleFontSize = 16 * scale;
const subTitleFontSize = 14 * scale; const subTitleFontSize = 14 * scale;
if (showSearchHeader) { if (showSearchHeader) {
return ( return <SearchHeader onSearchChangeText={onSearchChangeText} testID='rooms-list-view-search-input' />;
<View style={styles.container}>
<TextInput
autoFocus
style={[styles.subtitle, isLight && titleColorStyle, { fontSize: titleFontSize }]}
placeholder='Search'
onChangeText={onSearchChangeText}
testID='rooms-list-view-search-input'
/>
</View>
);
} }
let subtitle; let subtitle;
if (connecting) { if (connecting) {
@ -91,12 +77,12 @@ const Header = React.memo(
<View style={styles.container}> <View style={styles.container}>
<TouchableOpacity onPress={onPress} testID='rooms-list-header-server-dropdown-button'> <TouchableOpacity onPress={onPress} testID='rooms-list-header-server-dropdown-button'>
<View style={styles.button}> <View style={styles.button}>
<Text style={[styles.title, titleColorStyle, { fontSize: titleFontSize }]} numberOfLines={1}> <Text style={[styles.title, { fontSize: titleFontSize, color: colors.headerTitleColor }]} numberOfLines={1}>
{serverName} {serverName}
</Text> </Text>
<CustomIcon <CustomIcon
name='chevron-down' name='chevron-down'
color={themes[theme].headerTintColor} color={colors.headerTintColor}
style={[showServerDropdown && styles.upsideDown]} style={[showServerDropdown && styles.upsideDown]}
size={18} size={18}
/> />
@ -104,7 +90,7 @@ const Header = React.memo(
{subtitle ? ( {subtitle ? (
<Text <Text
testID='rooms-list-header-server-subtitle' testID='rooms-list-header-server-subtitle'
style={[styles.subtitle, { color: themes[theme].auxiliaryText, fontSize: subTitleFontSize }]} style={[styles.subtitle, { color: colors.auxiliaryText, fontSize: subTitleFontSize }]}
numberOfLines={1}> numberOfLines={1}>
{subtitle} {subtitle}
</Text> </Text>

View File

@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { toggleServerDropdown, closeServerDropdown, setSearch } from '../../../actions/rooms'; import { toggleServerDropdown, closeServerDropdown, setSearch } from '../../../actions/rooms';
import { TSupportedThemes, withTheme } from '../../../theme';
import EventEmitter from '../../../lib/methods/helpers/events'; import EventEmitter from '../../../lib/methods/helpers/events';
import { KEY_COMMAND, handleCommandOpenServerDropdown, IKeyCommandEvent } from '../../../commands'; import { KEY_COMMAND, handleCommandOpenServerDropdown, IKeyCommandEvent } from '../../../commands';
import { isTablet } from '../../../lib/methods/helpers'; import { isTablet } from '../../../lib/methods/helpers';
@ -18,7 +17,6 @@ interface IRoomsListHeaderViewProps {
connecting: boolean; connecting: boolean;
connected: boolean; connected: boolean;
isFetching: boolean; isFetching: boolean;
theme: TSupportedThemes;
server: string; server: string;
dispatch: Dispatch; dispatch: Dispatch;
} }
@ -87,4 +85,4 @@ const mapStateToProps = (state: IApplicationState) => ({
server: state.server.server server: state.server.server
}); });
export default connect(mapStateToProps)(withTheme(RoomsListHeaderView)); export default connect(mapStateToProps)(RoomsListHeaderView);

View File

@ -20,7 +20,7 @@ import { isTablet } from '../../lib/methods/helpers';
import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication'; import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication';
import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import log, { events, logEvent } from '../../lib/methods/helpers/log'; import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { headerHeight } from '../../containers/Header'; import { headerHeight } from '../../lib/methods/helpers/navigation';
import { goRoom } from '../../lib/methods/helpers/goRoom'; import { goRoom } from '../../lib/methods/helpers/goRoom';
import UserPreferences from '../../lib/methods/userPreferences'; import UserPreferences from '../../lib/methods/userPreferences';
import { IApplicationState, IBaseScreen, RootEnum, TServerModel } from '../../definitions'; import { IApplicationState, IBaseScreen, RootEnum, TServerModel } from '../../definitions';

View File

@ -7,6 +7,7 @@ import { Q } from '@nozbe/watermelondb';
import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { StackNavigationOptions } from '@react-navigation/stack'; import { StackNavigationOptions } from '@react-navigation/stack';
import { Header } from '@react-navigation/elements';
import database from '../../lib/database'; import database from '../../lib/database';
import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../containers/RoomItem'; import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../containers/RoomItem';
@ -21,6 +22,7 @@ import { serverInitAdd } from '../../actions/server';
import { animateNextTransition } from '../../lib/methods/helpers/layoutAnimation'; import { animateNextTransition } from '../../lib/methods/helpers/layoutAnimation';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import { themedHeader } from '../../lib/methods/helpers/navigation';
import { import {
KEY_COMMAND, KEY_COMMAND,
handleCommandAddNewServer, handleCommandAddNewServer,
@ -35,7 +37,6 @@ import {
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { goRoom } from '../../lib/methods/helpers/goRoom'; import { goRoom } from '../../lib/methods/helpers/goRoom';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import Header, { getHeaderTitlePosition } from '../../containers/Header';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry';
import { import {
@ -415,55 +416,59 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
this.setState({ canCreateRoom }, () => this.setHeader()); this.setState({ canCreateRoom }, () => this.setHeader());
}; };
getHeader = () => { getHeader = (): StackNavigationOptions => {
const { searching, canCreateRoom } = this.state; const { searching, canCreateRoom } = this.state;
const { navigation, isMasterDetail, insets, theme } = this.props; const { navigation, isMasterDetail } = this.props;
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: searching ? 0 : 3 }); if (searching) {
return {
return { headerTitleAlign: 'left',
headerTitleAlign: 'left', headerTitleContainerStyle: { flex: 1, marginHorizontal: 0, marginRight: 15, maxWidth: undefined },
headerLeft: () => headerRightContainerStyle: { flexGrow: 0 },
searching ? ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Item iconName='close' onPress={this.cancelSearch} /> <HeaderButton.Item iconName='close' onPress={this.cancelSearch} />
</HeaderButton.Container> </HeaderButton.Container>
) : (
<HeaderButton.Drawer
navigation={navigation}
testID='rooms-list-view-sidebar'
onPress={
isMasterDetail
? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' })
: // @ts-ignore
() => navigation.toggleDrawer()
}
/>
), ),
headerTitle: () => <RoomsListHeaderView theme={theme} />, headerTitle: () => <RoomsListHeaderView />,
headerTitleContainerStyle: { headerRight: () => null
left: headerTitlePosition.left, };
right: headerTitlePosition.right }
},
headerRight: () => return {
searching ? null : ( headerTitleAlign: 'left',
<HeaderButton.Container> headerTitleContainerStyle: { flex: 1, marginHorizontal: 4, maxWidth: undefined },
{canCreateRoom ? ( headerRightContainerStyle: { flexGrow: undefined, flexBasis: undefined },
<HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' /> headerLeft: () => (
) : null} <HeaderButton.Drawer
<HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' /> navigation={navigation}
<HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' /> testID='rooms-list-view-sidebar'
</HeaderButton.Container> onPress={
) isMasterDetail
? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' })
: // @ts-ignore
() => navigation.toggleDrawer()
}
/>
),
headerTitle: () => <RoomsListHeaderView />,
headerRight: () => (
<HeaderButton.Container>
{canCreateRoom ? (
<HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' />
) : null}
<HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' />
<HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' />
</HeaderButton.Container>
)
}; };
}; };
setHeader = () => { setHeader = () => {
const { navigation } = this.props; const { navigation } = this.props;
const options = this.getHeader() as Partial<StackNavigationOptions>; const options = this.getHeader();
navigation.setOptions(options); navigation.setOptions(options);
}; };
// internalSetState = (...args: { chats: TSubscriptionModel; chatsUpdate: TSubscriptionModel; loading: boolean }[]) => {
internalSetState = ( internalSetState = (
state: state:
| (( | ((
@ -923,14 +928,14 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
}; };
renderHeader = () => { renderHeader = () => {
const { isMasterDetail } = this.props; const { isMasterDetail, theme } = this.props;
if (!isMasterDetail) { if (!isMasterDetail) {
return null; return null;
} }
const options = this.getHeader(); const options = this.getHeader();
return <Header {...options} />; return <Header title='' {...themedHeader(theme)} {...options} />;
}; };
renderItem = ({ item }: { item: IRoomItem }) => { renderItem = ({ item }: { item: IRoomItem }) => {

View File

@ -3,11 +3,11 @@ import { Video } from 'expo-av';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { ScrollView, StyleSheet, Text } from 'react-native'; import { ScrollView, StyleSheet, Text } from 'react-native';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
import { useHeaderHeight } from '@react-navigation/elements';
import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
import { ImageViewer, types } from '../../containers/ImageViewer'; import { ImageViewer, types } from '../../containers/ImageViewer';
import { useDimensions, useOrientation } from '../../dimensions'; import { useDimensions } from '../../dimensions';
import { getHeaderHeight } from '../../containers/Header';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { isAndroid } from '../../lib/methods/helpers'; import { isAndroid } from '../../lib/methods/helpers';
@ -66,9 +66,8 @@ interface IPreview {
const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) => { const Preview = React.memo(({ item, theme, isShareExtension, length }: IPreview) => {
const type = item?.mime; const type = item?.mime;
const { width, height } = useDimensions(); const { width, height } = useDimensions();
const { isLandscape } = useOrientation();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const headerHeight = getHeaderHeight(isLandscape); const headerHeight = useHeaderHeight();
const thumbsHeight = length > 1 ? THUMBS_HEIGHT : 0; const thumbsHeight = length > 1 ? THUMBS_HEIGHT : 0;
const calculatedHeight = height - insets.top - insets.bottom - MESSAGEBOX_HEIGHT - thumbsHeight - headerHeight; const calculatedHeight = height - insets.top - insets.bottom - MESSAGEBOX_HEIGHT - thumbsHeight - headerHeight;

View File

@ -106,13 +106,13 @@ class ShareView extends Component<IShareViewProps, IShareViewState> {
// if is share extension show default back button // if is share extension show default back button
if (!this.isShareExtension) { if (!this.isShareExtension) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} color={themes[theme].previewTintColor} />;
} }
if (!attachments.length && !readOnly) { if (!attachments.length && !readOnly) {
options.headerRight = () => ( options.headerRight = () => (
<HeaderButton.Container> <HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Send')} onPress={this.send} /> <HeaderButton.Item title={I18n.t('Send')} onPress={this.send} color={themes[theme].previewTintColor} />
</HeaderButton.Container> </HeaderButton.Container>
); );
} }

View File

@ -3,7 +3,6 @@ import { StackNavigationOptions } from '@react-navigation/stack';
import { HeaderBackButton } from '@react-navigation/elements'; import { HeaderBackButton } from '@react-navigation/elements';
import React from 'react'; import React from 'react';
import { Alert, FlatList, Keyboard } from 'react-native'; import { Alert, FlatList, Keyboard } from 'react-native';
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { deleteRoom } from '../actions/room'; import { deleteRoom } from '../actions/room';
@ -11,7 +10,6 @@ import { DisplayMode, themes } from '../lib/constants';
import { TActionSheetOptions, TActionSheetOptionsItem, withActionSheet } from '../containers/ActionSheet'; import { TActionSheetOptions, TActionSheetOptionsItem, withActionSheet } from '../containers/ActionSheet';
import ActivityIndicator from '../containers/ActivityIndicator'; import ActivityIndicator from '../containers/ActivityIndicator';
import BackgroundContainer from '../containers/BackgroundContainer'; import BackgroundContainer from '../containers/BackgroundContainer';
import { getHeaderTitlePosition } from '../containers/Header';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import RoomHeader from '../containers/RoomHeader'; import RoomHeader from '../containers/RoomHeader';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
@ -74,7 +72,6 @@ interface ITeamChannelsViewState {
} }
interface ITeamChannelsViewProps extends IBaseScreen<ChatsStackParamList, 'TeamChannelsView'> { interface ITeamChannelsViewProps extends IBaseScreen<ChatsStackParamList, 'TeamChannelsView'> {
insets: EdgeInsets;
useRealName: boolean; useRealName: boolean;
width: number; width: number;
StoreLastMessage: boolean; StoreLastMessage: boolean;
@ -190,18 +187,18 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan
setHeader = () => { setHeader = () => {
const { isSearching, showCreate, data } = this.state; const { isSearching, showCreate, data } = this.state;
const { navigation, isMasterDetail, insets, theme } = this.props; const { navigation, isMasterDetail, theme } = this.props;
const { team } = this; const { team } = this;
if (!team) { if (!team) {
return; return;
} }
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 });
if (isSearching) { if (isSearching) {
const options: StackNavigationOptions = { const options: StackNavigationOptions = {
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { flex: 1, marginHorizontal: 0, marginRight: 15, maxWidth: undefined },
headerRightContainerStyle: { flexGrow: 0 },
headerLeft: () => ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} /> <HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
@ -210,27 +207,33 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan
headerTitle: () => ( headerTitle: () => (
<SearchHeader onSearchChangeText={this.onSearchChangeText} testID='team-channels-view-search-header' /> <SearchHeader onSearchChangeText={this.onSearchChangeText} testID='team-channels-view-search-header' />
), ),
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => null headerRight: () => null
}; };
return navigation.setOptions(options); return navigation.setOptions(options);
} }
const options: StackNavigationOptions = { const options: StackNavigationOptions = {
headerShown: true,
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { headerTitleContainerStyle: { flex: 1, marginLeft: 0, marginRight: 4, maxWidth: undefined },
left: headerTitlePosition.left, headerLeftContainerStyle: { minWidth: 60 },
right: headerTitlePosition.right headerRightContainerStyle: { flexGrow: undefined, flexBasis: undefined },
},
headerLeft: () => ( headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
), ),
headerTitle: () => ( headerTitle: () => (
<RoomHeader title={getRoomTitle(team)} subtitle={team.topic} type={team.t} onPress={this.goRoomActionsView} teamMain /> <RoomHeader title={getRoomTitle(team)} subtitle={team.topic} type={team.t} onPress={this.goRoomActionsView} teamMain />
),
headerRight: () => (
<HeaderButton.Container>
{showCreate ? (
<HeaderButton.Item
iconName='create'
testID='team-channels-view-create'
onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })}
/>
) : null}
<HeaderButton.Item iconName='search' testID='team-channels-view-search' onPress={this.onSearchPress} />
</HeaderButton.Container>
) )
}; };
@ -238,18 +241,6 @@ class TeamChannelsView extends React.Component<ITeamChannelsViewProps, ITeamChan
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} }
options.headerRight = () => (
<HeaderButton.Container>
{showCreate ? (
<HeaderButton.Item
iconName='create'
testID='team-channels-view-create'
onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })}
/>
) : null}
<HeaderButton.Item iconName='search' testID='team-channels-view-search' onPress={this.onSearchPress} />
</HeaderButton.Container>
);
navigation.setOptions(options); navigation.setOptions(options);
}; };
@ -575,4 +566,4 @@ const mapStateToProps = (state: IApplicationState) => ({
displayMode: state.sortPreferences.displayMode displayMode: state.sortPreferences.displayMode
}); });
export default connect(mapStateToProps)(withDimensions(withSafeAreaInsets(withTheme(withActionSheet(TeamChannelsView))))); export default connect(mapStateToProps)(withDimensions(withTheme(withActionSheet(TeamChannelsView))));

View File

@ -5,7 +5,7 @@ import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
import { TSupportedThemes, withTheme } from '../../../theme'; import { TSupportedThemes, withTheme } from '../../../theme';
import { headerHeight } from '../../../containers/Header'; import { headerHeight } from '../../../lib/methods/helpers/navigation';
import * as List from '../../../containers/List'; import * as List from '../../../containers/List';
import { Filter } from '../filters'; import { Filter } from '../filters';
import DropdownItemFilter from './DropdownItemFilter'; import DropdownItemFilter from './DropdownItemFilter';

View File

@ -3,7 +3,6 @@ import { FlatList } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { StackNavigationOptions } from '@react-navigation/stack'; import { StackNavigationOptions } from '@react-navigation/stack';
import { HeaderBackButton } from '@react-navigation/elements'; import { HeaderBackButton } from '@react-navigation/elements';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
@ -24,7 +23,6 @@ import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import BackgroundContainer from '../../containers/BackgroundContainer'; import BackgroundContainer from '../../containers/BackgroundContainer';
import { getBadgeColor, makeThreadName } from '../../lib/methods/helpers/room'; import { getBadgeColor, makeThreadName } from '../../lib/methods/helpers/room';
import { getHeaderTitlePosition } from '../../containers/Header';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import { LISTENER } from '../../containers/Toast'; import { LISTENER } from '../../containers/Toast';
import SearchHeader from '../../containers/SearchHeader'; import SearchHeader from '../../containers/SearchHeader';
@ -58,7 +56,6 @@ interface IThreadMessagesViewProps extends IBaseScreen<ChatsStackParamList, 'Thr
useRealName: boolean; useRealName: boolean;
theme: TSupportedThemes; theme: TSupportedThemes;
isMasterDetail: boolean; isMasterDetail: boolean;
insets: EdgeInsets;
} }
class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThreadMessagesViewState> { class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThreadMessagesViewState> {
@ -100,13 +97,6 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
this.init(); this.init();
} }
componentDidUpdate(prevProps: IThreadMessagesViewProps) {
const { insets } = this.props;
if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) {
this.setHeader();
}
}
componentWillUnmount() { componentWillUnmount() {
console.countReset(`${this.constructor.name}.render calls`); console.countReset(`${this.constructor.name}.render calls`);
if (this.subSubscription && this.subSubscription.unsubscribe) { if (this.subSubscription && this.subSubscription.unsubscribe) {
@ -119,12 +109,13 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
getHeader = (): StackNavigationOptions => { getHeader = (): StackNavigationOptions => {
const { isSearching } = this.state; const { isSearching } = this.state;
const { navigation, isMasterDetail, insets, theme } = this.props; const { navigation, isMasterDetail, theme } = this.props;
if (isSearching) { if (isSearching) {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
return { return {
headerTitleAlign: 'left', headerTitleAlign: 'left',
headerTitleContainerStyle: { flex: 1, marginHorizontal: 0, marginRight: 15, maxWidth: undefined },
headerRightContainerStyle: { flexGrow: 0 },
headerLeft: () => ( headerLeft: () => (
<HeaderButton.Container left> <HeaderButton.Container left>
<HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} /> <HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} />
@ -133,35 +124,28 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
headerTitle: () => ( headerTitle: () => (
<SearchHeader onSearchChangeText={this.onSearchChangeText} testID='thread-messages-view-search-header' /> <SearchHeader onSearchChangeText={this.onSearchChangeText} testID='thread-messages-view-search-header' />
), ),
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
},
headerRight: () => null headerRight: () => null
}; };
} }
const options: StackNavigationOptions = { const options: StackNavigationOptions = {
headerTitleAlign: 'center',
headerTitle: I18n.t('Threads'),
headerRightContainerStyle: { flexGrow: 1 },
headerLeft: () => ( headerLeft: () => (
<HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} />
), ),
headerTitleAlign: 'center', headerRight: () => (
headerTitle: I18n.t('Threads'), <HeaderButton.Container>
headerTitleContainerStyle: { <HeaderButton.Item iconName='search' onPress={this.onSearchPress} />
left: 0, </HeaderButton.Container>
right: 0 )
}
}; };
if (isMasterDetail) { if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
} }
options.headerRight = () => (
<HeaderButton.Container>
<HeaderButton.Item iconName='search' onPress={this.onSearchPress} />
</HeaderButton.Container>
);
return options; return options;
}; };
@ -549,4 +533,4 @@ const mapStateToProps = (state: IApplicationState) => ({
isMasterDetail: state.app.isMasterDetail isMasterDetail: state.app.isMasterDetail
}); });
export default connect(mapStateToProps)(withTheme(withSafeAreaInsets(ThreadMessagesView))); export default connect(mapStateToProps)(withTheme(ThreadMessagesView));

View File

@ -108,8 +108,13 @@ describe('Profile screen', () => {
await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`); await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`);
await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`); await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`);
await element(by.id('profile-view-submit')).tap(); await element(by.id('profile-view-submit')).tap();
await element(by.type(textInputType)).replaceText(`${profileChangeUser.password}`); await waitFor(element(by.id('profile-view-enter-password-sheet')))
await element(by[textMatcher]('Save').and(by.type(alertButtonType))).tap(); .toBeVisible()
.withTimeout(2000);
await element(by.id('profile-view-enter-password-sheet')).replaceText(`${profileChangeUser.password}`);
await element(by[textMatcher]('Save').withAncestor(by.id('action-sheet-content-with-input-and-submit')))
.atIndex(0)
.tap();
await waitForToast(); await waitForToast();
}); });

View File

@ -272,6 +272,9 @@ describe('Room actions screen', () => {
describe('Notification', () => { describe('Notification', () => {
it('should navigate to notification preference view', async () => { it('should navigate to notification preference view', async () => {
await waitFor(element(by.id('room-actions-scrollview')))
.toExist()
.withTimeout(2000);
await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await element(by.id('room-actions-scrollview')).scrollTo('bottom');
await waitFor(element(by.id('room-actions-notifications'))) await waitFor(element(by.id('room-actions-notifications')))
.toExist() .toExist()
@ -307,8 +310,6 @@ describe('Room actions screen', () => {
}); });
it('should have notification sound option', async () => { it('should have notification sound option', async () => {
// Ugly hack to scroll on detox
await element(by.id('room-actions-scrollview')).scrollTo('bottom');
await waitFor(element(by.id('notification-preference-view-sound'))) await waitFor(element(by.id('notification-preference-view-sound')))
.toExist() .toExist()
.withTimeout(4000); .withTimeout(4000);
@ -338,6 +339,9 @@ describe('Room actions screen', () => {
const user = data.users.alternate; const user = data.users.alternate;
it('should tap on leave channel and raise alert', async () => { it('should tap on leave channel and raise alert', async () => {
await waitFor(element(by.id('room-actions-scrollview')))
.toExist()
.withTimeout(2000);
await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await element(by.id('room-actions-scrollview')).scrollTo('bottom');
await waitFor(element(by.id('room-actions-leave-channel'))) await waitFor(element(by.id('room-actions-leave-channel')))
.toExist() .toExist()

View File

@ -290,10 +290,11 @@ describe('Room info screen', () => {
.toExist() .toExist()
.withTimeout(5000); .withTimeout(5000);
await element(by[textMatcher]('Yes, archive it!').and(by.type(alertButtonType))).tap(); await element(by[textMatcher]('Yes, archive it!').and(by.type(alertButtonType))).tap();
await waitFor(element(by.id('room-info-edit-view-unarchive'))) await waitForToast();
.toExist() // await waitFor(element(by.id('room-info-edit-view-unarchive')))
.withTimeout(60000); // .toExist()
await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible(); // .withTimeout(60000);
// await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible();
}); });
it('should delete room', async () => { it('should delete room', async () => {

View File

@ -0,0 +1,13 @@
diff --git a/node_modules/@react-navigation/elements/src/Header/HeaderBackButton.tsx b/node_modules/@react-navigation/elements/src/Header/HeaderBackButton.tsx
index 39a39b2..7a60a15 100644
--- a/node_modules/@react-navigation/elements/src/Header/HeaderBackButton.tsx
+++ b/node_modules/@react-navigation/elements/src/Header/HeaderBackButton.tsx
@@ -30,7 +30,7 @@ export default function HeaderBackButton({
titleLayout,
truncatedLabel = 'Back',
accessibilityLabel = label && label !== 'Back' ? `${label}, back` : 'Go back',
- testID,
+ testID = 'header-back',
style,
}: HeaderBackButtonProps) {
const { colors } = useTheme();

View File

@ -1,16 +1,31 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types */
import React from 'react'; import React from 'react';
import { storiesOf } from '@storybook/react-native'; import { storiesOf } from '@storybook/react-native';
import { View } from 'react-native'; import { SafeAreaView } from 'react-native';
import { Header, HeaderBackground } from '@react-navigation/elements';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import * as HeaderButton from '../../app/containers/HeaderButton'; import * as HeaderButton from '../../app/containers/HeaderButton';
import Header from '../../app/containers/Header'; import { TColors, ThemeContext, TSupportedThemes } from '../../app/theme';
import { ThemeContext } from '../../app/theme'; import { colors } from '../../app/lib/constants';
const stories = storiesOf('Header Buttons', module); const stories = storiesOf('Header Buttons', module).addDecorator(story => <SafeAreaProvider>{story()}</SafeAreaProvider>);
const HeaderExample = ({ left, right }) => ( interface IHeader {
<Header headerLeft={left} headerTitle={() => <View style={{ flex: 1 }} />} headerRight={right} /> left?: () => React.ReactElement | null;
right?: () => React.ReactElement;
title?: string;
colors?: TColors;
}
const HeaderExample = ({ left, right, colors, title = '' }: IHeader) => (
<SafeAreaView>
<Header
title={title}
headerLeft={left}
headerRight={right}
headerBackground={() => <HeaderBackground style={{ backgroundColor: colors?.headerBackground }} />}
/>
</SafeAreaView>
); );
stories.add('title', () => ( stories.add('title', () => (
@ -89,8 +104,8 @@ stories.add('badge', () => (
</> </>
)); ));
const ThemeStory = ({ theme }) => ( const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
<ThemeContext.Provider value={{ theme }}> <ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
<HeaderExample <HeaderExample
left={() => ( left={() => (
<HeaderButton.Container left> <HeaderButton.Container left>
@ -103,6 +118,7 @@ const ThemeStory = ({ theme }) => (
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} /> <HeaderButton.Item iconName='threads' badge={() => <HeaderButton.Badge tunread={[1]} />} />
</HeaderButton.Container> </HeaderButton.Container>
)} )}
colors={colors[theme]}
/> />
</ThemeContext.Provider> </ThemeContext.Provider>
); );

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long