Merge branch 'develop' into update.enable-multiline-android-tablets

This commit is contained in:
Gerzon Z 2022-03-28 11:28:26 -04:00 committed by GitHub
commit ac61087940
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 296 additions and 277 deletions

View File

@ -5,15 +5,15 @@ import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps'; import scrollPersistTaps from '../utils/scrollPersistTaps';
import KeyboardView from '../presentation/KeyboardView'; import KeyboardView from '../presentation/KeyboardView';
import { useTheme } from '../theme';
import StatusBar from './StatusBar'; import StatusBar from './StatusBar';
import AppVersion from './AppVersion'; import AppVersion from './AppVersion';
import { isTablet } from '../utils/deviceInfo'; import { isTablet } from '../utils/deviceInfo';
import SafeAreaView from './SafeAreaView'; import SafeAreaView from './SafeAreaView';
interface IFormContainer extends ScrollViewProps { interface IFormContainer extends ScrollViewProps {
theme: string;
testID: string; testID: string;
children: React.ReactNode; children: React.ReactElement | React.ReactElement[] | null;
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -22,27 +22,31 @@ const styles = StyleSheet.create({
} }
}); });
export const FormContainerInner = ({ children }: { children: React.ReactNode }): JSX.Element => ( export const FormContainerInner = ({ children }: { children: (React.ReactElement | null)[] }) => (
<View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}>{children}</View> <View style={[sharedStyles.container, isTablet && sharedStyles.tabletScreenContent]}>{children}</View>
); );
const FormContainer = ({ children, theme, testID, ...props }: IFormContainer): JSX.Element => ( const FormContainer = ({ children, testID, ...props }: IFormContainer) => {
<KeyboardView const { theme } = useTheme();
style={{ backgroundColor: themes[theme].backgroundColor }}
contentContainerStyle={sharedStyles.container} return (
keyboardVerticalOffset={128}> <KeyboardView
<StatusBar /> style={{ backgroundColor: themes[theme].backgroundColor }}
<ScrollView contentContainerStyle={sharedStyles.container}
style={sharedStyles.container} keyboardVerticalOffset={128}>
contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]} <StatusBar />
{...scrollPersistTaps} <ScrollView
{...props}> style={sharedStyles.container}
<SafeAreaView testID={testID} style={{ backgroundColor: themes[theme].backgroundColor }}> contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}
{children} {...scrollPersistTaps}
<AppVersion theme={theme} /> {...props}>
</SafeAreaView> <SafeAreaView testID={testID} style={{ backgroundColor: themes[theme].backgroundColor }}>
</ScrollView> {children}
</KeyboardView> <AppVersion theme={theme} />
); </SafeAreaView>
</ScrollView>
</KeyboardView>
);
};
export default FormContainer; export default FormContainer;

View File

