Merge branch 'develop' into appium-v2
This commit is contained in:
commit
3eff53c707
|
@ -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:
|
||||
|
|
10
.eslintrc.js
10
.eslintrc.js
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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}
|
||||
|
|
|
@ -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: '' })}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -23,4 +23,5 @@ export interface IAvatar {
|
|||
rid?: string;
|
||||
blockUnauthenticatedAccess?: boolean;
|
||||
serverVersion: string | null;
|
||||
externalProviderUrl?: string;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 }]}>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -19,7 +19,7 @@ interface IPasscodeBase {
|
|||
type: string;
|
||||
previousPasscode?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
subtitle?: string | null;
|
||||
showBiometry?: boolean;
|
||||
onEndProcess: Function;
|
||||
onError?: Function;
|
||||
|
|
|
@ -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('');
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -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
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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],
|
|
@ -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>
|
||||
);
|
|
@ -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,
|
|
@ -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}
|
|
@ -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 }]}>
|
|
@ -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'
|
|
@ -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);
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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}>
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
},
|
|
@ -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']}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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;
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}`}
|
||||
|
|
|
@ -26,7 +26,7 @@ import Input from './Input';
|
|||
import styles from './styles';
|
||||
|
||||
export interface IItemData {
|
||||
value: string;
|
||||
value: any;
|
||||
text: { text: string };
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
|
@ -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;
|
|
@ -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) => (
|
|
@ -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) => (
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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) }}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -59,6 +59,7 @@ export interface IMessageContent {
|
|||
useRealName?: boolean;
|
||||
isIgnored: boolean;
|
||||
type: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface IMessageEmoji {
|
||||
|
|
|
@ -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 '';
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
Loading…
Reference in New Issue