Merge branch 'develop' into appium-v2

This commit is contained in:
GleidsonDaniel 2022-04-27 11:25:53 -03:00
commit 3eff53c707
237 changed files with 2387 additions and 1714 deletions

View File

@ -339,7 +339,7 @@ jobs:
lint-testunit:
<<: *defaults
docker:
- image: circleci/node:15
- image: cimg/node:16.14
resource_class: large
environment:
CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6
@ -372,7 +372,7 @@ jobs:
android-build-experimental:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
environment:
<<: *android-env
<<: *bash-env
@ -383,7 +383,7 @@ jobs:
android-build-official:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
environment:
<<: *android-env
<<: *bash-env
@ -394,7 +394,7 @@ jobs:
android-internal-app-sharing-experimental:
<<: *defaults
docker:
- image: circleci/android:api-28-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-internal-app-sharing
@ -402,7 +402,7 @@ jobs:
android-google-play-beta-experimental:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-google-play-beta:
@ -411,14 +411,14 @@ jobs:
android-google-play-production-experimental:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-google-play-production
android-google-play-beta-official:
<<: *defaults
docker:
- image: circleci/android:api-29-node
- image: cimg/android:2022.03.1-node
steps:
- upload-to-google-play-beta:

View File

@ -17,7 +17,7 @@ module.exports = {
legacyDecorators: true
}
},
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest'],
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest', 'react-hooks'],
env: {
browser: true,
commonjs: true,
@ -148,7 +148,9 @@ module.exports = {
'no-async-promise-executor': [0],
'max-classes-per-file': [0],
'no-multiple-empty-lines': [0],
'no-sequences': 'off'
'no-sequences': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
},
globals: {
__DEV__: true
@ -237,7 +239,9 @@ module.exports = {
}
],
'new-cap': 'off',
'lines-between-class-members': 'off'
'lines-between-class-members': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
},
globals: {
JSX: true

View File

@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.26.2"
versionName "4.27.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
@ -282,6 +282,9 @@ dependencies {
playImplementation project(':@react-native-firebase_app')
playImplementation project(':@react-native-firebase_analytics')
playImplementation project(':@react-native-firebase_crashlytics')
implementation(project(':react-native-jitsi-meet')) { // https://github.com/skrafft/react-native-jitsi-meet#side-note
exclude group: 'com.facebook.react',module:'react-native-svg'
}
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext, memo, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { connect } from 'react-redux';
@ -27,21 +27,23 @@ const SetUsernameStack = () => (
// App
const Stack = createStackNavigator<StackParamList>();
const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
const App = memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => {
const { theme } = useContext(ThemeContext);
useEffect(() => {
if (root) {
const state = Navigation.navigationRef.current?.getRootState();
const currentRouteName = getActiveRouteName(state);
Navigation.routeNameRef.current = currentRouteName;
setCurrentScreen(currentRouteName);
}
}, [root]);
if (!root) {
return null;
}
const { theme } = React.useContext(ThemeContext);
const navTheme = navigationTheme(theme);
React.useEffect(() => {
const state = Navigation.navigationRef.current?.getRootState();
const currentRouteName = getActiveRouteName(state);
Navigation.routeNameRef.current = currentRouteName;
setCurrentScreen(currentRouteName);
}, []);
return (
<NavigationContainer
theme={navTheme}

View File

@ -5,6 +5,7 @@ import { themes } from '../lib/constants';
import sharedStyles from '../views/Styles';
import { getReadableVersion } from '../utils/deviceInfo';
import I18n from '../i18n';
import { TSupportedThemes } from '../theme';
const styles = StyleSheet.create({
container: {
@ -20,7 +21,7 @@ const styles = StyleSheet.create({
}
});
const AppVersion = React.memo(({ theme }: { theme: string }) => (
const AppVersion = React.memo(({ theme }: { theme: TSupportedThemes }) => (
<View style={styles.container}>
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>
{I18n.t('Version_no', { version: '' })}

View File

@ -29,14 +29,15 @@ const Avatar = React.memo(
text,
size = 25,
borderRadius = 4,
type = SubscriptionType.DIRECT
type = SubscriptionType.DIRECT,
externalProviderUrl
}: IAvatar) => {
const { theme } = useTheme();
if ((!text && !avatar && !emoji && !rid) || !server) {
return null;
}
const { theme } = useTheme();
const avatarStyle = {
width: size,
height: size,
@ -68,7 +69,8 @@ const Avatar = React.memo(
avatarETag,
serverVersion,
rid,
blockUnauthenticatedAccess
blockUnauthenticatedAccess,
externalProviderUrl
});
}

View File

@ -32,7 +32,10 @@ class AvatarContainer extends React.Component<IAvatar, any> {
shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) {
const { avatarETag } = this.state;
const { text, type } = this.props;
const { text, type, size, externalProviderUrl } = this.props;
if (nextProps.externalProviderUrl !== externalProviderUrl) {
return true;
}
if (nextState.avatarETag !== avatarETag) {
return true;
}
@ -42,6 +45,10 @@ class AvatarContainer extends React.Component<IAvatar, any> {
if (nextProps.type !== type) {
return true;
}
if (nextProps.size !== size) {
return true;
}
return false;
}
@ -100,6 +107,7 @@ const mapStateToProps = (state: IApplicationState) => ({
blockUnauthenticatedAccess:
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
true
true,
externalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string
});
export default connect(mapStateToProps)(AvatarContainer);

View File

@ -23,4 +23,5 @@ export interface IAvatar {
rid?: string;
blockUnauthenticatedAccess?: boolean;
serverVersion: string | null;
externalProviderUrl?: string;
}

View File

@ -2,6 +2,7 @@ import React from 'react';
import { ButtonProps, StyleSheet, Text } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import { testProps } from '../../lib/methods/testProps';
import sharedStyles from '../../views/Styles';
@ -14,7 +15,7 @@ interface IButtonProps extends ButtonProps {
disabled: boolean;
backgroundColor: string;
loading: boolean;
theme: string;
theme: TSupportedThemes;
color: string;
fontSize: any;
style: any;

View File

@ -6,14 +6,14 @@ import Avatar from '../Avatar';
import RoomTypeIcon from '../RoomTypeIcon';
import styles, { ROW_HEIGHT } from './styles';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { testProps } from '../../lib/methods/testProps';
import { TSupportedThemes, useTheme } from '../../theme';
export { ROW_HEIGHT };
interface IDirectoryItemLabel {
text?: string;
theme: string;
theme: TSupportedThemes;
}
interface IDirectoryItem {
@ -55,7 +55,7 @@ const DirectoryItem = ({
<Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} />
<View style={styles.directoryItemTextContainer}>
<View style={styles.directoryItemTextTitle}>
<RoomTypeIcon type={type} teamMain={teamMain} theme={theme} />
<RoomTypeIcon type={type} teamMain={teamMain} />
<Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>
{title}
</Text>

View File

@ -4,13 +4,14 @@ import { Text, TouchableOpacity, View } from 'react-native';
import styles from './styles';
import { themes } from '../../lib/constants';
import { testProps } from '../../lib/methods/testProps';
import { TSupportedThemes } from '../../theme';
interface ITabBarProps {
goToPage: Function;
activeTab: number;
tabs: [];
tabEmojiStyle: object;
theme: string;
theme: TSupportedThemes;
}
export default class TabBar extends React.Component<Partial<ITabBarProps>> {

View File

@ -16,7 +16,7 @@ import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import shortnameToUnicode from '../../utils/shortnameToUnicode';
import log from '../../utils/log';
import { themes } from '../../lib/constants';
import { withTheme } from '../../theme';
import { TSupportedThemes, withTheme } from '../../theme';
import { IEmoji } from '../../definitions/IEmoji';
const scrollProps = {
@ -30,7 +30,7 @@ interface IEmojiPickerProps {
baseUrl: string;
customEmojis?: any;
style: object;
theme?: string;
theme: TSupportedThemes;
onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void);
tabEmojiStyle?: object;
}
@ -186,7 +186,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
/* @ts-ignore*/
contentProps={scrollProps}
style={{ backgroundColor: themes[theme!].focusedBackground }}>
style={{ backgroundColor: themes[theme].focusedBackground }}>
{categories.tabs.map((tab, i) =>
i === 0 && frequentlyUsed.length === 0
? null // when no frequentlyUsed don't show the tab

View File

@ -4,7 +4,7 @@ import { ScrollView, ScrollViewProps, StyleSheet, View } from 'react-native';
import { themes } from '../lib/constants';
import sharedStyles from '../views/Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import KeyboardView from '../presentation/KeyboardView';
import KeyboardView from './KeyboardView';
import { useTheme } from '../theme';
import StatusBar from './StatusBar';
import AppVersion from './AppVersion';

View File

@ -1,7 +1,7 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import UnreadBadge from '../../presentation/UnreadBadge';
import UnreadBadge from '../UnreadBadge';
const styles = StyleSheet.create({
badgeContainer: {

View File

@ -10,7 +10,7 @@ import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { ROW_HEIGHT } from '../../presentation/RoomItem';
import { ROW_HEIGHT } from '../RoomItem';
import { goRoom } from '../../utils/goRoom';
import Navigation from '../../lib/navigation/appNavigation';
import { useOrientation } from '../../dimensions';

View File

@ -0,0 +1,24 @@
import React from 'react';
import { KeyboardAwareScrollView, KeyboardAwareScrollViewProps } from '@codler/react-native-keyboard-aware-scroll-view';
import scrollPersistTaps from '../utils/scrollPersistTaps';
interface IKeyboardViewProps extends KeyboardAwareScrollViewProps {
keyboardVerticalOffset?: number;
scrollEnabled?: boolean;
children: React.ReactElement[] | React.ReactElement;
}
const KeyboardView = ({ style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children }: IKeyboardViewProps) => (
<KeyboardAwareScrollView
{...scrollPersistTaps}
style={style}
contentContainerStyle={contentContainerStyle}
scrollEnabled={scrollEnabled}
alwaysBounceVertical={false}
extraHeight={keyboardVerticalOffset}>
{children}
</KeyboardAwareScrollView>
);
export default KeyboardView;

View File

@ -9,9 +9,10 @@ import { ICON_SIZE } from './constants';
interface IListIcon {
name: string;
color?: string;
color?: string | null;
style?: StyleProp<ViewStyle>;
testID?: string;
size?: number;
}
const styles = StyleSheet.create({
@ -21,12 +22,17 @@ const styles = StyleSheet.create({
}
});
const ListIcon = React.memo(({ name, color, style, testID }: IListIcon) => {
const ListIcon = React.memo(({ name, color, style, testID, size }: IListIcon) => {
const { theme } = useTheme();
return (
<View style={[styles.icon, style]}>
<CustomIcon name={name} color={color ?? themes[theme].auxiliaryText} size={ICON_SIZE} {...testProps(testID || '')} />
<CustomIcon
name={name}
color={color ?? themes[theme].auxiliaryText}
size={size ?? ICON_SIZE}
{...testProps(testID || '')}
/>
</View>
);
});

View File

@ -1,10 +1,10 @@
import React from 'react';
import { I18nManager, StyleSheet, Text, View } from 'react-native';
import { I18nManager, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
import Touch from '../../utils/touch';
import { themes } from '../../lib/constants';
import sharedStyles from '../../views/Styles';
import { useTheme } from '../../theme';
import { TSupportedThemes, useTheme } from '../../theme';
import I18n from '../../i18n';
import { Icon } from '.';
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
@ -60,13 +60,15 @@ interface IListItemContent {
left?: () => JSX.Element | null;
right?: () => JSX.Element | null;
disabled?: boolean;
theme: string;
theme: TSupportedThemes;
testID?: string;
color?: string;
translateTitle?: boolean;
translateSubtitle?: boolean;
showActionIndicator?: boolean;
alert?: boolean;
heightContainer?: number;
styleTitle?: StyleProp<TextStyle>;
}
const Content = React.memo(
@ -82,19 +84,21 @@ const Content = React.memo(
translateTitle = true,
translateSubtitle = true,
showActionIndicator = false,
theme
theme,
heightContainer,
styleTitle
}: IListItemContent) => {
const { fontScale } = useDimensions();
return (
<View
style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]}
style={[styles.container, disabled && styles.disabled, { height: (heightContainer || BASE_HEIGHT) * fontScale }]}
{...testProps(testID || '')}>
{left ? <View style={styles.leftContainer}>{left()}</View> : null}
<View style={styles.textContainer}>
<View style={styles.textAlertContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>
{translateTitle ? I18n.t(title) : title}
<Text style={[styles.title, styleTitle, { color: color || themes[theme].titleText }]} numberOfLines={1}>
{translateTitle && title ? I18n.t(title) : title}
</Text>
{alert ? (
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
@ -124,7 +128,7 @@ interface IListButtonPress extends IListItemButton {
interface IListItemButton {
title?: string;
disabled?: boolean;
theme: string;
theme: TSupportedThemes;
backgroundColor?: string;
underlayColor?: string;
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Animated, Modal, StyleSheet, View } from 'react-native';
import { withTheme } from '../theme';
import { TSupportedThemes, withTheme } from '../theme';
import { themes } from '../lib/constants';
import { testProps } from '../lib/methods/testProps';
@ -20,7 +20,7 @@ const styles = StyleSheet.create({
interface ILoadingProps {
visible: boolean;
theme?: string;
theme?: TSupportedThemes;
}
interface ILoadingState {

View File

@ -1,11 +1,11 @@
import React from 'react';
import { Animated, Easing, Linking, StyleSheet, Text, View } from 'react-native';
import { Animated, Easing, Linking, StyleSheet, Text, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import * as AppleAuthentication from 'expo-apple-authentication';
import { StackNavigationProp } from '@react-navigation/stack';
import { withTheme } from '../theme';
import { TSupportedThemes, withTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants';
import Button from './Button';
@ -100,7 +100,7 @@ interface ILoginServicesProps {
CAS_enabled: boolean;
CAS_login_url: string;
separator: boolean;
theme: string;
theme: TSupportedThemes;
}
interface ILoginServicesState {
@ -410,7 +410,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
const { servicesHeight } = this.state;
const { services, separator } = this.props;
const { length } = Object.values(services);
const style = {
const style: Animated.AnimatedProps<ViewStyle> = {
overflow: 'hidden',
height: servicesHeight
};

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { useTheme } from '../../theme';
import { TSupportedThemes, useTheme } from '../../theme';
import { themes } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import shortnameToUnicode from '../../utils/shortnameToUnicode';
@ -30,12 +30,12 @@ interface THeaderItem {
item: TItem;
onReaction: TOnReaction;
server: string;
theme: string;
theme: TSupportedThemes;
}
interface THeaderFooter {
onReaction: TOnReaction;
theme: string;
theme: TSupportedThemes;
}
export const HEADER_HEIGHT = 36;

View File

@ -16,10 +16,12 @@ interface IMessageBoxCommandsPreview {
const CommandsPreview = React.memo(
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
const { theme } = useTheme();
if (!showCommandPreview) {
return null;
}
const { theme } = useTheme();
return (
<FlatList
{...testProps('commandbox-container')}

View File

@ -6,12 +6,12 @@ import { store } from '../../lib/store/auxStore';
import EmojiPicker from '../EmojiPicker';
import styles from './styles';
import { themes } from '../../lib/constants';
import { withTheme } from '../../theme';
import { TSupportedThemes, withTheme } from '../../theme';
import { IEmoji } from '../../definitions/IEmoji';
import { testProps } from '../../lib/methods/testProps';
interface IMessageBoxEmojiKeyboard {
theme: string;
theme: TSupportedThemes;
}
export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {

View File

@ -17,10 +17,12 @@ interface IMessageBoxMentions {
const Mentions = React.memo(
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
const { theme } = useTheme();
if (!trackingType) {
return null;
}
const { theme } = useTheme();
return (
<View {...testProps('messagebox-container')}>
<FlatList

View File

@ -11,9 +11,10 @@ import { themes } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
import { events, logEvent } from '../../utils/log';
import { testProps } from '../../lib/methods/testProps';
import { TSupportedThemes } from '../../theme';
interface IMessageBoxRecordAudioProps {
theme: string;
theme: TSupportedThemes;
permissionToUpload: boolean;
recordingCallback: Function;
onFinish: Function;
@ -198,10 +199,11 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
<BorderlessButton
onPress={this.startRecordingAudio}
style={styles.actionButton}
{...testProps('messagebox-send-audio')}
// @ts-ignore
accessibilityTraits='button'>
<CustomIcon name='microphone' size={24} color={themes[theme].auxiliaryTintColor} />
testID='messagebox-send-audio'
{...testProps('messagebox-send-audio')}>
<View accessible accessibilityLabel={I18n.t('Send_audio_message')} accessibilityRole='button'>
<CustomIcon name='microphone' size={24} color={themes[theme].auxiliaryTintColor} />
</View>
</BorderlessButton>
);
}
@ -210,23 +212,17 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return (
<View style={styles.recordingContent}>
<View style={styles.textArea}>
<BorderlessButton
onPress={this.cancelRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Cancel_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
<BorderlessButton onPress={this.cancelRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Cancel_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
</View>
</BorderlessButton>
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.GetLastDuration}</Text>
</View>
<BorderlessButton
onPress={this.finishRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Finish_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
<BorderlessButton onPress={this.finishRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Finish_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
</View>
</BorderlessButton>
</View>
);
@ -235,24 +231,18 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
return (
<View style={styles.recordingContent}>
<View style={styles.textArea}>
<BorderlessButton
onPress={this.cancelRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Cancel_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
<BorderlessButton onPress={this.cancelRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Cancel_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
</View>
</BorderlessButton>
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.duration}</Text>
<CustomIcon size={24} color={themes[theme].dangerColor} name='record' />
</View>
<BorderlessButton
onPress={this.finishRecordingAudio}
// @ts-ignore
accessibilityLabel={I18n.t('Finish_recording')}
accessibilityTraits='button'
style={styles.actionButton}>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
<BorderlessButton onPress={this.finishRecordingAudio} style={styles.actionButton}>
<View accessible accessibilityLabel={I18n.t('Finish_recording')} accessibilityRole='button'>
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
</View>
</BorderlessButton>
</View>
);

View File

@ -56,10 +56,12 @@ interface IMessageBoxReplyPreview {
const ReplyPreview = React.memo(
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
const { theme } = useTheme();
if (!replying) {
return null;
}
const { theme } = useTheme();
const time = moment(message.ts).format(Message_TimeFormat);
return (
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>

View File

@ -1,11 +1,13 @@
import { BorderlessButton } from 'react-native-gesture-handler';
import React from 'react';
import { View } from 'react-native';
import styles from '../styles';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
import { testProps } from '../../../lib/methods/testProps';
import i18n from '../../../i18n';
interface IBaseButton {
onPress(): void;
@ -18,13 +20,13 @@ interface IBaseButton {
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
const { theme } = useTheme();
return (
<BorderlessButton
{...props}
style={styles.actionButton}
// @ts-ignore
accessibilityTraits='button'
{...testProps(props.testID)}>
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
<BorderlessButton {...props} style={styles.actionButton} {...testProps(props.testID)}>
<View
accessible
accessibilityLabel={accessibilityLabel ? i18n.t(accessibilityLabel) : accessibilityLabel}
accessibilityRole='button'>
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
</View>
</BorderlessButton>
);
};

View File

@ -53,6 +53,7 @@ import { forceJpgExtension } from './forceJpgExtension';
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { testProps } from '../../lib/methods/testProps';
import { TSupportedThemes } from '../../theme';
if (isAndroid) {
require('./EmojiKeyboard');
@ -96,7 +97,7 @@ export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackPar
editRequest: Function;
onSubmit: Function;
typing: Function;
theme: string;
theme: TSupportedThemes;
replyCancel(): void;
showSend: boolean;
children: JSX.Element;
@ -680,7 +681,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
if (result.success) {
return true;
}
Alert.alert(I18n.t('Error_uploading'), I18n.t(result.error));
Alert.alert(I18n.t('Error_uploading'), result.error && I18n.isTranslated(result.error) ? I18n.t(result.error) : result.error);
return false;
};
@ -762,7 +763,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
value = message;
replyCancel();
}
Navigation.navigate('ShareView', { room: this.room, value, attachments });
Navigation.navigate('ShareView', { room: this.room, thread: value, attachments });
};
createDiscussion = () => {

View File

@ -4,6 +4,7 @@ import { StyleSheet, Text, View } from 'react-native';
import I18n from '../i18n';
import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants';
import { TSupportedThemes } from '../theme';
const styles = StyleSheet.create({
container: {
@ -24,7 +25,7 @@ const styles = StyleSheet.create({
});
interface IOrSeparator {
theme: string;
theme: TSupportedThemes;
}
const OrSeparator = React.memo(({ theme }: IOrSeparator) => {

View File

@ -19,7 +19,7 @@ interface IPasscodeBase {
type: string;
previousPasscode?: string;
title: string;
subtitle?: string;
subtitle?: string | null;
showBiometry?: boolean;
onEndProcess: Function;
onError?: Function;

View File

@ -14,7 +14,7 @@ interface IPasscodeChoose {
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
const chooseRef = useRef<IBase>(null);
const confirmRef = useRef<IBase>(null);
const [subtitle, setSubtitle] = useState(null);
const [subtitle, setSubtitle] = useState<string | null>(null);
const [status, setStatus] = useState(TYPE.CHOOSE);
const [previousPasscode, setPreviouPasscode] = useState('');

View File

@ -8,7 +8,7 @@ import I18n from '../i18n';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants';
import { useTheme, withTheme } from '../theme';
import { TSupportedThemes, useTheme, withTheme } from '../theme';
import { TGetCustomEmoji } from '../definitions/IEmoji';
import { TMessageModel, ILoggedUser } from '../definitions';
import SafeAreaView from './SafeAreaView';
@ -77,7 +77,7 @@ interface IItem extends ISharedFields {
interface IModalContent extends ISharedFields {
message?: TMessageModel;
onClose: () => void;
theme: string;
theme: TSupportedThemes;
}
interface IReactionsModal extends ISharedFields {

View File

@ -2,13 +2,15 @@
import React from 'react';
import { Dimensions, View } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import { Provider } from 'react-redux';
import Header from '../Header';
import { longText } from '../../../storybook/utils';
import { ThemeContext } from '../../theme';
import { store } from '../../../storybook/stories';
import RoomHeaderComponent from './RoomHeader';
const stories = storiesOf('RoomHeader', module);
const stories = storiesOf('RoomHeader', module).addDecorator(story => <Provider store={store}>{story()}</Provider>);
// TODO: refactor after react-navigation v6
const HeaderExample = ({ title }) => (

View File

@ -6,7 +6,7 @@ import sharedStyles from '../../views/Styles';
import { themes } from '../../lib/constants';
import { MarkdownPreview } from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon';
import { TUserStatus } from '../../definitions';
import { TUserStatus, IOmnichannelSource } from '../../definitions';
import { useTheme } from '../../theme';
const HIT_SLOP = {
@ -67,12 +67,12 @@ interface IRoomHeader {
tmid: string;
teamMain: boolean;
status: TUserStatus;
theme?: string;
usersTyping: [];
isGroupChat: boolean;
parentTitle: string;
onPress: () => void;
testID: string;
sourceType?: IOmnichannelSource;
}
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
@ -136,7 +136,8 @@ const Header = React.memo(
isGroupChat,
teamMain,
testID,
usersTyping = []
usersTyping = [],
sourceType
}: IRoomHeader) => {
const { theme } = useTheme();
const portrait = height > width;
@ -172,7 +173,13 @@ const Header = React.memo(
hitSlop={HIT_SLOP}>
<View style={styles.titleContainer}>
{tmid ? null : (
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
<RoomTypeIcon
type={prid ? 'discussion' : type}
isGroupChat={isGroupChat}
status={status}
teamMain={teamMain}
sourceType={sourceType}
/>
)}
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} testID={testID} />
</View>

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@ import { dequal } from 'dequal';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { IApplicationState, TUserStatus } from '../../definitions';
import { IApplicationState, TUserStatus, IOmnichannelSource } from '../../definitions';
import { withDimensions } from '../../dimensions';
import I18n from '../../i18n';
import RoomHeader from './RoomHeader';
@ -27,12 +27,26 @@ interface IRoomHeaderContainerProps {
parentTitle: string;
isGroupChat: boolean;
testID: string;
sourceType?: IOmnichannelSource;
}
class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
shouldComponentUpdate(nextProps: IRoomHeaderContainerProps) {
const { type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain } =
this.props;
const {
type,
title,
subtitle,
status,
statusText,
connecting,
connected,
onPress,
usersTyping,
width,
height,
teamMain,
sourceType
} = this.props;
if (nextProps.type !== type) {
return true;
}
@ -63,6 +77,9 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
if (!dequal(nextProps.usersTyping, usersTyping)) {
return true;
}
if (!dequal(nextProps.sourceType, sourceType)) {
return true;
}
if (nextProps.onPress !== onPress) {
return true;
}
@ -90,7 +107,8 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
height,
parentTitle,
isGroupChat,
testID
testID,
sourceType
} = this.props;
let subtitle;
@ -118,6 +136,7 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
isGroupChat={isGroupChat}
testID={testID}
onPress={onPress}
sourceType={sourceType}
/>
);
}

View File

@ -6,31 +6,13 @@ import { isRTL } from '../../i18n';
import { CustomIcon } from '../../lib/Icons';
import { DisplayMode, themes } from '../../lib/constants';
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
interface ILeftActions {
theme: string;
transX: any;
isRead: boolean;
width: number;
onToggleReadPress(): void;
displayMode: string;
}
interface IRightActions {
theme: string;
transX: any;
favorite: boolean;
width: number;
toggleFav(): void;
onHidePress(): void;
displayMode: string;
}
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
const reverse = new Animated.Value(isRTL() ? -1 : 1);
const CONDENSED_ICON_SIZE = 24;
const EXPANDED_ICON_SIZE = 28;
export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActions) => {
export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActionsProps) => {
const translateX = Animated.multiply(
transX.interpolate({
inputRange: [0, ACTION_WIDTH],
@ -43,7 +25,7 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
return (
<View style={[styles.actionsContainer, styles.actionLeftContainer]} pointerEvents='box-none'>
<View style={[styles.actionsContainer, styles.actionsLeftContainer]} pointerEvents='box-none'>
<Animated.View
style={[
styles.actionLeftButtonContainer,
@ -70,7 +52,7 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
});
export const RightActions = React.memo(
({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActions) => {
({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActionsProps) => {
const translateXFav = Animated.multiply(
transX.interpolate({
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],

View File

@ -2,7 +2,7 @@ import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import Avatar from '../../containers/Avatar';
import Avatar from '../Avatar';
import { DisplayMode } from '../../lib/constants';
import TypeIcon from './TypeIcon';
import styles from './styles';
@ -18,7 +18,8 @@ const IconOrAvatar = ({
teamMain,
showLastMessage,
theme,
displayMode
displayMode,
sourceType
}) => {
if (showAvatar) {
return (
@ -38,6 +39,7 @@ const IconOrAvatar = ({
teamMain={teamMain}
size={24}
style={{ marginRight: 12 }}
sourceType={sourceType}
/>
</View>
);

View File

@ -3,27 +3,11 @@ import { dequal } from 'dequal';
import I18n from '../../i18n';
import styles from './styles';
import { MarkdownPreview } from '../../containers/markdown';
import { MarkdownPreview } from '../markdown';
import { E2E_MESSAGE_TYPE, E2E_STATUS, themes } from '../../lib/constants';
import { ILastMessageProps } from './interfaces';
interface ILastMessage {
theme: string;
lastMessage: {
u: any;
pinned: boolean;
t: string;
attachments: any;
msg: string;
e2e: string;
};
type: string;
showLastMessage: boolean;
username: string;
useRealName: boolean;
alert: boolean;
}
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessage>) => {
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessageProps>) => {
if (!showLastMessage) {
return '';
}
@ -63,7 +47,7 @@ const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
const LastMessage = React.memo(
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessage) => (
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessageProps) => (
<MarkdownPreview
msg={formatMsg({
lastMessage,

View File

@ -12,56 +12,7 @@ import Touchable from './Touchable';
import Tag from './Tag';
import I18n from '../../i18n';
import { DisplayMode } from '../../lib/constants';
import { TUserStatus } from '../../definitions';
interface IRoomItem {
rid: string;
type: string;
prid: string;
name: string;
avatar: string;
showLastMessage: boolean;
username: string;
avatarSize: number;
testID: string;
width: number;
status: TUserStatus;
useRealName: boolean;
theme: string;
isFocused: boolean;
isGroupChat: boolean;
isRead: boolean;
teamMain: boolean;
date: string;
accessibilityLabel: string;
lastMessage: {
u: any;
pinned: boolean;
t: string;
attachments: any;
msg: string;
e2e: string;
};
favorite: boolean;
alert: boolean;
hideUnreadStatus: boolean;
unread: number;
userMentions: number;
groupMentions: number;
tunread: [];
tunreadUser: [];
tunreadGroup: [];
swipeEnabled: boolean;
toggleFav(): void;
toggleRead(): void;
onPress(): void;
onLongPress(): void;
hideChannel(): void;
autoJoin: boolean;
size?: number;
showAvatar: boolean;
displayMode: string;
}
import { IRoomItemProps } from './interfaces';
const RoomItem = ({
rid,
@ -70,7 +21,6 @@ const RoomItem = ({
name,
avatar,
width,
avatarSize = 48,
username,
showLastMessage,
status = 'offline',
@ -101,8 +51,9 @@ const RoomItem = ({
teamMain,
autoJoin,
showAvatar,
displayMode
}: IRoomItem) => (
displayMode,
sourceType
}: IRoomItemProps) => (
<Touchable
onPress={onPress}
onLongPress={onLongPress}
@ -122,7 +73,6 @@ const RoomItem = ({
<Wrapper
accessibilityLabel={accessibilityLabel}
avatar={avatar}
avatarSize={avatarSize}
type={type}
theme={theme}
rid={rid}
@ -132,12 +82,20 @@ const RoomItem = ({
teamMain={teamMain}
displayMode={displayMode}
showAvatar={showAvatar}
showLastMessage={showLastMessage}>
showLastMessage={showLastMessage}
sourceType={sourceType}>
{showLastMessage && displayMode === DisplayMode.Expanded ? (
<>
<View style={styles.titleContainer}>
{showAvatar ? (
<TypeIcon type={type} prid={prid} status={status} isGroupChat={isGroupChat} theme={theme} teamMain={teamMain} />
<TypeIcon
type={type}
prid={prid}
status={status}
isGroupChat={isGroupChat}
teamMain={teamMain}
sourceType={sourceType}
/>
) : null}
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
{autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null}
@ -170,10 +128,10 @@ const RoomItem = ({
prid={prid}
status={status}
isGroupChat={isGroupChat}
theme={theme}
teamMain={teamMain}
size={22}
style={{ marginRight: 8 }}
sourceType={sourceType}
/>
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
{autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null}

View File

@ -11,7 +11,7 @@ interface ITag {
}
const Tag = React.memo(({ name, testID }: ITag) => {
const { theme }: any = useTheme();
const { theme } = useTheme();
return (
<View style={[styles.tagContainer, { backgroundColor: themes[theme].borderColor }]}>

View File

@ -3,15 +3,9 @@ import { Text } from 'react-native';
import styles from './styles';
import { themes } from '../../lib/constants';
import { ITitleProps } from './interfaces';
interface ITitle {
name: string;
theme: string;
hideUnreadStatus: boolean;
alert: boolean;
}
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitle) => (
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitleProps) => (
<Text
style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: themes[theme].titleText }]}
ellipsizeMode='tail'

View File

@ -1,45 +1,28 @@
import React from 'react';
import { Animated } from 'react-native';
import { LongPressGestureHandler, PanGestureHandler, State } from 'react-native-gesture-handler';
import {
GestureEvent,
HandlerStateChangeEventPayload,
LongPressGestureHandler,
PanGestureHandler,
PanGestureHandlerEventPayload,
State
} from 'react-native-gesture-handler';
import Touch from '../../utils/touch';
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
import { isRTL } from '../../i18n';
import { themes } from '../../lib/constants';
import { LeftActions, RightActions } from './Actions';
interface ITouchableProps {
children: JSX.Element;
type: string;
onPress(): void;
onLongPress(): void;
testID: string;
width: number;
favorite: boolean;
isRead: boolean;
rid: string;
toggleFav: Function;
toggleRead: Function;
hideChannel: Function;
theme: string;
isFocused: boolean;
swipeEnabled: boolean;
displayMode: string;
}
import { ITouchableProps } from './interfaces';
class Touchable extends React.Component<ITouchableProps, any> {
private dragX: Animated.Value;
private rowOffSet: Animated.Value;
private reverse: Animated.Value;
private transX: Animated.AnimatedAddition;
private transXReverse: Animated.AnimatedMultiplication;
private _onGestureEvent: (...args: any[]) => void;
private _onGestureEvent: (event: GestureEvent<PanGestureHandlerEventPayload>) => void;
private _value: number;
constructor(props: ITouchableProps) {
@ -56,19 +39,19 @@ class Touchable extends React.Component<ITouchableProps, any> {
this._value = 0;
}
_onHandlerStateChange = ({ nativeEvent }: any) => {
_onHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload & PanGestureHandlerEventPayload }) => {
if (nativeEvent.oldState === State.ACTIVE) {
this._handleRelease(nativeEvent);
}
};
onLongPressHandlerStateChange = ({ nativeEvent }: any) => {
onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
if (nativeEvent.state === State.ACTIVE) {
this.onLongPress();
}
};
_handleRelease = (nativeEvent: any) => {
_handleRelease = (nativeEvent: PanGestureHandlerEventPayload) => {
const { translationX } = nativeEvent;
const { rowState } = this.state;
this._value += translationX;
@ -154,7 +137,7 @@ class Touchable extends React.Component<ITouchableProps, any> {
this._animateRow(toValue);
};
_animateRow = (toValue: any) => {
_animateRow = (toValue: number) => {
this.rowOffSet.setValue(this._value);
this._value = toValue;
this.dragX.setValue(0);

View File

@ -0,0 +1,17 @@
import React from 'react';
import RoomTypeIcon from '../RoomTypeIcon';
import { ITypeIconProps } from './interfaces';
const TypeIcon = React.memo(({ type, prid, status, isGroupChat, teamMain, size, style }: ITypeIconProps) => (
<RoomTypeIcon
type={prid ? 'discussion' : type}
isGroupChat={isGroupChat}
status={status}
teamMain={teamMain}
size={size}
style={style}
/>
));
export default TypeIcon;

View File

@ -4,15 +4,9 @@ import { Text } from 'react-native';
import styles from './styles';
import { themes } from '../../lib/constants';
import { capitalize } from '../../utils/room';
import { IUpdatedAtProps } from './interfaces';
interface IUpdatedAt {
date: string;
theme: string;
hideUnreadStatus: boolean;
alert: boolean;
}
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAt) => {
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAtProps) => {
if (!date) {
return null;
}

View File

@ -3,26 +3,10 @@ import { View } from 'react-native';
import { DisplayMode, themes } from '../../lib/constants';
import IconOrAvatar from './IconOrAvatar';
import { IWrapperProps } from './interfaces';
import styles from './styles';
interface IWrapper {
accessibilityLabel: string;
avatar: string;
avatarSize: number;
type: string;
theme: string;
rid: string;
children: JSX.Element;
displayMode: string;
prid: string;
showLastMessage: boolean;
status: string;
isGroupChat: boolean;
teamMain: boolean;
showAvatar: boolean;
}
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapper) => (
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapperProps): React.ReactElement => (
<View
style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
accessibilityLabel={accessibilityLabel}>

View File

@ -5,36 +5,10 @@ import I18n from '../../i18n';
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
import { formatDate } from '../../utils/room';
import RoomItem from './RoomItem';
import { TUserStatus } from '../../definitions';
import { ISubscription, TUserStatus } from '../../definitions';
import { IRoomItemContainerProps } from './interfaces';
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
interface IRoomItemContainerProps {
item: any;
showLastMessage: boolean;
id: string;
onPress: Function;
onLongPress: Function;
username: string;
avatarSize: number;
width: number;
status: TUserStatus;
toggleFav(): void;
toggleRead(): void;
hideChannel(): void;
useRealName: boolean;
getUserPresence: Function;
connected: boolean;
theme: string;
isFocused: boolean;
getRoomTitle: Function;
getRoomAvatar: Function;
getIsGroupChat: Function;
getIsRead: Function;
swipeEnabled: boolean;
autoJoin: boolean;
showAvatar: boolean;
displayMode: string;
}
const attrs = [
'width',
@ -50,12 +24,9 @@ const attrs = [
];
class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
private mounted: boolean;
private roomSubscription: any;
private roomSubscription: ISubscription | undefined;
static defaultProps: Partial<IRoomItemContainerProps> = {
avatarSize: 48,
status: 'offline',
getUserPresence: () => {},
getRoomTitle: () => 'title',
@ -67,24 +38,22 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
constructor(props: IRoomItemContainerProps) {
super(props);
this.mounted = false;
this.init();
}
componentDidMount() {
this.mounted = true;
const { connected, getUserPresence, id } = this.props;
if (connected && this.isDirect) {
getUserPresence(id);
}
}
shouldComponentUpdate(nextProps: any) {
const { props }: any = this;
shouldComponentUpdate(nextProps: IRoomItemContainerProps) {
const { props } = this;
return !attrs.every(key => props[key] === nextProps[key]);
}
componentDidUpdate(prevProps: any) {
componentDidUpdate(prevProps: IRoomItemContainerProps) {
const { connected, getUserPresence, id } = this.props;
if (prevProps.connected !== connected && connected && this.isDirect) {
getUserPresence(id);
@ -106,7 +75,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
const {
item: { t },
id
}: any = this.props;
} = this.props;
return t === 'd' && id && !this.isGroupChat;
}
@ -144,7 +113,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
hideChannel,
theme,
isFocused,
avatarSize,
status,
showLastMessage,
username,
@ -177,7 +145,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
}
return (
// @ts-ignore
<RoomItem
name={name}
avatar={avatar}
@ -197,7 +164,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
type={item.t}
theme={theme}
isFocused={isFocused}
size={avatarSize}
prid={item.prid}
status={status}
hideUnreadStatus={item.hideUnreadStatus}
@ -217,6 +183,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
autoJoin={autoJoin}
showAvatar={showAvatar}
displayMode={displayMode}
sourceType={item.source}
/>
);
}

View File

@ -0,0 +1,166 @@
import React from 'react';
import { Animated } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { TUserStatus, ILastMessage, SubscriptionType, IOmnichannelSource } from '../../definitions';
export interface ILeftActionsProps {
theme: TSupportedThemes;
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
isRead: boolean;
width: number;
onToggleReadPress(): void;
displayMode: string;
}
export interface IRightActionsProps {
theme: TSupportedThemes;
transX: Animated.AnimatedAddition | Animated.AnimatedMultiplication;
favorite: boolean;
width: number;
toggleFav(): void;
onHidePress(): void;
displayMode: string;
}
export interface ITitleProps {
name: string;
theme: TSupportedThemes;
hideUnreadStatus: boolean;
alert: boolean;
}
export interface IUpdatedAtProps {
date: string;
theme: TSupportedThemes;
hideUnreadStatus: boolean;
alert: boolean;
}
export interface IWrapperProps {
accessibilityLabel: string;
avatar: string;
type: string;
theme: TSupportedThemes;
rid: string;
children: React.ReactElement;
displayMode: string;
prid: string;
showLastMessage: boolean;
status: string;
isGroupChat: boolean;
teamMain: boolean;
showAvatar: boolean;
sourceType: IOmnichannelSource;
}
export interface ITypeIconProps {
type: string;
status: TUserStatus;
prid: string;
isGroupChat: boolean;
teamMain: boolean;
theme?: TSupportedThemes;
size?: number;
style?: object;
sourceType: IOmnichannelSource;
}
export interface IRoomItemContainerProps {
[key: string]: string | boolean | Function | number;
item: any;
showLastMessage: boolean;
id: string;
onPress: (item: any) => void;
onLongPress: (item: any) => Promise<void>;
username: string;
width: number;
status: TUserStatus;
toggleFav(): void;
toggleRead(): void;
hideChannel(): void;
useRealName: boolean;
getUserPresence: (uid: string) => void;
connected: boolean;
theme: TSupportedThemes;
isFocused: boolean;
getRoomTitle: (item: any) => string;
getRoomAvatar: (item: any) => string;
getIsGroupChat: (item: any) => boolean;
getIsRead: (item: any) => boolean;
swipeEnabled: boolean;
autoJoin: boolean;
showAvatar: boolean;
displayMode: string;
}
export interface IRoomItemProps {
rid: string;
type: SubscriptionType;
prid: string;
name: string;
avatar: string;
showLastMessage: boolean;
username: string;
testID: string;
width: number;
status: TUserStatus;
useRealName: boolean;
theme: TSupportedThemes;
isFocused: boolean;
isGroupChat: boolean;
isRead: boolean;
teamMain: boolean;
date: string;
accessibilityLabel: string;
lastMessage: ILastMessage;
favorite: boolean;
alert: boolean;
hideUnreadStatus: boolean;
unread: number;
userMentions: number;
groupMentions: number;
tunread: [];
tunreadUser: [];
tunreadGroup: [];
swipeEnabled: boolean;
toggleFav(): void;
toggleRead(): void;
onPress(): void;
onLongPress(): void;
hideChannel(): void;
autoJoin: boolean;
size?: number;
showAvatar: boolean;
displayMode: string;
sourceType: IOmnichannelSource;
}
export interface ILastMessageProps {
theme: TSupportedThemes;
lastMessage: ILastMessage;
type: SubscriptionType;
showLastMessage: boolean;
username: string;
useRealName: boolean;
alert: boolean;
}
export interface ITouchableProps {
children: JSX.Element;
type: string;
onPress(): void;
onLongPress(): void;
testID: string;
width: number;
favorite: boolean;
isRead: boolean;
rid: string;
toggleFav: Function;
toggleRead: Function;
hideChannel: Function;
theme: TSupportedThemes;
isFocused: boolean;
swipeEnabled: boolean;
displayMode: string;
}

View File

@ -8,7 +8,7 @@ export const ACTION_WIDTH = 80;
export const SMALL_SWIPE = ACTION_WIDTH / 2;
export const LONG_SWIPE = ACTION_WIDTH * 3;
export default StyleSheet.create<any>({
export default StyleSheet.create({
flex: {
flex: 1
},

View File

@ -0,0 +1,51 @@
import React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { SvgUri } from 'react-native-svg';
import { useSelector } from 'react-redux';
import { OmnichannelSourceType, IApplicationState, IOmnichannelSource } from '../../definitions';
import { STATUS_COLORS } from '../../lib/constants';
import { CustomIcon } from '../../lib/Icons';
const iconMap = {
widget: 'livechat-monochromatic',
email: 'mail',
sms: 'sms',
app: 'omnichannel',
api: 'omnichannel',
other: 'omnichannel'
};
interface IOmnichannelRoomIconProps {
size: number;
type: string;
style?: StyleProp<ViewStyle>;
status?: string;
sourceType?: IOmnichannelSource;
}
export const OmnichannelRoomIcon = ({ size, style, sourceType, status }: IOmnichannelRoomIconProps) => {
const baseUrl = useSelector((state: IApplicationState) => state.server?.server);
const connected = useSelector((state: IApplicationState) => state.meteor?.connected);
if (sourceType?.type === OmnichannelSourceType.APP && sourceType.id && sourceType.sidebarIcon && connected) {
return (
<SvgUri
height={size}
width={size}
color={STATUS_COLORS[status || 'offline']}
uri={`${baseUrl}/api/apps/public/${sourceType.id}/get-sidebar-icon?icon=${sourceType.sidebarIcon}`}
style={style}
/>
);
}
return (
<CustomIcon
name={iconMap[sourceType?.type || 'other']}
size={size}
style={style}
color={STATUS_COLORS[status || 'offline']}
/>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import { StyleSheet, ViewStyle } from 'react-native';
import { CustomIcon } from '../lib/Icons';
import { STATUS_COLORS, themes } from '../lib/constants';
import Status from './Status/Status';
import { withTheme } from '../theme';
import { TUserStatus } from '../definitions';
import { OmnichannelRoomIcon } from './OmnichannelRoomIcon';
import { CustomIcon } from '../../lib/Icons';
import { STATUS_COLORS, themes } from '../../lib/constants';
import Status from '../Status/Status';
import { useTheme } from '../../theme';
import { TUserStatus, IOmnichannelSource } from '../../definitions';
const styles = StyleSheet.create({
icon: {
@ -14,21 +15,23 @@ const styles = StyleSheet.create({
});
interface IRoomTypeIcon {
theme?: string;
type: string;
isGroupChat?: boolean;
teamMain?: boolean;
status?: TUserStatus;
size?: number;
style?: ViewStyle;
sourceType?: IOmnichannelSource;
}
const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, teamMain, size = 16 }: IRoomTypeIcon) => {
const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, teamMain, size = 16, sourceType }: IRoomTypeIcon) => {
const { theme } = useTheme();
if (!type) {
return null;
}
const color = themes[theme!].titleText;
const color = themes[theme].titleText;
const iconStyle = [styles.icon, { color }, style];
if (type === 'd' && !isGroupChat) {
@ -38,6 +41,10 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
return <Status style={[iconStyle, { color: STATUS_COLORS[status] }]} size={size} status={status} />;
}
if (type === 'l') {
return <OmnichannelRoomIcon style={[styles.icon, style]} size={size} type={type} status={status} sourceType={sourceType} />;
}
// TODO: move this to a separate function
let icon = 'channel-private';
if (teamMain) {
@ -52,11 +59,9 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
} else {
icon = 'mention';
}
} else if (type === 'l') {
icon = 'omnichannel';
}
return <CustomIcon name={icon} size={size} style={iconStyle} />;
});
export default withTheme(RoomTypeIcon);
export default RoomTypeIcon;

View File

@ -8,6 +8,7 @@ import { themes } from '../lib/constants';
import { CustomIcon } from '../lib/Icons';
import ActivityIndicator from './ActivityIndicator';
import { testProps } from '../lib/methods/testProps';
import { TSupportedThemes } from '../theme';
const styles = StyleSheet.create({
error: {
@ -63,7 +64,7 @@ export interface IRCTextInputProps extends TextInputProps {
iconRight?: string;
left?: JSX.Element;
onIconRightPress?(): void;
theme: string;
theme: TSupportedThemes;
}
interface IRCTextInputState {

View File

@ -5,7 +5,7 @@ import EasyToast from 'react-native-easy-toast';
import { themes } from '../lib/constants';
import sharedStyles from '../views/Styles';
import EventEmitter from '../utils/events';
import { withTheme } from '../theme';
import { TSupportedThemes, withTheme } from '../theme';
const styles = StyleSheet.create({
toast: {
@ -22,7 +22,7 @@ const styles = StyleSheet.create({
export const LISTENER = 'Toast';
interface IToastProps {
theme?: string;
theme?: TSupportedThemes;
}
class Toast extends React.Component<IToastProps, any> {

View File

@ -8,19 +8,20 @@ import { textParser } from '../utils';
import { CustomIcon } from '../../../lib/Icons';
import styles from './styles';
import { IItemData } from '.';
import { TSupportedThemes } from '../../../theme';
interface IChip {
item: IItemData;
onSelect: (item: IItemData) => void;
style?: object;
theme: string;
theme: TSupportedThemes;
}
interface IChips {
items: IItemData[];
onSelect: (item: IItemData) => void;
style?: object;
theme: string;
theme: TSupportedThemes;
}
const keyExtractor = (item: IItemData) => item.value.toString();

View File

@ -6,11 +6,12 @@ import { CustomIcon } from '../../../lib/Icons';
import { themes } from '../../../lib/constants';
import ActivityIndicator from '../../ActivityIndicator';
import styles from './styles';
import { TSupportedThemes } from '../../../theme';
interface IInput {
children?: JSX.Element;
onPress: () => void;
theme: string;
theme: TSupportedThemes;
inputStyle?: object;
disabled?: boolean | null;
placeholder?: string;

View File

@ -9,26 +9,27 @@ import { textParser } from '../utils';
import { themes } from '../../../lib/constants';
import styles from './styles';
import { IItemData } from '.';
import { TSupportedThemes } from '../../../theme';
interface IItem {
item: IItemData;
selected?: string;
onSelect: Function;
theme: string;
theme: TSupportedThemes;
}
interface IItems {
items: IItemData[];
selected: string[];
onSelect: Function;
theme: string;
theme: TSupportedThemes;
}
const keyExtractor = (item: IItemData) => item.value.toString();
// RectButton doesn't work on modal (Android)
const Item = ({ item, selected, onSelect, theme }: IItem) => {
const itemName = item.value || item.text.text.toLowerCase();
const itemName = item.value?.name || item.text.text.toLowerCase();
return (
<Touchable
testID={`multi-select-item-${itemName}`}

View File

@ -26,7 +26,7 @@ import Input from './Input';
import styles from './styles';
export interface IItemData {
value: string;
value: any;
text: { text: string };
imageUrl?: string;
}

View File

@ -1,4 +1,4 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useContext } from 'react';
import { StyleSheet, Text } from 'react-native';
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';

View File

@ -1,3 +1,5 @@
import { TSupportedThemes } from '../../theme';
export enum ElementTypes {
IMAGE = 'image',
BUTTON = 'button',
@ -199,7 +201,7 @@ export interface IInput extends Partial<Block> {
description: string;
error: string;
hint: string;
theme: string;
theme: TSupportedThemes;
}
export interface IInputIndex {
@ -231,7 +233,7 @@ export interface IOverflow extends Partial<Block> {
interface PropsOption {
onOptionPress: Function;
parser: IParser;
theme: string;
theme: TSupportedThemes;
}
export interface IOptions extends PropsOption {
options: Option[];
@ -265,6 +267,6 @@ export interface ISection {
export interface IFields {
parser: IParser;
theme: string;
theme: TSupportedThemes;
fields: any[];
}

View File

@ -0,0 +1,36 @@
import { IUnreadBadge } from '.';
import { themes } from '../../lib/constants/colors';
import { TSupportedThemes } from '../../theme';
interface IGetUnreadStyle extends Omit<IUnreadBadge, 'small' | 'style'> {
theme: TSupportedThemes;
}
export const getUnreadStyle = ({
unread,
userMentions,
groupMentions,
theme,
tunread,
tunreadUser,
tunreadGroup
}: IGetUnreadStyle) => {
if ((!unread || unread <= 0) && !tunread?.length) {
return {};
}
let backgroundColor = themes[theme].unreadColor;
const color = themes[theme].buttonText;
if ((userMentions && userMentions > 0) || tunreadUser?.length) {
backgroundColor = themes[theme].mentionMeColor;
} else if ((groupMentions && groupMentions > 0) || tunreadGroup?.length) {
backgroundColor = themes[theme].mentionGroupColor;
} else if (tunread && tunread?.length > 0) {
backgroundColor = themes[theme].tunreadColor;
}
return {
backgroundColor,
color
};
};

View File

@ -1,9 +1,9 @@
import React from 'react';
import { StyleSheet, Text, View, ViewStyle } from 'react-native';
import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
import sharedStyles from '../../views/Styles';
import { getUnreadStyle } from './getUnreadStyle';
import { withTheme } from '../../theme';
import { useTheme } from '../../theme';
const styles = StyleSheet.create({
unreadNumberContainerNormal: {
@ -29,12 +29,11 @@ const styles = StyleSheet.create({
}
});
interface IUnreadBadge {
theme?: string;
export interface IUnreadBadge {
unread?: number;
userMentions?: number;
groupMentions?: number;
style?: ViewStyle;
style?: StyleProp<ViewStyle>;
tunread?: [];
tunreadUser?: [];
tunreadGroup?: [];
@ -42,10 +41,13 @@ interface IUnreadBadge {
}
const UnreadBadge = React.memo(
({ theme, unread, userMentions, groupMentions, style, tunread, tunreadUser, tunreadGroup, small }: IUnreadBadge) => {
({ unread, userMentions, groupMentions, style, tunread, tunreadUser, tunreadGroup, small }: IUnreadBadge) => {
const { theme } = useTheme();
if ((!unread || unread <= 0) && !tunread?.length) {
return null;
}
const { backgroundColor, color } = getUnreadStyle({
theme,
unread,
@ -88,4 +90,4 @@ const UnreadBadge = React.memo(
}
);
export default withTheme(UnreadBadge);
export default UnreadBadge;

View File

@ -1,12 +1,12 @@
import React from 'react';
// @ts-ignore
import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
import Avatar from '../containers/Avatar';
import Avatar from './Avatar';
import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants';
import { isIOS } from '../utils/deviceInfo';
import { TSupportedThemes } from '../theme';
const styles = StyleSheet.create({
button: {
@ -47,7 +47,7 @@ interface IUserItem {
onLongPress?: () => void;
style?: StyleProp<ViewStyle>;
icon?: string | null;
theme: string;
theme: TSupportedThemes;
}
const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, theme }: IUserItem) => (

View File

@ -1,12 +1,13 @@
import React from 'react';
import { View } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles';
interface IBlockQuote {
children: React.ReactElement | null;
theme: string;
theme: TSupportedThemes;
}
const BlockQuote = React.memo(({ children, theme }: IBlockQuote) => (

View File

@ -5,6 +5,7 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode';
import CustomEmoji from '../EmojiPicker/CustomEmoji';
import { themes } from '../../lib/constants';
import styles from './styles';
import { TSupportedThemes } from '../../theme';
interface IEmoji {
literal: string;
@ -13,7 +14,7 @@ interface IEmoji {
baseUrl: string;
customEmojis?: any;
style?: object;
theme: string;
theme: TSupportedThemes;
onEmojiSelected?: Function;
tabEmojiStyle?: object;
}

View File

@ -9,11 +9,12 @@ import EventEmitter from '../../utils/events';
import I18n from '../../i18n';
import openLink from '../../utils/openLink';
import { TOnLinkPress } from './interfaces';
import { TSupportedThemes } from '../../theme';
interface ILink {
children: React.ReactElement | null;
link: string;
theme: string;
theme: TSupportedThemes;
onLinkPress?: TOnLinkPress;
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
const style = StyleSheet.create({
@ -23,7 +24,7 @@ interface IListItem {
level: number;
ordered: boolean;
continue: boolean;
theme: string;
theme: TSupportedThemes;
index: number;
}

View File

@ -18,12 +18,12 @@ interface IMarkdownPreview {
}
const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview) => {
const { theme } = useTheme();
if (!msg) {
return null;
}
const { theme } = useTheme();
let m = formatText(msg);
m = formatHyperlink(m);
m = shortnameToUnicode(m);

View File

@ -5,12 +5,13 @@ import { CELL_WIDTH } from './TableCell';
import styles from './styles';
import Navigation from '../../lib/navigation/appNavigation';
import I18n from '../../i18n';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
interface ITable {
children: React.ReactElement | null;
numColumns: number;
theme: string;
theme: TSupportedThemes;
}
const MAX_HEIGHT = 300;

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Text, View, ViewStyle } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles';
@ -8,7 +9,7 @@ interface ITableCell {
align: '' | 'left' | 'center' | 'right';
children: React.ReactElement | null;
isLastCell: boolean;
theme: string;
theme: TSupportedThemes;
}
export const CELL_WIDTH = 100;

View File

@ -1,13 +1,14 @@
import React from 'react';
import { View, ViewStyle } from 'react-native';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
import styles from './styles';
interface ITableRow {
children: React.ReactElement | null;
isLastRow: boolean;
theme: string;
theme: TSupportedThemes;
}
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => {

View File

@ -23,13 +23,14 @@ import { formatText } from './formatText';
import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { formatHyperlink } from './formatHyperlink';
import { TSupportedThemes } from '../../theme';
import { themes } from '../../lib/constants';
export { default as MarkdownPreview } from './Preview';
interface IMarkdownProps {
msg?: string;
theme: string;
msg?: string | null;
theme: TSupportedThemes;
md?: MarkdownAST;
mentions?: IUserMention[];
getCustomEmoji?: TGetCustomEmoji;

View File

@ -4,7 +4,7 @@ import { createImageProgress } from 'react-native-image-progress';
import * as Progress from 'react-native-progress';
import FastImage from '@rocket.chat/react-native-fast-image';
import { useTheme } from '../../../theme';
import { TSupportedThemes, useTheme } from '../../../theme';
import { themes } from '../../../lib/constants';
import styles from '../../message/styles';
@ -14,7 +14,7 @@ interface IImageProps {
type TMessageImage = {
img: string;
theme: string;
theme: TSupportedThemes;
};
const ImageProgress = createImageProgress(FastImage);

View File

@ -24,11 +24,12 @@ export type TElement = {
};
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme();
if (!attachment.actions) {
return null;
}
const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme();
const attachedButtons = attachment.actions.map((element: TElement) => {
const onPress = () => {
@ -57,12 +58,12 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
const Attachments: React.FC<IMessageAttachments> = React.memo(
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
const { theme } = useTheme();
if (!attachments || attachments.length === 0) {
return null;
}
const { theme } = useTheme();
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
if (file && file.image_url) {
return (

View File

@ -18,11 +18,12 @@ import ActivityIndicator from '../ActivityIndicator';
import { withDimensions } from '../../dimensions';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
import { TSupportedThemes } from '../../theme';
interface IButton {
loading: boolean;
paused: boolean;
theme: string;
theme: TSupportedThemes;
disabled?: boolean;
onPress: () => void;
}
@ -31,7 +32,7 @@ interface IMessageAudioProps {
file: IAttachment;
isReply?: boolean;
style?: StyleProp<TextStyle>[];
theme: string;
theme: TSupportedThemes;
getCustomEmoji: TGetCustomEmoji;
scale?: number;
}
@ -264,6 +265,13 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
return null;
}
let thumbColor;
if (isAndroid && isReply) {
thumbColor = themes[theme].tintDisabled;
} else if (isAndroid) {
thumbColor = themes[theme].tintColor;
}
return (
<>
<Markdown
@ -286,7 +294,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
value={currentTime}
maximumValue={duration}
minimumValue={0}
thumbTintColor={isReply && isAndroid ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
thumbTintColor={thumbColor}
minimumTrackTintColor={themes[theme].tintColor}
maximumTrackTintColor={themes[theme].auxiliaryText}
onValueChange={this.onValueChange}

View File

@ -82,11 +82,13 @@ interface IMessageReply {
const Fields = React.memo(
({ attachment, getCustomEmoji }: IMessageFields) => {
const { theme } = useTheme();
const { baseUrl, user } = useContext(MessageContext);
if (!attachment.fields) {
return null;
}
const { baseUrl, user } = useContext(MessageContext);
const { theme } = useTheme();
return (
<>
{attachment.fields.map(field => (
@ -114,11 +116,12 @@ const Fields = React.memo(
const CollapsibleQuote = React.memo(
({ attachment, index, getCustomEmoji }: IMessageReply) => {
const { theme } = useTheme();
const [collapsed, setCollapsed] = useState(attachment?.collapsed);
if (!attachment) {
return null;
}
const [collapsed, setCollapsed] = useState(attachment.collapsed);
const { theme } = useTheme();
const onPress = () => {
setCollapsed(!collapsed);

View File

@ -16,6 +16,8 @@ import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
const Content = React.memo(
(props: IMessageContent) => {
const { theme } = useTheme();
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
if (props.isInfo) {
// @ts-ignore
const infoMessage = getInfoMessage({ ...props });
@ -49,7 +51,6 @@ const Content = React.memo(
} else if (isPreview) {
content = <MarkdownPreview msg={props.msg} />;
} else {
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
content = (
<Markdown
msg={props.msg}

View File

@ -10,11 +10,12 @@ import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
const Encrypted = React.memo(({ type }: { type: string }) => {
const { theme } = useTheme();
const { onEncryptedPress } = useContext(MessageContext);
if (type !== E2E_MESSAGE_TYPE) {
return null;
}
const { onEncryptedPress } = useContext(MessageContext);
return (
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />

View File

@ -12,14 +12,14 @@ import { themes } from '../../lib/constants';
import MessageContext from './Context';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
import { useTheme } from '../../theme';
import { TSupportedThemes, useTheme } from '../../theme';
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
interface IMessageButton {
children: React.ReactElement;
disabled?: boolean;
onPress: () => void;
theme: string;
theme: TSupportedThemes;
}
interface IMessageImage {
@ -43,7 +43,7 @@ const Button = React.memo(({ children, onPress, disabled, theme }: IMessageButto
</Touchable>
));
export const MessageImage = React.memo(({ imgUri, theme }: { imgUri: string; theme: string }) => (
export const MessageImage = React.memo(({ imgUri, theme }: { imgUri: string; theme: TSupportedThemes }) => (
<ImageProgress
style={[styles.image, { borderColor: themes[theme].borderColor }]}
source={{ uri: encodeURI(imgUri) }}

View File

@ -110,6 +110,9 @@ const Message = React.memo((props: IMessage) => {
Message.displayName = 'Message';
const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
const { onPress, onLongPress } = useContext(MessageContext);
const { theme } = useTheme();
if (props.hasError) {
return (
<View>
@ -117,15 +120,13 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
</View>
);
}
const { onPress, onLongPress } = useContext(MessageContext);
const { theme } = useTheme();
return (
<Touchable
onLongPress={onLongPress}
onPress={onPress}
disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp || props.type === 'jitsi_call_started'}
style={{ backgroundColor: props.highlighted ? themes[theme].headerBackground : null }}>
style={{ backgroundColor: props.highlighted ? themes[theme].headerBackground : undefined }}>
<View>
<Message {...props} />
</View>

View File

@ -11,11 +11,12 @@ import { useTheme } from '../../theme';
const MessageError = React.memo(
({ hasError }: { hasError: boolean }) => {
const { theme } = useTheme();
const { onErrorPress } = useContext(MessageContext);
if (!hasError) {
return null;
}
const { onErrorPress } = useContext(MessageContext);
return (
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />

View File

@ -7,7 +7,7 @@ import styles from './styles';
import Emoji from './Emoji';
import { BUTTON_HIT_SLOP } from './utils';
import { themes } from '../../lib/constants';
import { useTheme } from '../../theme';
import { TSupportedThemes, useTheme } from '../../theme';
import MessageContext from './Context';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { testProps } from '../../lib/methods/testProps';
@ -21,7 +21,7 @@ interface IReaction {
interface IMessageReaction {
reaction: IReaction;
getCustomEmoji: TGetCustomEmoji;
theme: string;
theme: TSupportedThemes;
}
interface IMessageReactions {
@ -29,7 +29,7 @@ interface IMessageReactions {
getCustomEmoji: TGetCustomEmoji;
}
const AddReaction = React.memo(({ theme }: { theme: string }) => {
const AddReaction = React.memo(({ theme }: { theme: TSupportedThemes }) => {
const { reactionInit } = useContext(MessageContext);
return (
<Touchable

View File

@ -12,16 +12,7 @@ import { testProps } from '../../lib/methods/testProps';
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
const { theme } = useTheme();
if (!tmid || !isHeader) {
return null;
}
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
const fetch = async () => {
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
setMsg(threadName);
};
useEffect(() => {
if (!msg) {
@ -29,6 +20,15 @@ const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncry
}
}, []);
if (!tmid || !isHeader) {
return null;
}
const fetch = async () => {
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
setMsg(threadName);
};
if (!msg) {
return null;
}

View File

@ -15,7 +15,7 @@ import { IAttachment } from '../../definitions/IAttachment';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import RCActivityIndicator from '../ActivityIndicator';
import Attachments from './Attachments';
import { useTheme } from '../../theme';
import { TSupportedThemes, useTheme } from '../../theme';
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
const styles = StyleSheet.create({
@ -77,8 +77,6 @@ const styles = StyleSheet.create({
marginBottom: 4
},
image: {
// @ts-ignore TODO - check with the team, change this to undefined
width: null,
height: 200,
flex: 1,
borderTopLeftRadius: 4,
@ -100,26 +98,38 @@ interface IMessageReply {
getCustomEmoji: TGetCustomEmoji;
}
const Title = React.memo(({ attachment, timeFormat, theme }: { attachment: IAttachment; timeFormat?: string; theme: string }) => {
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
return (
<View style={styles.authorContainer}>
{attachment.author_name ? (
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
) : null}
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
</View>
);
});
const Title = React.memo(
({ attachment, timeFormat, theme }: { attachment: IAttachment; timeFormat?: string; theme: TSupportedThemes }) => {
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
return (
<View style={styles.authorContainer}>
{attachment.author_name ? (
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
) : null}
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
</View>
);
}
);
const Description = React.memo(
({ attachment, getCustomEmoji, theme }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji; theme: string }) => {
({
attachment,
getCustomEmoji,
theme
}: {
attachment: IAttachment;
getCustomEmoji: TGetCustomEmoji;
theme: TSupportedThemes;
}) => {
const { baseUrl, user } = useContext(MessageContext);
const text = attachment.text || attachment.title;
if (!text) {
return null;
}
const { baseUrl, user } = useContext(MessageContext);
return (
<Markdown
msg={text}
@ -147,10 +157,12 @@ const Description = React.memo(
const UrlImage = React.memo(
({ image }: { image?: string }) => {
const { baseUrl, user } = useContext(MessageContext);
if (!image) {
return null;
}
const { baseUrl, user } = useContext(MessageContext);
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
},
@ -158,12 +170,21 @@ const UrlImage = React.memo(
);
const Fields = React.memo(
({ attachment, theme, getCustomEmoji }: { attachment: IAttachment; theme: string; getCustomEmoji: TGetCustomEmoji }) => {
({
attachment,
theme,
getCustomEmoji
}: {
attachment: IAttachment;
theme: TSupportedThemes;
getCustomEmoji: TGetCustomEmoji;
}) => {
const { baseUrl, user } = useContext(MessageContext);
if (!attachment.fields) {
return null;
}
const { baseUrl, user } = useContext(MessageContext);
return (
<View style={styles.fieldsContainer}>
{attachment.fields.map(field => (
@ -189,13 +210,12 @@ const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
const [loading, setLoading] = useState(false);
const { theme } = useTheme();
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
if (!attachment) {
return null;
}
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
const onPress = async () => {
let url = attachment.title_link || attachment.author_link;
if (attachment.message_link) {

View File

@ -13,12 +13,12 @@ import { testProps } from '../../lib/methods/testProps';
const Thread = React.memo(
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
const { theme } = useTheme();
const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext);
if (!tlm || isThreadRoom || tcount === 0) {
return null;
}
const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext);
return (
<View style={styles.buttonContainer}>
<View

View File

@ -8,7 +8,7 @@ import Touchable from './Touchable';
import openLink from '../../utils/openLink';
import sharedStyles from '../../views/Styles';
import { themes } from '../../lib/constants';
import { useTheme, withTheme } from '../../theme';
import { TSupportedThemes, useTheme, withTheme } from '../../theme';
import { LISTENER } from '../Toast';
import EventEmitter from '../../utils/events';
import I18n from '../../i18n';
@ -53,10 +53,12 @@ const styles = StyleSheet.create({
const UrlImage = React.memo(
({ image }: { image: string }) => {
const { baseUrl, user } = useContext(MessageContext);
if (!image) {
return null;
}
const { baseUrl, user } = useContext(MessageContext);
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
},
@ -64,7 +66,7 @@ const UrlImage = React.memo(
);
const UrlContent = React.memo(
({ title, description, theme }: { title: string; description: string; theme: string }) => (
({ title, description, theme }: { title: string; description: string; theme: TSupportedThemes }) => (
<View style={styles.textContainer}>
{title ? (
<Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}>
@ -93,7 +95,7 @@ const UrlContent = React.memo(
);
const Url = React.memo(
({ url, index, theme }: { url: IUrl; index: number; theme: string }) => {
({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => {
if (!url || url?.ignoreParse) {
return null;
}

View File

@ -57,9 +57,10 @@ interface IMessageUser {
const User = React.memo(
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
const { user } = useContext(MessageContext);
const { theme } = useTheme();
if (isHeader || hasError) {
const { user } = useContext(MessageContext);
const { theme } = useTheme();
const username = (useRealName && author?.name) || author?.username;
const aliasUsername = alias ? (
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>

View File

@ -6,7 +6,7 @@ import Message from './Message';
import MessageContext from './Context';
import debounce from '../../utils/debounce';
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
import { useTheme, withTheme } from '../../theme';
import { TSupportedThemes, withTheme } from '../../theme';
import openLink from '../../utils/openLink';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment, TAnyMessageModel } from '../../definitions';
@ -57,6 +57,7 @@ interface IMessageContainerProps {
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
jumpToMessage?: (link: string) => void;
onPress?: () => void;
theme: TSupportedThemes;
}
interface IMessageContainerState {
@ -72,7 +73,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
archived: false,
broadcast: false,
isIgnored: false,
theme: 'light'
theme: 'light' as TSupportedThemes
};
state = { isManualUnignored: false };
@ -292,8 +293,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
};
onLinkPress = (link: string): void => {
const { theme } = useTheme();
const { item, jumpToMessage } = this.props;
const { item, jumpToMessage, theme } = this.props;
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
if (isMessageLink && jumpToMessage) {
return jumpToMessage(link);
@ -354,7 +354,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
blocks,
autoTranslate: autoTranslateMessage,
replies,
md
md,
comment
} = item;
let message = msg;
@ -435,6 +436,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
callJitsi={callJitsi}
blockAction={blockAction}
highlighted={highlighted}
comment={comment}
/>
</MessageContext.Provider>
);

View File

@ -59,6 +59,7 @@ export interface IMessageContent {
useRealName?: boolean;
isIgnored: boolean;
type: string;
comment?: string;
}
export interface IMessageEmoji {

View File

@ -2,7 +2,7 @@ import { TMessageModel } from '../../definitions/IMessage';
import I18n from '../../i18n';
import { DISCUSSION } from './constants';
export const formatMessageCount = (count?: number, type?: string): string => {
export const formatMessageCount = (count?: number, type?: string): string | null => {
const discussion = type === DISCUSSION;
let text = discussion ? I18n.t('No_messages_yet') : null;
if (!count) {
@ -55,7 +55,9 @@ export const SYSTEM_MESSAGES = [
'user-converted-to-team',
'user-converted-to-channel',
'user-deleted-room-from-team',
'user-removed-room-from-team'
'user-removed-room-from-team',
'omnichannel_placed_chat_on_hold',
'omnichannel_on_hold_chat_resumed'
];
export const SYSTEM_MESSAGE_TYPES = {
@ -73,7 +75,9 @@ export const SYSTEM_MESSAGE_TYPES = {
CONVERTED_TO_TEAM: 'user-converted-to-team',
CONVERTED_TO_CHANNEL: 'user-converted-to-channel',
DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team'
REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team',
OMNICHANNEL_PLACED_CHAT_ON_HOLD: 'omnichannel_placed_chat_on_hold',
OMNICHANNEL_ON_HOLD_CHAT_RESUMED: 'omnichannel_on_hold_chat_resumed'
};
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
@ -99,9 +103,10 @@ type TInfoMessage = {
role: string;
msg: string;
author: { username: string };
comment?: string;
};
export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): string => {
export const getInfoMessage = ({ type, role, msg, author, comment }: TInfoMessage): string => {
const { username } = author;
if (type === 'rm') {
return I18n.t('Message_removed');
@ -193,6 +198,12 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): strin
if (type === 'user-removed-room-from-team') {
return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
}
if (type === 'omnichannel_placed_chat_on_hold') {
return I18n.t('Omnichannel_placed_chat_on_hold', { comment });
}
if (type === 'omnichannel_on_hold_chat_resumed') {
return I18n.t('Omnichannel_on_hold_chat_resumed', { comment });
}
return '';
};

View File

@ -1,11 +1,12 @@
import { TextInput } from 'react-native';
import { TSupportedThemes } from '../theme';
import { ILivechatVisitor } from './ILivechatVisitor';
import { ISubscription } from './ISubscription';
export interface ITitle {
title: string;
theme: string;
theme: TSupportedThemes;
}
export interface IInputs {

View File

@ -42,16 +42,16 @@ export interface ITranslations {
export type E2EType = 'pending' | 'done';
export interface ILastMessage {
_id: string;
rid: string;
_id?: string;
rid?: string;
tshow?: boolean;
t?: MessageType;
tmid?: string;
msg?: string;
e2e?: E2EType;
ts: string | Date;
ts?: string | Date;
u: IUserMessage;
_updatedAt: string | Date;
_updatedAt?: string | Date;
urls?: IUrlFromServer[];
mentions?: IUserMention[];
channels?: IUserChannel[];
@ -59,7 +59,9 @@ export interface ILastMessage {
attachments?: IAttachment[];
reactions?: IReaction[];
unread?: boolean;
pinned?: boolean;
status?: number;
token?: string;
}
interface IMessageFile {
@ -140,6 +142,7 @@ export interface IMessage extends IMessageFromServer {
blocks?: any;
e2e?: E2EType;
tshow?: boolean;
comment?: string;
subscription?: { id: string };
}

View File

@ -1,6 +1,7 @@
import { StackNavigationProp } from '@react-navigation/stack';
import React from 'react';
import { TSupportedThemes } from '../theme';
import { ProfileStackParamList } from '../stacks/types';
import { IUser } from './IUser';
@ -34,7 +35,7 @@ export interface IProfileViewProps {
Accounts_AllowUsernameChange: boolean;
Accounts_CustomFields: string;
setUser: Function;
theme: string;
theme: TSupportedThemes;
}
export interface IAvatar {

View File

@ -55,6 +55,8 @@ export interface IRoom {
uids: Array<string>;
lm?: Date;
sysMes?: string[];
onHold?: boolean;
waitingResponse?: boolean;
}
export enum OmnichannelSourceType {
@ -65,6 +67,24 @@ export enum OmnichannelSourceType {
API = 'api',
OTHER = 'other' // catch-all source type
}
export interface IOmnichannelSource {
// The source, or client, which created the Omnichannel room
type: OmnichannelSourceType;
// An optional identification of external sources, such as an App
id?: string;
// A human readable alias that goes with the ID, for post analytical purposes
alias?: string;
// A label to be shown in the room info
label?: string;
// The sidebar icon
sidebarIcon?: string;
// The default sidebar icon
defaultIcon?: string;
_updatedAt?: Date;
queuedAt?: Date;
}
export interface IOmnichannelRoom extends Partial<Omit<IRoom, 'default' | 'featured' | 'broadcast'>> {
_id: string;
rid: string;
@ -77,23 +97,7 @@ export interface IOmnichannelRoom extends Partial<Omit<IRoom, 'default' | 'featu
replyTo: string;
subject: string;
};
source: {
// TODO: looks like this is not so required as the definition suggests
// The source, or client, which created the Omnichannel room
type: OmnichannelSourceType;
// An optional identification of external sources, such as an App
id?: string;
// A human readable alias that goes with the ID, for post analytical purposes
alias?: string;
// A label to be shown in the room info
label?: string;
// The sidebar icon
sidebarIcon?: string;
// The default sidebar icon
defaultIcon?: string;
_updatedAt?: Date;
queuedAt?: Date;
};
source: IOmnichannelSource;
transcriptRequest?: IRequestTranscript;
servedBy?: IServedBy;
onHold?: boolean;

View File

@ -3,7 +3,7 @@ import Relation from '@nozbe/watermelondb/Relation';
import { ILastMessage, TMessageModel } from './IMessage';
import { IRocketChatRecord } from './IRocketChatRecord';
import { RoomID, RoomType } from './IRoom';
import { IOmnichannelSource, RoomID, RoomType } from './IRoom';
import { IServedBy } from './IServedBy';
import { TThreadModel } from './IThread';
import { TThreadMessageModel } from './IThreadMessage';
@ -98,6 +98,8 @@ export interface ISubscription {
teamMain?: boolean;
unsubscribe: () => Promise<any>;
separator?: boolean;
onHold?: boolean;
source?: IOmnichannelSource;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: RelationModified<TMessageModel>;
threads: RelationModified<TThreadModel>;

View File

@ -2,7 +2,7 @@ import { RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
import { TColors } from '../theme';
import { TColors, TSupportedThemes } from '../theme';
export * from './IAttachment';
export * from './INotification';
@ -36,7 +36,7 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
dispatch: Dispatch;
isMasterDetail: boolean;
// TODO: remove after migrating all Class components
theme: string;
theme: TSupportedThemes;
colors: TColors;
}

View File

@ -0,0 +1,46 @@
import React from 'react';
import { View, Text } from 'react-native';
import { useTheme } from '../../../../theme';
import { themes } from '../../../../lib/constants';
import { CustomIcon } from '../../../../lib/Icons';
import * as List from '../../../../containers/List';
import styles from './styles';
import UnreadBadge from '../../../../containers/UnreadBadge';
import i18n from '../../../../i18n';
interface IOmnichannelQueue {
queueSize?: number;
onPress(): void;
}
const OmnichannelQueue = ({ queueSize, onPress }: IOmnichannelQueue) => {
const { theme } = useTheme();
return (
<>
<List.Item
title='Omnichannel_queue'
heightContainer={50}
left={() => <List.Icon name='queue' size={24} color={themes[theme].auxiliaryTintColor} />}
color={themes[theme].bodyText}
onPress={queueSize ? onPress : undefined}
styleTitle={styles.titleOmnichannelQueue}
right={() => (
<View style={styles.omnichannelRightContainer}>
{queueSize ? (
<>
<UnreadBadge style={[styles.queueIcon, { backgroundColor: themes[theme].tintColor }]} unread={queueSize} />
<CustomIcon name='chevron-right' style={styles.actionIndicator} color={themes[theme].bodyText} size={24} />
</>
) : (
<Text style={[styles.emptyText, { color: themes[theme].auxiliaryTintColor }]}>{i18n.t('Empty')}</Text>
)}
</View>
)}
/>
<List.Separator />
</>
);
};
export default OmnichannelQueue;

View File

@ -0,0 +1,77 @@
import React, { memo, useEffect, useState } from 'react';
import { Switch, View } from 'react-native';
import * as List from '../../../../containers/List';
import styles from './styles';
import { SWITCH_TRACK_COLOR, themes } from '../../../../lib/constants';
import { useTheme } from '../../../../theme';
import RocketChat from '../../../../lib/rocketchat';
import { IUser } from '../../../../definitions/IUser';
import { showConfirmationAlert } from '../../../../utils/info';
import I18n from '../../../../i18n';
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../../lib';
import OmnichannelQueue from './OmnichannelQueue';
interface IOmnichannelStatus {
searching: boolean;
goQueue: () => void;
queueSize: number;
inquiryEnabled: boolean;
user: IUser;
}
const OmnichannelStatus = memo(({ searching, goQueue, queueSize, user }: IOmnichannelStatus) => {
const { theme } = useTheme();
const [status, setStatus] = useState(isOmnichannelStatusAvailable(user));
useEffect(() => {
setStatus(isOmnichannelStatusAvailable(user));
}, [user.statusLivechat]);
if (searching || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) {
return null;
}
const toggleLivechat = async () => {
// if not-available, prompt to change to available
if (!isOmnichannelStatusAvailable(user)) {
showConfirmationAlert({
message: I18n.t('Omnichannel_enable_alert'),
confirmationText: I18n.t('Yes'),
onPress: async () => {
try {
await changeLivechatStatus();
} catch {
// Do nothing
}
}
});
} else {
try {
setStatus(v => !v);
await changeLivechatStatus();
} catch {
setStatus(v => !v);
}
}
};
return (
<>
<List.Item
title='Omnichannel'
color={themes[theme].bodyText}
onPress={toggleLivechat}
right={() => (
<View style={styles.omnichannelRightContainer}>
<Switch value={status} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleLivechat} />
</View>
)}
/>
<List.Separator />
{status ? <OmnichannelQueue queueSize={queueSize} onPress={goQueue} /> : null}
</>
);
});
export default OmnichannelStatus;

View File

@ -0,0 +1,23 @@
import { I18nManager, StyleSheet } from 'react-native';
import sharedStyles from '../../../../views/Styles';
export default StyleSheet.create({
queueIcon: {
marginHorizontal: 10
},
omnichannelRightContainer: {
flexDirection: 'row',
alignItems: 'center'
},
titleOmnichannelQueue: {
...sharedStyles.textMedium
},
emptyText: {
...sharedStyles.textRegular,
fontSize: 12
},
actionIndicator: {
...(I18nManager.isRTL ? { transform: [{ rotate: '180deg' }] } : {})
}
});

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