@ -11,7 +11,7 @@ const styles = StyleSheet.create({
}); });
interface IListContainer { interface IListContainer {
children: React.ReactNode; children: (React.ReactElement | null)[] | React.ReactElement | null;
testID?: string; testID?: string;
} }
const ListContainer = React.memo(({ children, ...props }: IListContainer) => ( const ListContainer = React.memo(({ children, ...props }: IListContainer) => (

View File

@ -25,6 +25,7 @@ interface IListHeader {
const ListHeader = React.memo(({ title, translateTitle = true }: IListHeader) => { const ListHeader = React.memo(({ title, translateTitle = true }: IListHeader) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={[styles.title, { color: themes[theme].infoText }]} numberOfLines={1}> <Text style={[styles.title, { color: themes[theme].infoText }]} numberOfLines={1}>

View File

@ -3,11 +3,10 @@ import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { withTheme } from '../../theme'; import { useTheme } from '../../theme';
import { ICON_SIZE } from './constants'; import { ICON_SIZE } from './constants';
interface IListIcon { interface IListIcon {
theme?: string;
name: string; name: string;
color?: string; color?: string;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
@ -21,12 +20,16 @@ const styles = StyleSheet.create({
} }
}); });
const ListIcon = React.memo(({ theme, name, color, style, testID }: IListIcon) => ( const ListIcon = React.memo(({ name, color, style, testID }: IListIcon) => {
<View style={[styles.icon, style]}> const { theme } = useTheme();
<CustomIcon name={name} color={color ?? themes[theme!].auxiliaryText} size={ICON_SIZE} testID={testID} />
</View> return (
)); <View style={[styles.icon, style]}>
<CustomIcon name={name} color={color ?? themes[theme].auxiliaryText} size={ICON_SIZE} testID={testID} />
</View>
);
});
ListIcon.displayName = 'List.Icon'; ListIcon.displayName = 'List.Icon';
export default withTheme(ListIcon); export default ListIcon;

View File

@ -4,11 +4,11 @@ import { I18nManager, StyleSheet, Text, View } from 'react-native';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { withTheme } from '../../theme'; import { useTheme } from '../../theme';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { Icon } from '.'; import { Icon } from '.';
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants'; import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
import { withDimensions } from '../../dimensions'; import { useDimensions } from '../../dimensions';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -59,13 +59,12 @@ interface IListItemContent {
left?: () => JSX.Element | null; left?: () => JSX.Element | null;
right?: () => JSX.Element | null; right?: () => JSX.Element | null;
disabled?: boolean; disabled?: boolean;
theme: string;
testID?: string; testID?: string;
theme?: string;
color?: string; color?: string;
translateTitle?: boolean; translateTitle?: boolean;
translateSubtitle?: boolean; translateSubtitle?: boolean;
showActionIndicator?: boolean; showActionIndicator?: boolean;
fontScale?: number;
alert?: boolean; alert?: boolean;
} }
@ -78,78 +77,85 @@ const Content = React.memo(
left, left,
right, right,
color, color,
theme,
fontScale,
alert, alert,
translateTitle = true, translateTitle = true,
translateSubtitle = true, translateSubtitle = true,
showActionIndicator = false showActionIndicator = false,
}: IListItemContent) => ( theme
<View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale! }]} testID={testID}> }: IListItemContent) => {
{left ? <View style={styles.leftContainer}>{left()}</View> : null} const { fontScale } = useDimensions();
<View style={styles.textContainer}>
<View style={styles.textAlertContainer}> return (
<Text style={[styles.title, { color: color || themes[theme!].titleText }]} numberOfLines={1}> <View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}>
{translateTitle ? I18n.t(title) : title} {left ? <View style={styles.leftContainer}>{left()}</View> : null}
</Text> <View style={styles.textContainer}>
{alert ? ( <View style={styles.textAlertContainer}>
<CustomIcon style={[styles.alertIcon, { color: themes[theme!].dangerColor }]} size={ICON_SIZE} name='info' /> <Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>
{translateTitle ? I18n.t(title) : title}
</Text>
{alert ? (
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
) : null}
</View>
{subtitle ? (
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
{translateSubtitle ? I18n.t(subtitle) : subtitle}
</Text>
) : null} ) : null}
</View> </View>
{subtitle ? ( {right || showActionIndicator ? (
<Text style={[styles.subtitle, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}> <View style={styles.rightContainer}>
{translateSubtitle ? I18n.t(subtitle) : subtitle} {right ? right() : null}
</Text> {showActionIndicator ? <Icon name='chevron-right' style={styles.actionIndicator} /> : null}
</View>
) : null} ) : null}
</View> </View>
{right || showActionIndicator ? ( );
<View style={styles.rightContainer}> }
{right ? right() : null}
{showActionIndicator ? <Icon name='chevron-right' style={styles.actionIndicator} /> : null}
</View>
) : null}
</View>
)
); );
interface IListButtonPress { interface IListButtonPress extends IListItemButton {
onPress?: Function; onPress: Function;
} }
interface IListItemButton extends IListButtonPress { interface IListItemButton {
title?: string; title?: string;
disabled?: boolean; disabled?: boolean;
theme?: string; theme: string;
backgroundColor?: string; backgroundColor?: string;
underlayColor?: string; underlayColor?: string;
} }
const Button = React.memo<IListItemButton>(({ onPress, backgroundColor, underlayColor, ...props }: IListItemButton) => ( const Button = React.memo(({ onPress, backgroundColor, underlayColor, ...props }: IListButtonPress) => (
<Touch <Touch
onPress={() => onPress!(props.title)} onPress={() => onPress(props.title)}
style={{ backgroundColor: backgroundColor || themes[props.theme!].backgroundColor }} style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }}
underlayColor={underlayColor} underlayColor={underlayColor}
enabled={!props.disabled} enabled={!props.disabled}
theme={props.theme!}> theme={props.theme}>
<Content {...props} /> <Content {...props} />
</Touch> </Touch>
)); ));
interface IListItem extends IListItemContent, IListItemButton { interface IListItem extends Omit<IListItemContent, 'theme'>, Omit<IListItemButton, 'theme'> {
backgroundColor?: string; backgroundColor?: string;
onPress?: Function;
} }
const ListItem = React.memo<IListItem>(({ ...props }: IListItem) => { const ListItem = React.memo(({ ...props }: IListItem) => {
const { theme } = useTheme();
if (props.onPress) { if (props.onPress) {
return <Button {...props} />; const { onPress } = props;
return <Button {...props} theme={theme} onPress={onPress} />;
} }
return ( return (
<View style={{ backgroundColor: props.backgroundColor || themes[props.theme!].backgroundColor }}> <View style={{ backgroundColor: props.backgroundColor || themes[theme].backgroundColor }}>
<Content {...props} /> <Content {...props} theme={theme} />
</View> </View>
); );
}); });
ListItem.displayName = 'List.Item'; ListItem.displayName = 'List.Item';
export default withTheme(withDimensions(ListItem)); export default ListItem;

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { withTheme } from '../../theme';
import { Header } from '.'; import { Header } from '.';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -11,7 +10,7 @@ const styles = StyleSheet.create({
}); });
interface IListSection { interface IListSection {
children: React.ReactNode; children: (React.ReactElement | null)[] | React.ReactElement | null;
title?: string; title?: string;
translateTitle?: boolean; translateTitle?: boolean;
} }
@ -25,4 +24,4 @@ const ListSection = React.memo(({ children, title, translateTitle }: IListSectio
ListSection.displayName = 'List.Section'; ListSection.displayName = 'List.Section';
export default withTheme(ListSection); export default ListSection;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { StyleSheet, View, ViewStyle } from 'react-native'; import { StyleSheet, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { withTheme } from '../../theme'; import { useTheme } from '../../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
separator: { separator: {
@ -12,13 +12,14 @@ const styles = StyleSheet.create({
interface IListSeparator { interface IListSeparator {
style?: ViewStyle; style?: ViewStyle;
theme?: string;
} }
const ListSeparator = React.memo(({ style, theme }: IListSeparator) => ( const ListSeparator = React.memo(({ style }: IListSeparator) => {
<View style={[styles.separator, style, { backgroundColor: themes[theme!].separatorColor }]} /> const { theme } = useTheme();
));
return <View style={[styles.separator, style, { backgroundColor: themes[theme].separatorColor }]} />;
});
ListSeparator.displayName = 'List.Separator'; ListSeparator.displayName = 'List.Separator';
export default withTheme(ListSeparator); export default ListSeparator;

View File

@ -7,6 +7,7 @@ import { themes } from '../../constants/colors';
import { MarkdownPreview } from '../markdown'; import { MarkdownPreview } from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon'; import RoomTypeIcon from '../RoomTypeIcon';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { TUserStatus } from '../../definitions';
const HIT_SLOP = { const HIT_SLOP = {
top: 5, top: 5,
@ -67,7 +68,7 @@ interface IRoomHeader {
prid: string; prid: string;
tmid: string; tmid: string;
teamMain: boolean; teamMain: boolean;
status: string; status: TUserStatus;
theme?: string; theme?: string;
usersTyping: []; usersTyping: [];
isGroupChat: boolean; isGroupChat: boolean;

View File

@ -2,7 +2,7 @@ import { dequal } from 'dequal';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { IApplicationState } from '../../definitions'; import { IApplicationState, TUserStatus } from '../../definitions';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
import I18n from '../../i18n'; import I18n from '../../i18n';
import RoomHeader from './RoomHeader'; import RoomHeader from './RoomHeader';
@ -15,7 +15,7 @@ interface IRoomHeaderContainerProps {
tmid: string; tmid: string;
teamMain: boolean; teamMain: boolean;
usersTyping: []; usersTyping: [];
status: string; status: TUserStatus;
statusText: string; statusText: string;
connecting: boolean; connecting: boolean;
connected: boolean; connected: boolean;
@ -140,7 +140,7 @@ const mapStateToProps = (state: IApplicationState, ownProps: any) => {
connecting: state.meteor.connecting || state.server.loading, connecting: state.meteor.connecting || state.server.loading,
connected: state.meteor.connected, connected: state.meteor.connected,
usersTyping: state.usersTyping, usersTyping: state.usersTyping,
status, status: status as TUserStatus,
statusText statusText
}; };
}; };

View File

@ -5,6 +5,7 @@ import { CustomIcon } from '../lib/Icons';
import { STATUS_COLORS, themes } from '../constants/colors'; import { STATUS_COLORS, themes } from '../constants/colors';
import Status from './Status/Status'; import Status from './Status/Status';
import { withTheme } from '../theme'; import { withTheme } from '../theme';
import { TUserStatus } from '../definitions';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
icon: { icon: {
@ -17,7 +18,7 @@ interface IRoomTypeIcon {
type: string; type: string;
isGroupChat?: boolean; isGroupChat?: boolean;
teamMain?: boolean; teamMain?: boolean;
status?: string; status?: TUserStatus;
size?: number; size?: number;
style?: ViewStyle; style?: ViewStyle;
} }
@ -31,9 +32,10 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
const iconStyle = [styles.icon, { color }, style]; const iconStyle = [styles.icon, { color }, style];
if (type === 'd' && !isGroupChat) { if (type === 'd' && !isGroupChat) {
return ( if (!status) {
<Status style={[iconStyle, { color: STATUS_COLORS[status!] ?? STATUS_COLORS.offline }]} size={size} status={status!} /> status = 'offline';
); }
return <Status style={[iconStyle, { color: STATUS_COLORS[status] }]} size={size} status={status} />;
} }
// TODO: move this to a separate function // TODO: move this to a separate function

View File

@ -3,15 +3,9 @@ import { StyleProp, TextStyle } from 'react-native';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { STATUS_COLORS } from '../../constants/colors'; import { STATUS_COLORS } from '../../constants/colors';
import { IStatus } from './definition';
interface IStatus { const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: Omit<IStatus, 'id'>) => {
status: string;
size: number;
style?: StyleProp<TextStyle>;
testID?: string;
}
const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => {
const name = `status-${status}`; const name = `status-${status}`;
const isNameValid = CustomIcon.hasIcon(name); const isNameValid = CustomIcon.hasIcon(name);
const iconName = isNameValid ? name : 'status-offline'; const iconName = isNameValid ? name : 'status-offline';

View File

@ -0,0 +1,9 @@
import { TextProps } from 'react-native';
import { TUserStatus } from '../../definitions';
export interface IStatus extends TextProps {
id: string;
size: number;
status: TUserStatus;
}

View File

@ -1,20 +1,15 @@
import React, { memo } from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { useSelector } from 'react-redux';
import { IApplicationState, TUserStatus } from '../../definitions';
import Status from './Status'; import Status from './Status';
import { IStatus } from './definition';
interface IStatusContainer { const StatusContainer = ({ id, style, size = 32, ...props }: Omit<IStatus, 'status'>): React.ReactElement => {
style: any; const status = useSelector((state: IApplicationState) =>
size: number; state.meteor.connected ? state.activeUsers[id] && state.activeUsers[id].status : 'loading'
status: string; ) as TUserStatus;
} return <Status size={size} style={style} status={status} {...props} />;
};
const StatusContainer = memo(({ style, size = 32, status }: IStatusContainer) => ( export default StatusContainer;
<Status size={size} style={style} status={status} />
));
const mapStateToProps = (state: any, ownProps: any) => ({
status: state.meteor.connected ? state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status : 'loading'
});
export default connect(mapStateToProps)(StatusContainer);

View File

@ -1,18 +1,19 @@
import React from 'react'; import React from 'react';
import { Text } from 'react-native'; import { StyleProp, Text, TextStyle } from 'react-native';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
import { events, logEvent } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
import { IUserMention } from './interfaces';
interface IAtMention { interface IAtMention {
mention: string; mention: string;
username?: string; username?: string;
navToRoomInfo?: Function; navToRoomInfo?: Function;
style?: any; style?: StyleProp<TextStyle>[];
useRealName?: boolean; useRealName?: boolean;
mentions: any; mentions?: IUserMention[];
} }
const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => { const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName }: IAtMention) => {
@ -23,7 +24,7 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
style={[ style={[
styles.mention, styles.mention,
{ {
color: themes[theme!].mentionGroupColor color: themes[theme].mentionGroupColor
}, },
...style ...style
]}> ]}>
@ -35,11 +36,11 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
let mentionStyle = {}; let mentionStyle = {};
if (mention === username) { if (mention === username) {
mentionStyle = { mentionStyle = {
color: themes[theme!].mentionMeColor color: themes[theme].mentionMeColor
}; };
} else { } else {
mentionStyle = { mentionStyle = {
color: themes[theme!].mentionOtherColor color: themes[theme].mentionOtherColor
}; };
} }
@ -64,7 +65,7 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
); );
} }
return <Text style={[styles.text, { color: themes[theme!].bodyText }, ...style]}>{`@${mention}`}</Text>; return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`@${mention}`}</Text>;
}); });
export default AtMention; export default AtMention;

View File

@ -5,7 +5,7 @@ import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
interface IBlockQuote { interface IBlockQuote {
children: JSX.Element; children: React.ReactElement | null;
theme: string; theme: string;
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text, TextStyle, StyleProp } from 'react-native'; import { StyleProp, Text, TextStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { useTheme } from '../../theme'; import { useTheme } from '../../theme';
@ -33,7 +33,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
style={[ style={[
styles.mention, styles.mention,
{ {
color: themes[theme!].mentionOtherColor color: themes[theme].mentionOtherColor
}, },
...style ...style
]} ]}
@ -42,7 +42,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
</Text> </Text>
); );
} }
return <Text style={[styles.text, { color: themes[theme!].bodyText }, ...style]}>{`#${hashtag}`}</Text>; return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`#${hashtag}`}</Text>;
}); });
export default Hashtag; export default Hashtag;

