Merge branch 'develop' into appium-v2
This commit is contained in:
commit
3eff53c707
|
@ -339,7 +339,7 @@ jobs:
|
||||||
lint-testunit:
|
lint-testunit:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:15
|
- image: cimg/node:16.14
|
||||||
resource_class: large
|
resource_class: large
|
||||||
environment:
|
environment:
|
||||||
CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6
|
CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6
|
||||||
|
@ -372,7 +372,7 @@ jobs:
|
||||||
android-build-experimental:
|
android-build-experimental:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-29-node
|
- image: cimg/android:2022.03.1-node
|
||||||
environment:
|
environment:
|
||||||
<<: *android-env
|
<<: *android-env
|
||||||
<<: *bash-env
|
<<: *bash-env
|
||||||
|
@ -383,7 +383,7 @@ jobs:
|
||||||
android-build-official:
|
android-build-official:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-29-node
|
- image: cimg/android:2022.03.1-node
|
||||||
environment:
|
environment:
|
||||||
<<: *android-env
|
<<: *android-env
|
||||||
<<: *bash-env
|
<<: *bash-env
|
||||||
|
@ -394,7 +394,7 @@ jobs:
|
||||||
android-internal-app-sharing-experimental:
|
android-internal-app-sharing-experimental:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-28-node
|
- image: cimg/android:2022.03.1-node
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- upload-to-internal-app-sharing
|
- upload-to-internal-app-sharing
|
||||||
|
@ -402,7 +402,7 @@ jobs:
|
||||||
android-google-play-beta-experimental:
|
android-google-play-beta-experimental:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-29-node
|
- image: cimg/android:2022.03.1-node
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- upload-to-google-play-beta:
|
- upload-to-google-play-beta:
|
||||||
|
@ -411,14 +411,14 @@ jobs:
|
||||||
android-google-play-production-experimental:
|
android-google-play-production-experimental:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-29-node
|
- image: cimg/android:2022.03.1-node
|
||||||
steps:
|
steps:
|
||||||
- upload-to-google-play-production
|
- upload-to-google-play-production
|
||||||
|
|
||||||
android-google-play-beta-official:
|
android-google-play-beta-official:
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-29-node
|
- image: cimg/android:2022.03.1-node
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- upload-to-google-play-beta:
|
- upload-to-google-play-beta:
|
||||||
|
|
10
.eslintrc.js
10
.eslintrc.js
|
@ -17,7 +17,7 @@ module.exports = {
|
||||||
legacyDecorators: true
|
legacyDecorators: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest'],
|
plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel', 'jest', 'react-hooks'],
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
commonjs: true,
|
commonjs: true,
|
||||||
|
@ -148,7 +148,9 @@ module.exports = {
|
||||||
'no-async-promise-executor': [0],
|
'no-async-promise-executor': [0],
|
||||||
'max-classes-per-file': [0],
|
'max-classes-per-file': [0],
|
||||||
'no-multiple-empty-lines': [0],
|
'no-multiple-empty-lines': [0],
|
||||||
'no-sequences': 'off'
|
'no-sequences': 'off',
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn'
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
__DEV__: true
|
__DEV__: true
|
||||||
|
@ -237,7 +239,9 @@ module.exports = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'new-cap': 'off',
|
'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: {
|
globals: {
|
||||||
JSX: true
|
JSX: true
|
||||||
|
|
|
@ -144,7 +144,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "4.26.2"
|
versionName "4.27.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
|
@ -282,6 +282,9 @@ dependencies {
|
||||||
playImplementation project(':@react-native-firebase_app')
|
playImplementation project(':@react-native-firebase_app')
|
||||||
playImplementation project(':@react-native-firebase_analytics')
|
playImplementation project(':@react-native-firebase_analytics')
|
||||||
playImplementation project(':@react-native-firebase_crashlytics')
|
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"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
//noinspection GradleDynamicVersion
|
//noinspection GradleDynamicVersion
|
||||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
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 { NavigationContainer } from '@react-navigation/native';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -27,21 +27,23 @@ const SetUsernameStack = () => (
|
||||||
|
|
||||||
// App
|
// App
|
||||||
const Stack = createStackNavigator<StackParamList>();
|
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) {
|
if (!root) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = React.useContext(ThemeContext);
|
|
||||||
const navTheme = navigationTheme(theme);
|
const navTheme = navigationTheme(theme);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const state = Navigation.navigationRef.current?.getRootState();
|
|
||||||
const currentRouteName = getActiveRouteName(state);
|
|
||||||
Navigation.routeNameRef.current = currentRouteName;
|
|
||||||
setCurrentScreen(currentRouteName);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer
|
<NavigationContainer
|
||||||
theme={navTheme}
|
theme={navTheme}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { themes } from '../lib/constants';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { getReadableVersion } from '../utils/deviceInfo';
|
import { getReadableVersion } from '../utils/deviceInfo';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
import { TSupportedThemes } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
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}>
|
<View style={styles.container}>
|
||||||
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>
|
<Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>
|
||||||
{I18n.t('Version_no', { version: '' })}
|
{I18n.t('Version_no', { version: '' })}
|
||||||
|
|
|
@ -29,14 +29,15 @@ const Avatar = React.memo(
|
||||||
text,
|
text,
|
||||||
size = 25,
|
size = 25,
|
||||||
borderRadius = 4,
|
borderRadius = 4,
|
||||||
type = SubscriptionType.DIRECT
|
type = SubscriptionType.DIRECT,
|
||||||
|
externalProviderUrl
|
||||||
}: IAvatar) => {
|
}: IAvatar) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if ((!text && !avatar && !emoji && !rid) || !server) {
|
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const avatarStyle = {
|
const avatarStyle = {
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
|
@ -68,7 +69,8 @@ const Avatar = React.memo(
|
||||||
avatarETag,
|
avatarETag,
|
||||||
serverVersion,
|
serverVersion,
|
||||||
rid,
|
rid,
|
||||||
blockUnauthenticatedAccess
|
blockUnauthenticatedAccess,
|
||||||
|
externalProviderUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,10 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) {
|
shouldComponentUpdate(nextProps: IAvatar, nextState: { avatarETag: string }) {
|
||||||
const { avatarETag } = this.state;
|
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) {
|
if (nextState.avatarETag !== avatarETag) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -42,6 +45,10 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
if (nextProps.type !== type) {
|
if (nextProps.type !== type) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (nextProps.size !== size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +107,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
blockUnauthenticatedAccess:
|
blockUnauthenticatedAccess:
|
||||||
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
|
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
|
||||||
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
|
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
|
||||||
true
|
true,
|
||||||
|
externalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps)(AvatarContainer);
|
export default connect(mapStateToProps)(AvatarContainer);
|
||||||
|
|
|
@ -23,4 +23,5 @@ export interface IAvatar {
|
||||||
rid?: string;
|
rid?: string;
|
||||||
blockUnauthenticatedAccess?: boolean;
|
blockUnauthenticatedAccess?: boolean;
|
||||||
serverVersion: string | null;
|
serverVersion: string | null;
|
||||||
|
externalProviderUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { ButtonProps, StyleSheet, Text } from 'react-native';
|
import { ButtonProps, StyleSheet, Text } from 'react-native';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
@ -14,7 +15,7 @@ interface IButtonProps extends ButtonProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
color: string;
|
color: string;
|
||||||
fontSize: any;
|
fontSize: any;
|
||||||
style: any;
|
style: any;
|
||||||
|
|
|
@ -6,14 +6,14 @@ import Avatar from '../Avatar';
|
||||||
import RoomTypeIcon from '../RoomTypeIcon';
|
import RoomTypeIcon from '../RoomTypeIcon';
|
||||||
import styles, { ROW_HEIGHT } from './styles';
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
|
|
||||||
export { ROW_HEIGHT };
|
export { ROW_HEIGHT };
|
||||||
|
|
||||||
interface IDirectoryItemLabel {
|
interface IDirectoryItemLabel {
|
||||||
text?: string;
|
text?: string;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IDirectoryItem {
|
interface IDirectoryItem {
|
||||||
|
@ -55,7 +55,7 @@ const DirectoryItem = ({
|
||||||
<Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} />
|
<Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} />
|
||||||
<View style={styles.directoryItemTextContainer}>
|
<View style={styles.directoryItemTextContainer}>
|
||||||
<View style={styles.directoryItemTextTitle}>
|
<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}>
|
<Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -4,13 +4,14 @@ import { Text, TouchableOpacity, View } from 'react-native';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface ITabBarProps {
|
interface ITabBarProps {
|
||||||
goToPage: Function;
|
goToPage: Function;
|
||||||
activeTab: number;
|
activeTab: number;
|
||||||
tabs: [];
|
tabs: [];
|
||||||
tabEmojiStyle: object;
|
tabEmojiStyle: object;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TabBar extends React.Component<Partial<ITabBarProps>> {
|
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 shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
const scrollProps = {
|
const scrollProps = {
|
||||||
|
@ -30,7 +30,7 @@ interface IEmojiPickerProps {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
customEmojis?: any;
|
customEmojis?: any;
|
||||||
style: object;
|
style: object;
|
||||||
theme?: string;
|
theme: TSupportedThemes;
|
||||||
onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void);
|
onEmojiSelected?: ((emoji: any) => void) | ((keyboardId: any, params?: any) => void);
|
||||||
tabEmojiStyle?: object;
|
tabEmojiStyle?: object;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
||||||
/* @ts-ignore*/
|
/* @ts-ignore*/
|
||||||
contentProps={scrollProps}
|
contentProps={scrollProps}
|
||||||
style={{ backgroundColor: themes[theme!].focusedBackground }}>
|
style={{ backgroundColor: themes[theme].focusedBackground }}>
|
||||||
{categories.tabs.map((tab, i) =>
|
{categories.tabs.map((tab, i) =>
|
||||||
i === 0 && frequentlyUsed.length === 0
|
i === 0 && frequentlyUsed.length === 0
|
||||||
? null // when no frequentlyUsed don't show the tab
|
? 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 { themes } from '../lib/constants';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from './KeyboardView';
|
||||||
import { useTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
import StatusBar from './StatusBar';
|
import StatusBar from './StatusBar';
|
||||||
import AppVersion from './AppVersion';
|
import AppVersion from './AppVersion';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
import UnreadBadge from '../../presentation/UnreadBadge';
|
import UnreadBadge from '../UnreadBadge';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
badgeContainer: {
|
badgeContainer: {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { ROW_HEIGHT } from '../../presentation/RoomItem';
|
import { ROW_HEIGHT } from '../RoomItem';
|
||||||
import { goRoom } from '../../utils/goRoom';
|
import { goRoom } from '../../utils/goRoom';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { useOrientation } from '../../dimensions';
|
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 {
|
interface IListIcon {
|
||||||
name: string;
|
name: string;
|
||||||
color?: string;
|
color?: string | null;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
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();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.icon, style]}>
|
<View style={[styles.icon, style]}>
|
||||||
<CustomIcon name={name} color={color ?? themes[theme].auxiliaryText} size={ICON_SIZE} {...testProps(testID || '')} />
|
<CustomIcon
|
||||||
|
name={name}
|
||||||
|
color={color ?? themes[theme].auxiliaryText}
|
||||||
|
size={size ?? ICON_SIZE}
|
||||||
|
{...testProps(testID || '')}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
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 Touch from '../../utils/touch';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { Icon } from '.';
|
import { Icon } from '.';
|
||||||
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
|
import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
|
||||||
|
@ -60,13 +60,15 @@ interface IListItemContent {
|
||||||
left?: () => JSX.Element | null;
|
left?: () => JSX.Element | null;
|
||||||
right?: () => JSX.Element | null;
|
right?: () => JSX.Element | null;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
translateTitle?: boolean;
|
translateTitle?: boolean;
|
||||||
translateSubtitle?: boolean;
|
translateSubtitle?: boolean;
|
||||||
showActionIndicator?: boolean;
|
showActionIndicator?: boolean;
|
||||||
alert?: boolean;
|
alert?: boolean;
|
||||||
|
heightContainer?: number;
|
||||||
|
styleTitle?: StyleProp<TextStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Content = React.memo(
|
const Content = React.memo(
|
||||||
|
@ -82,19 +84,21 @@ const Content = React.memo(
|
||||||
translateTitle = true,
|
translateTitle = true,
|
||||||
translateSubtitle = true,
|
translateSubtitle = true,
|
||||||
showActionIndicator = false,
|
showActionIndicator = false,
|
||||||
theme
|
theme,
|
||||||
|
heightContainer,
|
||||||
|
styleTitle
|
||||||
}: IListItemContent) => {
|
}: IListItemContent) => {
|
||||||
const { fontScale } = useDimensions();
|
const { fontScale } = useDimensions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]}
|
style={[styles.container, disabled && styles.disabled, { height: (heightContainer || BASE_HEIGHT) * fontScale }]}
|
||||||
{...testProps(testID || '')}>
|
{...testProps(testID || '')}>
|
||||||
{left ? <View style={styles.leftContainer}>{left()}</View> : null}
|
{left ? <View style={styles.leftContainer}>{left()}</View> : null}
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<View style={styles.textAlertContainer}>
|
<View style={styles.textAlertContainer}>
|
||||||
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>
|
<Text style={[styles.title, styleTitle, { color: color || themes[theme].titleText }]} numberOfLines={1}>
|
||||||
{translateTitle ? I18n.t(title) : title}
|
{translateTitle && title ? I18n.t(title) : title}
|
||||||
</Text>
|
</Text>
|
||||||
{alert ? (
|
{alert ? (
|
||||||
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
|
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
|
||||||
|
@ -124,7 +128,7 @@ interface IListButtonPress extends IListItemButton {
|
||||||
interface IListItemButton {
|
interface IListItemButton {
|
||||||
title?: string;
|
title?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
underlayColor?: string;
|
underlayColor?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated, Modal, StyleSheet, View } from 'react-native';
|
import { Animated, Modal, StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
import { withTheme } from '../theme';
|
import { TSupportedThemes, withTheme } from '../theme';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { testProps } from '../lib/methods/testProps';
|
import { testProps } from '../lib/methods/testProps';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
interface ILoadingProps {
|
interface ILoadingProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
theme?: string;
|
theme?: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILoadingState {
|
interface ILoadingState {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react';
|
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 { connect } from 'react-redux';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
import * as AppleAuthentication from 'expo-apple-authentication';
|
import * as AppleAuthentication from 'expo-apple-authentication';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
|
||||||
import { withTheme } from '../theme';
|
import { TSupportedThemes, withTheme } from '../theme';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
@ -100,7 +100,7 @@ interface ILoginServicesProps {
|
||||||
CAS_enabled: boolean;
|
CAS_enabled: boolean;
|
||||||
CAS_login_url: string;
|
CAS_login_url: string;
|
||||||
separator: boolean;
|
separator: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILoginServicesState {
|
interface ILoginServicesState {
|
||||||
|
@ -410,7 +410,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
|
||||||
const { servicesHeight } = this.state;
|
const { servicesHeight } = this.state;
|
||||||
const { services, separator } = this.props;
|
const { services, separator } = this.props;
|
||||||
const { length } = Object.values(services);
|
const { length } = Object.values(services);
|
||||||
const style = {
|
const style: Animated.AnimatedProps<ViewStyle> = {
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
height: servicesHeight
|
height: servicesHeight
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
import { useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
|
@ -30,12 +30,12 @@ interface THeaderItem {
|
||||||
item: TItem;
|
item: TItem;
|
||||||
onReaction: TOnReaction;
|
onReaction: TOnReaction;
|
||||||
server: string;
|
server: string;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface THeaderFooter {
|
interface THeaderFooter {
|
||||||
onReaction: TOnReaction;
|
onReaction: TOnReaction;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HEADER_HEIGHT = 36;
|
export const HEADER_HEIGHT = 36;
|
||||||
|
|
|
@ -16,10 +16,12 @@ interface IMessageBoxCommandsPreview {
|
||||||
|
|
||||||
const CommandsPreview = React.memo(
|
const CommandsPreview = React.memo(
|
||||||
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
|
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!showCommandPreview) {
|
if (!showCommandPreview) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
{...testProps('commandbox-container')}
|
{...testProps('commandbox-container')}
|
||||||
|
|
|
@ -6,12 +6,12 @@ import { store } from '../../lib/store/auxStore';
|
||||||
import EmojiPicker from '../EmojiPicker';
|
import EmojiPicker from '../EmojiPicker';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
|
|
||||||
interface IMessageBoxEmojiKeyboard {
|
interface IMessageBoxEmojiKeyboard {
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {
|
export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiKeyboard, any> {
|
||||||
|
|
|
@ -17,10 +17,12 @@ interface IMessageBoxMentions {
|
||||||
|
|
||||||
const Mentions = React.memo(
|
const Mentions = React.memo(
|
||||||
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
|
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!trackingType) {
|
if (!trackingType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<View {...testProps('messagebox-container')}>
|
<View {...testProps('messagebox-container')}>
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|
|
@ -11,9 +11,10 @@ import { themes } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { events, logEvent } from '../../utils/log';
|
import { events, logEvent } from '../../utils/log';
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface IMessageBoxRecordAudioProps {
|
interface IMessageBoxRecordAudioProps {
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
permissionToUpload: boolean;
|
permissionToUpload: boolean;
|
||||||
recordingCallback: Function;
|
recordingCallback: Function;
|
||||||
onFinish: Function;
|
onFinish: Function;
|
||||||
|
@ -198,10 +199,11 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
||||||
<BorderlessButton
|
<BorderlessButton
|
||||||
onPress={this.startRecordingAudio}
|
onPress={this.startRecordingAudio}
|
||||||
style={styles.actionButton}
|
style={styles.actionButton}
|
||||||
{...testProps('messagebox-send-audio')}
|
testID='messagebox-send-audio'
|
||||||
// @ts-ignore
|
{...testProps('messagebox-send-audio')}>
|
||||||
accessibilityTraits='button'>
|
<View accessible accessibilityLabel={I18n.t('Send_audio_message')} accessibilityRole='button'>
|
||||||
<CustomIcon name='microphone' size={24} color={themes[theme].auxiliaryTintColor} />
|
<CustomIcon name='microphone' size={24} color={themes[theme].auxiliaryTintColor} />
|
||||||
|
</View>
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -210,23 +212,17 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
||||||
return (
|
return (
|
||||||
<View style={styles.recordingContent}>
|
<View style={styles.recordingContent}>
|
||||||
<View style={styles.textArea}>
|
<View style={styles.textArea}>
|
||||||
<BorderlessButton
|
<BorderlessButton onPress={this.cancelRecordingAudio} style={styles.actionButton}>
|
||||||
onPress={this.cancelRecordingAudio}
|
<View accessible accessibilityLabel={I18n.t('Cancel_recording')} accessibilityRole='button'>
|
||||||
// @ts-ignore
|
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
|
||||||
accessibilityLabel={I18n.t('Cancel_recording')}
|
</View>
|
||||||
accessibilityTraits='button'
|
|
||||||
style={styles.actionButton}>
|
|
||||||
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
|
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.GetLastDuration}</Text>
|
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.GetLastDuration}</Text>
|
||||||
</View>
|
</View>
|
||||||
<BorderlessButton
|
<BorderlessButton onPress={this.finishRecordingAudio} style={styles.actionButton}>
|
||||||
onPress={this.finishRecordingAudio}
|
<View accessible accessibilityLabel={I18n.t('Finish_recording')} accessibilityRole='button'>
|
||||||
// @ts-ignore
|
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
|
||||||
accessibilityLabel={I18n.t('Finish_recording')}
|
</View>
|
||||||
accessibilityTraits='button'
|
|
||||||
style={styles.actionButton}>
|
|
||||||
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
|
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -235,24 +231,18 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
||||||
return (
|
return (
|
||||||
<View style={styles.recordingContent}>
|
<View style={styles.recordingContent}>
|
||||||
<View style={styles.textArea}>
|
<View style={styles.textArea}>
|
||||||
<BorderlessButton
|
<BorderlessButton onPress={this.cancelRecordingAudio} style={styles.actionButton}>
|
||||||
onPress={this.cancelRecordingAudio}
|
<View accessible accessibilityLabel={I18n.t('Cancel_recording')} accessibilityRole='button'>
|
||||||
// @ts-ignore
|
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
|
||||||
accessibilityLabel={I18n.t('Cancel_recording')}
|
</View>
|
||||||
accessibilityTraits='button'
|
|
||||||
style={styles.actionButton}>
|
|
||||||
<CustomIcon size={24} color={themes[theme].dangerColor} name='delete' />
|
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.duration}</Text>
|
<Text style={[styles.recordingDurationText, { color: themes[theme].titleText }]}>{this.duration}</Text>
|
||||||
<CustomIcon size={24} color={themes[theme].dangerColor} name='record' />
|
<CustomIcon size={24} color={themes[theme].dangerColor} name='record' />
|
||||||
</View>
|
</View>
|
||||||
<BorderlessButton
|
<BorderlessButton onPress={this.finishRecordingAudio} style={styles.actionButton}>
|
||||||
onPress={this.finishRecordingAudio}
|
<View accessible accessibilityLabel={I18n.t('Finish_recording')} accessibilityRole='button'>
|
||||||
// @ts-ignore
|
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
|
||||||
accessibilityLabel={I18n.t('Finish_recording')}
|
</View>
|
||||||
accessibilityTraits='button'
|
|
||||||
style={styles.actionButton}>
|
|
||||||
<CustomIcon size={24} color={themes[theme].tintColor} name='send-filled' />
|
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,10 +56,12 @@ interface IMessageBoxReplyPreview {
|
||||||
|
|
||||||
const ReplyPreview = React.memo(
|
const ReplyPreview = React.memo(
|
||||||
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
|
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!replying) {
|
if (!replying) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { theme } = useTheme();
|
|
||||||
const time = moment(message.ts).format(Message_TimeFormat);
|
const time = moment(message.ts).format(Message_TimeFormat);
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import { testProps } from '../../../lib/methods/testProps';
|
import { testProps } from '../../../lib/methods/testProps';
|
||||||
|
import i18n from '../../../i18n';
|
||||||
|
|
||||||
interface IBaseButton {
|
interface IBaseButton {
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
|
@ -18,13 +20,13 @@ interface IBaseButton {
|
||||||
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
|
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<BorderlessButton
|
<BorderlessButton {...props} style={styles.actionButton} {...testProps(props.testID)}>
|
||||||
{...props}
|
<View
|
||||||
style={styles.actionButton}
|
accessible
|
||||||
// @ts-ignore
|
accessibilityLabel={accessibilityLabel ? i18n.t(accessibilityLabel) : accessibilityLabel}
|
||||||
accessibilityTraits='button'
|
accessibilityRole='button'>
|
||||||
{...testProps(props.testID)}>
|
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
|
||||||
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
|
</View>
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,6 +53,7 @@ import { forceJpgExtension } from './forceJpgExtension';
|
||||||
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
|
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
|
||||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
require('./EmojiKeyboard');
|
require('./EmojiKeyboard');
|
||||||
|
@ -96,7 +97,7 @@ export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackPar
|
||||||
editRequest: Function;
|
editRequest: Function;
|
||||||
onSubmit: Function;
|
onSubmit: Function;
|
||||||
typing: Function;
|
typing: Function;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
replyCancel(): void;
|
replyCancel(): void;
|
||||||
showSend: boolean;
|
showSend: boolean;
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
|
@ -680,7 +681,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return true;
|
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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -762,7 +763,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
value = message;
|
value = message;
|
||||||
replyCancel();
|
replyCancel();
|
||||||
}
|
}
|
||||||
Navigation.navigate('ShareView', { room: this.room, value, attachments });
|
Navigation.navigate('ShareView', { room: this.room, thread: value, attachments });
|
||||||
};
|
};
|
||||||
|
|
||||||
createDiscussion = () => {
|
createDiscussion = () => {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { StyleSheet, Text, View } from 'react-native';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
|
import { TSupportedThemes } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -24,7 +25,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IOrSeparator {
|
interface IOrSeparator {
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrSeparator = React.memo(({ theme }: IOrSeparator) => {
|
const OrSeparator = React.memo(({ theme }: IOrSeparator) => {
|
||||||
|
|
|
@ -19,7 +19,7 @@ interface IPasscodeBase {
|
||||||
type: string;
|
type: string;
|
||||||
previousPasscode?: string;
|
previousPasscode?: string;
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string;
|
subtitle?: string | null;
|
||||||
showBiometry?: boolean;
|
showBiometry?: boolean;
|
||||||
onEndProcess: Function;
|
onEndProcess: Function;
|
||||||
onError?: Function;
|
onError?: Function;
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface IPasscodeChoose {
|
||||||
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
|
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
|
||||||
const chooseRef = useRef<IBase>(null);
|
const chooseRef = useRef<IBase>(null);
|
||||||
const confirmRef = 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 [status, setStatus] = useState(TYPE.CHOOSE);
|
||||||
const [previousPasscode, setPreviouPasscode] = useState('');
|
const [previousPasscode, setPreviouPasscode] = useState('');
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import I18n from '../i18n';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { useTheme, withTheme } from '../theme';
|
import { TSupportedThemes, useTheme, withTheme } from '../theme';
|
||||||
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
||||||
import { TMessageModel, ILoggedUser } from '../definitions';
|
import { TMessageModel, ILoggedUser } from '../definitions';
|
||||||
import SafeAreaView from './SafeAreaView';
|
import SafeAreaView from './SafeAreaView';
|
||||||
|
@ -77,7 +77,7 @@ interface IItem extends ISharedFields {
|
||||||
interface IModalContent extends ISharedFields {
|
interface IModalContent extends ISharedFields {
|
||||||
message?: TMessageModel;
|
message?: TMessageModel;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IReactionsModal extends ISharedFields {
|
interface IReactionsModal extends ISharedFields {
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Dimensions, View } from 'react-native';
|
import { Dimensions, View } from 'react-native';
|
||||||
import { storiesOf } from '@storybook/react-native';
|
import { storiesOf } from '@storybook/react-native';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import Header from '../Header';
|
import Header from '../Header';
|
||||||
import { longText } from '../../../storybook/utils';
|
import { longText } from '../../../storybook/utils';
|
||||||
import { ThemeContext } from '../../theme';
|
import { ThemeContext } from '../../theme';
|
||||||
|
import { store } from '../../../storybook/stories';
|
||||||
import RoomHeaderComponent from './RoomHeader';
|
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
|
// TODO: refactor after react-navigation v6
|
||||||
const HeaderExample = ({ title }) => (
|
const HeaderExample = ({ title }) => (
|
||||||
|
|
|
@ -6,7 +6,7 @@ import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { MarkdownPreview } from '../markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import RoomTypeIcon from '../RoomTypeIcon';
|
import RoomTypeIcon from '../RoomTypeIcon';
|
||||||
import { TUserStatus } from '../../definitions';
|
import { TUserStatus, IOmnichannelSource } from '../../definitions';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const HIT_SLOP = {
|
const HIT_SLOP = {
|
||||||
|
@ -67,12 +67,12 @@ interface IRoomHeader {
|
||||||
tmid: string;
|
tmid: string;
|
||||||
teamMain: boolean;
|
teamMain: boolean;
|
||||||
status: TUserStatus;
|
status: TUserStatus;
|
||||||
theme?: string;
|
|
||||||
usersTyping: [];
|
usersTyping: [];
|
||||||
isGroupChat: boolean;
|
isGroupChat: boolean;
|
||||||
parentTitle: string;
|
parentTitle: string;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
testID: string;
|
testID: string;
|
||||||
|
sourceType?: IOmnichannelSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
|
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
|
||||||
|
@ -136,7 +136,8 @@ const Header = React.memo(
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
teamMain,
|
teamMain,
|
||||||
testID,
|
testID,
|
||||||
usersTyping = []
|
usersTyping = [],
|
||||||
|
sourceType
|
||||||
}: IRoomHeader) => {
|
}: IRoomHeader) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const portrait = height > width;
|
const portrait = height > width;
|
||||||
|
@ -172,7 +173,13 @@ const Header = React.memo(
|
||||||
hitSlop={HIT_SLOP}>
|
hitSlop={HIT_SLOP}>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
{tmid ? null : (
|
{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} />
|
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} testID={testID} />
|
||||||
</View>
|
</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 React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { IApplicationState, TUserStatus } from '../../definitions';
|
import { IApplicationState, TUserStatus, IOmnichannelSource } from '../../definitions';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { withDimensions } from '../../dimensions';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import RoomHeader from './RoomHeader';
|
import RoomHeader from './RoomHeader';
|
||||||
|
@ -27,12 +27,26 @@ interface IRoomHeaderContainerProps {
|
||||||
parentTitle: string;
|
parentTitle: string;
|
||||||
isGroupChat: boolean;
|
isGroupChat: boolean;
|
||||||
testID: string;
|
testID: string;
|
||||||
|
sourceType?: IOmnichannelSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
||||||
shouldComponentUpdate(nextProps: IRoomHeaderContainerProps) {
|
shouldComponentUpdate(nextProps: IRoomHeaderContainerProps) {
|
||||||
const { type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain } =
|
const {
|
||||||
this.props;
|
type,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
connecting,
|
||||||
|
connected,
|
||||||
|
onPress,
|
||||||
|
usersTyping,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
teamMain,
|
||||||
|
sourceType
|
||||||
|
} = this.props;
|
||||||
if (nextProps.type !== type) {
|
if (nextProps.type !== type) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +77,9 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
||||||
if (!dequal(nextProps.usersTyping, usersTyping)) {
|
if (!dequal(nextProps.usersTyping, usersTyping)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!dequal(nextProps.sourceType, sourceType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (nextProps.onPress !== onPress) {
|
if (nextProps.onPress !== onPress) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +107,8 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
||||||
height,
|
height,
|
||||||
parentTitle,
|
parentTitle,
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
testID
|
testID,
|
||||||
|
sourceType
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let subtitle;
|
let subtitle;
|
||||||
|
@ -118,6 +136,7 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
|
||||||
isGroupChat={isGroupChat}
|
isGroupChat={isGroupChat}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
sourceType={sourceType}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,31 +6,13 @@ import { isRTL } from '../../i18n';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { DisplayMode, themes } from '../../lib/constants';
|
import { DisplayMode, themes } from '../../lib/constants';
|
||||||
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
|
import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles';
|
||||||
|
import { ILeftActionsProps, IRightActionsProps } from './interfaces';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reverse = new Animated.Value(isRTL() ? -1 : 1);
|
const reverse = new Animated.Value(isRTL() ? -1 : 1);
|
||||||
const CONDENSED_ICON_SIZE = 24;
|
const CONDENSED_ICON_SIZE = 24;
|
||||||
const EXPANDED_ICON_SIZE = 28;
|
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(
|
const translateX = Animated.multiply(
|
||||||
transX.interpolate({
|
transX.interpolate({
|
||||||
inputRange: [0, ACTION_WIDTH],
|
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;
|
const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.actionsContainer, styles.actionLeftContainer]} pointerEvents='box-none'>
|
<View style={[styles.actionsContainer, styles.actionsLeftContainer]} pointerEvents='box-none'>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.actionLeftButtonContainer,
|
styles.actionLeftButtonContainer,
|
||||||
|
@ -70,7 +52,7 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RightActions = React.memo(
|
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(
|
const translateXFav = Animated.multiply(
|
||||||
transX.interpolate({
|
transX.interpolate({
|
||||||
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],
|
inputRange: [-width / 2, -ACTION_WIDTH * 2, 0],
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../Avatar';
|
||||||
import { DisplayMode } from '../../lib/constants';
|
import { DisplayMode } from '../../lib/constants';
|
||||||
import TypeIcon from './TypeIcon';
|
import TypeIcon from './TypeIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -18,7 +18,8 @@ const IconOrAvatar = ({
|
||||||
teamMain,
|
teamMain,
|
||||||
showLastMessage,
|
showLastMessage,
|
||||||
theme,
|
theme,
|
||||||
displayMode
|
displayMode,
|
||||||
|
sourceType
|
||||||
}) => {
|
}) => {
|
||||||
if (showAvatar) {
|
if (showAvatar) {
|
||||||
return (
|
return (
|
||||||
|
@ -38,6 +39,7 @@ const IconOrAvatar = ({
|
||||||
teamMain={teamMain}
|
teamMain={teamMain}
|
||||||
size={24}
|
size={24}
|
||||||
style={{ marginRight: 12 }}
|
style={{ marginRight: 12 }}
|
||||||
|
sourceType={sourceType}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
|
@ -3,27 +3,11 @@ import { dequal } from 'dequal';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { MarkdownPreview } from '../../containers/markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, themes } from '../../lib/constants';
|
import { E2E_MESSAGE_TYPE, E2E_STATUS, themes } from '../../lib/constants';
|
||||||
|
import { ILastMessageProps } from './interfaces';
|
||||||
|
|
||||||
interface ILastMessage {
|
const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessageProps>) => {
|
||||||
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>) => {
|
|
||||||
if (!showLastMessage) {
|
if (!showLastMessage) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -63,7 +47,7 @@ const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }
|
||||||
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
|
const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps);
|
||||||
|
|
||||||
const LastMessage = React.memo(
|
const LastMessage = React.memo(
|
||||||
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessage) => (
|
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessageProps) => (
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
msg={formatMsg({
|
msg={formatMsg({
|
||||||
lastMessage,
|
lastMessage,
|
|
@ -12,56 +12,7 @@ import Touchable from './Touchable';
|
||||||
import Tag from './Tag';
|
import Tag from './Tag';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { DisplayMode } from '../../lib/constants';
|
import { DisplayMode } from '../../lib/constants';
|
||||||
import { TUserStatus } from '../../definitions';
|
import { IRoomItemProps } from './interfaces';
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoomItem = ({
|
const RoomItem = ({
|
||||||
rid,
|
rid,
|
||||||
|
@ -70,7 +21,6 @@ const RoomItem = ({
|
||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
width,
|
width,
|
||||||
avatarSize = 48,
|
|
||||||
username,
|
username,
|
||||||
showLastMessage,
|
showLastMessage,
|
||||||
status = 'offline',
|
status = 'offline',
|
||||||
|
@ -101,8 +51,9 @@ const RoomItem = ({
|
||||||
teamMain,
|
teamMain,
|
||||||
autoJoin,
|
autoJoin,
|
||||||
showAvatar,
|
showAvatar,
|
||||||
displayMode
|
displayMode,
|
||||||
}: IRoomItem) => (
|
sourceType
|
||||||
|
}: IRoomItemProps) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
|
@ -122,7 +73,6 @@ const RoomItem = ({
|
||||||
<Wrapper
|
<Wrapper
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
avatarSize={avatarSize}
|
|
||||||
type={type}
|
type={type}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
rid={rid}
|
rid={rid}
|
||||||
|
@ -132,12 +82,20 @@ const RoomItem = ({
|
||||||
teamMain={teamMain}
|
teamMain={teamMain}
|
||||||
displayMode={displayMode}
|
displayMode={displayMode}
|
||||||
showAvatar={showAvatar}
|
showAvatar={showAvatar}
|
||||||
showLastMessage={showLastMessage}>
|
showLastMessage={showLastMessage}
|
||||||
|
sourceType={sourceType}>
|
||||||
{showLastMessage && displayMode === DisplayMode.Expanded ? (
|
{showLastMessage && displayMode === DisplayMode.Expanded ? (
|
||||||
<>
|
<>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
{showAvatar ? (
|
{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}
|
) : null}
|
||||||
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
||||||
{autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null}
|
{autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null}
|
||||||
|
@ -170,10 +128,10 @@ const RoomItem = ({
|
||||||
prid={prid}
|
prid={prid}
|
||||||
status={status}
|
status={status}
|
||||||
isGroupChat={isGroupChat}
|
isGroupChat={isGroupChat}
|
||||||
theme={theme}
|
|
||||||
teamMain={teamMain}
|
teamMain={teamMain}
|
||||||
size={22}
|
size={22}
|
||||||
style={{ marginRight: 8 }}
|
style={{ marginRight: 8 }}
|
||||||
|
sourceType={sourceType}
|
||||||
/>
|
/>
|
||||||
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
<Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} />
|
||||||
{autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null}
|
{autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null}
|
|
@ -11,7 +11,7 @@ interface ITag {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tag = React.memo(({ name, testID }: ITag) => {
|
const Tag = React.memo(({ name, testID }: ITag) => {
|
||||||
const { theme }: any = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.tagContainer, { backgroundColor: themes[theme].borderColor }]}>
|
<View style={[styles.tagContainer, { backgroundColor: themes[theme].borderColor }]}>
|
|
@ -3,15 +3,9 @@ import { Text } from 'react-native';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
import { ITitleProps } from './interfaces';
|
||||||
|
|
||||||
interface ITitle {
|
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitleProps) => (
|
||||||
name: string;
|
|
||||||
theme: string;
|
|
||||||
hideUnreadStatus: boolean;
|
|
||||||
alert: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitle) => (
|
|
||||||
<Text
|
<Text
|
||||||
style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: themes[theme].titleText }]}
|
style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: themes[theme].titleText }]}
|
||||||
ellipsizeMode='tail'
|
ellipsizeMode='tail'
|
|
@ -1,45 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated } from 'react-native';
|
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 Touch from '../../utils/touch';
|
||||||
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
|
import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles';
|
||||||
import { isRTL } from '../../i18n';
|
import { isRTL } from '../../i18n';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { LeftActions, RightActions } from './Actions';
|
import { LeftActions, RightActions } from './Actions';
|
||||||
|
import { ITouchableProps } from './interfaces';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Touchable extends React.Component<ITouchableProps, any> {
|
class Touchable extends React.Component<ITouchableProps, any> {
|
||||||
private dragX: Animated.Value;
|
private dragX: Animated.Value;
|
||||||
|
|
||||||
private rowOffSet: Animated.Value;
|
private rowOffSet: Animated.Value;
|
||||||
|
|
||||||
private reverse: Animated.Value;
|
private reverse: Animated.Value;
|
||||||
|
|
||||||
private transX: Animated.AnimatedAddition;
|
private transX: Animated.AnimatedAddition;
|
||||||
|
|
||||||
private transXReverse: Animated.AnimatedMultiplication;
|
private transXReverse: Animated.AnimatedMultiplication;
|
||||||
|
private _onGestureEvent: (event: GestureEvent<PanGestureHandlerEventPayload>) => void;
|
||||||
private _onGestureEvent: (...args: any[]) => void;
|
|
||||||
|
|
||||||
private _value: number;
|
private _value: number;
|
||||||
|
|
||||||
constructor(props: ITouchableProps) {
|
constructor(props: ITouchableProps) {
|
||||||
|
@ -56,19 +39,19 @@ class Touchable extends React.Component<ITouchableProps, any> {
|
||||||
this._value = 0;
|
this._value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHandlerStateChange = ({ nativeEvent }: any) => {
|
_onHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload & PanGestureHandlerEventPayload }) => {
|
||||||
if (nativeEvent.oldState === State.ACTIVE) {
|
if (nativeEvent.oldState === State.ACTIVE) {
|
||||||
this._handleRelease(nativeEvent);
|
this._handleRelease(nativeEvent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onLongPressHandlerStateChange = ({ nativeEvent }: any) => {
|
onLongPressHandlerStateChange = ({ nativeEvent }: { nativeEvent: HandlerStateChangeEventPayload }) => {
|
||||||
if (nativeEvent.state === State.ACTIVE) {
|
if (nativeEvent.state === State.ACTIVE) {
|
||||||
this.onLongPress();
|
this.onLongPress();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_handleRelease = (nativeEvent: any) => {
|
_handleRelease = (nativeEvent: PanGestureHandlerEventPayload) => {
|
||||||
const { translationX } = nativeEvent;
|
const { translationX } = nativeEvent;
|
||||||
const { rowState } = this.state;
|
const { rowState } = this.state;
|
||||||
this._value += translationX;
|
this._value += translationX;
|
||||||
|
@ -154,7 +137,7 @@ class Touchable extends React.Component<ITouchableProps, any> {
|
||||||
this._animateRow(toValue);
|
this._animateRow(toValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
_animateRow = (toValue: any) => {
|
_animateRow = (toValue: number) => {
|
||||||
this.rowOffSet.setValue(this._value);
|
this.rowOffSet.setValue(this._value);
|
||||||
this._value = toValue;
|
this._value = toValue;
|
||||||
this.dragX.setValue(0);
|
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 styles from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { capitalize } from '../../utils/room';
|
import { capitalize } from '../../utils/room';
|
||||||
|
import { IUpdatedAtProps } from './interfaces';
|
||||||
|
|
||||||
interface IUpdatedAt {
|
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAtProps) => {
|
||||||
date: string;
|
|
||||||
theme: string;
|
|
||||||
hideUnreadStatus: boolean;
|
|
||||||
alert: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAt) => {
|
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
|
@ -3,26 +3,10 @@ import { View } from 'react-native';
|
||||||
|
|
||||||
import { DisplayMode, themes } from '../../lib/constants';
|
import { DisplayMode, themes } from '../../lib/constants';
|
||||||
import IconOrAvatar from './IconOrAvatar';
|
import IconOrAvatar from './IconOrAvatar';
|
||||||
|
import { IWrapperProps } from './interfaces';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IWrapper {
|
const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapperProps): React.ReactElement => (
|
||||||
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) => (
|
|
||||||
<View
|
<View
|
||||||
style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
|
style={[styles.container, displayMode === DisplayMode.Condensed && styles.containerCondensed]}
|
||||||
accessibilityLabel={accessibilityLabel}>
|
accessibilityLabel={accessibilityLabel}>
|
|
@ -5,36 +5,10 @@ import I18n from '../../i18n';
|
||||||
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
|
import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles';
|
||||||
import { formatDate } from '../../utils/room';
|
import { formatDate } from '../../utils/room';
|
||||||
import RoomItem from './RoomItem';
|
import RoomItem from './RoomItem';
|
||||||
import { TUserStatus } from '../../definitions';
|
import { ISubscription, TUserStatus } from '../../definitions';
|
||||||
|
import { IRoomItemContainerProps } from './interfaces';
|
||||||
|
|
||||||
export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED };
|
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 = [
|
const attrs = [
|
||||||
'width',
|
'width',
|
||||||
|
@ -50,12 +24,9 @@ const attrs = [
|
||||||
];
|
];
|
||||||
|
|
||||||
class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
private mounted: boolean;
|
private roomSubscription: ISubscription | undefined;
|
||||||
|
|
||||||
private roomSubscription: any;
|
|
||||||
|
|
||||||
static defaultProps: Partial<IRoomItemContainerProps> = {
|
static defaultProps: Partial<IRoomItemContainerProps> = {
|
||||||
avatarSize: 48,
|
|
||||||
status: 'offline',
|
status: 'offline',
|
||||||
getUserPresence: () => {},
|
getUserPresence: () => {},
|
||||||
getRoomTitle: () => 'title',
|
getRoomTitle: () => 'title',
|
||||||
|
@ -67,24 +38,22 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
|
|
||||||
constructor(props: IRoomItemContainerProps) {
|
constructor(props: IRoomItemContainerProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.mounted = false;
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.mounted = true;
|
|
||||||
const { connected, getUserPresence, id } = this.props;
|
const { connected, getUserPresence, id } = this.props;
|
||||||
if (connected && this.isDirect) {
|
if (connected && this.isDirect) {
|
||||||
getUserPresence(id);
|
getUserPresence(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: any) {
|
shouldComponentUpdate(nextProps: IRoomItemContainerProps) {
|
||||||
const { props }: any = this;
|
const { props } = this;
|
||||||
return !attrs.every(key => props[key] === nextProps[key]);
|
return !attrs.every(key => props[key] === nextProps[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: any) {
|
componentDidUpdate(prevProps: IRoomItemContainerProps) {
|
||||||
const { connected, getUserPresence, id } = this.props;
|
const { connected, getUserPresence, id } = this.props;
|
||||||
if (prevProps.connected !== connected && connected && this.isDirect) {
|
if (prevProps.connected !== connected && connected && this.isDirect) {
|
||||||
getUserPresence(id);
|
getUserPresence(id);
|
||||||
|
@ -106,7 +75,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
const {
|
const {
|
||||||
item: { t },
|
item: { t },
|
||||||
id
|
id
|
||||||
}: any = this.props;
|
} = this.props;
|
||||||
return t === 'd' && id && !this.isGroupChat;
|
return t === 'd' && id && !this.isGroupChat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +113,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
hideChannel,
|
hideChannel,
|
||||||
theme,
|
theme,
|
||||||
isFocused,
|
isFocused,
|
||||||
avatarSize,
|
|
||||||
status,
|
status,
|
||||||
showLastMessage,
|
showLastMessage,
|
||||||
username,
|
username,
|
||||||
|
@ -177,7 +145,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
|
||||||
<RoomItem
|
<RoomItem
|
||||||
name={name}
|
name={name}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
|
@ -197,7 +164,6 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
type={item.t}
|
type={item.t}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
size={avatarSize}
|
|
||||||
prid={item.prid}
|
prid={item.prid}
|
||||||
status={status}
|
status={status}
|
||||||
hideUnreadStatus={item.hideUnreadStatus}
|
hideUnreadStatus={item.hideUnreadStatus}
|
||||||
|
@ -217,6 +183,7 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> {
|
||||||
autoJoin={autoJoin}
|
autoJoin={autoJoin}
|
||||||
showAvatar={showAvatar}
|
showAvatar={showAvatar}
|
||||||
displayMode={displayMode}
|
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 SMALL_SWIPE = ACTION_WIDTH / 2;
|
||||||
export const LONG_SWIPE = ACTION_WIDTH * 3;
|
export const LONG_SWIPE = ACTION_WIDTH * 3;
|
||||||
|
|
||||||
export default StyleSheet.create<any>({
|
export default StyleSheet.create({
|
||||||
flex: {
|
flex: {
|
||||||
flex: 1
|
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 React from 'react';
|
||||||
import { StyleSheet, ViewStyle } from 'react-native';
|
import { StyleSheet, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { OmnichannelRoomIcon } from './OmnichannelRoomIcon';
|
||||||
import { STATUS_COLORS, themes } from '../lib/constants';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import Status from './Status/Status';
|
import { STATUS_COLORS, themes } from '../../lib/constants';
|
||||||
import { withTheme } from '../theme';
|
import Status from '../Status/Status';
|
||||||
import { TUserStatus } from '../definitions';
|
import { useTheme } from '../../theme';
|
||||||
|
import { TUserStatus, IOmnichannelSource } from '../../definitions';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
icon: {
|
icon: {
|
||||||
|
@ -14,21 +15,23 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IRoomTypeIcon {
|
interface IRoomTypeIcon {
|
||||||
theme?: string;
|
|
||||||
type: string;
|
type: string;
|
||||||
isGroupChat?: boolean;
|
isGroupChat?: boolean;
|
||||||
teamMain?: boolean;
|
teamMain?: boolean;
|
||||||
status?: TUserStatus;
|
status?: TUserStatus;
|
||||||
size?: number;
|
size?: number;
|
||||||
style?: ViewStyle;
|
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) {
|
if (!type) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = themes[theme!].titleText;
|
const color = themes[theme].titleText;
|
||||||
const iconStyle = [styles.icon, { color }, style];
|
const iconStyle = [styles.icon, { color }, style];
|
||||||
|
|
||||||
if (type === 'd' && !isGroupChat) {
|
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} />;
|
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
|
// TODO: move this to a separate function
|
||||||
let icon = 'channel-private';
|
let icon = 'channel-private';
|
||||||
if (teamMain) {
|
if (teamMain) {
|
||||||
|
@ -52,11 +59,9 @@ const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, team
|
||||||
} else {
|
} else {
|
||||||
icon = 'mention';
|
icon = 'mention';
|
||||||
}
|
}
|
||||||
} else if (type === 'l') {
|
|
||||||
icon = 'omnichannel';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CustomIcon name={icon} size={size} style={iconStyle} />;
|
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 { CustomIcon } from '../lib/Icons';
|
||||||
import ActivityIndicator from './ActivityIndicator';
|
import ActivityIndicator from './ActivityIndicator';
|
||||||
import { testProps } from '../lib/methods/testProps';
|
import { testProps } from '../lib/methods/testProps';
|
||||||
|
import { TSupportedThemes } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
error: {
|
error: {
|
||||||
|
@ -63,7 +64,7 @@ export interface IRCTextInputProps extends TextInputProps {
|
||||||
iconRight?: string;
|
iconRight?: string;
|
||||||
left?: JSX.Element;
|
left?: JSX.Element;
|
||||||
onIconRightPress?(): void;
|
onIconRightPress?(): void;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRCTextInputState {
|
interface IRCTextInputState {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import EasyToast from 'react-native-easy-toast';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import { withTheme } from '../theme';
|
import { TSupportedThemes, withTheme } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
toast: {
|
toast: {
|
||||||
|
@ -22,7 +22,7 @@ const styles = StyleSheet.create({
|
||||||
export const LISTENER = 'Toast';
|
export const LISTENER = 'Toast';
|
||||||
|
|
||||||
interface IToastProps {
|
interface IToastProps {
|
||||||
theme?: string;
|
theme?: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Toast extends React.Component<IToastProps, any> {
|
class Toast extends React.Component<IToastProps, any> {
|
||||||
|
|
|
@ -8,19 +8,20 @@ import { textParser } from '../utils';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IItemData } from '.';
|
import { IItemData } from '.';
|
||||||
|
import { TSupportedThemes } from '../../../theme';
|
||||||
|
|
||||||
interface IChip {
|
interface IChip {
|
||||||
item: IItemData;
|
item: IItemData;
|
||||||
onSelect: (item: IItemData) => void;
|
onSelect: (item: IItemData) => void;
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IChips {
|
interface IChips {
|
||||||
items: IItemData[];
|
items: IItemData[];
|
||||||
onSelect: (item: IItemData) => void;
|
onSelect: (item: IItemData) => void;
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyExtractor = (item: IItemData) => item.value.toString();
|
const keyExtractor = (item: IItemData) => item.value.toString();
|
||||||
|
|
|
@ -6,11 +6,12 @@ import { CustomIcon } from '../../../lib/Icons';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import ActivityIndicator from '../../ActivityIndicator';
|
import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { TSupportedThemes } from '../../../theme';
|
||||||
|
|
||||||
interface IInput {
|
interface IInput {
|
||||||
children?: JSX.Element;
|
children?: JSX.Element;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
inputStyle?: object;
|
inputStyle?: object;
|
||||||
disabled?: boolean | null;
|
disabled?: boolean | null;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
|
@ -9,26 +9,27 @@ import { textParser } from '../utils';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { IItemData } from '.';
|
import { IItemData } from '.';
|
||||||
|
import { TSupportedThemes } from '../../../theme';
|
||||||
|
|
||||||
interface IItem {
|
interface IItem {
|
||||||
item: IItemData;
|
item: IItemData;
|
||||||
selected?: string;
|
selected?: string;
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IItems {
|
interface IItems {
|
||||||
items: IItemData[];
|
items: IItemData[];
|
||||||
selected: string[];
|
selected: string[];
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyExtractor = (item: IItemData) => item.value.toString();
|
const keyExtractor = (item: IItemData) => item.value.toString();
|
||||||
|
|
||||||
// RectButton doesn't work on modal (Android)
|
// RectButton doesn't work on modal (Android)
|
||||||
const Item = ({ item, selected, onSelect, theme }: IItem) => {
|
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 (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
testID={`multi-select-item-${itemName}`}
|
testID={`multi-select-item-${itemName}`}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import Input from './Input';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
export interface IItemData {
|
export interface IItemData {
|
||||||
value: string;
|
value: any;
|
||||||
text: { text: string };
|
text: { text: string };
|
||||||
imageUrl?: 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 React, { useContext } from 'react';
|
||||||
import { StyleSheet, Text } from 'react-native';
|
import { StyleSheet, Text } from 'react-native';
|
||||||
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
export enum ElementTypes {
|
export enum ElementTypes {
|
||||||
IMAGE = 'image',
|
IMAGE = 'image',
|
||||||
BUTTON = 'button',
|
BUTTON = 'button',
|
||||||
|
@ -199,7 +201,7 @@ export interface IInput extends Partial<Block> {
|
||||||
description: string;
|
description: string;
|
||||||
error: string;
|
error: string;
|
||||||
hint: string;
|
hint: string;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IInputIndex {
|
export interface IInputIndex {
|
||||||
|
@ -231,7 +233,7 @@ export interface IOverflow extends Partial<Block> {
|
||||||
interface PropsOption {
|
interface PropsOption {
|
||||||
onOptionPress: Function;
|
onOptionPress: Function;
|
||||||
parser: IParser;
|
parser: IParser;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
export interface IOptions extends PropsOption {
|
export interface IOptions extends PropsOption {
|
||||||
options: Option[];
|
options: Option[];
|
||||||
|
@ -265,6 +267,6 @@ export interface ISection {
|
||||||
|
|
||||||
export interface IFields {
|
export interface IFields {
|
||||||
parser: IParser;
|
parser: IParser;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
fields: any[];
|
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 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 sharedStyles from '../../views/Styles';
|
||||||
import { getUnreadStyle } from './getUnreadStyle';
|
import { getUnreadStyle } from './getUnreadStyle';
|
||||||
import { withTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
unreadNumberContainerNormal: {
|
unreadNumberContainerNormal: {
|
||||||
|
@ -29,12 +29,11 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IUnreadBadge {
|
export interface IUnreadBadge {
|
||||||
theme?: string;
|
|
||||||
unread?: number;
|
unread?: number;
|
||||||
userMentions?: number;
|
userMentions?: number;
|
||||||
groupMentions?: number;
|
groupMentions?: number;
|
||||||
style?: ViewStyle;
|
style?: StyleProp<ViewStyle>;
|
||||||
tunread?: [];
|
tunread?: [];
|
||||||
tunreadUser?: [];
|
tunreadUser?: [];
|
||||||
tunreadGroup?: [];
|
tunreadGroup?: [];
|
||||||
|
@ -42,10 +41,13 @@ interface IUnreadBadge {
|
||||||
}
|
}
|
||||||
|
|
||||||
const UnreadBadge = React.memo(
|
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) {
|
if ((!unread || unread <= 0) && !tunread?.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { backgroundColor, color } = getUnreadStyle({
|
const { backgroundColor, color } = getUnreadStyle({
|
||||||
theme,
|
theme,
|
||||||
unread,
|
unread,
|
||||||
|
@ -88,4 +90,4 @@ const UnreadBadge = React.memo(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default withTheme(UnreadBadge);
|
export default UnreadBadge;
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// @ts-ignore
|
|
||||||
import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
|
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 { CustomIcon } from '../lib/Icons';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../lib/constants';
|
import { themes } from '../lib/constants';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
|
import { TSupportedThemes } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -47,7 +47,7 @@ interface IUserItem {
|
||||||
onLongPress?: () => void;
|
onLongPress?: () => void;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
icon?: string | null;
|
icon?: string | null;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, theme }: IUserItem) => (
|
const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, theme }: IUserItem) => (
|
|
@ -1,12 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IBlockQuote {
|
interface IBlockQuote {
|
||||||
children: React.ReactElement | null;
|
children: React.ReactElement | null;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockQuote = React.memo(({ children, theme }: IBlockQuote) => (
|
const BlockQuote = React.memo(({ children, theme }: IBlockQuote) => (
|
||||||
|
|
|
@ -5,6 +5,7 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface IEmoji {
|
interface IEmoji {
|
||||||
literal: string;
|
literal: string;
|
||||||
|
@ -13,7 +14,7 @@ interface IEmoji {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
customEmojis?: any;
|
customEmojis?: any;
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
onEmojiSelected?: Function;
|
onEmojiSelected?: Function;
|
||||||
tabEmojiStyle?: object;
|
tabEmojiStyle?: object;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@ import EventEmitter from '../../utils/events';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { TOnLinkPress } from './interfaces';
|
import { TOnLinkPress } from './interfaces';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface ILink {
|
interface ILink {
|
||||||
children: React.ReactElement | null;
|
children: React.ReactElement | null;
|
||||||
link: string;
|
link: string;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
onLinkPress?: TOnLinkPress;
|
onLinkPress?: TOnLinkPress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
|
||||||
const style = StyleSheet.create({
|
const style = StyleSheet.create({
|
||||||
|
@ -23,7 +24,7 @@ interface IListItem {
|
||||||
level: number;
|
level: number;
|
||||||
ordered: boolean;
|
ordered: boolean;
|
||||||
continue: boolean;
|
continue: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,12 @@ interface IMarkdownPreview {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview) => {
|
const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
let m = formatText(msg);
|
let m = formatText(msg);
|
||||||
m = formatHyperlink(m);
|
m = formatHyperlink(m);
|
||||||
m = shortnameToUnicode(m);
|
m = shortnameToUnicode(m);
|
||||||
|
|
|
@ -5,12 +5,13 @@ import { CELL_WIDTH } from './TableCell';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
|
||||||
interface ITable {
|
interface ITable {
|
||||||
children: React.ReactElement | null;
|
children: React.ReactElement | null;
|
||||||
numColumns: number;
|
numColumns: number;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_HEIGHT = 300;
|
const MAX_HEIGHT = 300;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, ViewStyle } from 'react-native';
|
import { Text, View, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ interface ITableCell {
|
||||||
align: '' | 'left' | 'center' | 'right';
|
align: '' | 'left' | 'center' | 'right';
|
||||||
children: React.ReactElement | null;
|
children: React.ReactElement | null;
|
||||||
isLastCell: boolean;
|
isLastCell: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CELL_WIDTH = 100;
|
export const CELL_WIDTH = 100;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, ViewStyle } from 'react-native';
|
import { View, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface ITableRow {
|
interface ITableRow {
|
||||||
children: React.ReactElement | null;
|
children: React.ReactElement | null;
|
||||||
isLastRow: boolean;
|
isLastRow: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => {
|
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => {
|
||||||
|
|
|
@ -23,13 +23,14 @@ import { formatText } from './formatText';
|
||||||
import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces';
|
import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { formatHyperlink } from './formatHyperlink';
|
import { formatHyperlink } from './formatHyperlink';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
|
||||||
export { default as MarkdownPreview } from './Preview';
|
export { default as MarkdownPreview } from './Preview';
|
||||||
|
|
||||||
interface IMarkdownProps {
|
interface IMarkdownProps {
|
||||||
msg?: string;
|
msg?: string | null;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
md?: MarkdownAST;
|
md?: MarkdownAST;
|
||||||
mentions?: IUserMention[];
|
mentions?: IUserMention[];
|
||||||
getCustomEmoji?: TGetCustomEmoji;
|
getCustomEmoji?: TGetCustomEmoji;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { createImageProgress } from 'react-native-image-progress';
|
||||||
import * as Progress from 'react-native-progress';
|
import * as Progress from 'react-native-progress';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
|
|
||||||
import { useTheme } from '../../../theme';
|
import { TSupportedThemes, useTheme } from '../../../theme';
|
||||||
import { themes } from '../../../lib/constants';
|
import { themes } from '../../../lib/constants';
|
||||||
import styles from '../../message/styles';
|
import styles from '../../message/styles';
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ interface IImageProps {
|
||||||
|
|
||||||
type TMessageImage = {
|
type TMessageImage = {
|
||||||
img: string;
|
img: string;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImageProgress = createImageProgress(FastImage);
|
const ImageProgress = createImageProgress(FastImage);
|
||||||
|
|
|
@ -24,11 +24,12 @@ export type TElement = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
||||||
|
const { onAnswerButtonPress } = useContext(MessageContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!attachment.actions) {
|
if (!attachment.actions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { onAnswerButtonPress } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const attachedButtons = attachment.actions.map((element: TElement) => {
|
const attachedButtons = attachment.actions.map((element: TElement) => {
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
|
@ -57,12 +58,12 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
||||||
|
|
||||||
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
||||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!attachments || attachments.length === 0) {
|
if (!attachments || attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
|
const attachmentsElements = attachments.map((file: IAttachment, index: number) => {
|
||||||
if (file && file.image_url) {
|
if (file && file.image_url) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -18,11 +18,12 @@ import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { withDimensions } from '../../dimensions';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { IAttachment } from '../../definitions';
|
import { IAttachment } from '../../definitions';
|
||||||
|
import { TSupportedThemes } from '../../theme';
|
||||||
|
|
||||||
interface IButton {
|
interface IButton {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +32,7 @@ interface IMessageAudioProps {
|
||||||
file: IAttachment;
|
file: IAttachment;
|
||||||
isReply?: boolean;
|
isReply?: boolean;
|
||||||
style?: StyleProp<TextStyle>[];
|
style?: StyleProp<TextStyle>[];
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
}
|
}
|
||||||
|
@ -264,6 +265,13 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let thumbColor;
|
||||||
|
if (isAndroid && isReply) {
|
||||||
|
thumbColor = themes[theme].tintDisabled;
|
||||||
|
} else if (isAndroid) {
|
||||||
|
thumbColor = themes[theme].tintColor;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Markdown
|
<Markdown
|
||||||
|
@ -286,7 +294,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
value={currentTime}
|
value={currentTime}
|
||||||
maximumValue={duration}
|
maximumValue={duration}
|
||||||
minimumValue={0}
|
minimumValue={0}
|
||||||
thumbTintColor={isReply && isAndroid ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
|
thumbTintColor={thumbColor}
|
||||||
minimumTrackTintColor={themes[theme].tintColor}
|
minimumTrackTintColor={themes[theme].tintColor}
|
||||||
maximumTrackTintColor={themes[theme].auxiliaryText}
|
maximumTrackTintColor={themes[theme].auxiliaryText}
|
||||||
onValueChange={this.onValueChange}
|
onValueChange={this.onValueChange}
|
||||||
|
|
|
@ -82,11 +82,13 @@ interface IMessageReply {
|
||||||
|
|
||||||
const Fields = React.memo(
|
const Fields = React.memo(
|
||||||
({ attachment, getCustomEmoji }: IMessageFields) => {
|
({ attachment, getCustomEmoji }: IMessageFields) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!attachment.fields) {
|
if (!attachment.fields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{attachment.fields.map(field => (
|
{attachment.fields.map(field => (
|
||||||
|
@ -114,11 +116,12 @@ const Fields = React.memo(
|
||||||
|
|
||||||
const CollapsibleQuote = React.memo(
|
const CollapsibleQuote = React.memo(
|
||||||
({ attachment, index, getCustomEmoji }: IMessageReply) => {
|
({ attachment, index, getCustomEmoji }: IMessageReply) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const [collapsed, setCollapsed] = useState(attachment?.collapsed);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const [collapsed, setCollapsed] = useState(attachment.collapsed);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
setCollapsed(!collapsed);
|
setCollapsed(!collapsed);
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
|
||||||
const Content = React.memo(
|
const Content = React.memo(
|
||||||
(props: IMessageContent) => {
|
(props: IMessageContent) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
|
||||||
|
|
||||||
if (props.isInfo) {
|
if (props.isInfo) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const infoMessage = getInfoMessage({ ...props });
|
const infoMessage = getInfoMessage({ ...props });
|
||||||
|
@ -49,7 +51,6 @@ const Content = React.memo(
|
||||||
} else if (isPreview) {
|
} else if (isPreview) {
|
||||||
content = <MarkdownPreview msg={props.msg} />;
|
content = <MarkdownPreview msg={props.msg} />;
|
||||||
} else {
|
} else {
|
||||||
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
|
|
||||||
content = (
|
content = (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={props.msg}
|
msg={props.msg}
|
||||||
|
|
|
@ -10,11 +10,12 @@ import { E2E_MESSAGE_TYPE, themes } from '../../lib/constants';
|
||||||
|
|
||||||
const Encrypted = React.memo(({ type }: { type: string }) => {
|
const Encrypted = React.memo(({ type }: { type: string }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { onEncryptedPress } = useContext(MessageContext);
|
||||||
|
|
||||||
if (type !== E2E_MESSAGE_TYPE) {
|
if (type !== E2E_MESSAGE_TYPE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { onEncryptedPress } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
|
<Touchable onPress={onEncryptedPress} style={styles.encrypted} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
<CustomIcon name='encrypted' size={16} color={themes[theme].auxiliaryText} />
|
||||||
|
|
|
@ -12,14 +12,14 @@ import { themes } from '../../lib/constants';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { IAttachment } from '../../definitions';
|
import { IAttachment } from '../../definitions';
|
||||||
import { useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||||
|
|
||||||
interface IMessageButton {
|
interface IMessageButton {
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageImage {
|
interface IMessageImage {
|
||||||
|
@ -43,7 +43,7 @@ const Button = React.memo(({ children, onPress, disabled, theme }: IMessageButto
|
||||||
</Touchable>
|
</Touchable>
|
||||||
));
|
));
|
||||||
|
|
||||||
export const MessageImage = React.memo(({ imgUri, theme }: { imgUri: string; theme: string }) => (
|
export const MessageImage = React.memo(({ imgUri, theme }: { imgUri: string; theme: TSupportedThemes }) => (
|
||||||
<ImageProgress
|
<ImageProgress
|
||||||
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
||||||
source={{ uri: encodeURI(imgUri) }}
|
source={{ uri: encodeURI(imgUri) }}
|
||||||
|
|
|
@ -110,6 +110,9 @@ const Message = React.memo((props: IMessage) => {
|
||||||
Message.displayName = 'Message';
|
Message.displayName = 'Message';
|
||||||
|
|
||||||
const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
||||||
|
const { onPress, onLongPress } = useContext(MessageContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (props.hasError) {
|
if (props.hasError) {
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
@ -117,15 +120,13 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { onPress, onLongPress } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp || props.type === 'jitsi_call_started'}
|
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>
|
<View>
|
||||||
<Message {...props} />
|
<Message {...props} />
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -11,11 +11,12 @@ import { useTheme } from '../../theme';
|
||||||
const MessageError = React.memo(
|
const MessageError = React.memo(
|
||||||
({ hasError }: { hasError: boolean }) => {
|
({ hasError }: { hasError: boolean }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { onErrorPress } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { onErrorPress } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
<Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}>
|
||||||
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
<CustomIcon name='warning' color={themes[theme].dangerColor} size={18} />
|
||||||
|
|
|
@ -7,7 +7,7 @@ import styles from './styles';
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { testProps } from '../../lib/methods/testProps';
|
import { testProps } from '../../lib/methods/testProps';
|
||||||
|
@ -21,7 +21,7 @@ interface IReaction {
|
||||||
interface IMessageReaction {
|
interface IMessageReaction {
|
||||||
reaction: IReaction;
|
reaction: IReaction;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageReactions {
|
interface IMessageReactions {
|
||||||
|
@ -29,7 +29,7 @@ interface IMessageReactions {
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddReaction = React.memo(({ theme }: { theme: string }) => {
|
const AddReaction = React.memo(({ theme }: { theme: TSupportedThemes }) => {
|
||||||
const { reactionInit } = useContext(MessageContext);
|
const { reactionInit } = useContext(MessageContext);
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
|
|
|
@ -12,16 +12,7 @@ import { testProps } from '../../lib/methods/testProps';
|
||||||
|
|
||||||
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
|
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!tmid || !isHeader) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
|
const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
|
||||||
const fetch = async () => {
|
|
||||||
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
|
|
||||||
setMsg(threadName);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!msg) {
|
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) {
|
if (!msg) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { IAttachment } from '../../definitions/IAttachment';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import RCActivityIndicator from '../ActivityIndicator';
|
import RCActivityIndicator from '../ActivityIndicator';
|
||||||
import Attachments from './Attachments';
|
import Attachments from './Attachments';
|
||||||
import { useTheme } from '../../theme';
|
import { TSupportedThemes, useTheme } from '../../theme';
|
||||||
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
@ -77,8 +77,6 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 4
|
marginBottom: 4
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
// @ts-ignore TODO - check with the team, change this to undefined
|
|
||||||
width: null,
|
|
||||||
height: 200,
|
height: 200,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
borderTopLeftRadius: 4,
|
borderTopLeftRadius: 4,
|
||||||
|
@ -100,26 +98,38 @@ interface IMessageReply {
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Title = React.memo(({ attachment, timeFormat, theme }: { attachment: IAttachment; timeFormat?: string; theme: string }) => {
|
const Title = React.memo(
|
||||||
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
|
({ attachment, timeFormat, theme }: { attachment: IAttachment; timeFormat?: string; theme: TSupportedThemes }) => {
|
||||||
return (
|
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
|
||||||
<View style={styles.authorContainer}>
|
return (
|
||||||
{attachment.author_name ? (
|
<View style={styles.authorContainer}>
|
||||||
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
|
{attachment.author_name ? (
|
||||||
) : null}
|
<Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
|
||||||
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
|
) : null}
|
||||||
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
|
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
|
||||||
</View>
|
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
|
||||||
);
|
</View>
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const Description = React.memo(
|
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;
|
const text = attachment.text || attachment.title;
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<Markdown
|
<Markdown
|
||||||
msg={text}
|
msg={text}
|
||||||
|
@ -147,10 +157,12 @@ const Description = React.memo(
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
const UrlImage = React.memo(
|
||||||
({ image }: { image?: string }) => {
|
({ image }: { image?: string }) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
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} />;
|
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
||||||
},
|
},
|
||||||
|
@ -158,12 +170,21 @@ const UrlImage = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const Fields = 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) {
|
if (!attachment.fields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.fieldsContainer}>
|
<View style={styles.fieldsContainer}>
|
||||||
{attachment.fields.map(field => (
|
{attachment.fields.map(field => (
|
||||||
|
@ -189,13 +210,12 @@ const Reply = React.memo(
|
||||||
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
|
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
let url = attachment.title_link || attachment.author_link;
|
let url = attachment.title_link || attachment.author_link;
|
||||||
if (attachment.message_link) {
|
if (attachment.message_link) {
|
||||||
|
|
|
@ -13,12 +13,12 @@ import { testProps } from '../../lib/methods/testProps';
|
||||||
const Thread = React.memo(
|
const Thread = React.memo(
|
||||||
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
|
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!tlm || isThreadRoom || tcount === 0) {
|
if (!tlm || isThreadRoom || tcount === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext);
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Touchable from './Touchable';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
import { useTheme, withTheme } from '../../theme';
|
import { TSupportedThemes, useTheme, withTheme } from '../../theme';
|
||||||
import { LISTENER } from '../Toast';
|
import { LISTENER } from '../Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -53,10 +53,12 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
const UrlImage = React.memo(
|
||||||
({ image }: { image: string }) => {
|
({ image }: { image: string }) => {
|
||||||
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
|
||||||
image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`;
|
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} />;
|
return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />;
|
||||||
},
|
},
|
||||||
|
@ -64,7 +66,7 @@ const UrlImage = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const UrlContent = 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}>
|
<View style={styles.textContainer}>
|
||||||
{title ? (
|
{title ? (
|
||||||
<Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}>
|
<Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}>
|
||||||
|
@ -93,7 +95,7 @@ const UrlContent = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const Url = 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) {
|
if (!url || url?.ignoreParse) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,10 @@ interface IMessageUser {
|
||||||
|
|
||||||
const User = React.memo(
|
const User = React.memo(
|
||||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
|
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
|
||||||
|
const { user } = useContext(MessageContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (isHeader || hasError) {
|
if (isHeader || hasError) {
|
||||||
const { user } = useContext(MessageContext);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const username = (useRealName && author?.name) || author?.username;
|
const username = (useRealName && author?.name) || author?.username;
|
||||||
const aliasUsername = alias ? (
|
const aliasUsername = alias ? (
|
||||||
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Message from './Message';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
||||||
import { useTheme, withTheme } from '../../theme';
|
import { TSupportedThemes, withTheme } from '../../theme';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { IAttachment, TAnyMessageModel } from '../../definitions';
|
import { IAttachment, TAnyMessageModel } from '../../definitions';
|
||||||
|
@ -57,6 +57,7 @@ interface IMessageContainerProps {
|
||||||
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
||||||
jumpToMessage?: (link: string) => void;
|
jumpToMessage?: (link: string) => void;
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageContainerState {
|
interface IMessageContainerState {
|
||||||
|
@ -72,7 +73,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
archived: false,
|
archived: false,
|
||||||
broadcast: false,
|
broadcast: false,
|
||||||
isIgnored: false,
|
isIgnored: false,
|
||||||
theme: 'light'
|
theme: 'light' as TSupportedThemes
|
||||||
};
|
};
|
||||||
|
|
||||||
state = { isManualUnignored: false };
|
state = { isManualUnignored: false };
|
||||||
|
@ -292,8 +293,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
};
|
};
|
||||||
|
|
||||||
onLinkPress = (link: string): void => {
|
onLinkPress = (link: string): void => {
|
||||||
const { theme } = useTheme();
|
const { item, jumpToMessage, theme } = this.props;
|
||||||
const { item, jumpToMessage } = this.props;
|
|
||||||
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
|
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
|
||||||
if (isMessageLink && jumpToMessage) {
|
if (isMessageLink && jumpToMessage) {
|
||||||
return jumpToMessage(link);
|
return jumpToMessage(link);
|
||||||
|
@ -354,7 +354,8 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
blocks,
|
blocks,
|
||||||
autoTranslate: autoTranslateMessage,
|
autoTranslate: autoTranslateMessage,
|
||||||
replies,
|
replies,
|
||||||
md
|
md,
|
||||||
|
comment
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
let message = msg;
|
let message = msg;
|
||||||
|
@ -435,6 +436,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
|
||||||
callJitsi={callJitsi}
|
callJitsi={callJitsi}
|
||||||
blockAction={blockAction}
|
blockAction={blockAction}
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
|
comment={comment}
|
||||||
/>
|
/>
|
||||||
</MessageContext.Provider>
|
</MessageContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,6 +59,7 @@ export interface IMessageContent {
|
||||||
useRealName?: boolean;
|
useRealName?: boolean;
|
||||||
isIgnored: boolean;
|
isIgnored: boolean;
|
||||||
type: string;
|
type: string;
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageEmoji {
|
export interface IMessageEmoji {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { TMessageModel } from '../../definitions/IMessage';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { DISCUSSION } from './constants';
|
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;
|
const discussion = type === DISCUSSION;
|
||||||
let text = discussion ? I18n.t('No_messages_yet') : null;
|
let text = discussion ? I18n.t('No_messages_yet') : null;
|
||||||
if (!count) {
|
if (!count) {
|
||||||
|
@ -55,7 +55,9 @@ export const SYSTEM_MESSAGES = [
|
||||||
'user-converted-to-team',
|
'user-converted-to-team',
|
||||||
'user-converted-to-channel',
|
'user-converted-to-channel',
|
||||||
'user-deleted-room-from-team',
|
'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 = {
|
export const SYSTEM_MESSAGE_TYPES = {
|
||||||
|
@ -73,7 +75,9 @@ export const SYSTEM_MESSAGE_TYPES = {
|
||||||
CONVERTED_TO_TEAM: 'user-converted-to-team',
|
CONVERTED_TO_TEAM: 'user-converted-to-team',
|
||||||
CONVERTED_TO_CHANNEL: 'user-converted-to-channel',
|
CONVERTED_TO_CHANNEL: 'user-converted-to-channel',
|
||||||
DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
|
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 = [
|
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
|
||||||
|
@ -99,9 +103,10 @@ type TInfoMessage = {
|
||||||
role: string;
|
role: string;
|
||||||
msg: string;
|
msg: string;
|
||||||
author: { username: 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;
|
const { username } = author;
|
||||||
if (type === 'rm') {
|
if (type === 'rm') {
|
||||||
return I18n.t('Message_removed');
|
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') {
|
if (type === 'user-removed-room-from-team') {
|
||||||
return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
|
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 '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { TextInput } from 'react-native';
|
import { TextInput } from 'react-native';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../theme';
|
||||||
import { ILivechatVisitor } from './ILivechatVisitor';
|
import { ILivechatVisitor } from './ILivechatVisitor';
|
||||||
import { ISubscription } from './ISubscription';
|
import { ISubscription } from './ISubscription';
|
||||||
|
|
||||||
export interface ITitle {
|
export interface ITitle {
|
||||||
title: string;
|
title: string;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IInputs {
|
export interface IInputs {
|
||||||
|
|
|
@ -42,16 +42,16 @@ export interface ITranslations {
|
||||||
export type E2EType = 'pending' | 'done';
|
export type E2EType = 'pending' | 'done';
|
||||||
|
|
||||||
export interface ILastMessage {
|
export interface ILastMessage {
|
||||||
_id: string;
|
_id?: string;
|
||||||
rid: string;
|
rid?: string;
|
||||||
tshow?: boolean;
|
tshow?: boolean;
|
||||||
t?: MessageType;
|
t?: MessageType;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
e2e?: E2EType;
|
e2e?: E2EType;
|
||||||
ts: string | Date;
|
ts?: string | Date;
|
||||||
u: IUserMessage;
|
u: IUserMessage;
|
||||||
_updatedAt: string | Date;
|
_updatedAt?: string | Date;
|
||||||
urls?: IUrlFromServer[];
|
urls?: IUrlFromServer[];
|
||||||
mentions?: IUserMention[];
|
mentions?: IUserMention[];
|
||||||
channels?: IUserChannel[];
|
channels?: IUserChannel[];
|
||||||
|
@ -59,7 +59,9 @@ export interface ILastMessage {
|
||||||
attachments?: IAttachment[];
|
attachments?: IAttachment[];
|
||||||
reactions?: IReaction[];
|
reactions?: IReaction[];
|
||||||
unread?: boolean;
|
unread?: boolean;
|
||||||
|
pinned?: boolean;
|
||||||
status?: number;
|
status?: number;
|
||||||
|
token?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageFile {
|
interface IMessageFile {
|
||||||
|
@ -140,6 +142,7 @@ export interface IMessage extends IMessageFromServer {
|
||||||
blocks?: any;
|
blocks?: any;
|
||||||
e2e?: E2EType;
|
e2e?: E2EType;
|
||||||
tshow?: boolean;
|
tshow?: boolean;
|
||||||
|
comment?: string;
|
||||||
subscription?: { id: string };
|
subscription?: { id: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { TSupportedThemes } from '../theme';
|
||||||
import { ProfileStackParamList } from '../stacks/types';
|
import { ProfileStackParamList } from '../stacks/types';
|
||||||
import { IUser } from './IUser';
|
import { IUser } from './IUser';
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ export interface IProfileViewProps {
|
||||||
Accounts_AllowUsernameChange: boolean;
|
Accounts_AllowUsernameChange: boolean;
|
||||||
Accounts_CustomFields: string;
|
Accounts_CustomFields: string;
|
||||||
setUser: Function;
|
setUser: Function;
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAvatar {
|
export interface IAvatar {
|
||||||
|
|
|
@ -55,6 +55,8 @@ export interface IRoom {
|
||||||
uids: Array<string>;
|
uids: Array<string>;
|
||||||
lm?: Date;
|
lm?: Date;
|
||||||
sysMes?: string[];
|
sysMes?: string[];
|
||||||
|
onHold?: boolean;
|
||||||
|
waitingResponse?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OmnichannelSourceType {
|
export enum OmnichannelSourceType {
|
||||||
|
@ -65,6 +67,24 @@ export enum OmnichannelSourceType {
|
||||||
API = 'api',
|
API = 'api',
|
||||||
OTHER = 'other' // catch-all source type
|
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'>> {
|
export interface IOmnichannelRoom extends Partial<Omit<IRoom, 'default' | 'featured' | 'broadcast'>> {
|
||||||
_id: string;
|
_id: string;
|
||||||
rid: string;
|
rid: string;
|
||||||
|
@ -77,23 +97,7 @@ export interface IOmnichannelRoom extends Partial<Omit<IRoom, 'default' | 'featu
|
||||||
replyTo: string;
|
replyTo: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
};
|
};
|
||||||
source: {
|
source: IOmnichannelSource;
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
transcriptRequest?: IRequestTranscript;
|
transcriptRequest?: IRequestTranscript;
|
||||||
servedBy?: IServedBy;
|
servedBy?: IServedBy;
|
||||||
onHold?: boolean;
|
onHold?: boolean;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Relation from '@nozbe/watermelondb/Relation';
|
||||||
|
|
||||||
import { ILastMessage, TMessageModel } from './IMessage';
|
import { ILastMessage, TMessageModel } from './IMessage';
|
||||||
import { IRocketChatRecord } from './IRocketChatRecord';
|
import { IRocketChatRecord } from './IRocketChatRecord';
|
||||||
import { RoomID, RoomType } from './IRoom';
|
import { IOmnichannelSource, RoomID, RoomType } from './IRoom';
|
||||||
import { IServedBy } from './IServedBy';
|
import { IServedBy } from './IServedBy';
|
||||||
import { TThreadModel } from './IThread';
|
import { TThreadModel } from './IThread';
|
||||||
import { TThreadMessageModel } from './IThreadMessage';
|
import { TThreadMessageModel } from './IThreadMessage';
|
||||||
|
@ -98,6 +98,8 @@ export interface ISubscription {
|
||||||
teamMain?: boolean;
|
teamMain?: boolean;
|
||||||
unsubscribe: () => Promise<any>;
|
unsubscribe: () => Promise<any>;
|
||||||
separator?: boolean;
|
separator?: boolean;
|
||||||
|
onHold?: boolean;
|
||||||
|
source?: IOmnichannelSource;
|
||||||
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
|
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
|
||||||
messages: RelationModified<TMessageModel>;
|
messages: RelationModified<TMessageModel>;
|
||||||
threads: RelationModified<TThreadModel>;
|
threads: RelationModified<TThreadModel>;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { RouteProp } from '@react-navigation/native';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
|
|
||||||
import { TColors } from '../theme';
|
import { TColors, TSupportedThemes } from '../theme';
|
||||||
|
|
||||||
export * from './IAttachment';
|
export * from './IAttachment';
|
||||||
export * from './INotification';
|
export * from './INotification';
|
||||||
|
@ -36,7 +36,7 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
|
||||||
dispatch: Dispatch;
|
dispatch: Dispatch;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
// TODO: remove after migrating all Class components
|
// TODO: remove after migrating all Class components
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
colors: TColors;
|
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