[IMPROVEMENT] List Component (#2506)

* List.Item

* section

* Start removing theme as prop

* Remove StatusBar theme prop

* SafeAreaView theme prop

* Minor fixes

* List.Container

* Add translateTitle and translateSubtitle props

* Storybook

* Show action indicator

* Header

* Info

* Theme stories

* FlatList

* DisplayName

* Fix settings

* FlatList tweaks

* ThemeView

* Screen Lock Config

* DefaultBrowserView

* PickerView and User Prefs

* Notification Prefs

* StatusView

* Auto Translate

* InviteUsersEdit

* Visitor

* Minor fixes

* Remove Separator

* Remove iteminfo

* Font scale

* Legal

* Jitsi and e2e

* Block

* search, star, etc

* auto translate and notifications

* RoomInfo

* Refactor RoomActions

* lint

* Remove DisclosureIndicator

* padding horizontal 12

* Detox

* Tests

* Address review comments

* Fix vertical scroll

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
This commit is contained in:
Diego Mello 2020-10-30 10:59:44 -03:00 committed by GitHub
parent 46e3db97e8
commit 52850cbccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 35860 additions and 1904 deletions

View File

@ -1,4 +1,4 @@
export default { const activateKeepAwake = () => '';
activateKeepAwake: () => '', const deactivateKeepAwake = () => '';
deactivateKeepAwake: () => ''
}; export { activateKeepAwake, deactivateKeepAwake };

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ import { Button } from './Button';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles, { ITEM_HEIGHT } from './styles'; import styles, { ITEM_HEIGHT } from './styles';
import { isTablet, isIOS } from '../../utils/deviceInfo'; import { isTablet, isIOS } from '../../utils/deviceInfo';
import Separator from '../Separator'; import * as List from '../List';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { useOrientation, useDimensions } from '../../dimensions'; import { useOrientation, useDimensions } from '../../dimensions';
@ -142,8 +142,6 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
</Button> </Button>
) : null)); ) : null));
const renderSeparator = useCallback(() => <Separator theme={theme} style={styles.separator} />);
const renderItem = useCallback(({ item }) => <Item item={item} hide={hide} theme={theme} />); const renderItem = useCallback(({ item }) => <Item item={item} hide={hide} theme={theme} />);
const animatedPosition = React.useRef(new Value(0)); const animatedPosition = React.useRef(new Value(0));
@ -191,8 +189,8 @@ const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => {
keyExtractor={item => item.title} keyExtractor={item => item.title}
style={{ backgroundColor: themes[theme].focusedBackground }} style={{ backgroundColor: themes[theme].focusedBackground }}
contentContainerStyle={styles.content} contentContainerStyle={styles.content}
ItemSeparatorComponent={renderSeparator} ItemSeparatorComponent={List.Separator}
ListHeaderComponent={renderSeparator} ListHeaderComponent={List.Separator}
ListFooterComponent={renderFooter} ListFooterComponent={renderFooter}
getItemLayout={getItemLayout} getItemLayout={getItemLayout}
removeClippedSubviews={isIOS} removeClippedSubviews={isIOS}

View File

@ -1,37 +0,0 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../constants/colors';
import { CustomIcon } from '../lib/Icons';
const styles = StyleSheet.create({
disclosureContainer: {
marginLeft: 6,
marginRight: 9,
alignItems: 'center',
justifyContent: 'center'
}
});
export const DisclosureImage = React.memo(({ theme }) => (
<CustomIcon
name='chevron-right'
color={themes[theme].auxiliaryText}
size={20}
/>
));
DisclosureImage.propTypes = {
theme: PropTypes.string
};
const DisclosureIndicator = React.memo(({ theme }) => (
<View style={styles.disclosureContainer}>
<DisclosureImage theme={theme} />
</View>
));
DisclosureIndicator.propTypes = {
theme: PropTypes.string
};
export default DisclosureIndicator;

View File

@ -31,14 +31,14 @@ const FormContainer = ({
contentContainerStyle={sharedStyles.container} contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <ScrollView
style={sharedStyles.container} style={sharedStyles.container}
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
{...scrollPersistTaps} {...scrollPersistTaps}
{...props} {...props}
> >
<SafeAreaView testID={testID} theme={theme} style={{ backgroundColor: themes[theme].backgroundColor }}> <SafeAreaView testID={testID} style={{ backgroundColor: themes[theme].backgroundColor }}>
{children} {children}
<AppVersion theme={theme} /> <AppVersion theme={theme} />
</SafeAreaView> </SafeAreaView>

View File

@ -1,29 +0,0 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
const styles = StyleSheet.create({
infoContainer: {
padding: 15
},
infoText: {
fontSize: 14,
...sharedStyles.textRegular
}
});
const ItemInfo = React.memo(({ info, theme }) => (
<View style={[styles.infoContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}>
<Text style={[styles.infoText, { color: themes[theme].infoText }]}>{info}</Text>
</View>
));
ItemInfo.propTypes = {
info: PropTypes.string,
theme: PropTypes.string
};
export default ItemInfo;

View File

@ -0,0 +1,30 @@
import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../../theme';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
const styles = StyleSheet.create({
container: {
paddingVertical: 16
}
});
const ListContainer = React.memo(({ children, ...props }) => (
<ScrollView
contentContainerStyle={styles.container}
scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444
{...scrollPersistTaps}
{...props}
>
{children}
</ScrollView>
));
ListContainer.propTypes = {
children: PropTypes.array.isRequired
};
ListContainer.displayName = 'List.Container';
export default withTheme(ListContainer);

View File

@ -0,0 +1,40 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import I18n from '../../i18n';
import { withTheme } from '../../theme';
import { PADDING_HORIZONTAL } from './constants';
const styles = StyleSheet.create({
container: {
paddingBottom: 12,
paddingHorizontal: PADDING_HORIZONTAL
},
title: {
fontSize: 16,
...sharedStyles.textRegular
}
});
const ListHeader = React.memo(({ title, theme, translateTitle }) => (
<View style={styles.container}>
<Text style={[styles.title, { color: themes[theme].infoText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text>
</View>
));
ListHeader.propTypes = {
title: PropTypes.string,
theme: PropTypes.string,
translateTitle: PropTypes.bool
};
ListHeader.defaultProps = {
translateTitle: true
};
ListHeader.displayName = 'List.Header';
export default withTheme(ListHeader);

View File

@ -0,0 +1,34 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons';
import { withTheme } from '../../theme';
const styles = StyleSheet.create({
icon: {
alignItems: 'center',
justifyContent: 'center'
}
});
const ListIcon = React.memo(({ theme, name, color }) => (
<View style={styles.icon}>
<CustomIcon
name={name}
color={color ?? themes[theme].auxiliaryText}
size={20}
/>
</View>
));
ListIcon.propTypes = {
theme: PropTypes.string,
name: PropTypes.string,
color: PropTypes.string
};
ListIcon.displayName = 'List.Icon';
export default withTheme(ListIcon);

View File

@ -0,0 +1,40 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import { PADDING_HORIZONTAL } from './constants';
import I18n from '../../i18n';
const styles = StyleSheet.create({
container: {
paddingTop: 8,
paddingHorizontal: PADDING_HORIZONTAL
},
text: {
fontSize: 14,
...sharedStyles.textRegular
}
});
const ListInfo = React.memo(({ info, translateInfo, theme }) => (
<View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].infoText }]}>{translateInfo ? I18n.t(info) : info}</Text>
</View>
));
ListInfo.propTypes = {
info: PropTypes.string,
theme: PropTypes.string,
translateInfo: PropTypes.bool
};
ListInfo.defaultProps = {
translateInfo: true
};
ListInfo.displayName = 'List.Info';
export default withTheme(ListInfo);

View File

@ -0,0 +1,137 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Touch from '../../utils/touch';
import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles';
import { withTheme } from '../../theme';
import I18n from '../../i18n';
import { Icon } from '.';
import { BASE_HEIGHT, PADDING_HORIZONTAL } from './constants';
import { withDimensions } from '../../dimensions';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: PADDING_HORIZONTAL
},
leftContainer: {
paddingRight: PADDING_HORIZONTAL
},
rightContainer: {
paddingLeft: PADDING_HORIZONTAL
},
disabled: {
opacity: 0.3
},
textContainer: {
flex: 1,
justifyContent: 'center'
},
title: {
fontSize: 16,
...sharedStyles.textRegular
},
subtitle: {
fontSize: 14,
...sharedStyles.textRegular
}
});
const Content = React.memo(({
title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale
}) => (
<View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}>
{left
? (
<View style={styles.leftContainer}>
{left()}
</View>
)
: null}
<View style={styles.textContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text>
{subtitle
? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{translateSubtitle ? I18n.t(subtitle) : subtitle}</Text>
: null
}
</View>
{right || showActionIndicator
? (
<View style={styles.rightContainer}>
{right ? right() : null}
{showActionIndicator ? <Icon name='chevron-right' /> : null}
</View>
)
: null}
</View>
));
const Button = React.memo(({
onPress, ...props
}) => (
<Touch
onPress={() => onPress(props.title)}
style={{ backgroundColor: themes[props.theme].backgroundColor }}
enabled={!props.disabled}
theme={props.theme}
>
<Content {...props} />
</Touch>
));
const ListItem = React.memo(({ ...props }) => {
if (props.onPress) {
return <Button {...props} />;
}
return (
<View style={{ backgroundColor: themes[props.theme].backgroundColor }}>
<Content {...props} />
</View>
);
});
ListItem.propTypes = {
onPress: PropTypes.func,
theme: PropTypes.string
};
ListItem.displayName = 'List.Item';
Content.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
left: PropTypes.func,
right: PropTypes.func,
disabled: PropTypes.bool,
testID: PropTypes.string,
theme: PropTypes.string,
color: PropTypes.string,
translateTitle: PropTypes.bool,
translateSubtitle: PropTypes.bool,
showActionIndicator: PropTypes.bool,
fontScale: PropTypes.number
};
Content.defaultProps = {
translateTitle: true,
translateSubtitle: true,
showActionIndicator: false
};
Button.propTypes = {
title: PropTypes.string,
onPress: PropTypes.func,
disabled: PropTypes.bool,
theme: PropTypes.string
};
Button.defaultProps = {
disabled: false
};
export default withTheme(withDimensions(ListItem));

View File

@ -0,0 +1,28 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { withTheme } from '../../theme';
import { Header } from '.';
const styles = StyleSheet.create({
container: {
marginVertical: 16
}
});
const ListSection = React.memo(({ children, title, translateTitle }) => (
<View style={styles.container}>
{title ? <Header {...{ title, translateTitle }} /> : null}
{children}
</View>
));
ListSection.propTypes = {
children: PropTypes.array.isRequired,
title: PropTypes.string,
translateTitle: PropTypes.bool
};
ListSection.displayName = 'List.Section';
export default withTheme(ListSection);

View File