View File

@ -10,7 +10,7 @@ import openLink from '../../utils/openLink';
import { TOnLinkPress } from './interfaces'; import { TOnLinkPress } from './interfaces';
interface ILink { interface ILink {
children: JSX.Element; children: React.ReactElement | null;
link: string; link: string;
theme: string; theme: string;
onLinkPress?: TOnLinkPress; onLinkPress?: TOnLinkPress;

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
interface IList { interface IList {
children: JSX.Element; children: React.ReactElement[] | null;
ordered: boolean; ordered: boolean;
start: number; start: number;
tight: boolean; tight: boolean;
@ -11,9 +11,8 @@ interface IList {
const List = React.memo(({ children, ordered, tight, start = 1, numberOfLines = 0 }: IList) => { const List = React.memo(({ children, ordered, tight, start = 1, numberOfLines = 0 }: IList) => {
let bulletWidth = 15; let bulletWidth = 15;
if (ordered) { if (ordered && children) {
// @ts-ignore const lastNumber = start + children?.length - 1;
const lastNumber = start + children.length - 1;
bulletWidth = 9 * lastNumber.toString().length + 7; bulletWidth = 9 * lastNumber.toString().length + 7;
} }

View File

@ -18,7 +18,7 @@ const style = StyleSheet.create({
}); });
interface IListItem { interface IListItem {
children: JSX.Element; children: React.ReactElement | null;
bulletWidth: number; bulletWidth: number;
level: number; level: number;
ordered: boolean; ordered: boolean;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { StyleProp, Text, TextStyle } from 'react-native'; import { Text, TextStyle } from 'react-native';
import removeMarkdown from 'remove-markdown'; import removeMarkdown from 'remove-markdown';
import shortnameToUnicode from '../../utils/shortnameToUnicode'; import shortnameToUnicode from '../../utils/shortnameToUnicode';
@ -13,10 +13,10 @@ interface IMarkdownPreview {
msg?: string; msg?: string;
numberOfLines?: number; numberOfLines?: number;
testID?: string; testID?: string;
style?: StyleProp<TextStyle>[]; style?: TextStyle[];
} }
const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview): React.ReactElement | null => { const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview) => {
if (!msg) { if (!msg) {
return null; return null;
} }

View File

@ -8,7 +8,7 @@ import I18n from '../../i18n';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
interface ITable { interface ITable {
children: JSX.Element; children: React.ReactElement | null;
numColumns: number; numColumns: number;
theme: string; theme: string;
} }

View File

@ -6,7 +6,7 @@ import styles from './styles';
interface ITableCell { interface ITableCell {
align: '' | 'left' | 'center' | 'right'; align: '' | 'left' | 'center' | 'right';
children: JSX.Element; children: React.ReactElement | null;
isLastCell: boolean; isLastCell: boolean;
theme: string; theme: string;
} }

View File

@ -5,7 +5,7 @@ import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
interface ITableRow { interface ITableRow {
children: JSX.Element; children: React.ReactElement | null;
isLastRow: boolean; isLastRow: boolean;
theme: string; theme: string;
} }
@ -16,7 +16,7 @@ const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRo
rowStyle.push(styles.rowBottomBorder); rowStyle.push(styles.rowBottomBorder);
} }
const children: any = React.Children.toArray(_children); const children = React.Children.toArray(_children) as React.ReactElement[];
children[children.length - 1] = React.cloneElement(children[children.length - 1], { children[children.length - 1] = React.cloneElement(children[children.length - 1], {
isLastCell: true isLastCell: true
}); });

View File

@ -2,6 +2,7 @@ export interface IUserMention {
_id: string; _id: string;
username: string; username: string;
name?: string; name?: string;
type?: string;
} }
export interface IUserChannel { export interface IUserChannel {

View File

@ -14,7 +14,7 @@ const styles = StyleSheet.create({
} }
}); });
const BigEmoji = ({ value }: IBigEmojiProps): JSX.Element => ( const BigEmoji = ({ value }: IBigEmojiProps) => (
<View style={styles.container}> <View style={styles.container}>
{value.map(block => ( {value.map(block => (
<Emoji value={block.value} isBigEmoji /> <Emoji value={block.value} isBigEmoji />

View File

@ -18,7 +18,7 @@ const styles = StyleSheet.create({
} }
}); });
const Bold = ({ value }: IBoldProps): JSX.Element => ( const Bold = ({ value }: IBoldProps) => (
<Text style={styles.text}> <Text style={styles.text}>
{value.map(block => { {value.map(block => {
switch (block.type) { switch (block.type) {

View File

@ -11,7 +11,7 @@ interface ICodeProps {
value: CodeProps['value']; value: CodeProps['value'];
} }
const Code = ({ value }: ICodeProps): JSX.Element => { const Code = ({ value }: ICodeProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
@ -19,9 +19,9 @@ const Code = ({ value }: ICodeProps): JSX.Element => {
style={[ style={[
styles.codeBlock, styles.codeBlock,
{ {
color: themes[theme!].bodyText, color: themes[theme].bodyText,
backgroundColor: themes[theme!].bannerBackground, backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme!].borderColor borderColor: themes[theme].borderColor
} }
]}> ]}>
{value.map(block => { {value.map(block => {

View File

@ -6,7 +6,7 @@ interface ICodeLineProps {
value: CodeLineProps['value']; value: CodeLineProps['value'];
} }
const CodeLine = ({ value }: ICodeLineProps): JSX.Element | null => { const CodeLine = ({ value }: ICodeLineProps) => {
if (value.type !== 'PLAIN_TEXT') { if (value.type !== 'PLAIN_TEXT') {
return null; return null;
} }

View File

@ -14,7 +14,7 @@ interface IEmojiProps {
isBigEmoji?: boolean; isBigEmoji?: boolean;
} }
const Emoji = ({ value, isBigEmoji }: IEmojiProps): JSX.Element => { const Emoji = ({ value, isBigEmoji }: IEmojiProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext); const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
const emojiUnicode = shortnameToUnicode(`:${value.value}:`); const emojiUnicode = shortnameToUnicode(`:${value.value}:`);
@ -23,7 +23,7 @@ const Emoji = ({ value, isBigEmoji }: IEmojiProps): JSX.Element => {
if (emoji) { if (emoji) {
return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />; return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
} }
return <Text style={[{ color: themes[theme!].bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{emojiUnicode}</Text>; return <Text style={[{ color: themes[theme].bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{emojiUnicode}</Text>;
}; };
export default Emoji; export default Emoji;

View File

@ -11,12 +11,12 @@ interface IHeadingProps {
level: HeadingProps['level']; level: HeadingProps['level'];
} }
const Heading = ({ value, level }: IHeadingProps): JSX.Element => { const Heading = ({ value, level }: IHeadingProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
const textStyle = styles[`heading${level}`]; const textStyle = styles[`heading${level}`];
return ( return (
<Text style={[textStyle, { color: themes[theme!].bodyText }]}> <Text style={[textStyle, { color: themes[theme].bodyText }]}>
{value.map(block => { {value.map(block => {
switch (block.type) { switch (block.type) {
case 'PLAIN_TEXT': case 'PLAIN_TEXT':

View File

@ -31,11 +31,11 @@ const MessageImage = ({ img, theme }: TMessageImage) => (
/> />
); );
const Image = ({ value }: IImageProps): JSX.Element => { const Image = ({ value }: IImageProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
const { src } = value; const { src } = value;
return <MessageImage img={src.value} theme={theme!} />; return <MessageImage img={src.value} theme={theme} />;
}; };
export default Image; export default Image;

View File

@ -19,7 +19,7 @@ interface IParagraphProps {
value: ParagraphProps['value']; value: ParagraphProps['value'];
} }
const Inline = ({ value }: IParagraphProps): JSX.Element => { const Inline = ({ value }: IParagraphProps) => {
const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext); const { useRealName, username, navToRoomInfo, mentions, channels } = useContext(MarkdownContext);
return ( return (
<Text style={styles.inline}> <Text style={styles.inline}>

View File

@ -10,7 +10,7 @@ interface IInlineCodeProps {
value: InlineCodeProps['value']; value: InlineCodeProps['value'];
} }
const InlineCode = ({ value }: IInlineCodeProps): JSX.Element => { const InlineCode = ({ value }: IInlineCodeProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
@ -18,9 +18,9 @@ const InlineCode = ({ value }: IInlineCodeProps): JSX.Element => {
style={[ style={[
styles.codeInline, styles.codeInline,
{ {
color: themes[theme!].bodyText, color: themes[theme].bodyText,
backgroundColor: themes[theme!].bannerBackground, backgroundColor: themes[theme].bannerBackground,
borderColor: themes[theme!].borderColor borderColor: themes[theme].borderColor
} }
]}> ]}>
{(block => { {(block => {

View File

@ -17,7 +17,7 @@ const styles = StyleSheet.create({
} }
}); });
const Italic = ({ value }: IItalicProps): JSX.Element => ( const Italic = ({ value }: IItalicProps) => (
<Text style={styles.text}> <Text style={styles.text}>
{value.map(block => { {value.map(block => {
switch (block.type) { switch (block.type) {

View File

@ -18,7 +18,7 @@ interface ILinkProps {
value: LinkProps['value']; value: LinkProps['value'];
} }
const Link = ({ value }: ILinkProps): JSX.Element => { const Link = ({ value }: ILinkProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
const { onLinkPress } = useContext(MarkdownContext); const { onLinkPress } = useContext(MarkdownContext);
const { src, label } = value; const { src, label } = value;
@ -38,7 +38,7 @@ const Link = ({ value }: ILinkProps): JSX.Element => {
}; };
return ( return (
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme!].actionTintColor }]}> <Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme].actionTintColor }]}>
{(block => { {(block => {
switch (block.type) { switch (block.type) {
case 'PLAIN_TEXT': case 'PLAIN_TEXT':

View File

@ -11,13 +11,13 @@ interface IOrderedListProps {
value: OrderedListProps['value']; value: OrderedListProps['value'];
} }
const OrderedList = ({ value }: IOrderedListProps): JSX.Element => { const OrderedList = ({ value }: IOrderedListProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<View> <View>
{value.map((item, index) => ( {value.map((item, index) => (
<View style={styles.row}> <View style={styles.row}>
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>{index + 1}. </Text> <Text style={[styles.text, { color: themes[theme].bodyText }]}>{index + 1}. </Text>
<Inline value={item.value} /> <Inline value={item.value} />
</View> </View>
))} ))}

View File

@ -11,10 +11,10 @@ interface IParagraphProps {
value: ParagraphProps['value']; value: ParagraphProps['value'];
} }
const Paragraph = ({ value }: IParagraphProps): JSX.Element => { const Paragraph = ({ value }: IParagraphProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<Text style={[styles.text, { color: themes[theme!].bodyText }]}> <Text style={[styles.text, { color: themes[theme].bodyText }]}>
<Inline value={value} /> <Inline value={value} />
</Text> </Text>
); );

View File

@ -10,10 +10,10 @@ interface IPlainProps {
value: PlainProps['value']; value: PlainProps['value'];
} }
const Plain = ({ value }: IPlainProps): JSX.Element => { const Plain = ({ value }: IPlainProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme!].bodyText }]}> <Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme].bodyText }]}>
{value} {value}
</Text> </Text>
); );

View File

@ -11,11 +11,11 @@ interface IQuoteProps {
value: QuoteProps['value']; value: QuoteProps['value'];
} }
const Quote = ({ value }: IQuoteProps): JSX.Element => { const Quote = ({ value }: IQuoteProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={[styles.quote, { backgroundColor: themes[theme!].borderColor }]} /> <View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} />
<View style={styles.childContainer}> <View style={styles.childContainer}>
{value.map(item => ( {value.map(item => (
<Paragraph value={item.value} /> <Paragraph value={item.value} />

View File

@ -17,7 +17,7 @@ const styles = StyleSheet.create({
} }
}); });
const Strike = ({ value }: IStrikeProps): JSX.Element => ( const Strike = ({ value }: IStrikeProps) => (
<Text style={styles.text}> <Text style={styles.text}>
{value.map(block => { {value.map(block => {
switch (block.type) { switch (block.type) {

View File

@ -11,13 +11,13 @@ interface ITasksProps {
value: TasksProps['value']; value: TasksProps['value'];
} }
const TaskList = ({ value = [] }: ITasksProps): JSX.Element => { const TaskList = ({ value = [] }: ITasksProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<View> <View>
{value.map(item => ( {value.map(item => (
<View style={styles.row}> <View style={styles.row}>
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text> <Text style={[styles.text, { color: themes[theme].bodyText }]}>{item.status ? '- [x] ' : '- [ ] '}</Text>
<Inline value={item.value} /> <Inline value={item.value} />
</View> </View>
))} ))}

View File

@ -11,13 +11,13 @@ interface IUnorderedListProps {
value: UnorderedListProps['value']; value: UnorderedListProps['value'];
} }
const UnorderedList = ({ value }: IUnorderedListProps): JSX.Element => { const UnorderedList = ({ value }: IUnorderedListProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
return ( return (
<View> <View>
{value.map(item => ( {value.map(item => (
<View style={styles.row}> <View style={styles.row}>
<Text style={[styles.text, { color: themes[theme!].bodyText }]}>- </Text> <Text style={[styles.text, { color: themes[theme].bodyText }]}>- </Text>
<Inline value={item.value} /> <Inline value={item.value} />
</View> </View>
))} ))}

View File

@ -35,7 +35,7 @@ const Body = ({
getCustomEmoji, getCustomEmoji,
baseUrl, baseUrl,
onLinkPress onLinkPress
}: IBodyProps): React.ReactElement | null => { }: IBodyProps) => {
if (isEmpty(tokens)) { if (isEmpty(tokens)) {
return null; return null;
} }

View File

@ -82,7 +82,7 @@ const Attachments = React.memo(
if (file && file.actions && file.actions.length > 0) { if (file && file.actions && file.actions.length > 0) {
return <AttachedActions attachment={file} />; return <AttachedActions attachment={file} />;
} }
if (file.title) { if (typeof file.collapsed === 'boolean') {
return ( return (
<CollapsibleQuote key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} /> <CollapsibleQuote key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />
); );

View File

@ -1,7 +1,7 @@
import Model from '@nozbe/watermelondb/Model'; import Model from '@nozbe/watermelondb/Model';
import { IUserEmail, IUserSettings } from './IUser'; import { IUserEmail, IUserSettings } from './IUser';
import { UserStatus } from './UserStatus'; import { TUserStatus } from './TUserStatus';
export interface ILoggedUser { export interface ILoggedUser {
id: string; id: string;
@ -9,7 +9,7 @@ export interface ILoggedUser {
username: string; username: string;
name: string; name: string;
language?: string; language?: string;
status: UserStatus; status: TUserStatus;
statusText?: string; statusText?: string;
customFields?: { customFields?: {
[key: string]: any; [key: string]: any;

View File

@ -1,6 +1,6 @@
import Model from '@nozbe/watermelondb/Model'; import Model from '@nozbe/watermelondb/Model';
import { UserStatus } from './UserStatus'; import { TUserStatus } from './TUserStatus';
import { IRocketChatRecord } from './IRocketChatRecord'; import { IRocketChatRecord } from './IRocketChatRecord';
import { ILoggedUser } from './ILoggedUser'; import { ILoggedUser } from './ILoggedUser';
@ -25,7 +25,7 @@ export interface IPersonalAccessToken extends ILoginToken {
export interface IUserRegistered { export interface IUserRegistered {
_id: string; _id: string;
type: string; type: string;
status: UserStatus; status: TUserStatus;
active: boolean; active: boolean;
name: string; name: string;
username: string; username: string;
@ -133,14 +133,14 @@ export interface IUser extends IRocketChatRecord, Omit<ILoggedUser, 'username' |
name?: string; name?: string;
services?: IUserServices; services?: IUserServices;
emails?: IUserEmail[]; emails?: IUserEmail[];
status: UserStatus; status: TUserStatus;
statusConnection?: string; statusConnection?: string;
lastLogin?: Date; lastLogin?: Date;
avatarOrigin?: string; avatarOrigin?: string;
avatarETag?: string; avatarETag?: string;
utcOffset?: number; utcOffset?: number;
language?: string; language?: string;
statusDefault?: UserStatus; statusDefault?: TUserStatus;
statusText?: string; statusText?: string;
oauth?: { oauth?: {
authorizedClients: string[]; authorizedClients: string[];

View File

@ -0,0 +1,3 @@
export const STATUSES = ['offline', 'online', 'away', 'busy'] as const;
export type TUserStatus = typeof STATUSES[number];

View File

@ -1,6 +0,0 @@
export enum UserStatus {
ONLINE = 'online',
AWAY = 'away',
OFFLINE = 'offline',
BUSY = 'busy'
}

View File

@ -26,6 +26,7 @@ export * from './ICertificate';
export * from './IUrl'; export * from './IUrl';
export * from './ICredentials'; export * from './ICredentials';
export * from './ISearch'; export * from './ISearch';
export * from './TUserStatus';
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> { export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
navigation: StackNavigationProp<T, S>; navigation: StackNavigationProp<T, S>;

View File

@ -8,7 +8,7 @@ export interface IDimensionsContextProps {
width: number; width: number;
height: number; height: number;
scale?: number; scale?: number;
fontScale?: number; fontScale: number;
setDimensions?: ({ setDimensions?: ({
width, width,
height, height,
@ -22,7 +22,9 @@ export interface IDimensionsContextProps {
}) => void; }) => void;
} }
export const DimensionsContext = React.createContext<IDimensionsContextProps>(Dimensions.get('window')); export const DimensionsContext = React.createContext<IDimensionsContextProps>(
Dimensions.get('window') as IDimensionsContextProps
);
export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component { export function withDimensions<T extends object>(Component: React.ComponentType<T> & TNavigationOptions): typeof Component {
const DimensionsComponent = (props: T) => ( const DimensionsComponent = (props: T) => (

View File

@ -73,7 +73,6 @@ export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
export const MIN_ROCKETCHAT_VERSION = '0.70.0'; export const MIN_ROCKETCHAT_VERSION = '0.70.0';
export const STATUSES = ['offline', 'online', 'away', 'busy'];
const RocketChat = { const RocketChat = {
TOKEN_KEY, TOKEN_KEY,

View File

@ -6,7 +6,6 @@ import { Q } from '@nozbe/watermelondb';
import log from '../../../utils/log'; import log from '../../../utils/log';
import { onRolesChanged } from '../../methods/getRoles'; import { onRolesChanged } from '../../methods/getRoles';
import { UserStatus } from '../../../definitions/UserStatus';
import { setActiveUsers } from '../../../actions/activeUsers'; import { setActiveUsers } from '../../../actions/activeUsers';
import protectedFunction from '../../methods/helpers/protectedFunction'; import protectedFunction from '../../methods/helpers/protectedFunction';
import database from '../../database'; import database from '../../database';
@ -17,8 +16,8 @@ import { store } from '../../auxStore';
import { loginRequest, setLoginServices, setUser } from '../../../actions/login'; import { loginRequest, setLoginServices, setUser } from '../../../actions/login';
import sdk from './sdk'; import sdk from './sdk';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import RocketChat, { MIN_ROCKETCHAT_VERSION, STATUSES } from '../rocketchat'; import RocketChat, { MIN_ROCKETCHAT_VERSION } from '../rocketchat';
import { ICredentials, ILoggedUser, IRocketChat } from '../../../definitions'; import { ICredentials, ILoggedUser, IRocketChat, STATUSES } from '../../../definitions';
import { isIOS } from '../../../utils/deviceInfo'; import { isIOS } from '../../../utils/deviceInfo';
import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../../actions/connect'; import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../../actions/connect';
import { updatePermission } from '../../../actions/permissions'; import { updatePermission } from '../../../actions/permissions';
@ -195,7 +194,7 @@ function connect(
const { user: loggedUser } = store.getState().login; const { user: loggedUser } = store.getState().login;
if (loggedUser && loggedUser.id === id) { if (loggedUser && loggedUser.id === id) {
store.dispatch(setUser({ status: STATUSES[status] as UserStatus, statusText })); store.dispatch(setUser({ status: STATUSES[status], statusText }));
} }
} else if (/updateAvatar/.test(eventName)) { } else if (/updateAvatar/.test(eventName)) {
const { username, etag } = ddpMessage.fields.args[0]; const { username, etag } = ddpMessage.fields.args[0];

View File

@ -900,4 +900,4 @@ export const e2eFetchMyKeys = async () => {
}; };
} }
return result; return result;
}; };

View File

@ -12,6 +12,7 @@ import Touchable from './Touchable';
import Tag from './Tag'; import Tag from './Tag';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DisplayMode } from '../../constants/constantDisplayMode'; import { DisplayMode } from '../../constants/constantDisplayMode';
import { TUserStatus } from '../../definitions';
interface IRoomItem { interface IRoomItem {
rid: string; rid: string;
@ -24,7 +25,7 @@ interface IRoomItem {
avatarSize: number; avatarSize: number;
testID: string; testID: string;
width: number; width: number;
status: string; status: TUserStatus;
useRealName: boolean; useRealName: boolean;
theme: string; theme: string;
isFocused: boolean; isFocused: boolean;

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { TUserStatus } from '../../definitions';
import RoomTypeIcon from '../../containers/RoomTypeIcon'; import RoomTypeIcon from '../../containers/RoomTypeIcon';
interface ITypeIcon { interface ITypeIcon {
type: string; type: string;
status: string; status: TUserStatus;
prid: string; prid: string;
isGroupChat: boolean; isGroupChat: boolean;
teamMain: boolean; teamMain: boolean;

View File

@ -5,6 +5,7 @@ import I18n from '../../i18n';
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles'; import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
import { formatDate } from '../../utils/room'; import { formatDate } from '../../utils/room';
import RoomItem from './RoomItem'; import RoomItem from './RoomItem';
import { TUserStatus } from '../../definitions';
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED }; export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
interface IRoomItemContainerProps { interface IRoomItemContainerProps {
@ -16,7 +17,7 @@ interface IRoomItemContainerProps {
username: string; username: string;
avatarSize: number; avatarSize: number;
width: number; width: number;
status: string; status: TUserStatus;
toggleFav(): void; toggleFav(): void;
toggleRead(): void; toggleRead(): void;
hideChannel(): void; hideChannel(): void;
@ -53,7 +54,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
private roomSubscription: any; private roomSubscription: any;
static defaultProps = { static defaultProps: Partial<IRoomItemContainerProps> = {
avatarSize: 48, avatarSize: 48,
status: 'offline', status: 'offline',
getUserPresence: () => {}, getUserPresence: () => {},
@ -233,7 +234,7 @@ const mapStateToProps = (state: any, ownProps: any) => {
} }
return { return {
connected: state.meteor.connected, connected: state.meteor.connected,
status status: status as TUserStatus
}; };
}; };

View File

@ -1,5 +1,4 @@
import { clearActiveUsers, setActiveUsers } from '../actions/activeUsers'; import { clearActiveUsers, setActiveUsers } from '../actions/activeUsers';
import { UserStatus } from '../definitions/UserStatus';
import { IActiveUsers, initialState } from './activeUsers'; import { IActiveUsers, initialState } from './activeUsers';
import { mockedStore } from './mockedStore'; import { mockedStore } from './mockedStore';
@ -9,7 +8,7 @@ describe('test reducer', () => {
expect(state).toEqual(initialState); expect(state).toEqual(initialState);
}); });
it('should return modified store after action', () => { it('should return modified store after action', () => {
const activeUsers: IActiveUsers = { any: { status: UserStatus.ONLINE, statusText: 'any' } }; const activeUsers: IActiveUsers = { any: { status: 'online', statusText: 'any' } };
mockedStore.dispatch(setActiveUsers(activeUsers)); mockedStore.dispatch(setActiveUsers(activeUsers));
const state = mockedStore.getState().activeUsers; const state = mockedStore.getState().activeUsers;
expect(state).toEqual({ ...activeUsers }); expect(state).toEqual({ ...activeUsers });

View File

@ -1,9 +1,8 @@
import { ACTIVE_USERS } from '../actions/actionsTypes'; import { ACTIVE_USERS } from '../actions/actionsTypes';
import { TApplicationActions } from '../definitions'; import { TApplicationActions, TUserStatus } from '../definitions';
import { UserStatus } from '../definitions/UserStatus';
export interface IActiveUser { export interface IActiveUser {
status: UserStatus; status: TUserStatus;
statusText: string; statusText: string;
} }

View File

@ -1,3 +1,4 @@
import { TUserStatus } from '../definitions';
import { import {
clearUser, clearUser,
loginFailure, loginFailure,
@ -8,7 +9,6 @@ import {
setLoginServices, setLoginServices,
setUser setUser
} from '../actions/login'; } from '../actions/login';
import { UserStatus } from '../definitions/UserStatus';
import { initialState } from './login'; import { initialState } from './login';
import { mockedStore } from './mockedStore'; import { mockedStore } from './mockedStore';
@ -49,7 +49,7 @@ describe('test selectedUsers reducer', () => {
isFromWebView: false, isFromWebView: false,
showMessageInMainThread: false, showMessageInMainThread: false,
enableMessageParserEarlyAdoption: false, enableMessageParserEarlyAdoption: false,
status: UserStatus.ONLINE, status: 'online' as TUserStatus,
statusText: 'online' statusText: 'online'
}; };
mockedStore.dispatch(loginSuccess(user)); mockedStore.dispatch(loginSuccess(user));

View File

@ -1,7 +1,6 @@
import { UserStatus } from '../definitions/UserStatus';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { TActionsLogin } from '../actions/login'; import { TActionsLogin } from '../actions/login';
import { IUser } from '../definitions'; import { IUser, TUserStatus } from '../definitions';
export interface IUserLogin { export interface IUserLogin {
id: string; id: string;
@ -9,7 +8,7 @@ export interface IUserLogin {
username: string; username: string;
name: string; name: string;
language?: string; language?: string;
status: UserStatus; status: TUserStatus;
statusText: string; statusText: string;
roles: string[]; roles: string[];
avatarETag?: string; avatarETag?: string;

View File

@ -6,7 +6,6 @@ import { CompositeNavigationProp } from '@react-navigation/core';
import * as List from '../containers/List'; import * as List from '../containers/List';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { useTheme } from '../theme';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import I18n from '../i18n'; import I18n from '../i18n';
@ -41,7 +40,6 @@ const setHeader = ({
const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTeamView) => { const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTeamView) => {
const { teamId, teamChannels } = route.params; const { teamId, teamChannels } = route.params;
const { theme } = useTheme();
useEffect(() => { useEffect(() => {
setHeader({ navigation, isMasterDetail }); setHeader({ navigation, isMasterDetail });
@ -67,7 +65,6 @@ const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTe
testID='add-channel-team-view-create-channel' testID='add-channel-team-view-create-channel'
left={() => <List.Icon name='team' />} left={() => <List.Icon name='team' />}
right={() => <List.Icon name='chevron-right' />} right={() => <List.Icon name='chevron-right' />}
theme={theme}
/> />
<List.Separator /> <List.Separator />
<List.Item <List.Item
@ -76,7 +73,6 @@ const AddChannelTeamView = ({ navigation, route, isMasterDetail }: IAddChannelTe
testID='add-channel-team-view-add-existing' testID='add-channel-team-view-add-existing'
left={() => <List.Icon name='channel-public' />} left={() => <List.Icon name='channel-public' />}
right={() => <List.Icon name='chevron-right' />} right={() => <List.Icon name='chevron-right' />}
theme={theme}
/> />
<List.Separator /> <List.Separator />
</List.Container> </List.Container>

View File

@ -92,7 +92,7 @@ class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForg
const { theme } = this.props; const { theme } = this.props;
return ( return (
<FormContainer theme={theme} testID='forgot-password-view'> <FormContainer testID='forgot-password-view'>
<FormContainerInner> <FormContainerInner>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}> <Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}>
{I18n.t('Forgot_password')} {I18n.t('Forgot_password')}

View File

@ -229,7 +229,7 @@ class LoginView extends React.Component<ILoginViewProps, any> {
render() { render() {
const { Accounts_ShowFormLogin, theme, navigation } = this.props; const { Accounts_ShowFormLogin, theme, navigation } = this.props;
return ( return (
<FormContainer theme={theme} testID='login-view'> <FormContainer testID='login-view'>
<FormContainerInner> <FormContainerInner>
<LoginServices separator={Accounts_ShowFormLogin} navigation={navigation} theme={theme} /> <LoginServices separator={Accounts_ShowFormLogin} navigation={navigation} theme={theme} />
{this.renderUserForm()} {this.renderUserForm()}

View File

@ -321,7 +321,7 @@ class NewServerView extends React.Component<INewServerViewProps, INewServerViewS
const marginTop = previousServer ? 0 : 35; const marginTop = previousServer ? 0 : 35;
return ( return (
<FormContainer theme={theme} testID='new-server-view' keyboardShouldPersistTaps='never'> <FormContainer testID='new-server-view' keyboardShouldPersistTaps='never'>
<FormContainerInner> <FormContainerInner>
<Image <Image
style={[ style={[

View File

@ -172,60 +172,64 @@ class RegisterView extends React.Component<IProps, any> {
return null; return null;
} }
try { try {
return Object.keys(this.parsedCustomFields).map((key, index, array) => { return (
if (this.parsedCustomFields[key].type === 'select') { <>
const options = this.parsedCustomFields[key].options.map((option: string) => ({ label: option, value: option })); {Object.keys(this.parsedCustomFields).map((key, index, array) => {
return ( if (this.parsedCustomFields[key].type === 'select') {
<RNPickerSelect const options = this.parsedCustomFields[key].options.map((option: string) => ({ label: option, value: option }));
key={key} return (
items={options} <RNPickerSelect
onValueChange={value => { key={key}
const newValue: { [key: string]: string | number } = {}; items={options}
newValue[key] = value; onValueChange={value => {
this.setState({ customFields: { ...customFields, ...newValue } }); const newValue: { [key: string]: string | number } = {};
}} newValue[key] = value;
value={customFields[key]}> this.setState({ customFields: { ...customFields, ...newValue } });
}}
value={customFields[key]}>
<TextInput
inputRef={(e: any) => {
// @ts-ignore
this[key] = e;
}}
placeholder={key}
value={customFields[key]}
testID='register-view-custom-picker'
theme={theme}
/>
</RNPickerSelect>
);
}
return (
<TextInput <TextInput
inputRef={e => { inputRef={e => {
// @ts-ignore // @ts-ignore
this[key] = e; this[key] = e;
}} }}
key={key}
label={key}
placeholder={key} placeholder={key}
value={customFields[key]} value={customFields[key]}
testID='register-view-custom-picker' onChangeText={(value: string) => {
const newValue: { [key: string]: string | number } = {};
newValue[key] = value;
this.setState({ customFields: { ...customFields, ...newValue } });
}}
onSubmitEditing={() => {
if (array.length - 1 > index) {
// @ts-ignore
return this[array[index + 1]].focus();
}
this.avatarUrl.focus();
}}
containerStyle={styles.inputContainer}
theme={theme} theme={theme}
/> />
</RNPickerSelect> );
); })}
} </>
);
return (
<TextInput
inputRef={(e: any) => {
// @ts-ignore
this[key] = e;
}}
key={key}
label={key}
placeholder={key}
value={customFields[key]}
onChangeText={(value: string) => {
const newValue: { [key: string]: string | number } = {};
newValue[key] = value;
this.setState({ customFields: { ...customFields, ...newValue } });
}}
onSubmitEditing={() => {
if (array.length - 1 > index) {
// @ts-ignore
return this[array[index + 1]].focus();
}
this.avatarUrl.focus();
}}
containerStyle={styles.inputContainer}
theme={theme}
/>
);
});
} catch (error) { } catch (error) {
return null; return null;
} }
@ -235,7 +239,7 @@ class RegisterView extends React.Component<IProps, any> {
const { saving } = this.state; const { saving } = this.state;
const { theme, showLoginButton, navigation } = this.props; const { theme, showLoginButton, navigation } = this.props;
return ( return (
<FormContainer theme={theme} testID='register-view'> <FormContainer testID='register-view'>
<FormContainerInner> <FormContainerInner>
<LoginServices navigation={navigation} theme={theme} separator /> <LoginServices navigation={navigation} theme={theme} separator />
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text> <Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Sign_Up')}</Text>

View File

@ -223,7 +223,7 @@ class ScreenLockConfigView extends React.Component<IScreenLockConfigViewProps, I
return ( return (
<List.Section> <List.Section>
<List.Separator /> <List.Separator />
{items.map(item => this.renderItem({ item }))} <>{items.map(item => this.renderItem({ item }))}</>
</List.Section> </List.Section>
); );
}; };

View File

@ -62,7 +62,7 @@ const SendEmailConfirmationView = ({ navigation, route }: ISendEmailConfirmation
}, []); }, []);
return ( return (
<FormContainer theme={theme} testID='send-email-confirmation-view'> <FormContainer testID='send-email-confirmation-view'>
<FormContainerInner> <FormContainerInner>
<TextInput <TextInput
autoFocus autoFocus

View File

@ -19,6 +19,7 @@ import Navigation from '../../lib/Navigation';
import SidebarItem from './SidebarItem'; import SidebarItem from './SidebarItem';
import styles from './styles'; import styles from './styles';
import { DrawerParamList } from '../../stacks/types'; import { DrawerParamList } from '../../stacks/types';
import { TUserStatus } from '../../definitions';
interface ISeparatorProps { interface ISeparatorProps {
theme: string; theme: string;
@ -40,7 +41,7 @@ interface ISidebarProps {
Site_Name: string; Site_Name: string;
user: { user: {
statusText: string; statusText: string;
status: string; status: TUserStatus;
username: string; username: string;
name: string; name: string;
roles: string[]; roles: string[];

View File

@ -2,7 +2,6 @@ import React from 'react';
import { FlatList, StyleSheet } from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { UserStatus } from '../definitions/UserStatus';
import { setUser } from '../actions/login'; import { setUser } from '../actions/login';
import * as HeaderButton from '../containers/HeaderButton'; import * as HeaderButton from '../containers/HeaderButton';
import * as List from '../containers/List'; import * as List from '../containers/List';
@ -11,7 +10,7 @@ import SafeAreaView from '../containers/SafeAreaView';
import Status from '../containers/Status/Status'; import Status from '../containers/Status/Status';
import TextInput from '../containers/TextInput'; import TextInput from '../containers/TextInput';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import { IApplicationState, IBaseScreen, IUser } from '../definitions'; import { IApplicationState, IBaseScreen, IUser, TUserStatus } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { getUserSelector } from '../selectors/login'; import { getUserSelector } from '../selectors/login';
@ -20,7 +19,12 @@ import EventEmitter from '../utils/events';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import log, { events, logEvent } from '../utils/log'; import log, { events, logEvent } from '../utils/log';
const STATUS = [ interface IStatus {
id: TUserStatus;
name: string;
}
const STATUS: IStatus[] = [
{ {
id: 'online', id: 'online',
name: 'Online' name: 'Online'
@ -135,7 +139,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
value={statusText} value={statusText}
containerStyle={styles.inputContainer} containerStyle={styles.inputContainer}
onChangeText={text => this.setState({ statusText: text })} onChangeText={text => this.setState({ statusText: text })}
left={<Status testID={`status-view-current-${user.status}`} style={styles.inputLeft} status={user.status!} size={24} />} left={<Status testID={`status-view-current-${user.status}`} style={styles.inputLeft} status={user.status} size={24} />}
inputStyle={styles.inputStyle} inputStyle={styles.inputStyle}
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'
@ -145,7 +149,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
); );
}; };
renderItem = ({ item }: { item: { id: string; name: string } }) => { renderItem = ({ item }: { item: IStatus }) => {
const { statusText } = this.state; const { statusText } = this.state;
const { user, dispatch } = this.props; const { user, dispatch } = this.props;
const { id, name } = item; const { id, name } = item;
@ -159,7 +163,7 @@ class StatusView extends React.Component<IStatusViewProps, IStatusViewState> {
try { try {
const result = await RocketChat.setUserStatus(item.id, statusText); const result = await RocketChat.setUserStatus(item.id, statusText);
if (result.success) { if (result.success) {
dispatch(setUser({ status: item.id as UserStatus })); dispatch(setUser({ status: item.id }));
} }
} catch (e: any) { } catch (e: any) {
showErrorAlert(I18n.t(e.data.errorType)); showErrorAlert(I18n.t(e.data.errorType));

View File

@ -131,11 +131,11 @@ class ThemeView extends React.Component<IThemeViewProps> {
<List.Container> <List.Container>
<List.Section title='Theme'> <List.Section title='Theme'>
<List.Separator /> <List.Separator />
{themeGroup.map(item => this.renderItem({ item }))} <>{themeGroup.map(item => this.renderItem({ item }))}</>
</List.Section> </List.Section>
<List.Section title='Dark_level'> <List.Section title='Dark_level'>
<List.Separator /> <List.Separator />
{darkGroup.map(item => this.renderItem({ item }))} <>{darkGroup.map(item => this.renderItem({ item }))}</>
</List.Section> </List.Section>
</List.Container> </List.Container>
</SafeAreaView> </SafeAreaView>

View File

@ -74,7 +74,7 @@ class WorkspaceView extends React.Component<IWorkSpaceProp, any> {
const { theme, Site_Name, Site_Url, Assets_favicon_512, server, showLoginButton } = this.props; const { theme, Site_Name, Site_Url, Assets_favicon_512, server, showLoginButton } = this.props;
return ( return (
<FormContainer theme={theme} testID='workspace-view'> <FormContainer testID='workspace-view'>
<FormContainerInner> <FormContainerInner>
<View style={styles.alignItemsCenter}> <View style={styles.alignItemsCenter}>
<ServerAvatar theme={theme} url={server} image={Assets_favicon_512?.url ?? Assets_favicon_512?.defaultUrl} /> <ServerAvatar theme={theme} url={server} image={Assets_favicon_512?.url ?? Assets_favicon_512?.defaultUrl} />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long