@ -2,7 +2,8 @@ import React from 'react';
import { View, StyleSheet } from 'react-native'; import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { themes } from '../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
separator: { separator: {
@ -11,7 +12,7 @@ const styles = StyleSheet.create({
}); });
const Separator = React.memo(({ style, theme }) => ( const ListSeparator = React.memo(({ style, theme }) => (
<View <View
style={[ style={[
styles.separator, styles.separator,
@ -21,9 +22,11 @@ const Separator = React.memo(({ style, theme }) => (
/> />
)); ));
Separator.propTypes = { ListSeparator.propTypes = {
style: PropTypes.object, style: PropTypes.object,
theme: PropTypes.string theme: PropTypes.string
}; };
export default Separator; ListSeparator.displayName = 'List.Separator';
export default withTheme(ListSeparator);

View File

@ -0,0 +1,2 @@
export const PADDING_HORIZONTAL = 12;
export const BASE_HEIGHT = 46;

View File

@ -0,0 +1,8 @@
export { default as Container } from './ListContainer';
export { default as Item } from './ListItem';
export { default as Section } from './ListSection';
export { default as Icon } from './ListIcon';
export { default as Separator } from './ListSeparator';
export { default as Header } from './ListHeader';
export { default as Info } from './ListInfo';
export * from './styles';

View File

@ -0,0 +1,7 @@
import { StyleSheet } from 'react-native';
export const styles = StyleSheet.create({
contentContainerStyleFlatList: {
paddingVertical: 32
}
});

View File

@ -1,102 +0,0 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Touch from '../utils/touch';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
height: 46,
paddingHorizontal: 15
},
disabled: {
opacity: 0.3
},
textContainer: {
flex: 1,
justifyContent: 'center'
},
title: {
fontSize: 16,
...sharedStyles.textRegular
},
subtitle: {
fontSize: 14,
...sharedStyles.textRegular
}
});
const Content = React.memo(({
title, subtitle, disabled, testID, left, right, color, theme
}) => (
<View style={[styles.container, disabled && styles.disabled]} testID={testID}>
{left ? left() : null}
<View style={styles.textContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]}>{title}</Text>
{subtitle
? <Text style={[styles.subtitle, { color: themes[theme].bodyText }]}>{subtitle}</Text>
: null
}
</View>
{right ? right() : null}
</View>
));
const Button = React.memo(({
onPress, ...props
}) => (
<Touch
onPress={() => onPress(props.title)}
style={{ backgroundColor: themes[props.theme].backgroundColor }}
enabled={!props.disabled}
theme={props.theme}
>
<Content {...props} />
</Touch>
));
const Item = React.memo(({ ...props }) => {
if (props.onPress) {
return <Button {...props} />;
}
return (
<View style={{ backgroundColor: themes[props.theme].backgroundColor }}>
<Content {...props} />
</View>
);
});
Item.propTypes = {
onPress: PropTypes.func,
theme: PropTypes.string
};
Content.propTypes = {
title: PropTypes.string.isRequired,
subtitle: PropTypes.string,
left: PropTypes.func,
right: PropTypes.func,
disabled: PropTypes.bool,
testID: PropTypes.string,
theme: PropTypes.string,
color: PropTypes.string
};
Button.propTypes = {
title: PropTypes.string,
onPress: PropTypes.func,
disabled: PropTypes.bool,
theme: PropTypes.string
};
Button.defaultProps = {
disabled: false
};
export default Item;

View File

@ -99,7 +99,7 @@ const ModalContent = React.memo(({
}) => { }) => {
if (message && message.reactions) { if (message && message.reactions) {
return ( return (
<SafeAreaView theme={props.theme} style={styles.safeArea}> <SafeAreaView style={styles.safeArea}>
<Touchable onPress={onClose}> <Touchable onPress={onClose}>
<View style={styles.titleContainer}> <View style={styles.titleContainer}>
<CustomIcon <CustomIcon

View File

@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context'; import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import { withTheme } from '../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
view: { view: {
@ -31,4 +32,4 @@ SafeAreaView.propTypes = {
children: PropTypes.element children: PropTypes.element
}; };
export default SafeAreaView; export default withTheme(SafeAreaView);

View File

@ -3,6 +3,7 @@ import { StatusBar as StatusBarRN } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import { withTheme } from '../theme';
const StatusBar = React.memo(({ theme, barStyle, backgroundColor }) => { const StatusBar = React.memo(({ theme, barStyle, backgroundColor }) => {
if (!barStyle) { if (!barStyle) {
@ -20,4 +21,4 @@ StatusBar.propTypes = {
backgroundColor: PropTypes.string backgroundColor: PropTypes.string
}; };
export default StatusBar; export default withTheme(StatusBar);

View File

@ -1,8 +1,7 @@
import React from 'react'; import React from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Separator from '../Separator'; import * as List from '../List';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
separator: { separator: {
@ -12,7 +11,4 @@ const styles = StyleSheet.create({
} }
}); });
export const Divider = ({ theme }) => <Separator style={styles.separator} theme={theme} />; export const Divider = () => <List.Separator style={styles.separator} />;
Divider.propTypes = {
theme: PropTypes.string
};

View File

@ -4,9 +4,8 @@ import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
import Separator from '../../Separator';
import Check from '../../Check'; import Check from '../../Check';
import * as List from '../../List';
import { textParser } from '../utils'; import { textParser } from '../utils';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
@ -48,7 +47,7 @@ const Items = ({
style={[styles.items, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.items, { backgroundColor: themes[theme].backgroundColor }]}
contentContainerStyle={[styles.itemContent, { backgroundColor: themes[theme].backgroundColor }]} contentContainerStyle={[styles.itemContent, { backgroundColor: themes[theme].backgroundColor }]}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
ItemSeparatorComponent={() => <Separator theme={theme} />} ItemSeparatorComponent={List.Separator}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
renderItem={({ item }) => <Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} />} renderItem={({ item }) => <Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} />}
/> />

View File

@ -5,10 +5,10 @@ import Popover from 'react-native-popover-view';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import Separator from '../Separator';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { BUTTON_HIT_SLOP } from '../message/utils'; import { BUTTON_HIT_SLOP } from '../message/utils';
import * as List from '../List';
const keyExtractor = item => item.value; const keyExtractor = item => item.value;
@ -50,7 +50,7 @@ const Options = ({
data={options} data={options}
renderItem={({ item }) => <Option option={item} onOptionPress={onOptionPress} parser={parser} theme={theme} />} renderItem={({ item }) => <Option option={item} onOptionPress={onOptionPress} parser={parser} theme={theme} />}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
ItemSeparatorComponent={() => <Separator theme={theme} />} ItemSeparatorComponent={List.Separator}
/> />
); );
Options.propTypes = { Options.propTypes = {

View File

@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
import shortnameToUnicode from '../../utils/shortnameToUnicode'; import shortnameToUnicode from '../../utils/shortnameToUnicode';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import DisclosureIndicator from '../DisclosureIndicator';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import I18n from '../../i18n'; import I18n from '../../i18n';
@ -33,7 +32,13 @@ const RepliedThread = React.memo(({
<View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}> <View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}>
<CustomIcon name='threads' size={20} style={styles.repliedThreadIcon} color={themes[theme].tintColor} /> <CustomIcon name='threads' size={20} style={styles.repliedThreadIcon} color={themes[theme].tintColor} />
<Text style={[styles.repliedThreadName, { color: themes[theme].tintColor }]} numberOfLines={1}>{msg}</Text> <Text style={[styles.repliedThreadName, { color: themes[theme].tintColor }]} numberOfLines={1}>{msg}</Text>
<DisclosureIndicator theme={theme} /> <View style={styles.repliedThreadDisclosure}>
<CustomIcon
name='chevron-right'
color={themes[theme].auxiliaryText}
size={20}
/>
</View>
</View> </View>
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {

View File

@ -164,6 +164,12 @@ export default StyleSheet.create({
flex: 1, flex: 1,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
repliedThreadDisclosure: {
marginLeft: 4,
marginRight: 4,
alignItems: 'center',
justifyContent: 'center'
},
readReceipt: { readReceipt: {
lineHeight: 20 lineHeight: 20
}, },

View File

@ -128,8 +128,8 @@ class QueueListView extends React.Component {
render() { render() {
const { queued, theme } = this.props; const { queued, theme } = this.props;
return ( return (
<SafeAreaView testID='queue-list-view' theme={theme} style={{ backgroundColor: themes[theme].backgroundColor }}> <SafeAreaView testID='queue-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
ref={this.getScrollRef} ref={this.getScrollRef}
data={queued} data={queued}

View File

@ -100,7 +100,6 @@ export default {
announcement: 'Ankündigung', announcement: 'Ankündigung',
Announcement: 'Ankündigung', Announcement: 'Ankündigung',
Apply_Your_Certificate: 'Wende dein Zertifikat an', Apply_Your_Certificate: 'Wende dein Zertifikat an',
Applying_a_theme_will_change_how_the_app_looks: 'Das Erscheinungsbild festzulegen wird das Aussehen der Anwendung ändern.',
ARCHIVE: 'ARCHIV', ARCHIVE: 'ARCHIV',
archive: 'Archiv', archive: 'Archiv',
are_typing: 'tippen', are_typing: 'tippen',
@ -180,7 +179,7 @@ export default {
deleting_room: 'lösche Raum', deleting_room: 'lösche Raum',
description: 'Beschreibung', description: 'Beschreibung',
Description: 'Beschreibung', Description: 'Beschreibung',
DESKTOP_OPTIONS: 'Desktop-Einstellungen', Desktop_Options: 'Desktop-Einstellungen',
Directory: 'Verzeichnis', Directory: 'Verzeichnis',
Direct_Messages: 'Direkte Nachrichten', Direct_Messages: 'Direkte Nachrichten',
Disable_notifications: 'Benachrichtigungen deaktiveren', Disable_notifications: 'Benachrichtigungen deaktiveren',
@ -198,9 +197,8 @@ export default {
Edit_Status: 'Status ändern', Edit_Status: 'Status ändern',
Edit_Invite: 'Einladung bearbeiten', Edit_Invite: 'Einladung bearbeiten',
Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer', Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer',
Email: 'Email', Email: 'E-mail',
EMAIL: 'EMAIL', email: 'E-mail',
email: 'Email',
Empty_title: 'leerer Titel', Empty_title: 'leerer Titel',
Enable_Auto_Translate: 'Automatische Übersetzung aktivieren', Enable_Auto_Translate: 'Automatische Übersetzung aktivieren',
Enable_notifications: 'Benachrichtigungen aktivieren', Enable_notifications: 'Benachrichtigungen aktivieren',
@ -247,7 +245,7 @@ export default {
Message_HideType_room_unarchived: 'Raum nicht mehr archiviert', Message_HideType_room_unarchived: 'Raum nicht mehr archiviert',
IP: 'IP', IP: 'IP',
In_app: 'In-App-Browser', In_app: 'In-App-Browser',
IN_APP_AND_DESKTOP: 'IN-APP UND DESKTOP', In_App_And_Desktop: 'In-app und Desktop',
In_App_and_Desktop_Alert_info: 'Zeigt ein Banner oben am Bildschirm, wenn die App geöffnet ist und eine Benachrichtigung auf dem Desktop.', In_App_and_Desktop_Alert_info: 'Zeigt ein Banner oben am Bildschirm, wenn die App geöffnet ist und eine Benachrichtigung auf dem Desktop.',
Invisible: 'Unsichtbar', Invisible: 'Unsichtbar',
Invite: 'Einladen', Invite: 'Einladen',
@ -373,7 +371,7 @@ export default {
Profile: 'Profil', Profile: 'Profil',
Public_Channel: 'Öffentlicher Kanal', Public_Channel: 'Öffentlicher Kanal',
Public: 'Öffentlich', Public: 'Öffentlich',
PUSH_NOTIFICATIONS: 'Push-Benachrichtigungen', Push_Notifications: 'Push-Benachrichtigungen',
Push_Notifications_Alert_Info: 'Diese Benachrichtigungen werden Ihnen zugestellt, wenn die App nicht geöffnet ist.', Push_Notifications_Alert_Info: 'Diese Benachrichtigungen werden Ihnen zugestellt, wenn die App nicht geöffnet ist.',
Quote: 'Zitat', Quote: 'Zitat',
Reactions_are_disabled: 'Reaktionen sind deaktiviert', Reactions_are_disabled: 'Reaktionen sind deaktiviert',

View File

@ -100,7 +100,6 @@ export default {
announcement: 'announcement', announcement: 'announcement',
Announcement: 'Announcement', Announcement: 'Announcement',
Apply_Your_Certificate: 'Apply Your Certificate', Apply_Your_Certificate: 'Apply Your Certificate',
Applying_a_theme_will_change_how_the_app_looks: 'Applying a theme will change how the app looks.',
ARCHIVE: 'ARCHIVE', ARCHIVE: 'ARCHIVE',
archive: 'archive', archive: 'archive',
are_typing: 'are typing', are_typing: 'are typing',
@ -184,8 +183,8 @@ export default {
deleting_room: 'deleting room', deleting_room: 'deleting room',
description: 'description', description: 'description',
Description: 'Description', Description: 'Description',
DESKTOP_OPTIONS: 'DESKTOP OPTIONS', Desktop_Options: 'Desktop Options',
DESKTOP_NOTIFICATIONS: 'DESKTOP NOTIFICATIONS', Desktop_Notifications: 'Desktop Notifications',
Desktop_Alert_info: 'These notifications are delivered in desktop', Desktop_Alert_info: 'These notifications are delivered in desktop',
Directory: 'Directory', Directory: 'Directory',
Direct_Messages: 'Direct Messages', Direct_Messages: 'Direct Messages',
@ -212,8 +211,7 @@ export default {
Email_Notification_Mode_All: 'Every Mention/DM', Email_Notification_Mode_All: 'Every Mention/DM',
Email_Notification_Mode_Disabled: 'Disabled', Email_Notification_Mode_Disabled: 'Disabled',
Email_or_password_field_is_empty: 'Email or password field is empty', Email_or_password_field_is_empty: 'Email or password field is empty',
Email: 'Email', Email: 'E-mail',
EMAIL: 'EMAIL',
email: 'e-mail', email: 'e-mail',
Empty_title: 'Empty title', Empty_title: 'Empty title',
Enable_Auto_Translate: 'Enable Auto-Translate', Enable_Auto_Translate: 'Enable Auto-Translate',
@ -270,7 +268,7 @@ export default {
I_Saved_My_E2E_Password: 'I Saved My E2E Password', I_Saved_My_E2E_Password: 'I Saved My E2E Password',
IP: 'IP', IP: 'IP',
In_app: 'In-app', In_app: 'In-app',
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP', In_App_And_Desktop: 'In-app and Desktop',
In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop', In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop',
Invisible: 'Invisible', Invisible: 'Invisible',
Invite: 'Invite', Invite: 'Invite',
@ -398,7 +396,7 @@ export default {
Profile: 'Profile', Profile: 'Profile',
Public_Channel: 'Public Channel', Public_Channel: 'Public Channel',
Public: 'Public', Public: 'Public',
PUSH_NOTIFICATIONS: 'PUSH NOTIFICATIONS', Push_Notifications: 'Push Notifications',
Push_Notifications_Alert_Info: 'These notifications are delivered to you when the app is not open', Push_Notifications_Alert_Info: 'These notifications are delivered to you when the app is not open',
Quote: 'Quote', Quote: 'Quote',
Reactions_are_disabled: 'Reactions are disabled', Reactions_are_disabled: 'Reactions are disabled',

View File

@ -95,7 +95,6 @@ export default {
announcement: 'anuncio', announcement: 'anuncio',
Announcement: 'Anuncio', Announcement: 'Anuncio',
Apply_Your_Certificate: 'Applica tu Certificación', Apply_Your_Certificate: 'Applica tu Certificación',
Applying_a_theme_will_change_how_the_app_looks: 'Aplicando un tema modificará el aspecto de la App.',
ARCHIVE: 'FICHERO', ARCHIVE: 'FICHERO',
archive: 'Fichero', archive: 'Fichero',
are_typing: 'escribiendo', are_typing: 'escribiendo',
@ -160,7 +159,7 @@ export default {
deleting_room: 'eliminando sala', deleting_room: 'eliminando sala',
description: 'descripción', description: 'descripción',
Description: 'Descripción', Description: 'Descripción',
DESKTOP_OPTIONS: 'OPCIONES DE ESCRITORIO', Desktop_Options: 'Opciones De Escritorio',
Directory: 'Directorio', Directory: 'Directorio',
Direct_Messages: 'Mensajes directo', Direct_Messages: 'Mensajes directo',
Disable_notifications: 'Desactivar notificaciones', Disable_notifications: 'Desactivar notificaciones',
@ -172,8 +171,7 @@ export default {
edited: 'editado', edited: 'editado',
Edit: 'Editar', Edit: 'Editar',
Email_or_password_field_is_empty: 'El email o la contraseña están vacios', Email_or_password_field_is_empty: 'El email o la contraseña están vacios',
Email: 'Email', Email: 'E-mail',
EMAIL: 'EMAIL',
email: 'e-mail', email: 'e-mail',
Enable_Auto_Translate: 'Permitir Auto-Translate', Enable_Auto_Translate: 'Permitir Auto-Translate',
Enable_notifications: 'Permitir notificaciones', Enable_notifications: 'Permitir notificaciones',
@ -197,7 +195,7 @@ export default {
Has_joined_the_channel: 'Se ha unido al canal', Has_joined_the_channel: 'Se ha unido al canal',
Has_joined_the_conversation: 'Se ha unido a la conversación', Has_joined_the_conversation: 'Se ha unido a la conversación',
Has_left_the_channel: 'Ha dejado el canal', Has_left_the_channel: 'Ha dejado el canal',
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP', In_App_And_Desktop: 'In-app and Desktop',
In_App_and_Desktop_Alert_info: 'Muestra un banner en la parte superior de la pantalla cuando la aplicación está abierta y muestra una notificación en el escritorio', In_App_and_Desktop_Alert_info: 'Muestra un banner en la parte superior de la pantalla cuando la aplicación está abierta y muestra una notificación en el escritorio',
Invisible: 'Invisible', Invisible: 'Invisible',
Invite: 'Invitar', Invite: 'Invitar',
@ -290,7 +288,7 @@ export default {
Profile: 'Perfil', Profile: 'Perfil',
Public_Channel: 'Canal público', Public_Channel: 'Canal público',
Public: 'Público', Public: 'Público',
PUSH_NOTIFICATIONS: 'PUSH NOTIFICATIONS', Push_Notifications: 'Push Notifications',
Push_Notifications_Alert_Info: 'Estas notificaciones se le entregan cuando la aplicación no está abierta', Push_Notifications_Alert_Info: 'Estas notificaciones se le entregan cuando la aplicación no está abierta',
Quote: 'Citar', Quote: 'Citar',
Reactions_are_disabled: 'Las reacciones están desactivadas', Reactions_are_disabled: 'Las reacciones están desactivadas',

View File

@ -100,7 +100,6 @@ export default {
announcement: 'annonce', announcement: 'annonce',
Announcement: 'Annonce', Announcement: 'Annonce',
Apply_Your_Certificate: 'Valider le Certificat', Apply_Your_Certificate: 'Valider le Certificat',
Applying_a_theme_will_change_how_the_app_looks: 'Valider un thème va modifier l\'affichage de l\'application.',
ARCHIVE: 'ARCHIVER', ARCHIVE: 'ARCHIVER',
archive: 'archiver', archive: 'archiver',
are_typing: 'sont en train d\'écrire', are_typing: 'sont en train d\'écrire',
@ -180,7 +179,7 @@ export default {
deleting_room: 'effacement de la salle', deleting_room: 'effacement de la salle',
description: 'la description', description: 'la description',
Description: 'La description', Description: 'La description',
DESKTOP_OPTIONS: 'DESKTOP OPTIONS', Desktop_Options: 'Desktop Options',
Directory: 'Répertoire', Directory: 'Répertoire',
Direct_Messages: 'Messages directs', Direct_Messages: 'Messages directs',
Disable_notifications: 'Désactiver les notifications', Disable_notifications: 'Désactiver les notifications',
@ -199,7 +198,6 @@ export default {
Edit_Invite: 'Modifier l\'invitation', Edit_Invite: 'Modifier l\'invitation',
Email_or_password_field_is_empty: 'Le champ e-mail ou mot de passe est vide', Email_or_password_field_is_empty: 'Le champ e-mail ou mot de passe est vide',
Email: 'E-mail', Email: 'E-mail',
EMAIL: 'EMAIL',
email: 'e-mail', email: 'e-mail',
Empty_title: 'Titre vide', Empty_title: 'Titre vide',
Enable_Auto_Translate: 'Activer la traduction-auto', Enable_Auto_Translate: 'Activer la traduction-auto',
@ -247,7 +245,7 @@ export default {
Message_HideType_room_unarchived: 'Salon Désarchivé', Message_HideType_room_unarchived: 'Salon Désarchivé',
IP: 'IP', IP: 'IP',
In_app: 'In-app', In_app: 'In-app',
IN_APP_AND_DESKTOP: 'IN-APP ET BUREAU', In_App_And_Desktop: 'In-app et Bureau',
In_App_and_Desktop_Alert_info: 'Affiche une bannière en haut de l\'écran lorsque l\'application est ouverte et affiche une notification sur le bureau', In_App_and_Desktop_Alert_info: 'Affiche une bannière en haut de l\'écran lorsque l\'application est ouverte et affiche une notification sur le bureau',
Invisible: 'Invisible', Invisible: 'Invisible',
Invite: 'Inviter', Invite: 'Inviter',
@ -373,7 +371,7 @@ export default {
Profile: 'Profil', Profile: 'Profil',
Public_Channel: 'Canal Public', Public_Channel: 'Canal Public',
Public: 'Public', Public: 'Public',
PUSH_NOTIFICATIONS: 'NOTIFICATIONS PUSH', Push_Notifications: 'Notifications Push',
Push_Notifications_Alert_Info: 'Ces notifications vous sont livrées lorsque l\'application n\'est pas ouverte', Push_Notifications_Alert_Info: 'Ces notifications vous sont livrées lorsque l\'application n\'est pas ouverte',
Quote: 'Citation', Quote: 'Citation',
Reactions_are_disabled: 'Les réactions sont désactivées', Reactions_are_disabled: 'Les réactions sont désactivées',

View File

@ -100,7 +100,6 @@ export default {
announcement: 'annuncio', announcement: 'annuncio',
Announcement: 'Annuncio', Announcement: 'Annuncio',
Apply_Your_Certificate: 'Applica il tuo certificato', Apply_Your_Certificate: 'Applica il tuo certificato',
Applying_a_theme_will_change_how_the_app_looks: 'Applicare un tema cambierà l\'aspetto dell\'app.',
ARCHIVE: 'ARCHIVIO', ARCHIVE: 'ARCHIVIO',
archive: 'archivio', archive: 'archivio',
are_typing: 'stanno scrivendo', are_typing: 'stanno scrivendo',
@ -184,8 +183,8 @@ export default {
deleting_room: 'cancellazione stanza', deleting_room: 'cancellazione stanza',
description: 'descrizione', description: 'descrizione',
Description: 'Descrizione', Description: 'Descrizione',
DESKTOP_OPTIONS: 'OPZIONI DESKTOP', Desktop_Options: 'Opzioni Desktop',
DESKTOP_NOTIFICATIONS: 'NOTIFICHE DESKTOP', Desktop_Notifications: 'Notifiche Desktop',
Desktop_Alert_info: 'Queste notifiche vengono inviate sul client desktop', Desktop_Alert_info: 'Queste notifiche vengono inviate sul client desktop',
Directory: 'Rubrica', Directory: 'Rubrica',
Direct_Messages: 'Messaggi diretti', Direct_Messages: 'Messaggi diretti',
@ -213,7 +212,6 @@ export default {
Email_Notification_Mode_Disabled: 'Disabilitato', Email_Notification_Mode_Disabled: 'Disabilitato',
Email_or_password_field_is_empty: 'Il campo e-mail o password sono vuoti', Email_or_password_field_is_empty: 'Il campo e-mail o password sono vuoti',
Email: 'E-mail', Email: 'E-mail',
EMAIL: 'E-MAIL',
email: 'e-mail', email: 'e-mail',
Empty_title: 'Titolo vuoto', Empty_title: 'Titolo vuoto',
Enable_Auto_Translate: 'Abilita traduzione automatica', Enable_Auto_Translate: 'Abilita traduzione automatica',
@ -270,7 +268,7 @@ export default {
I_Saved_My_E2E_Password: 'Ho salvato la mia Password E2E', I_Saved_My_E2E_Password: 'Ho salvato la mia Password E2E',
IP: 'Indirizzo IP', IP: 'Indirizzo IP',
In_app: 'In-app', In_app: 'In-app',
IN_APP_AND_DESKTOP: 'IN-APP E DESKTOP', In_App_And_Desktop: 'In-app e Desktop',
In_App_and_Desktop_Alert_info: 'Mostra una notifica in cima allo schermo quando l\'app è aperta, e mostra una notifica sul desktop', In_App_and_Desktop_Alert_info: 'Mostra una notifica in cima allo schermo quando l\'app è aperta, e mostra una notifica sul desktop',
Invisible: 'Invisibile', Invisible: 'Invisibile',
Invite: 'Invita', Invite: 'Invita',
@ -398,7 +396,7 @@ export default {
Profile: 'Profilo', Profile: 'Profilo',
Public_Channel: 'Canale pubblico', Public_Channel: 'Canale pubblico',
Public: 'Pubblico', Public: 'Pubblico',
PUSH_NOTIFICATIONS: 'NOTIFICHE PUSH', Push_Notifications: 'Notifiche Push',
Push_Notifications_Alert_Info: 'Queste notifiche ti vengono recapitate quando l\'app non è aperta', Push_Notifications_Alert_Info: 'Queste notifiche ti vengono recapitate quando l\'app non è aperta',
Quote: 'Cita', Quote: 'Cita',
Reactions_are_disabled: 'Le reazioni sono disabilitate', Reactions_are_disabled: 'Le reazioni sono disabilitate',

View File

@ -110,8 +110,6 @@ export default {
announcement: 'アナウンス', announcement: 'アナウンス',
Announcement: 'アナウンス', Announcement: 'アナウンス',
Apply_Your_Certificate: '証明書を適用する', Apply_Your_Certificate: '証明書を適用する',
Applying_a_theme_will_change_how_the_app_looks:
'テーマを変更すると見た目が変わります',
ARCHIVE: 'アーカイブ', ARCHIVE: 'アーカイブ',
archive: 'アーカイブ', archive: 'アーカイブ',
are_typing: 'が入力中', are_typing: 'が入力中',
@ -183,7 +181,7 @@ export default {
deleting_room: 'ルームを削除', deleting_room: 'ルームを削除',
description: '概要', description: '概要',
Description: '概要', Description: '概要',
DESKTOP_OPTIONS: 'デスクトップオプション', Desktop_Options: 'デスクトップオプション',
Directory: 'ディレクトリ', Directory: 'ディレクトリ',
Direct_Messages: 'ダイレクトメッセージ', Direct_Messages: 'ダイレクトメッセージ',
Disable_notifications: '通知を無効化', Disable_notifications: '通知を無効化',
@ -198,7 +196,6 @@ export default {
Edit_Invite: '編集に招待', Edit_Invite: '編集に招待',
Email_or_password_field_is_empty: 'メールアドレスかパスワードの入力欄が空です', Email_or_password_field_is_empty: 'メールアドレスかパスワードの入力欄が空です',
Email: 'メールアドレス', Email: 'メールアドレス',
EMAIL: 'メールアドレス',
email: 'メールアドレス', email: 'メールアドレス',
Enable_Auto_Translate: '自動翻訳を有効にする', Enable_Auto_Translate: '自動翻訳を有効にする',
Enable_markdown: 'マークダウンを有効にする', Enable_markdown: 'マークダウンを有効にする',
@ -227,7 +224,7 @@ export default {
Has_joined_the_channel: 'はチャンネルに参加しました', Has_joined_the_channel: 'はチャンネルに参加しました',
Has_joined_the_conversation: 'は会話に参加しました', Has_joined_the_conversation: 'は会話に参加しました',
Has_left_the_channel: 'はチャンネルを退出しました', Has_left_the_channel: 'はチャンネルを退出しました',
IN_APP_AND_DESKTOP: 'アプリ内とデスクトップ', In_App_And_Desktop: 'アプリ内とデスクトップ',
In_App_and_Desktop_Alert_info: In_App_and_Desktop_Alert_info:
'アプリを表示中にはバナーを上部に表示し、デスクトップには通知を送ります。', 'アプリを表示中にはバナーを上部に表示し、デスクトップには通知を送ります。',
Invisible: '状態を隠す', Invisible: '状態を隠す',
@ -332,7 +329,7 @@ export default {
Profile: 'プロフィール', Profile: 'プロフィール',
Public_Channel: 'パブリックチャンネル', Public_Channel: 'パブリックチャンネル',
Public: 'パブリック', Public: 'パブリック',
PUSH_NOTIFICATIONS: 'プッシュ通知', Push_Notifications: 'プッシュ通知',
Push_Notifications_Alert_Info: Push_Notifications_Alert_Info:
'通知はアプリを開いていない時に送られます。', '通知はアプリを開いていない時に送られます。',
Quote: '引用', Quote: '引用',

View File

@ -96,7 +96,6 @@ export default {
announcement: 'aankondiging', announcement: 'aankondiging',
Announcement: 'Aankondiging', Announcement: 'Aankondiging',
Apply_Your_Certificate: 'Gebruik je certificaat', Apply_Your_Certificate: 'Gebruik je certificaat',
Applying_a_theme_will_change_how_the_app_looks: 'Een thema toepassen verandert de looks van de app.',
ARCHIVE: 'ARCHIVEER', ARCHIVE: 'ARCHIVEER',
archive: 'archiveer', archive: 'archiveer',
are_typing: 'zijn aan het typen', are_typing: 'zijn aan het typen',
@ -162,7 +161,7 @@ export default {
deleting_room: 'kamer legen', deleting_room: 'kamer legen',
description: 'beschrijving', description: 'beschrijving',
Description: 'Beschrijving', Description: 'Beschrijving',
DESKTOP_OPTIONS: 'DESKTOP OPTIES', Desktop_Options: 'Desktop Opties',
Directory: 'Map', Directory: 'Map',
Direct_Messages: 'Directe berichten', Direct_Messages: 'Directe berichten',
Disable_notifications: 'Zet notificaties uit', Disable_notifications: 'Zet notificaties uit',
@ -175,8 +174,7 @@ export default {
Edit: 'Bewerk', Edit: 'Bewerk',
Edit_Invite: 'Bewerk uitnodiging', Edit_Invite: 'Bewerk uitnodiging',
Email_or_password_field_is_empty: 'Email of wachtwoord veld is leeg', Email_or_password_field_is_empty: 'Email of wachtwoord veld is leeg',
Email: 'Email', Email: 'E-mail',
EMAIL: 'EMAIL',
email: 'e-mail', email: 'e-mail',
Enable_Auto_Translate: 'Zet Auto-Translate aan', Enable_Auto_Translate: 'Zet Auto-Translate aan',
Enable_notifications: 'Zet notifications aan', Enable_notifications: 'Zet notifications aan',
@ -202,7 +200,7 @@ export default {
Has_joined_the_channel: 'Is bij het kanaal gekomen', Has_joined_the_channel: 'Is bij het kanaal gekomen',
Has_joined_the_conversation: 'Neemt deel aan het gesprek', Has_joined_the_conversation: 'Neemt deel aan het gesprek',
Has_left_the_channel: 'Heeft het kanaal verlaten', Has_left_the_channel: 'Heeft het kanaal verlaten',
IN_APP_AND_DESKTOP: 'IN-APP EN DESKTOP', In_App_And_Desktop: 'In-app en Desktop',
In_App_and_Desktop_Alert_info: 'Laat een banner bovenaan het scherm zien als de app open is en geeft een notificatie op de desktop', In_App_and_Desktop_Alert_info: 'Laat een banner bovenaan het scherm zien als de app open is en geeft een notificatie op de desktop',
Invisible: 'Onzichtbaar', Invisible: 'Onzichtbaar',
Invite: 'Nodig uit', Invite: 'Nodig uit',
@ -301,7 +299,7 @@ export default {
Profile: 'Profiel', Profile: 'Profiel',
Public_Channel: 'Publiek kanaal', Public_Channel: 'Publiek kanaal',
Public: 'Publiek', Public: 'Publiek',
PUSH_NOTIFICATIONS: 'PUSHNOTIFICATIES', Push_Notifications: 'Pushnotificaties',
Push_Notifications_Alert_Info: 'Deze notificaties krijg je als de app niet geopend is', Push_Notifications_Alert_Info: 'Deze notificaties krijg je als de app niet geopend is',
Quote: 'Quote', Quote: 'Quote',
Reactions_are_disabled: 'Reacties zijn uitgeschakeld', Reactions_are_disabled: 'Reacties zijn uitgeschakeld',

View File

@ -102,7 +102,6 @@ export default {
and: 'e', and: 'e',
announcement: 'anúncio', announcement: 'anúncio',
Announcement: 'Anúncio', Announcement: 'Anúncio',
Applying_a_theme_will_change_how_the_app_looks: 'Aplicar um tema mudará a aparência do app.',
ARCHIVE: 'ARQUIVAR', ARCHIVE: 'ARQUIVAR',
archive: 'arquivar', archive: 'arquivar',
are_typing: 'estão digitando', are_typing: 'estão digitando',
@ -178,8 +177,8 @@ export default {
DELETE: 'EXCLUIR', DELETE: 'EXCLUIR',
deleting_room: 'excluindo sala', deleting_room: 'excluindo sala',
Direct_Messages: 'Mensagens Diretas', Direct_Messages: 'Mensagens Diretas',
DESKTOP_OPTIONS: 'OPÇÕES DE ÁREA DE TRABALHO', Desktop_Options: 'Opções De Área De Trabalho',
DESKTOP_NOTIFICATIONS: 'NOTIFICAÇÕES DE ÁREA DE TRABALHO', Desktop_Notifications: 'Notificações da Área de Trabalho',
Desktop_Alert_info: 'Essas notificações são entregues a você na área de trabalho', Desktop_Alert_info: 'Essas notificações são entregues a você na área de trabalho',
Directory: 'Diretório', Directory: 'Diretório',
description: 'descrição', description: 'descrição',
@ -204,7 +203,7 @@ export default {
End_to_end_encrypted_room: 'Sala criptografada de ponta a ponta', End_to_end_encrypted_room: 'Sala criptografada de ponta a ponta',
end_to_end_encryption: 'criptografia de ponta a ponta', end_to_end_encryption: 'criptografia de ponta a ponta',
Email_or_password_field_is_empty: 'Email ou senha estão vazios', Email_or_password_field_is_empty: 'Email ou senha estão vazios',
Email: 'Email', Email: 'E-mail',
email: 'e-mail', email: 'e-mail',
Empty_title: 'Título vazio', Empty_title: 'Título vazio',
Email_Notification_Mode_All: 'Cada Menção / Mensagem Direta', Email_Notification_Mode_All: 'Cada Menção / Mensagem Direta',
@ -372,6 +371,7 @@ export default {
Profile: 'Perfil', Profile: 'Perfil',
Public_Channel: 'Canal Público', Public_Channel: 'Canal Público',
Public: 'Público', Public: 'Público',
Push_Notifications: 'Notificações Push',
Push_Notifications_Alert_Info: 'Essas notificações são entregues a você quando o aplicativo não está aberto', Push_Notifications_Alert_Info: 'Essas notificações são entregues a você quando o aplicativo não está aberto',
Quote: 'Citar', Quote: 'Citar',
Reactions_are_disabled: 'Reagir está desabilitado', Reactions_are_disabled: 'Reagir está desabilitado',

View File

@ -154,7 +154,7 @@ export default {
DELETE: 'УДАЛИТЬ', DELETE: 'УДАЛИТЬ',
description: 'описание', description: 'описание',
Description: 'Описание', Description: 'Описание',
DESKTOP_OPTIONS: 'ПАРАМЕТРЫ РАБОЧЕГО СТОЛА', Desktop_Options: 'Параметры Рабочего Стола',
Directory: 'Директория', Directory: 'Директория',
Direct_Messages: 'Личные сообщения', Direct_Messages: 'Личные сообщения',
Disable_notifications: 'Отключить уведомления', Disable_notifications: 'Отключить уведомления',
@ -166,8 +166,7 @@ export default {
edited: 'отредактировано', edited: 'отредактировано',
Edit: 'Редактировать', Edit: 'Редактировать',
Email_or_password_field_is_empty: 'Поле электронной почты или пароля пусты', Email_or_password_field_is_empty: 'Поле электронной почты или пароля пусты',
Email: 'Email', Email: 'E-mail',
EMAIL: 'EMAIL',
email: 'e-mail', email: 'e-mail',
Enable_Auto_Translate: 'Включить автоперевод', Enable_Auto_Translate: 'Включить автоперевод',
Enable_notifications: 'Включить уведомления', Enable_notifications: 'Включить уведомления',
@ -192,7 +191,7 @@ export default {
Has_joined_the_channel: 'Присоединился к каналу', Has_joined_the_channel: 'Присоединился к каналу',
Has_joined_the_conversation: 'Присоединился к беседе', Has_joined_the_conversation: 'Присоединился к беседе',
Has_left_the_channel: 'Покинул канал', Has_left_the_channel: 'Покинул канал',
IN_APP_AND_DESKTOP: 'В приложении и на десктопе', In_App_And_Desktop: 'В приложении и на десктопе',
In_App_and_Desktop_Alert_info: 'Отображает баннер в верхней части экрана, когда приложение открыто, и отображает уведомление на рабочем столе.', In_App_and_Desktop_Alert_info: 'Отображает баннер в верхней части экрана, когда приложение открыто, и отображает уведомление на рабочем столе.',
Invisible: 'Невидимый', Invisible: 'Невидимый',
Invite: 'Приглашение', Invite: 'Приглашение',
@ -283,7 +282,7 @@ export default {
Profile: 'Профиль', Profile: 'Профиль',
Public_Channel: 'Публичный канал', Public_Channel: 'Публичный канал',
Public: 'Публичный', Public: 'Публичный',
PUSH_NOTIFICATIONS: 'PUSH УВЕДОМЛЕНИЯ', Push_Notifications: 'Push Уведомления',
Push_Notifications_Alert_Info: 'Эти уведомления доставляются вам, когда приложение не открыто', Push_Notifications_Alert_Info: 'Эти уведомления доставляются вам, когда приложение не открыто',
Quote: 'Цитата', Quote: 'Цитата',
Reactions_are_disabled: 'Реакции отключены', Reactions_are_disabled: 'Реакции отключены',

View File

@ -100,7 +100,6 @@ export default {
announcement: '公告', announcement: '公告',
Announcement: '公告', Announcement: '公告',
Apply_Your_Certificate: '使用自己的凭证', Apply_Your_Certificate: '使用自己的凭证',
Applying_a_theme_will_change_how_the_app_looks: '套用主题将会改变 App 的外观',
ARCHIVE: '封存', ARCHIVE: '封存',
archive: '封存', archive: '封存',
are_typing: '正在输入', are_typing: '正在输入',
@ -184,8 +183,8 @@ export default {
deleting_room: '正在删除聊天室', deleting_room: '正在删除聊天室',
description: '描述', description: '描述',
Description: '描述', Description: '描述',
DESKTOP_OPTIONS: '桌面选项', Desktop_Options: '桌面选项',
DESKTOP_NOTIFICATIONS: '桌面通知', Desktop_Notifications: '桌面通知',
Desktop_Alert_info: '这些通知将发送至桌面', Desktop_Alert_info: '这些通知将发送至桌面',
Directory: '目录', Directory: '目录',
Direct_Messages: '私訊', Direct_Messages: '私訊',
@ -213,7 +212,6 @@ export default {
Email_Notification_Mode_Disabled: '禁用', Email_Notification_Mode_Disabled: '禁用',
Email_or_password_field_is_empty: '邮件或密码字段为空', Email_or_password_field_is_empty: '邮件或密码字段为空',
Email: '邮箱', Email: '邮箱',
EMAIL: 'EMAIL',
email: '邮箱', email: '邮箱',
Empty_title: '空白标题', Empty_title: '空白标题',
Enable_Auto_Translate: '开启自动翻译', Enable_Auto_Translate: '开启自动翻译',
@ -270,7 +268,7 @@ export default {
I_Saved_My_E2E_Password: '保存我的 E2E 密码', I_Saved_My_E2E_Password: '保存我的 E2E 密码',
IP: 'IP', IP: 'IP',
In_app: 'App 内', In_app: 'App 内',
IN_APP_AND_DESKTOP: 'App 内及桌面', In_App_And_Desktop: 'App 内及桌面',
In_App_and_Desktop_Alert_info: '打开应用程序时,在屏幕顶部显示横幅,并在桌面上显示通知', In_App_and_Desktop_Alert_info: '打开应用程序时,在屏幕顶部显示横幅,并在桌面上显示通知',
Invisible: '隐身', Invisible: '隐身',
Invite: '邀请', Invite: '邀请',
@ -397,7 +395,7 @@ export default {
Profile: '个人资料', Profile: '个人资料',
Public_Channel: '公共频道', Public_Channel: '公共频道',
Public: '公共', Public: '公共',
PUSH_NOTIFICATIONS: '推送通知', Push_Notifications: '推送通知',
Push_Notifications_Alert_Info: '这些通知将在未开启 App 时发送给您', Push_Notifications_Alert_Info: '这些通知将在未开启 App 时发送给您',
Quote: '引用', Quote: '引用',
Reactions_are_disabled: '表情貼被禁用', Reactions_are_disabled: '表情貼被禁用',

View File

@ -100,7 +100,6 @@ export default {
announcement: '公告', announcement: '公告',
Announcement: '公告', Announcement: '公告',
Apply_Your_Certificate: '使用自己的憑證', Apply_Your_Certificate: '使用自己的憑證',
Applying_a_theme_will_change_how_the_app_looks: '套用主題將會改變 App 的外觀',
ARCHIVE: '封存', ARCHIVE: '封存',
archive: '封存', archive: '封存',
are_typing: '正在輸入', are_typing: '正在輸入',
@ -184,8 +183,8 @@ export default {
deleting_room: '正在刪除聊天室', deleting_room: '正在刪除聊天室',
description: '描述', description: '描述',
Description: '描述', Description: '描述',
DESKTOP_OPTIONS: '桌面選項', Desktop_Options: '桌面選項',
DESKTOP_NOTIFICATIONS: '桌面通知', Desktop_Notifications: '桌面通知',
Desktop_Alert_info: '這些通知將發送至桌面', Desktop_Alert_info: '這些通知將發送至桌面',
Directory: '目錄', Directory: '目錄',
Direct_Messages: '私訊', Direct_Messages: '私訊',
@ -213,7 +212,6 @@ export default {
Email_Notification_Mode_Disabled: '禁用', Email_Notification_Mode_Disabled: '禁用',
Email_or_password_field_is_empty: '電子郵件或密碼字段為空', Email_or_password_field_is_empty: '電子郵件或密碼字段為空',
Email: '電子郵件', Email: '電子郵件',
EMAIL: 'EMAIL',
email: '電子郵件', email: '電子郵件',
Empty_title: '空白標題', Empty_title: '空白標題',
Enable_Auto_Translate: '開啟自動翻譯', Enable_Auto_Translate: '開啟自動翻譯',
@ -270,7 +268,7 @@ export default {
I_Saved_My_E2E_Password: '儲存我的 E2E 密碼', I_Saved_My_E2E_Password: '儲存我的 E2E 密碼',
IP: 'IP', IP: 'IP',
In_app: 'App 內', In_app: 'App 內',
IN_APP_AND_DESKTOP: 'App 內及桌面', In_App_And_Desktop: 'App 內及桌面',
In_App_and_Desktop_Alert_info: '當在應用程序打開時,螢幕頂端顯示橫幅,並在桌面上顯示通知', In_App_and_Desktop_Alert_info: '當在應用程序打開時,螢幕頂端顯示橫幅,並在桌面上顯示通知',
Invisible: '隱身', Invisible: '隱身',
Invite: '邀請', Invite: '邀請',
@ -397,7 +395,7 @@ export default {
Profile: '個人資料', Profile: '個人資料',
Public_Channel: '公共頻道', Public_Channel: '公共頻道',
Public: '公共', Public: '公共',
PUSH_NOTIFICATIONS: '推送通知', Push_Notifications: '推送通知',
Push_Notifications_Alert_Info: '這些通知將在未開啟 App 時發送給您', Push_Notifications_Alert_Info: '這些通知將在未開啟 App 時發送給您',
Quote: '引用', Quote: '引用',
Reactions_are_disabled: '表情貼被禁用', Reactions_are_disabled: '表情貼被禁用',

View File

@ -68,7 +68,9 @@ export default class Root extends React.Component {
if (!isFDroidBuild) { if (!isFDroidBuild) {
this.initCrashReport(); this.initCrashReport();
} }
const { width, height, scale } = Dimensions.get('window'); const {
width, height, scale, fontScale
} = Dimensions.get('window');
this.state = { this.state = {
theme: defaultTheme(), theme: defaultTheme(),
themePreferences: { themePreferences: {
@ -77,7 +79,8 @@ export default class Root extends React.Component {
}, },
width, width,
height, height,
scale scale,
fontScale
}; };
if (isTablet) { if (isTablet) {
this.initTablet(); this.initTablet();
@ -134,8 +137,14 @@ export default class Root extends React.Component {
}; };
// Dimensions update fires twice // Dimensions update fires twice
onDimensionsChange = debounce(({ window: { width, height, scale } }) => { onDimensionsChange = debounce(({
this.setDimensions({ width, height, scale }); window: {
width, height, scale, fontScale
}
}) => {
this.setDimensions({
width, height, scale, fontScale
});
this.setMasterDetail(width); this.setMasterDetail(width);
}) })
@ -148,8 +157,12 @@ export default class Root extends React.Component {
}); });
} }
setDimensions = ({ width, height, scale }) => { setDimensions = ({
this.setState({ width, height, scale }); width, height, scale, fontScale
}) => {
this.setState({
width, height, scale, fontScale
});
} }
initTablet = () => { initTablet = () => {
@ -176,7 +189,7 @@ export default class Root extends React.Component {
render() { render() {
const { const {
themePreferences, theme, width, height, scale themePreferences, theme, width, height, scale, fontScale
} = this.state; } = this.state;
return ( return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}> <SafeAreaProvider initialMetrics={initialWindowMetrics}>
@ -194,6 +207,7 @@ export default class Root extends React.Component {
width, width,
height, height,
scale, scale,
fontScale,
setDimensions: this.setDimensions setDimensions: this.setDimensions
}} }}
> >

View File

@ -117,7 +117,9 @@ App.propTypes = {
class Root extends React.Component { class Root extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const { width, height, scale } = Dimensions.get('screen'); const {
width, height, scale, fontScale
} = Dimensions.get('screen');
this.state = { this.state = {
theme: defaultTheme(), theme: defaultTheme(),
themePreferences: { themePreferences: {
@ -127,7 +129,8 @@ class Root extends React.Component {
root: '', root: '',
width, width,
height, height,
scale scale,
fontScale
}; };
this.init(); this.init();
} }
@ -166,18 +169,28 @@ class Root extends React.Component {
} }
// Dimensions update fires twice // Dimensions update fires twice
onDimensionsChange = debounce(({ window: { width, height, scale } }) => { onDimensionsChange = debounce(({
this.setDimensions({ width, height, scale }); window: {
width, height, scale, fontScale
}
}) => {
this.setDimensions({
width, height, scale, fontScale
});
this.setMasterDetail(width); this.setMasterDetail(width);
}) })
setDimensions = ({ width, height, scale }) => { setDimensions = ({
this.setState({ width, height, scale }); width, height, scale, fontScale
}) => {
this.setState({
width, height, scale, fontScale
});
} }
render() { render() {
const { const {
theme, root, width, height, scale theme, root, width, height, scale, fontScale
} = this.state; } = this.state;
const navTheme = navigationTheme(theme); const navTheme = navigationTheme(theme);
return ( return (
@ -189,6 +202,7 @@ class Root extends React.Component {
width, width,
height, height,
scale, scale,
fontScale,
setDimensions: this.setDimensions setDimensions: this.setDimensions
}} }}
> >

View File

@ -18,18 +18,17 @@ class AdminPanelView extends React.Component {
static propTypes = { static propTypes = {
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
token: PropTypes.string, token: PropTypes.string
theme: PropTypes.string
} }
render() { render() {
const { baseUrl, token, theme } = this.props; const { baseUrl, token } = this.props;
if (!baseUrl) { if (!baseUrl) {
return null; return null;
} }
return ( return (
<SafeAreaView theme={theme}> <SafeAreaView>
<StatusBar theme={theme} /> <StatusBar />
<WebView <WebView
// https://github.com/react-native-community/react-native-webview/issues/1311 // https://github.com/react-native-community/react-native-webview/issues/1311
onMessage={() => {}} onMessage={() => {}}

View File

@ -28,7 +28,7 @@ const styles = StyleSheet.create({
const AuthLoadingView = React.memo(({ theme, text }) => ( const AuthLoadingView = React.memo(({ theme, text }) => (
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}> <View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
<StatusBar theme={theme} /> <StatusBar />
{text && ( {text && (
<> <>
<ActivityIndicator color={themes[theme].auxiliaryText} size='large' /> <ActivityIndicator color={themes[theme].auxiliaryText} size='large' />

View File

@ -155,7 +155,7 @@ class AuthenticationWebView extends React.PureComponent {
return ( return (
<> <>
<StatusBar theme={theme} /> <StatusBar />
<WebView <WebView
source={{ uri: url }} source={{ uri: url }}
userAgent={userAgent} userAgent={userAgent}

View File

@ -1,53 +1,22 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { FlatList, Switch, StyleSheet } from 'react-native';
FlatList, Switch, View, StyleSheet, ScrollView
} from 'react-native';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n'; import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { CustomIcon } from '../../lib/Icons'; import * as List from '../../containers/List';
import sharedStyles from '../Styles';
import ListItem from '../../containers/ListItem';
import Separator from '../../containers/Separator';
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
contentContainerStyle: { list: {
borderTopWidth: StyleSheet.hairlineWidth, paddingTop: 16
marginTop: 10,
paddingBottom: 30
},
flatListContainerStyle: {
borderBottomWidth: StyleSheet.hairlineWidth
},
sectionSeparator: {
...sharedStyles.separatorVertical,
height: 10
} }
}); });
const SectionSeparator = React.memo(({ theme }) => (
<View
style={[
styles.sectionSeparator,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderColor: themes[theme].separatorColor
}
]}
/>
));
SectionSeparator.propTypes = {
theme: PropTypes.string
};
class AutoTranslateView extends React.Component { class AutoTranslateView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
title: I18n.t('Auto_Translate') title: I18n.t('Auto_Translate')
@ -55,8 +24,7 @@ class AutoTranslateView extends React.Component {
static propTypes = { static propTypes = {
route: PropTypes.object, route: PropTypes.object,
theme: PropTypes.string, theme: PropTypes.string
navigation: PropTypes.object
} }
constructor(props) { constructor(props) {
@ -135,14 +103,9 @@ class AutoTranslateView extends React.Component {
} }
} }
renderSeparator = () => {
const { theme } = this.props;
return <Separator theme={theme} />;
}
renderIcon = () => { renderIcon = () => {
const { theme } = this.props; const { theme } = this.props;
return <CustomIcon name='check' size={20} style={{ color: themes[theme].tintColor }} />; return <List.Icon name='check' style={{ color: themes[theme].tintColor }} />;
} }
renderSwitch = () => { renderSwitch = () => {
@ -158,54 +121,46 @@ class AutoTranslateView extends React.Component {
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { selectedLanguage } = this.state; const { selectedLanguage } = this.state;
const { theme } = this.props;
const { language, name } = item; const { language, name } = item;
const isSelected = selectedLanguage === language; const isSelected = selectedLanguage === language;
return ( return (
<ListItem <List.Item
title={name || language} title={name || language}
onPress={() => this.saveAutoTranslateLanguage(language)} onPress={() => this.saveAutoTranslateLanguage(language)}
testID={`auto-translate-view-${ language }`} testID={`auto-translate-view-${ language }`}
right={isSelected ? this.renderIcon : null} right={isSelected ? this.renderIcon : null}
theme={theme} translateTitle={false}
/> />
); );
} }
render() { render() {
const { languages } = this.state; const { languages } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView testID='auto-translate-view' theme={theme}> <SafeAreaView testID='auto-translate-view'>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container testID='auto-translate-view-list'>
{...scrollPersistTaps} <List.Section>
contentContainerStyle={[ <List.Separator />
styles.contentContainerStyle, <List.Item
{ title='Enable_Auto_Translate'
backgroundColor: themes[theme].auxiliaryBackground,
borderColor: themes[theme].separatorColor
}
]}
testID='auto-translate-view-list'
>
<ListItem
title={I18n.t('Enable_Auto_Translate')}
testID='auto-translate-view-switch' testID='auto-translate-view-switch'
right={() => this.renderSwitch()} right={() => this.renderSwitch()}
theme={theme}
/> />
<SectionSeparator theme={theme} /> <List.Separator />
</List.Section>
<FlatList <FlatList
data={languages} data={languages}
extraData={this.state} extraData={this.state}
keyExtractor={item => item.language} keyExtractor={item => item.language}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
contentContainerStyle={[styles.flatListContainerStyle, { borderColor: themes[theme].separatorColor }]} ListFooterComponent={List.Separator}
ListHeaderComponent={List.Separator}
contentContainerStyle={[List.styles.contentContainerStyleFlatList, styles.list]}
/> />
</ScrollView> </List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -323,8 +323,8 @@ class CreateChannelView extends React.Component {
contentContainerStyle={[sharedStyles.container, styles.container]} contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar theme={theme} /> <StatusBar />
<SafeAreaView testID='create-channel-view' theme={theme}> <SafeAreaView testID='create-channel-view'>
<ScrollView {...scrollPersistTaps}> <ScrollView {...scrollPersistTaps}>
<View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}> <View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}>
<TextInput <TextInput

View File

@ -151,8 +151,8 @@ class CreateChannelView extends React.Component {
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar theme={theme} /> <StatusBar />
<SafeAreaView testID='create-discussion-view' style={styles.container} theme={theme}> <SafeAreaView testID='create-discussion-view' style={styles.container}>
<ScrollView {...scrollPersistTaps}> <ScrollView {...scrollPersistTaps}>
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text> <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text>
<SelectChannel <SelectChannel

View File

@ -1,17 +1,12 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { FlatList, Linking } from 'react-native';
StyleSheet, FlatList, View, Text, Linking
} from 'react-native';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from './Styles';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import Separator from '../containers/Separator'; import * as List from '../containers/List';
import ListItem from '../containers/ListItem';
import { CustomIcon } from '../lib/Icons';
import { DEFAULT_BROWSER_KEY } from '../utils/openLink'; import { DEFAULT_BROWSER_KEY } from '../utils/openLink';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../utils/deviceInfo';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
@ -44,21 +39,6 @@ const BROWSERS = [
} }
]; ];
const styles = StyleSheet.create({
list: {
paddingBottom: 18
},
info: {
paddingTop: 25,
paddingBottom: 18,
paddingHorizontal: 16
},
infoText: {
fontSize: 16,
...sharedStyles.textRegular
}
});
class DefaultBrowserView extends React.Component { class DefaultBrowserView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
title: I18n.t('Default_browser') title: I18n.t('Default_browser')
@ -120,59 +100,44 @@ class DefaultBrowserView extends React.Component {
} }
} }
renderSeparator = () => {
const { theme } = this.props;
return <Separator theme={theme} />;
}
renderIcon = () => { renderIcon = () => {
const { theme } = this.props; const { theme } = this.props;
return <CustomIcon name='check' size={20} color={themes[theme].tintColor} />; return <List.Icon name='check' color={themes[theme].tintColor} />;
} }
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { theme } = this.props;
const { title, value } = item; const { title, value } = item;
return ( return (
<ListItem <List.Item
title={I18n.t(title, { defaultValue: title })} title={I18n.t(title, { defaultValue: title })}
onPress={() => this.changeDefaultBrowser(value)} onPress={() => this.changeDefaultBrowser(value)}
testID={`default-browser-view-${ title }`} testID={`default-browser-view-${ title }`}
right={this.isSelected(value) ? this.renderIcon : null} right={this.isSelected(value) ? this.renderIcon : null}
theme={theme} translateTitle={false}
/> />
); );
} }
renderHeader = () => { renderHeader = () => (
const { theme } = this.props;
return (
<> <>
<View style={styles.info}> <List.Header title='Choose_where_you_want_links_be_opened' />
<Text style={[styles.infoText, { color: themes[theme].infoText }]}>{I18n.t('Choose_where_you_want_links_be_opened')}</Text> <List.Separator />
</View>
{this.renderSeparator()}
</> </>
); )
}
render() { render() {
const { supported } = this.state; const { supported } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView testID='default-browser-view' theme={theme}> <SafeAreaView testID='default-browser-view'>
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
data={DEFAULT_BROWSERS.concat(supported)} data={DEFAULT_BROWSERS.concat(supported)}
keyExtractor={item => item.value} keyExtractor={item => item.value}
contentContainerStyle={[ contentContainerStyle={List.styles.contentContainerStyleFlatList}
styles.list,
{ borderColor: themes[theme].separatorColor }
]}
renderItem={this.renderItem} renderItem={this.renderItem}
ListHeaderComponent={this.renderHeader} ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderSeparator} ListFooterComponent={List.Separator}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
/> />
</SafeAreaView> </SafeAreaView>
); );

View File

@ -240,9 +240,8 @@ class DirectoryView extends React.Component {
<SafeAreaView <SafeAreaView
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
testID='directory-view' testID='directory-view'
theme={theme}
> >
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
data={data} data={data}
style={styles.list} style={styles.list}

View File

@ -62,9 +62,9 @@ class E2EEnterYourPasswordView extends React.Component {
contentContainerStyle={sharedStyles.container} contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar theme={theme} /> <StatusBar />
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}> <ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}>
<SafeAreaView theme={theme} style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}> <SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
<TextInput <TextInput
inputRef={(e) => { this.passwordInput = e; }} inputRef={(e) => { this.passwordInput = e; }}
placeholder={I18n.t('Password')} placeholder={I18n.t('Password')}

View File

@ -43,7 +43,6 @@ class E2EHowItWorksView extends React.Component {
<SafeAreaView <SafeAreaView
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
testID='e2e-how-it-works-view' testID='e2e-how-it-works-view'
theme={theme}
> >
<Markdown <Markdown
msg={I18n.t('E2E_How_It_Works_info1')} msg={I18n.t('E2E_How_It_Works_info1')}

View File

@ -126,8 +126,8 @@ class E2ESaveYourPasswordView extends React.Component {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<SafeAreaView theme={theme} style={{ backgroundColor: themes[theme].backgroundColor }}> <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={sharedStyles.containerScrollView}> <ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={sharedStyles.containerScrollView}>
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}> <View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.warning, { color: themes[theme].dangerColor }]}>{I18n.t('Save_Your_Encryption_Password_warning')}</Text> <Text style={[styles.warning, { color: themes[theme].dangerColor }]}>{I18n.t('Save_Your_Encryption_Password_warning')}</Text>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ScrollView, View } from 'react-native'; import { View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import RNPickerSelect from 'react-native-picker-select'; import RNPickerSelect from 'react-native-picker-select';
@ -8,15 +8,13 @@ import {
inviteLinksSetParams as inviteLinksSetParamsAction, inviteLinksSetParams as inviteLinksSetParamsAction,
inviteLinksCreate as inviteLinksCreateAction inviteLinksCreate as inviteLinksCreateAction
} from '../../actions/inviteLinks'; } from '../../actions/inviteLinks';
import ListItem from '../../containers/ListItem'; import * as List from '../../containers/List';
import styles from './styles'; import styles from './styles';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import I18n from '../../i18n'; import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import Separator from '../../containers/Separator';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
@ -62,7 +60,6 @@ class InviteUsersView extends React.Component {
navigation: PropTypes.object, navigation: PropTypes.object,
route: PropTypes.object, route: PropTypes.object,
theme: PropTypes.string, theme: PropTypes.string,
timeDateFormat: PropTypes.string,
createInviteLink: PropTypes.func, createInviteLink: PropTypes.func,
inviteLinksSetParams: PropTypes.func inviteLinksSetParams: PropTypes.func
} }
@ -110,29 +107,23 @@ class InviteUsersView extends React.Component {
render() { render() {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} theme={theme}> <SafeAreaView>
<ScrollView <List.Container>
{...scrollPersistTaps} <StatusBar />
style={{ backgroundColor: themes[theme].auxiliaryBackground }} <List.Section>
contentContainerStyle={styles.contentContainer} <List.Separator />
showsVerticalScrollIndicator={false} <List.Item
> title='Expiration_Days'
<StatusBar theme={theme} />
<Separator theme={theme} />
<ListItem
title={I18n.t('Expiration_Days')}
right={() => this.renderPicker('days', 'Never')} right={() => this.renderPicker('days', 'Never')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Max_number_of_uses')} title='Max_number_of_uses'
right={() => this.renderPicker('maxUses', 'No_limit')} right={() => this.renderPicker('maxUses', 'No_limit')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
</List.Section>
<View style={styles.innerContainer}> <View style={styles.innerContainer}>
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
<Button <Button
title={I18n.t('Generate_New_Link')} title={I18n.t('Generate_New_Link')}
type='primary' type='primary'
@ -140,7 +131,7 @@ class InviteUsersView extends React.Component {
theme={theme} theme={theme}
/> />
</View> </View>
</ScrollView> </List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -1,36 +1,12 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { PADDING_HORIZONTAL } from '../../containers/List/constants';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
export default StyleSheet.create({ export default StyleSheet.create({
innerContainer: { innerContainer: {
paddingHorizontal: 20 paddingHorizontal: PADDING_HORIZONTAL,
}, paddingTop: 16
divider: {
width: '100%',
height: StyleSheet.hairlineWidth,
marginVertical: 20
},
sectionSeparatorBorder: {
height: 10
},
marginBottom: {
height: 30
},
contentContainer: {
marginVertical: 10
},
infoText: {
...sharedStyles.textRegular,
fontSize: 13,
paddingHorizontal: 15,
paddingVertical: 10
},
sectionTitle: {
...sharedStyles.separatorBottom,
paddingHorizontal: 15,
paddingVertical: 10,
fontSize: 14
}, },
viewContainer: { viewContainer: {
justifyContent: 'center' justifyContent: 'center'

View File

@ -102,14 +102,14 @@ class InviteUsersView extends React.Component {
theme, invite theme, invite
} = this.props; } = this.props;
return ( return (
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} theme={theme}> <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}>
<ScrollView <ScrollView
{...scrollPersistTaps} {...scrollPersistTaps}
style={{ backgroundColor: themes[theme].auxiliaryBackground }} style={{ backgroundColor: themes[theme].auxiliaryBackground }}
contentContainerStyle={styles.contentContainer} contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> >
<StatusBar theme={theme} /> <StatusBar />
<View style={styles.innerContainer}> <View style={styles.innerContainer}>
<RCTextInput <RCTextInput
label={I18n.t('Invite_Link')} label={I18n.t('Invite_Link')}

View File

@ -1,8 +1,9 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { PADDING_HORIZONTAL } from '../../containers/List/constants';
export default StyleSheet.create({ export default StyleSheet.create({
innerContainer: { innerContainer: {
padding: 20, padding: PADDING_HORIZONTAL,
paddingBottom: 0 paddingBottom: 0
}, },
divider: { divider: {

View File

@ -9,10 +9,7 @@ import { showErrorAlert } from '../../utils/info';
import log, { logEvent, events } from '../../utils/log'; import log, { logEvent, events } from '../../utils/log';
import { setUser as setUserAction } from '../../actions/login'; import { setUser as setUserAction } from '../../actions/login';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { CustomIcon } from '../../lib/Icons'; import * as List from '../../containers/List';
import sharedStyles from '../Styles';
import ListItem from '../../containers/ListItem';
import Separator from '../../containers/Separator';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { appStart as appStartAction, ROOT_LOADING, ROOT_INSIDE } from '../../actions/app'; import { appStart as appStartAction, ROOT_LOADING, ROOT_INSIDE } from '../../actions/app';
@ -108,50 +105,39 @@ class LanguageView extends React.Component {
} }
} }
renderSeparator = () => {
const { theme } = this.props;
return <Separator theme={theme} />;
}
renderIcon = () => { renderIcon = () => {
const { theme } = this.props; const { theme } = this.props;
return <CustomIcon name='check' size={20} style={{ color: themes[theme].tintColor }} />; return <List.Icon name='check' color={themes[theme].tintColor} />;
} }
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { value, label } = item; const { value, label } = item;
const { language } = this.state; const { language } = this.state;
const { theme } = this.props;
const isSelected = language === value; const isSelected = language === value;
return ( return (
<ListItem <List.Item
title={label} title={label}
onPress={() => this.submit(value)} onPress={() => this.submit(value)}
testID={`language-view-${ value }`} testID={`language-view-${ value }`}
right={isSelected ? this.renderIcon : null} right={isSelected ? this.renderIcon : null}
theme={theme} translateTitle={false}
/> />
); );
} }
render() { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView testID='language-view' theme={theme}> <SafeAreaView testID='language-view'>
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
data={LANGUAGES} data={LANGUAGES}
keyExtractor={item => item.value} keyExtractor={item => item.value}
contentContainerStyle={[ ListHeaderComponent={List.Separator}
sharedStyles.listContentContainer, ListFooterComponent={List.Separator}
{ contentContainerStyle={List.styles.contentContainerStyleFlatList}
backgroundColor: themes[theme].auxiliaryBackground,
borderColor: themes[theme].separatorColor
}
]}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
/> />
</SafeAreaView> </SafeAreaView>
); );

View File

@ -1,51 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {
Text, ScrollView, View, StyleSheet
} from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Touch from '../utils/touch';
import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import I18n from '../i18n'; import I18n from '../i18n';
import DisclosureIndicator from '../containers/DisclosureIndicator';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { themes } from '../constants/colors';
import openLink from '../utils/openLink'; import openLink from '../utils/openLink';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import * as List from '../containers/List';
const styles = StyleSheet.create({
scroll: {
marginTop: 35,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth
},
separator: {
height: StyleSheet.hairlineWidth,
width: '100%',
marginLeft: 20
},
item: {
width: '100%',
height: 48,
paddingLeft: 20,
paddingRight: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
},
text: {
...sharedStyles.textMedium,
fontSize: 18
}
});
const Separator = ({ theme }) => <View style={[styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
Separator.propTypes = {
theme: PropTypes.string
};
class LegalView extends React.Component { class LegalView extends React.Component {
static propTypes = { static propTypes = {
@ -61,40 +23,29 @@ class LegalView extends React.Component {
openLink(`${ server }/${ route }`, theme); openLink(`${ server }/${ route }`, theme);
} }
renderItem = ({ text, route, testID }) => {
const { theme } = this.props;
return (
<Touch
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}
onPress={() => this.onPressItem({ route })}
testID={testID}
theme={theme}
>
<Text style={[styles.text, { color: themes[theme].titleText }]}>{I18n.t(text)}</Text>
<DisclosureIndicator theme={theme} />
</Touch>
);
}
render() { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView testID='legal-view' theme={theme}> <SafeAreaView testID='legal-view'>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container>
contentContainerStyle={[ <List.Section>
styles.scroll, <List.Separator />
{ <List.Item
backgroundColor: themes[theme].backgroundColor, title='Terms_of_Service'
borderColor: themes[theme].separatorColor onPress={() => this.onPressItem({ route: 'terms-of-service' })}
} testID='legal-terms-button'
]} showActionIndicator
{...scrollPersistTaps} />
> <List.Separator />
{this.renderItem({ text: 'Terms_of_Service', route: 'terms-of-service', testID: 'legal-terms-button' })} <List.Item
<Separator theme={theme} /> title='Privacy_Policy'
{this.renderItem({ text: 'Privacy_Policy', route: 'privacy-policy', testID: 'legal-privacy-button' })} onPress={() => this.onPressItem({ route: 'privacy-policy' })}
</ScrollView> testID='legal-privacy-button'
showActionIndicator
/>
<List.Separator />
</List.Section>
</List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -151,7 +151,7 @@ const LivechatEditView = ({
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<ScrollView {...scrollPersistTaps} style={styles.container}> <ScrollView {...scrollPersistTaps} style={styles.container}>
<SafeAreaView theme={theme}> <SafeAreaView>
<Title <Title
title={visitor?.username} title={visitor?.username}
theme={theme} theme={theme}

View File

@ -296,9 +296,8 @@ class MessagesView extends React.Component {
<SafeAreaView <SafeAreaView
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
testID={this.content.testID} testID={this.content.testID}
theme={theme}
> >
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
data={messages} data={messages}
renderItem={this.renderItem} renderItem={this.renderItem}

View File

@ -261,11 +261,10 @@ class NewMessageView extends React.Component {
); );
} }
render = () => { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView testID='new-message-view' theme={theme}> <SafeAreaView testID='new-message-view'>
<StatusBar theme={theme} /> <StatusBar />
{this.renderList()} {this.renderList()}
</SafeAreaView> </SafeAreaView>
); );

View File

@ -3,9 +3,9 @@ import { View, FlatList, StyleSheet } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TextInput from '../../../containers/TextInput'; import TextInput from '../../../containers/TextInput';
import * as List from '../../../containers/List';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import Item from './Item'; import Item from './Item';
import Separator from '../../../containers/Separator';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -64,7 +64,7 @@ const ServerInput = ({
<FlatList <FlatList
data={serversHistory} data={serversHistory}
renderItem={({ item }) => <Item item={item} theme={theme} onPress={() => onPressServerHistory(item)} onDelete={onDelete} />} renderItem={({ item }) => <Item item={item} theme={theme} onPress={() => onPressServerHistory(item)} onDelete={onDelete} />}
ItemSeparatorComponent={() => <Separator theme={theme} />} ItemSeparatorComponent={List.Separator}
keyExtractor={item => item.id} keyExtractor={item => item.id}
/> />
</View> </View>

View File

@ -1,29 +0,0 @@
import React from 'react';
import {
Text
} from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../constants/colors';
const Info = React.memo(({ info, theme }) => (
<Text
style={[
styles.infoText,
{
color: themes[theme].infoText,
backgroundColor: themes[theme].auxiliaryBackground
}
]}
>
{info}
</Text>
));
Info.propTypes = {
info: PropTypes.string,
theme: PropTypes.string
};
export default Info;

View File

@ -1,23 +0,0 @@
import React from 'react';
import {
View
} from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../constants/colors';
const SectionSeparator = React.memo(({ theme }) => (
<View
style={[
styles.sectionSeparatorBorder,
{ backgroundColor: themes[theme].auxiliaryBackground }
]}
/>
));
SectionSeparator.propTypes = {
theme: PropTypes.string
};
export default SectionSeparator;

View File

@ -1,29 +0,0 @@
import React from 'react';
import {
Text
} from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../constants/colors';
const SectionTitle = React.memo(({ title, theme }) => (
<Text
style={[
styles.sectionTitle,
{
backgroundColor: themes[theme].auxiliaryBackground,
color: themes[theme].infoText
}
]}
>
{title}
</Text>
));
SectionTitle.propTypes = {
title: PropTypes.string,
theme: PropTypes.string
};
export default SectionTitle;

View File

@ -1,26 +1,26 @@
import React from 'react'; import React from 'react';
import { import { Switch, Text, StyleSheet } from 'react-native';
View, ScrollView, Switch, Text
} from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import database from '../../lib/database'; import database from '../../lib/database';
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ListItem from '../../containers/ListItem'; import * as List from '../../containers/List';
import Separator from '../../containers/Separator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import styles from './styles';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import log, { events, logEvent } from '../../utils/log'; import log, { events, logEvent } from '../../utils/log';
import SectionTitle from './SectionTitle';
import SectionSeparator from './SectionSeparator';
import Info from './Info';
import { OPTIONS } from './options'; import { OPTIONS } from './options';
import sharedStyles from '../Styles';
const styles = StyleSheet.create({
pickerText: {
...sharedStyles.textRegular,
fontSize: 16
}
});
class NotificationPreferencesView extends React.Component { class NotificationPreferencesView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -132,120 +132,103 @@ class NotificationPreferencesView extends React.Component {
render() { render() {
const { room } = this.state; const { room } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView testID='notification-preference-view' theme={theme}> <SafeAreaView testID='notification-preference-view'>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container testID='notification-preference-view-list'>
{...scrollPersistTaps} <List.Section>
style={{ backgroundColor: themes[theme].auxiliaryBackground }} <List.Separator />
contentContainerStyle={styles.contentContainer} <List.Item
testID='notification-preference-view-list' title='Receive_Notification'
>
<Separator theme={theme} />
<ListItem
title={I18n.t('Receive_Notification')}
testID='notification-preference-view-receive-notification' testID='notification-preference-view-receive-notification'
right={() => this.renderSwitch('disableNotifications')} right={() => this.renderSwitch('disableNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('Receive_notifications_from', { name: room.name })} theme={theme} /> <List.Info info={I18n.t('Receive_notifications_from', { name: room.name })} translateInfo={false} />
<SectionSeparator theme={theme} /> </List.Section>
<Separator theme={theme} /> <List.Section>
<ListItem <List.Separator />
title={I18n.t('Receive_Group_Mentions')} <List.Item
title='Receive_Group_Mentions'
testID='notification-preference-view-group-mentions' testID='notification-preference-view-group-mentions'
right={() => this.renderSwitch('muteGroupMentions')} right={() => this.renderSwitch('muteGroupMentions')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('Receive_Group_Mentions_Info')} theme={theme} /> <List.Info info='Receive_Group_Mentions_Info' />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section>
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Show_Unread_Counter')} title='Show_Unread_Counter'
testID='notification-preference-view-unread-count' testID='notification-preference-view-unread-count'
right={() => this.renderSwitch('hideUnreadStatus')} right={() => this.renderSwitch('hideUnreadStatus')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('Show_Unread_Counter_Info')} theme={theme} /> <List.Info info='Show_Unread_Counter_Info' />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section title='In_App_And_Desktop'>
<SectionTitle title={I18n.t('IN_APP_AND_DESKTOP')} theme={theme} /> <List.Separator />
<Separator theme={theme} /> <List.Item
title='Alert'
<ListItem
title={I18n.t('Alert')}
testID='notification-preference-view-alert' testID='notification-preference-view-alert'
onPress={title => this.pickerSelection(title, 'desktopNotifications')} onPress={title => this.pickerSelection(title, 'desktopNotifications')}
right={() => this.renderPickerOption('desktopNotifications')} right={() => this.renderPickerOption('desktopNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('In_App_and_Desktop_Alert_info')} theme={theme} /> <List.Info info='In_App_and_Desktop_Alert_info' />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section title='Push_Notifications'>
<SectionTitle title={I18n.t('PUSH_NOTIFICATIONS')} theme={theme} /> <List.Separator />
<Separator theme={theme} /> <List.Item
title='Alert'
<ListItem
title={I18n.t('Alert')}
testID='notification-preference-view-push-notification' testID='notification-preference-view-push-notification'
onPress={title => this.pickerSelection(title, 'mobilePushNotifications')} onPress={title => this.pickerSelection(title, 'mobilePushNotifications')}
right={() => this.renderPickerOption('mobilePushNotifications')} right={() => this.renderPickerOption('mobilePushNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('Push_Notifications_Alert_Info')} theme={theme} /> <List.Info info='Push_Notifications_Alert_Info' />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section title='Desktop_Options'>
<SectionTitle title={I18n.t('DESKTOP_OPTIONS')} theme={theme} /> <List.Separator />
<Separator theme={theme} /> <List.Item
title='Audio'
<ListItem
title={I18n.t('Audio')}
testID='notification-preference-view-audio' testID='notification-preference-view-audio'
onPress={title => this.pickerSelection(title, 'audioNotifications')} onPress={title => this.pickerSelection(title, 'audioNotifications')}
right={() => this.renderPickerOption('audioNotifications')} right={() => this.renderPickerOption('audioNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Sound')} title='Sound'
testID='notification-preference-view-sound' testID='notification-preference-view-sound'
onPress={title => this.pickerSelection(title, 'audioNotificationValue')} onPress={title => this.pickerSelection(title, 'audioNotificationValue')}
right={() => this.renderPickerOption('audioNotificationValue')} right={() => this.renderPickerOption('audioNotificationValue')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Notification_Duration')} title='Notification_Duration'
testID='notification-preference-view-notification-duration' testID='notification-preference-view-notification-duration'
onPress={title => this.pickerSelection(title, 'desktopNotificationDuration')} onPress={title => this.pickerSelection(title, 'desktopNotificationDuration')}
right={() => this.renderPickerOption('desktopNotificationDuration')} right={() => this.renderPickerOption('desktopNotificationDuration')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section title='Email'>
<SectionTitle title={I18n.t('EMAIL')} theme={theme} /> <List.Separator />
<Separator theme={theme} /> <List.Item
title='Alert'
<ListItem
title={I18n.t('Alert')}
testID='notification-preference-view-email-alert' testID='notification-preference-view-email-alert'
onPress={title => this.pickerSelection(title, 'emailNotifications')} onPress={title => this.pickerSelection(title, 'emailNotifications')}
right={() => this.renderPickerOption('emailNotifications')} right={() => this.renderPickerOption('emailNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
</List.Section>
<View style={[styles.marginBottom, { backgroundColor: themes[theme].auxiliaryBackground }]} /> </List.Container>
</ScrollView>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -1,31 +0,0 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../Styles';
export default StyleSheet.create({
sectionSeparatorBorder: {
height: 10
},
marginBottom: {
height: 30
},
contentContainer: {
marginVertical: 10
},
infoText: {
...sharedStyles.textRegular,
fontSize: 13,
paddingHorizontal: 15,
paddingVertical: 10
},
sectionTitle: {
...sharedStyles.separatorBottom,
paddingHorizontal: 15,
paddingVertical: 10,
fontSize: 14
},
pickerText: {
...sharedStyles.textRegular,
fontSize: 16
}
});

View File

@ -10,15 +10,11 @@ import { themes } from '../constants/colors';
import debounce from '../utils/debounce'; import debounce from '../utils/debounce';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import ListItem from '../containers/ListItem'; import * as List from '../containers/List';
import Check from '../containers/Check';
import Separator from '../containers/Separator';
import SearchBox from '../containers/SearchBox'; import SearchBox from '../containers/SearchBox';
import SafeAreaView from '../containers/SafeAreaView';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
check: {
marginHorizontal: 0
},
search: { search: {
width: '100%', width: '100%',
height: 56 height: 56
@ -28,10 +24,6 @@ const styles = StyleSheet.create({
paddingVertical: 56, paddingVertical: 56,
...sharedStyles.textAlignCenter, ...sharedStyles.textAlignCenter,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
},
withoutBorder: {
borderBottomWidth: 0,
borderTopWidth: 0
} }
}); });
@ -41,11 +33,11 @@ const Item = React.memo(({
onItemPress, onItemPress,
theme theme
}) => ( }) => (
<ListItem <List.Item
title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })} title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })}
right={selected && (() => <Check theme={theme} style={styles.check} />)} right={selected && (() => <List.Icon name='check' color={themes[theme].tintColor} />)}
onPress={onItemPress} onPress={onItemPress}
theme={theme} translateTitle={false}
/> />
)); ));
Item.propTypes = { Item.propTypes = {
@ -109,7 +101,7 @@ class PickerView extends React.PureComponent {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<> <SafeAreaView>
{this.renderSearch()} {this.renderSearch()}
<FlatList <FlatList
data={data} data={data}
@ -122,19 +114,13 @@ class PickerView extends React.PureComponent {
onItemPress={() => this.onChangeValue(item.value)} onItemPress={() => this.onChangeValue(item.value)}
/> />
)} )}
ItemSeparatorComponent={() => <Separator theme={theme} />} ItemSeparatorComponent={List.Separator}
ListHeaderComponent={List.Separator}
ListFooterComponent={List.Separator}
ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>} ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>}
contentContainerStyle={[ contentContainerStyle={[List.styles.contentContainerStyleFlatList]}
sharedStyles.listContentContainer,
{
backgroundColor: themes[theme].auxiliaryBackground,
borderColor: themes[theme].separatorColor
},
!data.length && styles.withoutBorder
]}
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
/> />
</> </SafeAreaView>
); );
} }
} }

View File

@ -469,8 +469,8 @@ class ProfileView extends React.Component {
contentContainerStyle={sharedStyles.container} contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar theme={theme} /> <StatusBar />
<SafeAreaView testID='profile-view' theme={theme}> <SafeAreaView testID='profile-view'>
<ScrollView <ScrollView
contentContainerStyle={sharedStyles.containerScrollView} contentContainerStyle={sharedStyles.containerScrollView}
testID='profile-view-list' testID='profile-view-list'

View File

@ -135,8 +135,8 @@ class ReadReceiptView extends React.Component {
} }
return ( return (
<SafeAreaView testID='read-receipt-view' theme={theme}> <SafeAreaView testID='read-receipt-view'>
<StatusBar theme={theme} /> <StatusBar />
{loading {loading
? <ActivityIndicator theme={theme} /> ? <ActivityIndicator theme={theme} />
: ( : (

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
View, SectionList, Text, Alert, Share, Switch View, Text, Alert, Share, Switch
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import _ from 'lodash'; import _ from 'lodash';
@ -13,24 +13,22 @@ import styles from './styles';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import Status from '../../containers/Status'; import Status from '../../containers/Status';
import * as List from '../../containers/List';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import log, { logEvent, events } from '../../utils/log'; import log, { logEvent, events } from '../../utils/log';
import RoomTypeIcon from '../../containers/RoomTypeIcon'; import RoomTypeIcon from '../../containers/RoomTypeIcon';
import I18n from '../../i18n'; import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { CustomIcon } from '../../lib/Icons';
import DisclosureIndicator from '../../containers/DisclosureIndicator';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { themes, SWITCH_TRACK_COLOR } from '../../constants/colors'; import { themes, SWITCH_TRACK_COLOR } from '../../constants/colors';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { CloseModalButton } from '../../containers/HeaderButton'; import { CloseModalButton } from '../../containers/HeaderButton';
import { getUserSelector } from '../../selectors/login';
import Markdown from '../../containers/markdown'; import Markdown from '../../containers/markdown';
import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { E2E_ROOM_TYPES } from '../../lib/encryption/constants'; import { E2E_ROOM_TYPES } from '../../lib/encryption/constants';
import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import database from '../../lib/database'; import database from '../../lib/database';
import { withDimensions } from '../../dimensions';
class RoomActionsView extends React.Component { class RoomActionsView extends React.Component {
static navigationOptions = ({ navigation, isMasterDetail }) => { static navigationOptions = ({ navigation, isMasterDetail }) => {
@ -44,19 +42,15 @@ class RoomActionsView extends React.Component {
} }
static propTypes = { static propTypes = {
baseUrl: PropTypes.string,
navigation: PropTypes.object, navigation: PropTypes.object,
route: PropTypes.object, route: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
}),
leaveRoom: PropTypes.func, leaveRoom: PropTypes.func,
jitsiEnabled: PropTypes.bool, jitsiEnabled: PropTypes.bool,
e2eEnabled: PropTypes.bool, e2eEnabled: PropTypes.bool,
setLoadingInvite: PropTypes.func, setLoadingInvite: PropTypes.func,
closeRoom: PropTypes.func, closeRoom: PropTypes.func,
theme: PropTypes.string theme: PropTypes.string,
fontScale: PropTypes.number
} }
constructor(props) { constructor(props) {
@ -240,281 +234,6 @@ class RoomActionsView extends React.Component {
} }
} }
get sections() {
const {
room, member, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue, canEdit
} = this.state;
const { jitsiEnabled, e2eEnabled } = this.props;
const {
rid, t, blocker, encrypted
} = room;
const isGroupChat = RocketChat.isGroupChat(room);
const notificationsAction = {
icon: 'notification',
name: I18n.t('Notifications'),
route: 'NotificationPrefView',
params: { rid, room },
testID: 'room-actions-notifications',
right: this.renderDisclosure
};
const jitsiActions = jitsiEnabled ? [
{
icon: 'phone',
name: I18n.t('Voice_call'),
event: () => RocketChat.callJitsi(rid, true),
testID: 'room-actions-voice',
right: this.renderDisclosure
},
{
icon: 'camera',
name: I18n.t('Video_call'),
event: () => RocketChat.callJitsi(rid),
testID: 'room-actions-video',
right: this.renderDisclosure
}
] : [];
const sections = [{
data: [{
icon: 'star',
name: I18n.t('Room_Info'),
route: 'RoomInfoView',
// forward room only if room isn't joined
params: {
rid, t, room, member
},
disabled: isGroupChat,
testID: 'room-actions-info'
}],
renderItem: this.renderRoomInfo
}, {
data: jitsiActions,
renderItem: this.renderItem
}, {
data: [
{
icon: 'attach',
name: I18n.t('Files'),
route: 'MessagesView',
params: { rid, t, name: 'Files' },
testID: 'room-actions-files',
right: this.renderDisclosure
},
{
icon: 'mention',
name: I18n.t('Mentions'),
route: 'MessagesView',
params: { rid, t, name: 'Mentions' },
testID: 'room-actions-mentioned',
right: this.renderDisclosure
},
{
icon: 'star',
name: I18n.t('Starred'),
route: 'MessagesView',
params: { rid, t, name: 'Starred' },
testID: 'room-actions-starred',
right: this.renderDisclosure
},
{
icon: 'search',
name: I18n.t('Search'),
route: 'SearchMessagesView',
params: { rid, encrypted },
testID: 'room-actions-search',
right: this.renderDisclosure
},
{
icon: 'share',
name: I18n.t('Share'),
event: this.handleShare,
testID: 'room-actions-share',
right: this.renderDisclosure
},
{
icon: 'pin',
name: I18n.t('Pinned'),
route: 'MessagesView',
params: { rid, t, name: 'Pinned' },
testID: 'room-actions-pinned',
right: this.renderDisclosure
}
],
renderItem: this.renderItem
}];
if (canAutoTranslate) {
sections[2].data.push({
icon: 'language',
name: I18n.t('Auto_Translate'),
route: 'AutoTranslateView',
params: { rid, room },
testID: 'room-actions-auto-translate',
right: this.renderDisclosure
});
}
if (isGroupChat) {
sections[2].data.unshift({
icon: 'team',
name: I18n.t('Members'),
description: membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null,
route: 'RoomMembersView',
params: { rid, room },
testID: 'room-actions-members',
right: this.renderDisclosure
});
}
if (t === 'd' && !isGroupChat) {
sections.push({
data: [
{
icon: 'ban',
name: I18n.t(`${ blocker ? 'Unblock' : 'Block' }_user`),
type: 'danger',
event: this.toggleBlockUser,
testID: 'room-actions-block-user',
right: this.renderDisclosure
}
],
renderItem: this.renderItem
});
sections[2].data.push(notificationsAction);
} else if (t === 'c' || t === 'p') {
const actions = [];
if (canViewMembers) {
actions.push({
icon: 'team',
name: I18n.t('Members'),
description: membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null,
route: 'RoomMembersView',
params: { rid, room },
testID: 'room-actions-members',
right: this.renderDisclosure
});
}
if (canAddUser) {
actions.push({
icon: 'add',
name: I18n.t('Add_users'),
route: 'SelectedUsersView',
params: {
rid,
title: I18n.t('Add_users'),
nextAction: this.addUser
},
testID: 'room-actions-add-user',
right: this.renderDisclosure
});
}
if (canInviteUser) {
actions.push({
icon: 'user-add',
name: I18n.t('Invite_users'),
route: 'InviteUsersView',
params: {
rid
},
testID: 'room-actions-invite-user',
right: this.renderDisclosure
});
}
sections[2].data = [...actions, ...sections[2].data];
if (joined) {
sections[2].data.push(notificationsAction);
sections.push({
data: [
{
icon: 'logout',
name: I18n.t('Leave_channel'),
type: 'danger',
event: this.leaveChannel,
testID: 'room-actions-leave-channel',
right: this.renderDisclosure
}
],
renderItem: this.renderItem
});
}
} else if (t === 'l') {
sections[2].data = [];
if (!this.isOmnichannelPreview) {
sections[2].data.push({
icon: 'close',
name: I18n.t('Close'),
event: this.closeLivechat,
right: this.renderDisclosure
});
if (canForwardGuest) {
sections[2].data.push({
icon: 'user-forward',
name: I18n.t('Forward'),
route: 'ForwardLivechatView',
params: { rid },
right: this.renderDisclosure
});
}
if (canReturnQueue) {
sections[2].data.push({
icon: 'undo',
name: I18n.t('Return'),
event: this.returnLivechat,
right: this.renderDisclosure
});
}
sections[2].data.push({
icon: 'history',
name: I18n.t('Navigation_history'),
route: 'VisitorNavigationView',
params: { rid },
right: this.renderDisclosure
});
}
sections.push({
data: [notificationsAction],
renderItem: this.renderItem
});
}
// If can edit this room
// If this room type can be Encrypted
// If e2e is enabled for this server
if (canEdit && E2E_ROOM_TYPES[t] && e2eEnabled) {
sections.splice(2, 0, {
data: [{
icon: 'encrypted',
name: I18n.t('Encrypted'),
testID: 'room-actions-encrypt',
right: this.renderEncryptedSwitch
}],
renderItem: this.renderItem
});
}
return sections;
}
renderDisclosure = () => {
const { theme } = this.props;
return <DisclosureIndicator theme={theme} />;
}
renderSeparator = () => {
const { theme } = this.props;
return <View style={[styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
}
renderEncryptedSwitch = () => { renderEncryptedSwitch = () => {
const { room } = this.state; const { room } = this.state;
const { encrypted } = room; const { encrypted } = room;
@ -523,7 +242,6 @@ class RoomActionsView extends React.Component {
value={encrypted} value={encrypted}
trackColor={SWITCH_TRACK_COLOR} trackColor={SWITCH_TRACK_COLOR}
onValueChange={this.toggleEncrypted} onValueChange={this.toggleEncrypted}
style={styles.encryptedSwitch}
/> />
); );
} }
@ -667,21 +385,35 @@ class RoomActionsView extends React.Component {
); );
} }
renderRoomInfo = ({ item }) => { renderRoomInfo = () => {
const { room, member } = this.state; const { room, member } = this.state;
const { const {
name, rid, name, t, topic
t,
rid,
topic
} = room; } = room;
const { theme } = this.props; const { theme, fontScale } = this.props;
const avatar = RocketChat.getRoomAvatar(room); const avatar = RocketChat.getRoomAvatar(room);
const isGroupChat = RocketChat.isGroupChat(room);
return ( return (
this.renderTouchableItem(( <List.Section>
<> <List.Separator />
<Touch
onPress={() => this.onPressTouchable({
route: 'RoomInfoView',
// forward room only if room isn't joined
params: {
rid, t, room, member
}
})}
style={{ backgroundColor: themes[theme].backgroundColor }}
accessibilityLabel={I18n.t('Room_Info')}
accessibilityTraits='button'
enabled={!isGroupChat}
testID='room-actions-info'
theme={theme}
>
<View style={[styles.roomInfoContainer, { height: 72 * fontScale }]}>
<Avatar <Avatar
text={avatar} text={avatar}
style={styles.avatar} style={styles.avatar}
@ -691,7 +423,7 @@ class RoomActionsView extends React.Component {
> >
{t === 'd' && member._id ? <Status style={sharedStyles.status} id={member._id} /> : null } {t === 'd' && member._id ? <Status style={sharedStyles.status} id={member._id} /> : null }
</Avatar> </Avatar>
<View style={[styles.roomTitleContainer, item.disabled && styles.roomTitlePadding]}> <View style={styles.roomTitleContainer}>
{room.t === 'd' {room.t === 'd'
? <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.fname}</Text> ? <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.fname}</Text>
: ( : (
@ -710,85 +442,410 @@ class RoomActionsView extends React.Component {
/> />
{room.t === 'd' && <Markdown msg={member.statusText} style={[styles.roomDescription, { color: themes[theme].auxiliaryText }]} preview theme={theme} numberOfLines={1} />} {room.t === 'd' && <Markdown msg={member.statusText} style={[styles.roomDescription, { color: themes[theme].auxiliaryText }]} preview theme={theme} numberOfLines={1} />}
</View> </View>
{!item.disabled && <DisclosureIndicator theme={theme} />} {isGroupChat ? null : <List.Icon name='chevron-right' />}
</>
), item)
);
}
renderTouchableItem = (subview, item) => {
const { theme } = this.props;
return (
<Touch
onPress={() => this.onPressTouchable(item)}
style={{ backgroundColor: themes[theme].backgroundColor }}
accessibilityLabel={item.name}
accessibilityTraits='button'
enabled={!item.disabled}
testID={item.testID}
theme={theme}
>
<View style={styles.sectionItem}>
{subview}
</View> </View>
</Touch> </Touch>
<List.Separator />
</List.Section>
); );
} }
renderItem = ({ item }) => { renderJitsi = () => {
const { theme } = this.props; const { room } = this.state;
const colorDanger = { color: themes[theme].dangerColor }; const { jitsiEnabled } = this.props;
const subview = item.type === 'danger' ? ( if (!jitsiEnabled) {
<> return null;
<CustomIcon name={item.icon} size={24} style={[styles.sectionItemIcon, colorDanger]} /> }
<Text style={[styles.sectionItemName, colorDanger]}>{ item.name }</Text> return (
</> <List.Section>
) : ( <List.Separator />
<> <List.Item
<CustomIcon name={item.icon} size={24} style={[styles.sectionItemIcon, { color: themes[theme].bodyText }]} /> title='Voice_call'
<Text style={[styles.sectionItemName, { color: themes[theme].bodyText }]}>{ item.name }</Text> onPress={() => RocketChat.callJitsi(room?.rid, true)}
{item.description ? <Text style={[styles.sectionItemDescription, { color: themes[theme].auxiliaryText }]}>{ item.description }</Text> : null} testID='room-actions-voice'
{item?.right?.()} left={() => <List.Icon name='phone' />}
</> showActionIndicator
/>
<List.Separator />
<List.Item
title='Video_call'
onPress={() => RocketChat.callJitsi(room?.rid)}
testID='room-actions-video'
left={() => <List.Icon name='camera' />}
showActionIndicator
/>
<List.Separator />
</List.Section>
); );
return this.renderTouchableItem(subview, item);
} }
renderSectionSeparator = (data) => { renderE2EEncryption = () => {
const { theme } = this.props; const {
if (data.trailingItem) { room, canEdit
return <View style={[styles.sectionSeparator, data.leadingSection && styles.sectionSeparatorBorder, { backgroundColor: themes[theme].auxiliaryBackground, borderColor: themes[theme].separatorColor }]} />; } = this.state;
} const { e2eEnabled } = this.props;
if (!data.trailingSection) {
return <View style={[styles.sectionSeparatorBorder, { backgroundColor: themes[theme].auxiliaryBackground, borderColor: themes[theme].separatorColor }]} />; // If can edit this room
// If this room type can be Encrypted
// If e2e is enabled for this server
if (canEdit && E2E_ROOM_TYPES[room?.t] && e2eEnabled) {
return (
<List.Section>
<List.Separator />
<List.Item
title='Encrypted'
testID='room-actions-encrypt'
left={() => <List.Icon name='encrypted' />}
right={this.renderEncryptedSwitch}
/>
<List.Separator />
</List.Section>
);
} }
return null; return null;
} }
render() { renderLastSection = () => {
const { room, joined } = this.state;
const { theme } = this.props; const { theme } = this.props;
const { t, blocker } = room;
if (!joined || t === 'l') {
return null;
}
if (t === 'd') {
return ( return (
<SafeAreaView testID='room-actions-view' theme={theme}> <List.Section>
<StatusBar theme={theme} /> <List.Separator />
<SectionList <List.Item
contentContainerStyle={[styles.contentContainer, { backgroundColor: themes[theme].auxiliaryBackground }]} title={`${ blocker ? 'Unblock' : 'Block' }_user`}
style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]} onPress={() => this.onPressTouchable({
stickySectionHeadersEnabled={false} event: this.toggleBlockUser
sections={this.sections} })}
SectionSeparatorComponent={this.renderSectionSeparator} testID='room-actions-block-user'
ItemSeparatorComponent={this.renderSeparator} left={() => <List.Icon name='ban' color={themes[theme].dangerColor} />}
keyExtractor={item => item.name} showActionIndicator
testID='room-actions-list' color={themes[theme].dangerColor}
{...scrollPersistTaps}
/> />
<List.Separator />
</List.Section>
);
}
if (t === 'p' || t === 'c') {
return (
<List.Section>
<List.Separator />
<List.Item
title='Leave_channel'
onPress={() => this.onPressTouchable({
event: this.leaveChannel
})}
testID='room-actions-leave-channel'
left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />}
showActionIndicator
color={themes[theme].dangerColor}
/>
<List.Separator />
</List.Section>
);
}
}
render() {
const {
room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue
} = this.state;
const {
rid, t, encrypted
} = room;
const isGroupChat = RocketChat.isGroupChat(room);
return (
<SafeAreaView testID='room-actions-view'>
<StatusBar />
<List.Container>
{this.renderRoomInfo()}
{this.renderJitsi()}
{this.renderE2EEncryption()}
<List.Section>
<List.Separator />
{(['c', 'p'].includes(t) && canViewMembers) || isGroupChat
? (
<>
<List.Item
title='Members'
subtitle={membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null}
onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room } })}
testID='room-actions-members'
left={() => <List.Icon name='team' />}
showActionIndicator
translateSubtitle={false}
/>
<List.Separator />
</>
)
: null}
{['c', 'p'].includes(t) && canAddUser
? (
<>
<List.Item
title='Add_users'
onPress={() => this.onPressTouchable({
route: 'SelectedUsersView',
params: {
rid,
title: I18n.t('Add_users'),
nextAction: this.addUser
}
})}
testID='room-actions-add-user'
left={() => <List.Icon name='add' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p'].includes(t) && canInviteUser
? (
<>
<List.Item
title='Invite_users'
onPress={() => this.onPressTouchable({
route: 'InviteUsersView',
params: { rid }
})}
testID='room-actions-invite-user'
left={() => <List.Icon name='user-add' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Files'
onPress={() => this.onPressTouchable({
route: 'MessagesView',
params: { rid, t, name: 'Files' }
})}
testID='room-actions-files'
left={() => <List.Icon name='attach' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Mentions'
onPress={() => this.onPressTouchable({
route: 'MessagesView',
params: { rid, t, name: 'Mentions' }
})}
testID='room-actions-mentioned'
left={() => <List.Icon name='mention' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Starred'
onPress={() => this.onPressTouchable({
route: 'MessagesView',
params: { rid, t, name: 'Starred' }
})}
testID='room-actions-starred'
left={() => <List.Icon name='star' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Search'
onPress={() => this.onPressTouchable({
route: 'SearchMessagesView',
params: { rid, encrypted }
})}
testID='room-actions-search'
left={() => <List.Icon name='search' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Share'
onPress={() => this.onPressTouchable({
event: this.handleShare
})}
testID='room-actions-share'
left={() => <List.Icon name='share' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Pinned'
onPress={() => this.onPressTouchable({
route: 'MessagesView',
params: { rid, t, name: 'Pinned' }
})}
testID='room-actions-pinned'
left={() => <List.Icon name='pin' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t) && canAutoTranslate
? (
<>
<List.Item
title='Auto_Translate'
onPress={() => this.onPressTouchable({
route: 'AutoTranslateView',
params: { rid, room }
})}
testID='room-actions-auto-translate'
left={() => <List.Icon name='language' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t) && joined
? (
<>
<List.Item
title='Notifications'
onPress={() => this.onPressTouchable({
route: 'NotificationPrefView',
params: { rid, room }
})}
testID='room-actions-notifications'
left={() => <List.Icon name='notification' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['l'].includes(t) && !this.isOmnichannelPreview
? (
<>
<List.Item
title='Close'
onPress={() => this.onPressTouchable({
event: this.closeLivechat
})}
left={() => <List.Icon name='close' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['l'].includes(t) && !this.isOmnichannelPreview && canForwardGuest
? (
<>
<List.Item
title='Forward'
onPress={() => this.onPressTouchable({
route: 'ForwardLivechatView',
params: { rid }
})}
left={() => <List.Icon name='user-forward' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['l'].includes(t) && !this.isOmnichannelPreview && canReturnQueue
? (
<>
<List.Item
title='Return'
onPress={() => this.onPressTouchable({
event: this.returnLivechat
})}
left={() => <List.Icon name='undo' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['l'].includes(t) && !this.isOmnichannelPreview
? (
<>
<List.Item
title='Navigation_history'
onPress={() => this.onPressTouchable({
route: 'VisitorNavigationView',
params: { rid }
})}
left={() => <List.Icon name='history' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
</List.Section>
{this.renderLastSection()}
</List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
user: getUserSelector(state),
baseUrl: state.server.server,
jitsiEnabled: state.settings.Jitsi_Enabled || false, jitsiEnabled: state.settings.Jitsi_Enabled || false,
e2eEnabled: state.settings.E2E_Enable || false e2eEnabled: state.settings.E2E_Enable || false
}); });
@ -799,4 +856,4 @@ const mapDispatchToProps = dispatch => ({
setLoadingInvite: loading => dispatch(setLoadingAction(loading)) setLoadingInvite: loading => dispatch(setLoadingAction(loading))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomActionsView)); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(withDimensions(RoomActionsView)));

View File

@ -1,51 +1,20 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { PADDING_HORIZONTAL } from '../../containers/List/constants';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
export default StyleSheet.create({ export default StyleSheet.create({
contentContainer: { roomInfoContainer: {
paddingBottom: 30 paddingHorizontal: PADDING_HORIZONTAL,
},
container: {
flex: 1
},
sectionItem: {
paddingVertical: 11,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center'
}, },
sectionItemIcon: {
width: 56,
textAlign: 'center'
},
sectionItemName: {
flex: 1,
fontSize: 14,
...sharedStyles.textRegular
},
sectionItemDescription: {
fontSize: 14,
...sharedStyles.textRegular
},
separator: {
height: StyleSheet.hairlineWidth
},
sectionSeparator: {
borderBottomWidth: StyleSheet.hairlineWidth,
height: 36
},
sectionSeparatorBorder: {
borderTopWidth: StyleSheet.hairlineWidth
},
avatar: { avatar: {
marginHorizontal: 16 marginRight: PADDING_HORIZONTAL
}, },
roomTitleContainer: { roomTitleContainer: {
flex: 1 flex: 1
}, },
roomTitlePadding: {
paddingRight: 16
},
roomTitle: { roomTitle: {
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium ...sharedStyles.textMedium
@ -58,8 +27,5 @@ export default StyleSheet.create({
paddingRight: 16, paddingRight: 16,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center'
},
encryptedSwitch: {
marginHorizontal: 16
} }
}); });

View File

@ -418,10 +418,9 @@ class RoomInfoEditView extends React.Component {
contentContainerStyle={sharedStyles.container} contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128} keyboardVerticalOffset={128}
> >
<StatusBar theme={theme} /> <StatusBar />
<SafeAreaView <SafeAreaView
testID='room-info-edit-view' testID='room-info-edit-view'
theme={theme}
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
> >
<ScrollView <ScrollView

View File

@ -350,10 +350,9 @@ class RoomInfoView extends React.Component {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<ScrollView style={[styles.scroll, { backgroundColor: themes[theme].backgroundColor }]}> <ScrollView style={[styles.scroll, { backgroundColor: themes[theme].backgroundColor }]}>
<StatusBar theme={theme} /> <StatusBar />
<SafeAreaView <SafeAreaView
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
theme={theme}
testID='room-info-view' testID='room-info-view'
> >
<View style={[styles.avatarContainer, this.isDirect && styles.avatarContainerDirectRoom, { backgroundColor: themes[theme].auxiliaryBackground }]}> <View style={[styles.avatarContainer, this.isDirect && styles.avatarContainerDirectRoom, { backgroundColor: themes[theme].auxiliaryBackground }]}>

View File

@ -250,8 +250,8 @@ class RoomMembersView extends React.Component {
} = this.state; } = this.state;
const { theme } = this.props; const { theme } = this.props;
return ( return (
<SafeAreaView testID='room-members-view' theme={theme}> <SafeAreaView testID='room-members-view'>
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
data={filtering ? membersFiltered : members} data={filtering ? membersFiltered : members}
renderItem={this.renderItem} renderItem={this.renderItem}

View File

@ -1020,9 +1020,8 @@ class RoomView extends React.Component {
<SafeAreaView <SafeAreaView
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
testID='room-view' testID='room-view'
theme={theme}
> >
<StatusBar theme={theme} /> <StatusBar />
<Banner <Banner
rid={rid} rid={rid}
title={I18n.t('Announcement')} title={I18n.t('Announcement')}

View File

@ -1008,8 +1008,8 @@ class RoomsListView extends React.Component {
} = this.props; } = this.props;
return ( return (
<SafeAreaView testID='rooms-list-view' theme={theme} style={{ backgroundColor: themes[theme].backgroundColor }}> <SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
<StatusBar theme={theme} /> <StatusBar />
{this.renderHeader()} {this.renderHeader()}
{this.renderScroll()} {this.renderScroll()}
{showSortDropdown ? ( {showSortDropdown ? (

View File

@ -1,32 +1,19 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StyleSheet, Switch, ScrollView } from 'react-native'; import { Switch } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { themes, SWITCH_TRACK_COLOR } from '../constants/colors'; import { themes, SWITCH_TRACK_COLOR } from '../constants/colors';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import Separator from '../containers/Separator'; import * as List from '../containers/List';
import ListItem from '../containers/ListItem';
import ItemInfo from '../containers/ItemInfo';
import { CustomIcon } from '../lib/Icons';
import database from '../lib/database'; import database from '../lib/database';
import { supportedBiometryLabel, changePasscode, checkHasPasscode } from '../utils/localAuthentication'; import { supportedBiometryLabel, changePasscode, checkHasPasscode } from '../utils/localAuthentication';
import { DisclosureImage } from '../containers/DisclosureIndicator';
import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication'; import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { events, logEvent } from '../utils/log'; import { events, logEvent } from '../utils/log';
const styles = StyleSheet.create({
listPadding: {
paddingVertical: 36
},
emptySpace: {
marginTop: 36
}
});
const DEFAULT_BIOMETRY = false; const DEFAULT_BIOMETRY = false;
class ScreenLockConfigView extends React.Component { class ScreenLockConfigView extends React.Component {
@ -161,29 +148,23 @@ class ScreenLockConfigView extends React.Component {
this.setState({ autoLockTime }, () => this.save()); this.setState({ autoLockTime }, () => this.save());
} }
renderSeparator = () => {
const { theme } = this.props;
return <Separator theme={theme} />;
}
renderIcon = () => { renderIcon = () => {
const { theme } = this.props; const { theme } = this.props;
return <CustomIcon name='check' size={20} color={themes[theme].tintColor} />; return <List.Icon name='check' color={themes[theme].tintColor} />;
} }
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { theme } = this.props;
const { title, value, disabled } = item; const { title, value, disabled } = item;
return ( return (
<> <>
<ListItem <List.Item
title={title} title={title}
onPress={() => this.changeAutoLockTime(value)} onPress={() => this.changeAutoLockTime(value)}
right={this.isSelected(value) ? this.renderIcon : null} right={this.isSelected(value) ? this.renderIcon : null}
theme={theme}
disabled={disabled} disabled={disabled}
translateTitle={false}
/> />
<Separator theme={theme} /> <List.Separator />
</> </>
); );
} }
@ -214,7 +195,7 @@ class ScreenLockConfigView extends React.Component {
renderAutoLockItems = () => { renderAutoLockItems = () => {
const { autoLock, autoLockTime } = this.state; const { autoLock, autoLockTime } = this.state;
const { theme, Force_Screen_Lock_After, Force_Screen_Lock } = this.props; const { Force_Screen_Lock_After, Force_Screen_Lock } = this.props;
if (!autoLock) { if (!autoLock) {
return null; return null;
} }
@ -233,75 +214,62 @@ class ScreenLockConfigView extends React.Component {
}); });
} }
return ( return (
<> <List.Section>
<Separator style={styles.emptySpace} theme={theme} /> <List.Separator />
{items.map(item => this.renderItem({ item }))} {items.map(item => this.renderItem({ item }))}
</> </List.Section>
); );
} }
renderDisclosure = () => {
const { theme } = this.props;
return <DisclosureImage theme={theme} />;
}
renderBiometry = () => { renderBiometry = () => {
const { autoLock, biometryLabel } = this.state; const { autoLock, biometryLabel } = this.state;
const { theme } = this.props;
if (!autoLock || !biometryLabel) { if (!autoLock || !biometryLabel) {
return null; return null;
} }
return ( return (
<> <List.Section>
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Local_authentication_unlock_with_label', { label: biometryLabel })} title={I18n.t('Local_authentication_unlock_with_label', { label: biometryLabel })}
right={() => this.renderBiometrySwitch()} right={() => this.renderBiometrySwitch()}
theme={theme} translateTitle={false}
/> />
<Separator theme={theme} /> <List.Separator />
</> </List.Section>
); );
} }
render() { render() {
const { autoLock } = this.state; const { autoLock } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView theme={theme}> <SafeAreaView>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container>
keyExtractor={item => item.value} <List.Section>
contentContainerStyle={styles.listPadding} <List.Separator />
> <List.Item
<Separator theme={theme} /> title='Local_authentication_unlock_option'
<ListItem
title={I18n.t('Local_authentication_unlock_option')}
right={() => this.renderAutoLockSwitch()} right={() => this.renderAutoLockSwitch()}
theme={theme}
/> />
{autoLock {autoLock
? ( ? (
<> <>
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Local_authentication_change_passcode')} title='Local_authentication_change_passcode'
theme={theme}
right={this.renderDisclosure}
onPress={this.changePasscode} onPress={this.changePasscode}
showActionIndicator
/> />
</> </>
) )
: null : null
} }
<Separator theme={theme} /> <List.Separator />
<ItemInfo <List.Info info='Local_authentication_info' />
info={I18n.t('Local_authentication_info')} </List.Section>
theme={theme}
/>
{this.renderBiometry()} {this.renderBiometry()}
{this.renderAutoLockItems()} {this.renderAutoLockItems()}
</ScrollView> </List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -183,8 +183,8 @@ class SearchMessagesView extends React.Component {
render() { render() {
const { theme } = this.props; const { theme } = this.props;
return ( return (
<SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='search-messages-view' theme={theme}> <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='search-messages-view'>
<StatusBar theme={theme} /> <StatusBar />
<View style={styles.searchContainer}> <View style={styles.searchContainer}>
<RCTextInput <RCTextInput
autoFocus autoFocus

View File

@ -83,8 +83,8 @@ class SelectServerView extends React.Component {
const { servers } = this.state; const { servers } = this.state;
const { theme } = this.props; const { theme } = this.props;
return ( return (
<SafeAreaView theme={theme}> <SafeAreaView>
<StatusBar theme={theme} /> <StatusBar />
<View style={[styles.list, { borderColor: themes[theme].separatorColor }]}> <View style={[styles.list, { borderColor: themes[theme].separatorColor }]}>
<FlatList <FlatList
data={servers} data={servers}

View File

@ -307,10 +307,10 @@ class SelectedUsersView extends React.Component {
} }
render = () => { render = () => {
const { loading, theme } = this.props; const { loading } = this.props;
return ( return (
<SafeAreaView testID='select-users-view' theme={theme}> <SafeAreaView testID='select-users-view'>
<StatusBar theme={theme} /> <StatusBar />
{this.renderList()} {this.renderList()}
<Loading visible={loading} /> <Loading visible={loading} />
</SafeAreaView> </SafeAreaView>

View File

@ -104,9 +104,9 @@ class SetUsernameView extends React.Component {
style={{ backgroundColor: themes[theme].auxiliaryBackground }} style={{ backgroundColor: themes[theme].auxiliaryBackground }}
contentContainerStyle={sharedStyles.container} contentContainerStyle={sharedStyles.container}
> >
<StatusBar theme={theme} /> <StatusBar />
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}> <ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView testID='set-username-view' theme={theme}> <SafeAreaView testID='set-username-view'>
<Text <Text
style={[ style={[
sharedStyles.loginTitle, sharedStyles.loginTitle,

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { import {
View, Linking, ScrollView, Switch, Share, Clipboard Linking, Switch, Share, Clipboard
} from 'react-native'; } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -14,19 +14,14 @@ import { toggleCrashReport as toggleCrashReportAction, toggleAnalyticsEvents as
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
import { DrawerButton, CloseModalButton } from '../../containers/HeaderButton'; import { DrawerButton, CloseModalButton } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ListItem from '../../containers/ListItem'; import * as List from '../../containers/List';
import ItemInfo from '../../containers/ItemInfo';
import { DisclosureImage } from '../../containers/DisclosureIndicator';
import Separator from '../../containers/Separator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import RocketChat, { CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY } from '../../lib/rocketchat'; import RocketChat, { CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY } from '../../lib/rocketchat';
import { import {
getReadableVersion, getDeviceModel, isAndroid getReadableVersion, getDeviceModel, isAndroid
} from '../../utils/deviceInfo'; } from '../../utils/deviceInfo';
import openLink from '../../utils/openLink'; import openLink from '../../utils/openLink';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { showErrorAlert, showConfirmationAlert } from '../../utils/info'; import { showErrorAlert, showConfirmationAlert } from '../../utils/info';
import styles from './styles';
import { import {
loggerConfig, analytics, logEvent, events loggerConfig, analytics, logEvent, events
} from '../../utils/log'; } from '../../utils/log';
@ -44,22 +39,6 @@ import database from '../../lib/database';
import { isFDroidBuild } from '../../constants/environment'; import { isFDroidBuild } from '../../constants/environment';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
const SectionSeparator = React.memo(({ theme }) => (
<View
style={[
styles.sectionSeparatorBorder,
{
borderColor: themes[theme].separatorColor,
backgroundColor: themes[theme].auxiliaryBackground
}
]}
/>
));
SectionSeparator.propTypes = {
theme: PropTypes.string
};
class SettingsView extends React.Component { class SettingsView extends React.Component {
static navigationOptions = ({ navigation, isMasterDetail }) => ({ static navigationOptions = ({ navigation, isMasterDetail }) => ({
headerLeft: () => (isMasterDetail ? ( headerLeft: () => (isMasterDetail ? (
@ -223,11 +202,6 @@ class SettingsView extends React.Component {
openLink(LICENSE_LINK, theme); openLink(LICENSE_LINK, theme);
} }
renderDisclosure = () => {
const { theme } = this.props;
return <DisclosureImage theme={theme} />;
}
renderCrashReportSwitch = () => { renderCrashReportSwitch = () => {
const { allowCrashReport } = this.props; const { allowCrashReport } = this.props;
return ( return (
@ -253,170 +227,153 @@ class SettingsView extends React.Component {
render() { render() {
const { server, isMasterDetail, theme } = this.props; const { server, isMasterDetail, theme } = this.props;
return ( return (
<SafeAreaView testID='settings-view' theme={theme}> <SafeAreaView testID='settings-view'>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container testID='settings-view-list'>
{...scrollPersistTaps}
contentContainerStyle={styles.listPadding}
showsVerticalScrollIndicator={false}
testID='settings-view-list'
>
{isMasterDetail ? ( {isMasterDetail ? (
<> <>
<Separator theme={theme} /> <List.Section>
<SidebarView theme={theme} /> <List.Separator />
<SectionSeparator theme={theme} /> <SidebarView />
<ListItem <List.Separator />
title={I18n.t('Profile')} </List.Section>
<List.Section>
<List.Separator />
<List.Item
title='Profile'
onPress={() => this.navigateToScreen('ProfileView')} onPress={() => this.navigateToScreen('ProfileView')}
showActionIndicator showActionIndicator
testID='settings-profile' testID='settings-profile'
right={this.renderDisclosure}
theme={theme}
/> />
<List.Separator />
</List.Section>
</> </>
) : null} ) : null}
<Separator theme={theme} /> <List.Section>
<ListItem <List.Separator />
title={I18n.t('Contact_us')} <List.Item
title='Contact_us'
onPress={this.sendEmail} onPress={this.sendEmail}
showActionIndicator showActionIndicator
testID='settings-view-contact' testID='settings-view-contact'
right={this.renderDisclosure}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Language')} title='Language'
onPress={() => this.navigateToScreen('LanguageView')} onPress={() => this.navigateToScreen('LanguageView')}
showActionIndicator showActionIndicator
testID='settings-view-language' testID='settings-view-language'
right={this.renderDisclosure}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
{!isFDroidBuild ? ( {!isFDroidBuild ? (
<> <>
<ListItem <List.Item
title={I18n.t('Review_this_app')} title='Review_this_app'
showActionIndicator showActionIndicator
onPress={onReviewPress} onPress={onReviewPress}
testID='settings-view-review-app' testID='settings-view-review-app'
right={this.renderDisclosure}
theme={theme}
/> />
</> </>
) : null} ) : null}
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Share_this_app')} title='Share_this_app'
showActionIndicator showActionIndicator
onPress={this.shareApp} onPress={this.shareApp}
testID='settings-view-share-app' testID='settings-view-share-app'
right={this.renderDisclosure}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Default_browser')} title='Default_browser'
showActionIndicator showActionIndicator
onPress={() => this.navigateToScreen('DefaultBrowserView')} onPress={() => this.navigateToScreen('DefaultBrowserView')}
testID='settings-view-default-browser' testID='settings-view-default-browser'
right={this.renderDisclosure}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Theme')} title='Theme'
showActionIndicator showActionIndicator
onPress={() => this.navigateToScreen('ThemeView')} onPress={() => this.navigateToScreen('ThemeView')}
testID='settings-view-theme' testID='settings-view-theme'
right={this.renderDisclosure}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Screen_lock')} title='Screen_lock'
showActionIndicator showActionIndicator
onPress={() => this.navigateToScreen('ScreenLockConfigView')} onPress={() => this.navigateToScreen('ScreenLockConfigView')}
right={this.renderDisclosure}
theme={theme}
/> />
<List.Separator />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section>
<List.Separator />
<ListItem <List.Item
title={I18n.t('License')} title='License'
onPress={this.onPressLicense} onPress={this.onPressLicense}
showActionIndicator showActionIndicator
testID='settings-view-license' testID='settings-view-license'
right={this.renderDisclosure}
theme={theme}
/> />
<List.Separator />
<Separator theme={theme} /> <List.Item
<ListItem
title={I18n.t('Version_no', { version: getReadableVersion })} title={I18n.t('Version_no', { version: getReadableVersion })}
onPress={this.copyAppVersion} onPress={this.copyAppVersion}
testID='settings-view-version' testID='settings-view-version'
theme={theme} translateTitle={false}
/> />
<Separator theme={theme} /> <List.Separator />
<List.Item
<ListItem
title={I18n.t('Server_version', { version: server.version })} title={I18n.t('Server_version', { version: server.version })}
onPress={this.copyServerVersion} onPress={this.copyServerVersion}
subtitle={`${ server.server.split('//')[1] }`} subtitle={`${ server.server.split('//')[1] }`}
testID='settings-view-server-version' testID='settings-view-server-version'
theme={theme} translateTitle={false}
translateSubtitle={false}
/> />
<List.Separator />
<SectionSeparator theme={theme} /> </List.Section>
{!isFDroidBuild ? ( {!isFDroidBuild ? (
<> <>
<ListItem <List.Section>
title={I18n.t('Log_analytics_events')} <List.Separator />
<List.Item
title='Log_analytics_events'
testID='settings-view-analytics-events' testID='settings-view-analytics-events'
right={() => this.renderAnalyticsEventsSwitch()} right={() => this.renderAnalyticsEventsSwitch()}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Send_crash_report')} title='Send_crash_report'
testID='settings-view-crash-report' testID='settings-view-crash-report'
right={() => this.renderCrashReportSwitch()} right={() => this.renderCrashReportSwitch()}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ItemInfo <List.Info info='Crash_report_disclaimer' />
info={I18n.t('Crash_report_disclaimer')} </List.Section>
theme={theme}
/>
<Separator theme={theme} />
</> </>
) : null} ) : null}
<ListItem <List.Section>
title={I18n.t('Clear_cache')} <List.Separator />
<List.Item
title='Clear_cache'
testID='settings-clear-cache' testID='settings-clear-cache'
onPress={this.handleClearCache} onPress={this.handleClearCache}
right={this.renderDisclosure} showActionIndicator
color={themes[theme].dangerColor} color={themes[theme].dangerColor}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<ListItem <List.Item
title={I18n.t('Logout')} title='Logout'
testID='settings-logout' testID='settings-logout'
onPress={this.handleLogout} onPress={this.handleLogout}
right={this.renderDisclosure} showActionIndicator
color={themes[theme].dangerColor} color={themes[theme].dangerColor}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
</ScrollView> </List.Section>
</List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -1,13 +0,0 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../Styles';
export default StyleSheet.create({
sectionSeparatorBorder: {
...sharedStyles.separatorVertical,
height: 36
},
listPadding: {
paddingVertical: 36
}
});

View File

@ -458,10 +458,9 @@ class ShareListView extends React.Component {
} }
render() { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView theme={theme}> <SafeAreaView>
<StatusBar theme={theme} /> <StatusBar />
{this.renderContent()} {this.renderContent()}
</SafeAreaView> </SafeAreaView>
); );

View File

@ -324,7 +324,6 @@ class ShareView extends Component {
return ( return (
<SafeAreaView <SafeAreaView
style={{ backgroundColor: themes[theme].backgroundColor }} style={{ backgroundColor: themes[theme].backgroundColor }}
theme={theme}
> >
<StatusBar barStyle='light-content' backgroundColor={themes[theme].previewBackground} /> <StatusBar barStyle='light-content' backgroundColor={themes[theme].previewBackground} />
{this.renderContent()} {this.renderContent()}

View File

@ -215,7 +215,7 @@ class Sidebar extends Component {
return null; return null;
} }
return ( return (
<SafeAreaView testID='sidebar-view' style={{ backgroundColor: themes[theme].focusedBackground }} vertical={isMasterDetail} theme={theme}> <SafeAreaView testID='sidebar-view' style={{ backgroundColor: themes[theme].focusedBackground }} vertical={isMasterDetail}>
<ScrollView <ScrollView
style={[ style={[
styles.container, styles.container,

View File

@ -4,8 +4,7 @@ import { FlatList, StyleSheet } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import I18n from '../i18n'; import I18n from '../i18n';
import Separator from '../containers/Separator'; import * as List from '../containers/List';
import ListItem from '../containers/ListItem';
import Status from '../containers/Status/Status'; import Status from '../containers/Status/Status';
import TextInput from '../containers/TextInput'; import TextInput from '../containers/TextInput';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
@ -14,7 +13,6 @@ import RocketChat from '../lib/rocketchat';
import log, { logEvent, events } from '../utils/log'; import log, { logEvent, events } from '../utils/log';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import { themes } from '../constants/colors';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { getUserSelector } from '../selectors/login'; import { getUserSelector } from '../selectors/login';
import { CustomHeaderButtons, Item, CancelModalButton } from '../containers/HeaderButton'; import { CustomHeaderButtons, Item, CancelModalButton } from '../containers/HeaderButton';
@ -37,9 +35,6 @@ const STATUS = [{
}]; }];
const styles = StyleSheet.create({ const styles = StyleSheet.create({
status: {
marginRight: 16
},
inputContainer: { inputContainer: {
marginTop: 32, marginTop: 32,
marginBottom: 32 marginBottom: 32
@ -129,11 +124,6 @@ class StatusView extends React.Component {
this.setState({ loading: false }); this.setState({ loading: false });
} }
renderSeparator = () => {
const { theme } = this.props;
return <Separator theme={theme} />;
}
renderHeader = () => { renderHeader = () => {
const { statusText } = this.state; const { statusText } = this.state;
const { user, theme } = this.props; const { user, theme } = this.props;
@ -157,18 +147,18 @@ class StatusView extends React.Component {
placeholder={I18n.t('What_are_you_doing_right_now')} placeholder={I18n.t('What_are_you_doing_right_now')}
testID='status-view-input' testID='status-view-input'
/> />
<Separator theme={theme} /> <List.Separator />
</> </>
); );
} }
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { statusText } = this.state; const { statusText } = this.state;
const { theme, user } = this.props; const { user } = this.props;
const { id, name } = item; const { id, name } = item;
return ( return (
<ListItem <List.Item
title={I18n.t(name)} title={name}
onPress={async() => { onPress={async() => {
logEvent(events[`STATUS_${ item.id.toUpperCase() }`]); logEvent(events[`STATUS_${ item.id.toUpperCase() }`]);
if (user.status !== item.id) { if (user.status !== item.id) {
@ -184,25 +174,22 @@ class StatusView extends React.Component {
} }
}} }}
testID={`status-view-${ id }`} testID={`status-view-${ id }`}
left={() => <Status style={styles.status} size={12} status={item.id} />} left={() => <Status size={12} status={item.id} />}
theme={theme}
/> />
); );
} }
render() { render() {
const { loading } = this.state; const { loading } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView testID='status-view' theme={theme}> <SafeAreaView testID='status-view'>
<FlatList <FlatList
data={STATUS} data={STATUS}
keyExtractor={item => item.id} keyExtractor={item => item.id}
contentContainerStyle={{ borderColor: themes[theme].separatorColor }}
renderItem={this.renderItem} renderItem={this.renderItem}
ListHeaderComponent={this.renderHeader} ListHeaderComponent={this.renderHeader}
ListFooterComponent={() => <Separator theme={theme} />} ListFooterComponent={List.Separator}
ItemSeparatorComponent={this.renderSeparator} ItemSeparatorComponent={List.Separator}
/> />
<Loading visible={loading} /> <Loading visible={loading} />
</SafeAreaView> </SafeAreaView>

View File

@ -121,11 +121,6 @@ export default StyleSheet.create({
inputLastChild: { inputLastChild: {
marginBottom: 15 marginBottom: 15
}, },
listContentContainer: {
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
marginVertical: 36
},
notchLandscapeContainer: { notchLandscapeContainer: {
marginTop: -34, marginTop: -34,
paddingHorizontal: 30 paddingHorizontal: 30

View File

@ -1,17 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {
FlatList, Text, View, StyleSheet
} from 'react-native';
import I18n from '../i18n'; import I18n from '../i18n';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from './Styles';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import Separator from '../containers/Separator'; import * as List from '../containers/List';
import ListItem from '../containers/ListItem';
import { CustomIcon } from '../lib/Icons';
import { THEME_PREFERENCES_KEY } from '../lib/rocketchat'; import { THEME_PREFERENCES_KEY } from '../lib/rocketchat';
import { supportSystemTheme } from '../utils/deviceInfo'; import { supportSystemTheme } from '../utils/deviceInfo';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
@ -39,8 +33,6 @@ const THEMES = [
}, { }, {
label: 'Dark', label: 'Dark',
value: 'dark', value: 'dark',
separator: true,
header: 'Dark_level',
group: DARK_GROUP group: DARK_GROUP
}, { }, {
label: 'Black', label: 'Black',
@ -53,20 +45,8 @@ if (supportSystemTheme()) {
THEMES.unshift(SYSTEM_THEME); THEMES.unshift(SYSTEM_THEME);
} }
const styles = StyleSheet.create({ const themeGroup = THEMES.filter(item => item.group === THEME_GROUP);
list: { const darkGroup = THEMES.filter(item => item.group === DARK_GROUP);
paddingBottom: 18
},
info: {
paddingTop: 25,
paddingBottom: 18,
paddingHorizontal: 16
},
infoText: {
fontSize: 16,
...sharedStyles.textRegular
}
});
class ThemeView extends React.Component { class ThemeView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -114,74 +94,44 @@ class ThemeView extends React.Component {
await UserPreferences.setMapAsync(THEME_PREFERENCES_KEY, newTheme); await UserPreferences.setMapAsync(THEME_PREFERENCES_KEY, newTheme);
}; };
renderSeparator = () => {
const { theme } = this.props;
return <Separator theme={theme} />;
}
renderIcon = () => { renderIcon = () => {
const { theme } = this.props; const { theme } = this.props;
return <CustomIcon name='check' size={20} color={themes[theme].tintColor} />; return <List.Icon name='check' color={themes[theme].tintColor} />;
} }
renderItem = ({ item, index }) => { renderItem = ({ item }) => {
const { theme } = this.props;
const { label, value } = item; const { label, value } = item;
const isFirst = index === 0;
return ( return (
<> <>
{item.separator || isFirst ? this.renderSectionHeader(item.header) : null} <List.Item
<ListItem title={label}
title={I18n.t(label)}
onPress={() => this.onClick(item)} onPress={() => this.onClick(item)}
testID={`theme-view-${ value }`} testID={`theme-view-${ value }`}
right={this.isSelected(item) ? this.renderIcon : null} right={this.isSelected(item) ? this.renderIcon : null}
theme={theme}
/> />
<List.Separator />
</> </>
); );
} }
renderSectionHeader = (header = 'Theme') => {
const { theme } = this.props;
return (
<>
<View style={styles.info}>
<Text style={[styles.infoText, { color: themes[theme].infoText }]}>{I18n.t(header)}</Text>
</View>
{this.renderSeparator()}
</>
);
}
renderFooter = () => {
const { theme } = this.props;
return (
<View style={[styles.info, sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]}>
<Text style={{ color: themes[theme].infoText }}>
{I18n.t('Applying_a_theme_will_change_how_the_app_looks')}
</Text>
</View>
);
}
render() { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView testID='theme-view' theme={theme}> <SafeAreaView testID='theme-view'>
<StatusBar theme={theme} /> <StatusBar />
<FlatList <List.Container>
data={THEMES} <List.Section title='Theme'>
keyExtractor={item => item.value + item.group} <List.Separator />
contentContainerStyle={[ {
styles.list, themeGroup.map(item => this.renderItem({ item }))
{ borderColor: themes[theme].separatorColor } }
]} </List.Section>
renderItem={this.renderItem} <List.Section title='Dark_level'>
ListHeaderComponent={this.renderHeader} <List.Separator />
ListFooterComponent={this.renderFooter} {
ItemSeparatorComponent={this.renderSeparator} darkGroup.map(item => this.renderItem({ item }))
/> }
</List.Section>
</List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -334,8 +334,8 @@ class ThreadMessagesView extends React.Component {
} }
return ( return (
<SafeAreaView testID='thread-messages-view' theme={theme}> <SafeAreaView testID='thread-messages-view'>
<StatusBar theme={theme} /> <StatusBar />
<FlatList <FlatList
data={messages} data={messages}
extraData={this.state} extraData={this.state}

View File

@ -1,29 +0,0 @@
import React from 'react';
import {
Text
} from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../constants/colors';
const Info = React.memo(({ info, theme }) => (
<Text
style={[
styles.infoText,
{
color: themes[theme].infoText,
backgroundColor: themes[theme].auxiliaryBackground
}
]}
>
{info}
</Text>
));
Info.propTypes = {
info: PropTypes.string,
theme: PropTypes.string
};
export default Info;

View File

@ -1,23 +0,0 @@
import React from 'react';
import {
View
} from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../constants/colors';
const SectionSeparator = React.memo(({ theme }) => (
<View
style={[
styles.sectionSeparatorBorder,
{ backgroundColor: themes[theme].auxiliaryBackground }
]}
/>
));
SectionSeparator.propTypes = {
theme: PropTypes.string
};
export default SectionSeparator;

View File

@ -1,29 +0,0 @@
import React from 'react';
import {
Text
} from 'react-native';
import PropTypes from 'prop-types';
import styles from './styles';
import { themes } from '../../constants/colors';
const SectionTitle = React.memo(({ title, theme }) => (
<Text
style={[
styles.sectionTitle,
{
backgroundColor: themes[theme].auxiliaryBackground,
color: themes[theme].infoText
}
]}
>
{title}
</Text>
));
SectionTitle.propTypes = {
title: PropTypes.string,
theme: PropTypes.string
};
export default SectionTitle;

View File

@ -1,27 +1,26 @@
import React from 'react'; import React from 'react';
import { import { Text, StyleSheet } from 'react-native';
View, ScrollView, Text
} from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ListItem from '../../containers/ListItem'; import * as List from '../../containers/List';
import Separator from '../../containers/Separator';
import I18n from '../../i18n'; import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import styles from './styles';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import SectionTitle from './SectionTitle';
import SectionSeparator from './SectionSeparator';
import Info from './Info';
import { OPTIONS } from './options'; import { OPTIONS } from './options';
import ActivityIndicator from '../../containers/ActivityIndicator'; import ActivityIndicator from '../../containers/ActivityIndicator';
import { DisclosureImage } from '../../containers/DisclosureIndicator';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import sharedStyles from '../Styles';
const styles = StyleSheet.create({
pickerText: {
...sharedStyles.textRegular,
fontSize: 16
}
});
class UserNotificationPreferencesView extends React.Component { class UserNotificationPreferencesView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -30,7 +29,6 @@ class UserNotificationPreferencesView extends React.Component {
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object,
route: PropTypes.object,
theme: PropTypes.string, theme: PropTypes.string,
user: PropTypes.shape({ user: PropTypes.shape({
id: PropTypes.string id: PropTypes.string
@ -90,72 +88,55 @@ class UserNotificationPreferencesView extends React.Component {
this.setState({ preferences: settings.preferences }); this.setState({ preferences: settings.preferences });
} }
renderDisclosure = () => {
const { theme } = this.props;
return <DisclosureImage theme={theme} />;
}
render() { render() {
const { theme } = this.props; const { theme } = this.props;
const { loading } = this.state; const { loading } = this.state;
return ( return (
<SafeAreaView testID='user-notification-preference-view' theme={theme}> <SafeAreaView testID='user-notification-preference-view'>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container>
{...scrollPersistTaps}
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
contentContainerStyle={styles.contentContainer}
testID='user-notification-preference-view-list'
>
{loading {loading
? ( ? (
<> <>
<SectionSeparator theme={theme} /> <List.Section title='Desktop_Notifications'>
<SectionTitle title={I18n.t('DESKTOP_NOTIFICATIONS')} theme={theme} /> <List.Separator />
<List.Item
<ListItem title='Alert'
title={I18n.t('Alert')}
testID='user-notification-preference-view-alert' testID='user-notification-preference-view-alert'
onPress={title => this.pickerSelection(title, 'desktopNotifications')} onPress={title => this.pickerSelection(title, 'desktopNotifications')}
right={() => this.renderPickerOption('desktopNotifications')} right={() => this.renderPickerOption('desktopNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('Desktop_Alert_info')} theme={theme} /> <List.Info info='Desktop_Alert_info' />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section title='Push_Notifications'>
<SectionTitle title={I18n.t('PUSH_NOTIFICATIONS')} theme={theme} /> <List.Separator />
<Separator theme={theme} /> <List.Item
title='Alert'
<ListItem
title={I18n.t('Alert')}
testID='user-notification-preference-view-push-notification' testID='user-notification-preference-view-push-notification'
onPress={title => this.pickerSelection(title, 'mobileNotifications')} onPress={title => this.pickerSelection(title, 'mobileNotifications')}
right={() => this.renderPickerOption('mobileNotifications')} right={() => this.renderPickerOption('mobileNotifications')}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
<Info info={I18n.t('Push_Notifications_Alert_Info')} theme={theme} /> <List.Info info='Push_Notifications_Alert_Info' />
</List.Section>
<SectionSeparator theme={theme} /> <List.Section title='Email'>
<SectionTitle title={I18n.t('EMAIL')} theme={theme} /> <List.Separator />
<Separator theme={theme} /> <List.Item
title='Alert'
<ListItem
title={I18n.t('Alert')}
testID='user-notification-preference-view-email-alert' testID='user-notification-preference-view-email-alert'
onPress={title => this.pickerSelection(title, 'emailNotificationMode')} onPress={title => this.pickerSelection(title, 'emailNotificationMode')}
right={() => this.renderPickerOption('emailNotificationMode')} right={() => this.renderPickerOption('emailNotificationMode')}
theme={theme}
/> />
<List.Separator />
<Separator theme={theme} /> <List.Info info='You_need_to_verifiy_your_email_address_to_get_notications' />
<Info info={I18n.t('You_need_to_verifiy_your_email_address_to_get_notications')} theme={theme} /> </List.Section>
</> </>
) : <ActivityIndicator theme={theme} /> ) : <ActivityIndicator theme={theme} />
} }
<View style={[styles.marginBottom, { backgroundColor: themes[theme].auxiliaryBackground }]} /> </List.Container>
</ScrollView>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -1,31 +0,0 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../Styles';
export default StyleSheet.create({
sectionSeparatorBorder: {
height: 10
},
marginBottom: {
height: 30
},
contentContainer: {
marginVertical: 10
},
infoText: {
...sharedStyles.textRegular,
fontSize: 13,
paddingHorizontal: 15,
paddingVertical: 10
},
sectionTitle: {
...sharedStyles.separatorBottom,
paddingHorizontal: 15,
paddingVertical: 10,
fontSize: 14
},
pickerText: {
...sharedStyles.textRegular,
fontSize: 16
}
});

View File

@ -1,18 +1,13 @@
import React from 'react'; import React from 'react';
import { ScrollView } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { import {
logEvent, events logEvent, events
} from '../../utils/log'; } from '../../utils/log';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import Separator from '../../containers/Separator';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import ListItem from '../../containers/ListItem'; import * as List from '../../containers/List';
import { DisclosureImage } from '../../containers/DisclosureIndicator';
import { withTheme } from '../../theme';
class UserPreferencesView extends React.Component { class UserPreferencesView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -20,13 +15,7 @@ class UserPreferencesView extends React.Component {
}); });
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object
theme: PropTypes.string
}
renderDisclosure = () => {
const { theme } = this.props;
return <DisclosureImage theme={theme} />;
} }
navigateToScreen = (screen, params) => { navigateToScreen = (screen, params) => {
@ -36,30 +25,24 @@ class UserPreferencesView extends React.Component {
} }
render() { render() {
const { theme } = this.props;
return ( return (
<SafeAreaView testID='preferences-view' theme={theme}> <SafeAreaView testID='preferences-view'>
<StatusBar theme={theme} /> <StatusBar />
<ScrollView <List.Container>
{...scrollPersistTaps} <List.Section>
contentContainerStyle={{ paddingVertical: 36 }} <List.Separator />
showsVerticalScrollIndicator={false} <List.Item
testID='preferences-view-list'
>
<ListItem
title={I18n.t('Notifications')} title={I18n.t('Notifications')}
onPress={() => this.navigateToScreen('UserNotificationPrefView')} onPress={() => this.navigateToScreen('UserNotificationPrefView')}
showActionIndicator showActionIndicator
testID='preferences-view-notifications' testID='preferences-view-notifications'
right={this.renderDisclosure}
theme={theme}
/> />
<Separator theme={theme} /> <List.Separator />
</ScrollView> </List.Section>
</List.Container>
</SafeAreaView> </SafeAreaView>
); );
} }
} }
export default withTheme(UserPreferencesView); export default UserPreferencesView;

View File

@ -5,12 +5,12 @@ import PropTypes from 'prop-types';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import Separator from '../containers/Separator';
import openLink from '../utils/openLink'; import openLink from '../utils/openLink';
import I18n from '../i18n'; import I18n from '../i18n';
import debounce from '../utils/debounce'; import debounce from '../utils/debounce';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
import ListItem from '../containers/ListItem'; import * as List from '../containers/List';
import SafeAreaView from '../containers/SafeAreaView';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
noResult: { noResult: {
@ -18,23 +18,18 @@ const styles = StyleSheet.create({
paddingVertical: 56, paddingVertical: 56,
...sharedStyles.textAlignCenter, ...sharedStyles.textAlignCenter,
...sharedStyles.textSemibold ...sharedStyles.textSemibold
},
withoutBorder: {
borderBottomWidth: 0,
borderTopWidth: 0
} }
}); });
const Item = ({ item, theme }) => ( const Item = ({ item }) => (
<ListItem <List.Item
title={item.navigation?.page?.title || I18n.t('Empty_title')} title={item.navigation?.page?.title || I18n.t('Empty_title')}
onPress={() => openLink(item.navigation?.page?.location?.href)} onPress={() => openLink(item.navigation?.page?.location?.href)}
theme={theme} translateTitle={false}
/> />
); );
Item.propTypes = { Item.propTypes = {
item: PropTypes.object, item: PropTypes.object
theme: PropTypes.string
}; };
const VisitorNavigationView = ({ route, theme }) => { const VisitorNavigationView = ({ route, theme }) => {
@ -67,24 +62,20 @@ const VisitorNavigationView = ({ route, theme }) => {
}, 300); }, 300);
return ( return (
<SafeAreaView>
<FlatList <FlatList
data={pages} data={pages}
renderItem={({ item }) => <Item item={item} theme={theme} />} renderItem={({ item }) => <Item item={item} theme={theme} />}
ItemSeparatorComponent={() => <Separator theme={theme} />} ItemSeparatorComponent={List.Separator}
contentContainerStyle={[ ListFooterComponent={List.Separator}
sharedStyles.listContentContainer, ListHeaderComponent={List.Separator}
{ contentContainerStyle={List.styles.contentContainerStyleFlatList}
backgroundColor: themes[theme].auxiliaryBackground,
borderColor: themes[theme].separatorColor
},
!pages.length && styles.withoutBorder
]}
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>} ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>}
keyExtractor={item => item} keyExtractor={item => item}
onEndReached={onEndReached} onEndReached={onEndReached}
onEndReachedThreshold={5} onEndReachedThreshold={5}
/> />
</SafeAreaView>
); );
}; };
VisitorNavigationView.propTypes = { VisitorNavigationView.propTypes = {

View File

@ -37,7 +37,7 @@ describe('Status screen', () => {
describe('Usage', async () => { describe('Usage', async () => {
it('should change status', async () => { it('should change status', async () => {
await element(by.id('status-view-busy')).tap(); await element(by.id('status-view-busy')).tap();
await expect(element(by.id('status-view-current-busy'))).toExist(); await waitFor(element(by.id('status-view-current-busy'))).toExist().withTimeout(2000);
}); });
it('should change status text', async () => { it('should change status text', async () => {

View File

@ -15,7 +15,7 @@ async function navigateToRoomInfo(type) {
} }
await searchRoom(room); await searchRoom(room);
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 }`)).atIndex(0).tap();
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
await element(by.id('room-view-header-actions')).tap(); await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);

View File

@ -5,7 +5,7 @@ import RNBootSplash from 'react-native-bootsplash';
import 'react-native-gesture-handler'; import 'react-native-gesture-handler';
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
jest.mock('react-native/Libraries/Components/Touchable/TouchableOpacity', () => jest.fn(() => null)); // jest.mock('react-native/Libraries/Components/Touchable/TouchableOpacity', () => jest.fn(() => null));
RNBootSplash.hide(); RNBootSplash.hide();

196
storybook/stories/List.js Normal file
View File

@ -0,0 +1,196 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types */
import React from 'react';
import { FlatList } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import * as List from '../../app/containers/List';
import SafeAreaView from '../../app/containers/SafeAreaView';
import { longText } from '../utils';
import { ThemeContext } from '../../app/theme';
import { DimensionsContext } from '../../app/dimensions';
const stories = storiesOf('List', module);
stories.add('title and subtitle', () => (
<List.Container>
<List.Separator />
<List.Item title='Chats' />
<List.Separator />
<List.Item title='Chats' subtitle='All' />
<List.Separator />
<List.Item title={longText} subtitle={longText} translateTitle={false} translateSubtitle={false} testID='test-id' />
<List.Separator />
</List.Container>
));
stories.add('pressable', () => (
<List.Container>
<List.Separator />
<List.Item title='Press me' onPress={() => alert('Hi there!')} translateTitle={false} />
<List.Separator />
<List.Item title={'I\'m disabled'} onPress={() => alert('Hi there!')} disabled translateTitle={false} />
<List.Separator />
</List.Container>
));
stories.add('header', () => (
<List.Container>
<List.Header title='Chats' />
<List.Header title={longText} translateTitle={false} />
</List.Container>
));
stories.add('icon', () => (
<List.Container>
<List.Icon name='emoji' />
</List.Container>
));
stories.add('separator', () => (
<List.Container>
<List.Separator />
</List.Container>
));
stories.add('with section and info', () => (
<SafeAreaView>
<List.Container>
<List.Section>
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
</List.Section>
<List.Section>
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
</List.Section>
<List.Section title='Chats'>
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
<List.Info info='Chats' />
</List.Section>
<List.Section title={longText} translateTitle={false}>
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
<List.Item title='Section Item' translateTitle={false} />
<List.Separator />
<List.Info info={longText} translateInfo={false} />
</List.Section>
</List.Container>
</SafeAreaView>
));
stories.add('with icon', () => (
<List.Container>
<List.Separator />
<List.Item title='Icon Left' translateTitle={false} left={() => <List.Icon name='emoji' />} />
<List.Separator />
<List.Item title='Icon Right' translateTitle={false} right={() => <List.Icon name='emoji' />} />
<List.Separator />
<List.Item
title={longText}
subtitle={longText}
translateTitle={false}
translateSubtitle={false}
left={() => <List.Icon name='emoji' />}
right={() => <List.Icon name='emoji' />}
/>
<List.Separator />
<List.Item title='Show Action Indicator' translateTitle={false} showActionIndicator />
<List.Separator />
</List.Container>
));
stories.add('with custom color', () => (
<List.Container>
<List.Separator />
<List.Item title='Chats' color='red' />
<List.Separator />
</List.Container>
));
const ListItemFull = ({ ...props }) => (
<List.Item
title='Chats'
subtitle='All'
onPress={() => alert('Hi')}
left={() => <List.Icon name='emoji' />}
right={() => <List.Icon name='emoji' />}
{...props}
/>
);
const ListFull = () => (
<SafeAreaView>
<List.Container>
<List.Section title='Chats'>
<List.Separator />
<ListItemFull />
<List.Separator />
<ListItemFull disabled />
<List.Separator />
<List.Info info='Chats' />
</List.Section>
<List.Section title='Chats'>
<List.Separator />
<ListItemFull />
<List.Separator />
<ListItemFull disabled />
<List.Separator />
<List.Info info='Chats' />
</List.Section>
</List.Container>
</SafeAreaView>
);
const ThemeStory = ({ theme }) => (
<ThemeContext.Provider
value={{ theme }}
>
<ListFull />
</ThemeContext.Provider>
);
stories.add('with dark theme', () => <ThemeStory theme='dark' />);
stories.add('with black theme', () => <ThemeStory theme='black' />);
const FontStory = ({ fontScale }) => (
<DimensionsContext.Provider
value={{ fontScale }}
>
<ListFull />
</DimensionsContext.Provider>
);
/**
* It's going to test height only.
* Font scale on text and icons is applied based on OS setting
*/
stories.add('with small font', () => <FontStory fontScale={0.8} />);
stories.add('with bigger font', () => <FontStory fontScale={1.5} />);
stories.add('with FlatList', () => (
<SafeAreaView>
<FlatList
data={[...Array(30).keys()]}
contentContainerStyle={List.styles.contentContainerStyleFlatList}
renderItem={({ item }) => <List.Item title={item} translateTitle={false} />}
ListHeaderComponent={List.Separator}
ListFooterComponent={List.Separator}
ItemSeparatorComponent={List.Separator}
keyExtractor={item => item}
/>
</SafeAreaView>
));

Some files were not shown because too many files have changed in this diff Show More