Merge branch 'develop' into update.enable-multiline-android-tablets
This commit is contained in:
commit
c369baf63f
|
@ -3,7 +3,7 @@ defaults: &defaults
|
||||||
|
|
||||||
macos: &macos
|
macos: &macos
|
||||||
macos:
|
macos:
|
||||||
xcode: "12.5.0"
|
xcode: "13.3.0"
|
||||||
resource_class: large
|
resource_class: large
|
||||||
|
|
||||||
bash-env: &bash-env
|
bash-env: &bash-env
|
||||||
|
|
|
@ -1,45 +1,8 @@
|
||||||
import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots';
|
import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots';
|
||||||
import { render } from '@testing-library/react-native';
|
import { render } from '@testing-library/react-native';
|
||||||
|
|
||||||
jest.mock('rn-fetch-blob', () => ({
|
|
||||||
fs: {
|
|
||||||
dirs: {
|
|
||||||
DocumentDir: '/data/com.rocket.chat/documents',
|
|
||||||
DownloadDir: '/data/com.rocket.chat/downloads'
|
|
||||||
},
|
|
||||||
exists: jest.fn(() => null)
|
|
||||||
},
|
|
||||||
fetch: jest.fn(() => null),
|
|
||||||
config: jest.fn(() => null)
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('react-native-file-viewer', () => ({
|
|
||||||
open: jest.fn(() => null)
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../app/lib/database', () => jest.fn(() => null));
|
|
||||||
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
|
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
|
||||||
|
|
||||||
jest.mock('react-native-mmkv-storage', () => {
|
|
||||||
return {
|
|
||||||
Loader: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
setProcessingMode: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
withEncryption: jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
initialize: jest.fn()
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
create: jest.fn(),
|
|
||||||
MODES: { MULTI_PROCESS: '' }
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const converter = new Stories2SnapsConverter();
|
const converter = new Stories2SnapsConverter();
|
||||||
|
|
||||||
initStoryshots({
|
initStoryshots({
|
||||||
|
|
|
@ -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.0"
|
versionName "4.26.2"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
if (!isFoss) {
|
if (!isFoss) {
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
|
||||||
import { MESSAGES } from './actionsTypes';
|
import { MESSAGES } from './actionsTypes';
|
||||||
|
import { IMessage } from '../definitions';
|
||||||
type IMessage = Record<string, string>;
|
|
||||||
|
|
||||||
interface IReplyBroadcast extends Action {
|
interface IReplyBroadcast extends Action {
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
|
|
|
@ -19,7 +19,7 @@ const mentions = {
|
||||||
mentionOtherColor: '#F3BE08'
|
mentionOtherColor: '#F3BE08'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const themes: any = {
|
export const colors = {
|
||||||
light: {
|
light: {
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
focusedBackground: '#ffffff',
|
focusedBackground: '#ffffff',
|
||||||
|
@ -174,3 +174,5 @@ export const themes: any = {
|
||||||
...mentions
|
...mentions
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const themes: any = colors;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react';
|
||||||
|
|
||||||
import ActionSheet from './ActionSheet';
|
import ActionSheet from './ActionSheet';
|
||||||
|
|
||||||
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void };
|
export type TActionSheetOptionsItem = { title: string; icon: string; onPress: () => void; danger?: boolean };
|
||||||
|
|
||||||
export type TActionSheetOptions = {
|
export type TActionSheetOptions = {
|
||||||
options: TActionSheetOptionsItem[];
|
options: TActionSheetOptionsItem[];
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { avatarURL } from '../../utils/avatar';
|
||||||
import { SubscriptionType } from '../../definitions/ISubscription';
|
import { SubscriptionType } from '../../definitions/ISubscription';
|
||||||
import Emoji from '../markdown/Emoji';
|
import Emoji from '../markdown/Emoji';
|
||||||
import { IAvatar } from './interfaces';
|
import { IAvatar } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const Avatar = React.memo(
|
const Avatar = React.memo(
|
||||||
({
|
({
|
||||||
|
@ -18,7 +19,6 @@ const Avatar = React.memo(
|
||||||
user,
|
user,
|
||||||
onPress,
|
onPress,
|
||||||
emoji,
|
emoji,
|
||||||
theme,
|
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
avatarETag,
|
avatarETag,
|
||||||
isStatic,
|
isStatic,
|
||||||
|
@ -34,6 +34,8 @@ const Avatar = React.memo(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const avatarStyle = {
|
const avatarStyle = {
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
|
@ -44,7 +46,7 @@ const Avatar = React.memo(
|
||||||
if (emoji) {
|
if (emoji) {
|
||||||
image = (
|
image = (
|
||||||
<Emoji
|
<Emoji
|
||||||
theme={theme!}
|
theme={theme}
|
||||||
baseUrl={server}
|
baseUrl={server}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
isMessageContainsOnlyEmoji
|
isMessageContainsOnlyEmoji
|
||||||
|
|
|
@ -5,13 +5,11 @@ import { Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import { TSubscriptionModel, TUserModel } from '../../definitions';
|
import { IApplicationState, TSubscriptionModel, TUserModel } from '../../definitions';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import { IAvatar } from './interfaces';
|
import { IAvatar } from './interfaces';
|
||||||
|
|
||||||
class AvatarContainer extends React.Component<IAvatar, any> {
|
class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
private mounted: boolean;
|
|
||||||
|
|
||||||
private subscription?: Subscription;
|
private subscription?: Subscription;
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -21,16 +19,11 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
|
|
||||||
constructor(props: IAvatar) {
|
constructor(props: IAvatar) {
|
||||||
super(props);
|
super(props);
|
||||||
this.mounted = false;
|
|
||||||
this.state = { avatarETag: '' };
|
this.state = { avatarETag: '' };
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidUpdate(prevProps: IAvatar) {
|
||||||
this.mounted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: any) {
|
|
||||||
const { text, type } = this.props;
|
const { text, type } = this.props;
|
||||||
if (prevProps.text !== text || prevProps.type !== type) {
|
if (prevProps.text !== text || prevProps.type !== type) {
|
||||||
this.init();
|
this.init();
|
||||||
|
@ -88,12 +81,7 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
|
const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
|
||||||
this.subscription = observable.subscribe(r => {
|
this.subscription = observable.subscribe(r => {
|
||||||
const { avatarETag } = r;
|
const { avatarETag } = r;
|
||||||
if (this.mounted) {
|
|
||||||
this.setState({ avatarETag });
|
this.setState({ avatarETag });
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
this.state.avatarETag = avatarETag;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -105,12 +93,12 @@ class AvatarContainer extends React.Component<IAvatar, any> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
server: state.share.server.server || state.server.server,
|
server: state.share.server.server || state.server.server,
|
||||||
serverVersion: state.share.server.version || state.server.version,
|
serverVersion: state.share.server.version || state.server.version,
|
||||||
blockUnauthenticatedAccess:
|
blockUnauthenticatedAccess:
|
||||||
state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess ??
|
(state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess as boolean) ??
|
||||||
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
|
state.settings.Accounts_AvatarBlockUnauthenticatedAccess ??
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,12 +16,11 @@ export interface IAvatar {
|
||||||
id?: string;
|
id?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
};
|
};
|
||||||
theme?: string;
|
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
getCustomEmoji?: TGetCustomEmoji;
|
getCustomEmoji?: TGetCustomEmoji;
|
||||||
avatarETag?: string;
|
avatarETag?: string;
|
||||||
isStatic?: boolean | string;
|
isStatic?: boolean | string;
|
||||||
rid?: string;
|
rid?: string;
|
||||||
blockUnauthenticatedAccess?: boolean;
|
blockUnauthenticatedAccess?: boolean;
|
||||||
serverVersion: string;
|
serverVersion: string | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ const CustomEmoji = React.memo(
|
||||||
<FastImage
|
<FastImage
|
||||||
style={style}
|
style={style}
|
||||||
source={{
|
source={{
|
||||||
|
// @ts-ignore
|
||||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
||||||
priority: FastImage.priority.high
|
priority: FastImage.priority.high
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -56,6 +56,7 @@ class EmojiCategory extends React.Component<Partial<IEmojiCategory>> {
|
||||||
contentContainerStyle={{ marginHorizontal }}
|
contentContainerStyle={{ marginHorizontal }}
|
||||||
// rerender FlatList in case of width changes
|
// rerender FlatList in case of width changes
|
||||||
key={`emoji-category-${width}`}
|
key={`emoji-category-${width}`}
|
||||||
|
// @ts-ignore
|
||||||
keyExtractor={item => (item && item.isCustom && item.content) || item}
|
keyExtractor={item => (item && item.isCustom && item.content) || item}
|
||||||
data={emojis}
|
data={emojis}
|
||||||
extraData={this.props}
|
extraData={this.props}
|
||||||
|
|
|
@ -109,6 +109,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||||
let freqEmojiRecord: any;
|
let freqEmojiRecord: any;
|
||||||
try {
|
try {
|
||||||
|
// @ts-ignore
|
||||||
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
|
@ -285,7 +285,7 @@ class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServi
|
||||||
toValue: height,
|
toValue: height,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
easing: Easing.inOut(Easing.quad),
|
easing: Easing.inOut(Easing.quad),
|
||||||
useNativeDriver: true
|
useNativeDriver: false
|
||||||
}).start();
|
}).start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useCallback, 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 { withTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
|
@ -10,26 +10,30 @@ import database from '../../lib/database';
|
||||||
import { Button } from '../ActionSheet';
|
import { Button } from '../ActionSheet';
|
||||||
import { useDimensions } from '../../dimensions';
|
import { useDimensions } from '../../dimensions';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { IEmoji } from '../../definitions/IEmoji';
|
|
||||||
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
|
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
|
||||||
|
import { TAnyMessageModel } from '../../definitions';
|
||||||
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
interface IHeader {
|
type TItem = TFrequentlyUsedEmojiModel | string;
|
||||||
handleReaction: Function;
|
|
||||||
|
export interface IHeader {
|
||||||
|
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void;
|
||||||
server: string;
|
server: string;
|
||||||
message: object;
|
message: TAnyMessageModel;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
theme?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TOnReaction = ({ emoji }: { emoji: TItem }) => void;
|
||||||
|
|
||||||
interface THeaderItem {
|
interface THeaderItem {
|
||||||
item: IEmoji;
|
item: TItem;
|
||||||
onReaction: Function;
|
onReaction: TOnReaction;
|
||||||
server: string;
|
server: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface THeaderFooter {
|
interface THeaderFooter {
|
||||||
onReaction: any;
|
onReaction: TOnReaction;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,25 +66,32 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const keyExtractor = (item: any) => item?.id || item;
|
const keyExtractor = (item: TItem) => {
|
||||||
|
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
||||||
|
return (emojiModel.id ? emojiModel.content : item) as string;
|
||||||
|
};
|
||||||
|
|
||||||
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
|
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
|
||||||
|
|
||||||
const HeaderItem = React.memo(({ item, onReaction, server, theme }: THeaderItem) => (
|
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
|
||||||
|
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
||||||
|
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
testID={`message-actions-emoji-${item.content || item}`}
|
testID={`message-actions-emoji-${emoji}`}
|
||||||
onPress={() => onReaction({ emoji: `:${item.content || item}:` })}
|
onPress={() => onReaction({ emoji: `:${emoji}:` })}
|
||||||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||||
theme={theme}>
|
theme={theme}>
|
||||||
{item?.isCustom ? (
|
{emojiModel?.isCustom ? (
|
||||||
<CustomEmoji style={styles.customEmoji} emoji={item} baseUrl={server} />
|
<CustomEmoji style={styles.customEmoji} emoji={emojiModel as IEmoji} baseUrl={server} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${item.content || item}:`)}</Text>
|
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
));
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
|
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
||||||
<Button
|
<Button
|
||||||
testID='add-reaction'
|
testID='add-reaction'
|
||||||
onPress={onReaction}
|
onPress={onReaction}
|
||||||
|
@ -88,17 +99,19 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
|
||||||
theme={theme}>
|
theme={theme}>
|
||||||
<CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} />
|
<CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} />
|
||||||
</Button>
|
</Button>
|
||||||
));
|
);
|
||||||
|
|
||||||
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
|
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => {
|
||||||
const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
|
const [items, setItems] = useState<TItem[]>([]);
|
||||||
const { width, height }: any = useDimensions();
|
const { width, height } = useDimensions();
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
// TODO: create custom hook to re-render based on screen size
|
||||||
const setEmojis = async () => {
|
const setEmojis = async () => {
|
||||||
try {
|
try {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||||
let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
|
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
|
||||||
|
|
||||||
const isLandscape = width > height;
|
const isLandscape = width > height;
|
||||||
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
||||||
|
@ -115,22 +128,21 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
|
||||||
setEmojis();
|
setEmojis();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message);
|
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = ({ item }: { item: TItem }) => (
|
||||||
({ item }) => <HeaderItem item={item} onReaction={onReaction} server={server} theme={theme!} />,
|
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderFooter = useCallback(() => <HeaderFooter onReaction={onReaction} theme={theme!} />, []);
|
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, { backgroundColor: themes[theme!].focusedBackground }]}>
|
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={items}
|
data={items}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
ListFooterComponent={renderFooter}
|
ListFooterComponent={renderFooter}
|
||||||
style={{ backgroundColor: themes[theme!].focusedBackground }}
|
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
scrollEnabled={false}
|
scrollEnabled={false}
|
||||||
|
@ -140,4 +152,4 @@ const Header = React.memo(({ handleReaction, server, message, isMasterDetail, th
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withTheme(Header);
|
export default Header;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
import { Alert, Clipboard, Share } from 'react-native';
|
import { Alert, Share } from 'react-native';
|
||||||
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
@ -12,33 +13,33 @@ import { getMessageTranslation } from '../message/utils';
|
||||||
import { LISTENER } from '../Toast';
|
import { LISTENER } from '../Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import { showConfirmationAlert } from '../../utils/info';
|
import { showConfirmationAlert } from '../../utils/info';
|
||||||
import { useActionSheet } from '../ActionSheet';
|
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
||||||
import Header, { HEADER_HEIGHT } from './Header';
|
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||||
import events from '../../utils/log/events';
|
import events from '../../utils/log/events';
|
||||||
import { ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||||
|
|
||||||
export interface IMessageActions {
|
export interface IMessageActions {
|
||||||
room: TSubscriptionModel;
|
room: TSubscriptionModel;
|
||||||
tmid?: string;
|
tmid?: string;
|
||||||
user: Pick<ILoggedUser, 'id'>;
|
user: Pick<ILoggedUser, 'id'>;
|
||||||
editInit: Function;
|
editInit: (message: TAnyMessageModel) => void;
|
||||||
reactionInit: Function;
|
reactionInit: (message: TAnyMessageModel) => void;
|
||||||
onReactionPress: Function;
|
onReactionPress: (shortname: string, messageId: string) => void;
|
||||||
replyInit: Function;
|
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
Message_AllowDeleting: boolean;
|
Message_AllowDeleting?: boolean;
|
||||||
Message_AllowDeleting_BlockDeleteInMinutes: number;
|
Message_AllowDeleting_BlockDeleteInMinutes?: number;
|
||||||
Message_AllowEditing: boolean;
|
Message_AllowEditing?: boolean;
|
||||||
Message_AllowEditing_BlockEditInMinutes: number;
|
Message_AllowEditing_BlockEditInMinutes?: number;
|
||||||
Message_AllowPinning: boolean;
|
Message_AllowPinning?: boolean;
|
||||||
Message_AllowStarring: boolean;
|
Message_AllowStarring?: boolean;
|
||||||
Message_Read_Receipt_Store_Users: boolean;
|
Message_Read_Receipt_Store_Users?: boolean;
|
||||||
server: string;
|
server: string;
|
||||||
editMessagePermission: [];
|
editMessagePermission?: string[];
|
||||||
deleteMessagePermission: [];
|
deleteMessagePermission?: string[];
|
||||||
forceDeleteMessagePermission: [];
|
forceDeleteMessagePermission?: string[];
|
||||||
pinMessagePermission: [];
|
pinMessagePermission?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageActions = React.memo(
|
const MessageActions = React.memo(
|
||||||
|
@ -68,9 +69,14 @@ const MessageActions = React.memo(
|
||||||
pinMessagePermission
|
pinMessagePermission
|
||||||
}: IMessageActions,
|
}: IMessageActions,
|
||||||
ref
|
ref
|
||||||
): any => {
|
) => {
|
||||||
let permissions: any = {};
|
let permissions = {
|
||||||
const { showActionSheet, hideActionSheet }: any = useActionSheet();
|
hasEditPermission: false,
|
||||||
|
hasDeletePermission: false,
|
||||||
|
hasForceDeletePermission: false,
|
||||||
|
hasPinPermission: false
|
||||||
|
};
|
||||||
|
const { showActionSheet, hideActionSheet } = useActionSheet();
|
||||||
|
|
||||||
const getPermissions = async () => {
|
const getPermissions = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -87,9 +93,9 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isOwn = (message: any) => message.u && message.u._id === user.id;
|
const isOwn = (message: TAnyMessageModel) => message.u && message.u._id === user.id;
|
||||||
|
|
||||||
const allowEdit = (message: any) => {
|
const allowEdit = (message: TAnyMessageModel) => {
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -104,7 +110,7 @@ const MessageActions = React.memo(
|
||||||
if (message.ts != null) {
|
if (message.ts != null) {
|
||||||
msgTs = moment(message.ts);
|
msgTs = moment(message.ts);
|
||||||
}
|
}
|
||||||
let currentTsDiff: any;
|
let currentTsDiff = 0;
|
||||||
if (msgTs != null) {
|
if (msgTs != null) {
|
||||||
currentTsDiff = moment().diff(msgTs, 'minutes');
|
currentTsDiff = moment().diff(msgTs, 'minutes');
|
||||||
}
|
}
|
||||||
|
@ -113,7 +119,7 @@ const MessageActions = React.memo(
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allowDelete = (message: any) => {
|
const allowDelete = (message: TAnyMessageModel) => {
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -135,7 +141,7 @@ const MessageActions = React.memo(
|
||||||
if (message.ts != null) {
|
if (message.ts != null) {
|
||||||
msgTs = moment(message.ts);
|
msgTs = moment(message.ts);
|
||||||
}
|
}
|
||||||
let currentTsDiff: any;
|
let currentTsDiff = 0;
|
||||||
if (msgTs != null) {
|
if (msgTs != null) {
|
||||||
currentTsDiff = moment().diff(msgTs, 'minutes');
|
currentTsDiff = moment().diff(msgTs, 'minutes');
|
||||||
}
|
}
|
||||||
|
@ -144,19 +150,19 @@ const MessageActions = React.memo(
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPermalink = (message: any) => RocketChat.getPermalinkMessage(message);
|
const getPermalink = (message: TAnyMessageModel) => RocketChat.getPermalinkMessage(message);
|
||||||
|
|
||||||
const handleReply = (message: any) => {
|
const handleReply = (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REPLY);
|
logEvent(events.ROOM_MSG_ACTION_REPLY);
|
||||||
replyInit(message, true);
|
replyInit(message, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (message: any) => {
|
const handleEdit = (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_EDIT);
|
logEvent(events.ROOM_MSG_ACTION_EDIT);
|
||||||
editInit(message);
|
editInit(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateDiscussion = (message: any) => {
|
const handleCreateDiscussion = (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
|
logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
|
||||||
const params = { message, channel: room, showCloseModal: true };
|
const params = { message, channel: room, showCloseModal: true };
|
||||||
if (isMasterDetail) {
|
if (isMasterDetail) {
|
||||||
|
@ -166,7 +172,7 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnread = async (message: any) => {
|
const handleUnread = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_UNREAD);
|
logEvent(events.ROOM_MSG_ACTION_UNREAD);
|
||||||
const { id: messageId, ts } = message;
|
const { id: messageId, ts } = message;
|
||||||
const { rid } = room;
|
const { rid } = room;
|
||||||
|
@ -178,7 +184,7 @@ const MessageActions = React.memo(
|
||||||
const subRecord = await subCollection.find(rid);
|
const subRecord = await subCollection.find(rid);
|
||||||
await db.write(async () => {
|
await db.write(async () => {
|
||||||
try {
|
try {
|
||||||
await subRecord.update(sub => (sub.lastOpen = ts));
|
await subRecord.update(sub => (sub.lastOpen = ts as Date)); // TODO: reevaluate IMessage
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
@ -191,42 +197,44 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePermalink = async (message: any) => {
|
const handlePermalink = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_PERMALINK);
|
logEvent(events.ROOM_MSG_ACTION_PERMALINK);
|
||||||
try {
|
try {
|
||||||
const permalink: any = await getPermalink(message);
|
const permalink = await getPermalink(message);
|
||||||
Clipboard.setString(permalink);
|
Clipboard.setString(permalink ?? '');
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
|
||||||
} catch {
|
} catch {
|
||||||
logEvent(events.ROOM_MSG_ACTION_PERMALINK_F);
|
logEvent(events.ROOM_MSG_ACTION_PERMALINK_F);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopy = async (message: any) => {
|
const handleCopy = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_COPY);
|
logEvent(events.ROOM_MSG_ACTION_COPY);
|
||||||
await Clipboard.setString(message?.attachments?.[0]?.description || message.msg);
|
await Clipboard.setString((message?.attachments?.[0]?.description || message.msg) ?? '');
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShare = async (message: any) => {
|
const handleShare = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_SHARE);
|
logEvent(events.ROOM_MSG_ACTION_SHARE);
|
||||||
try {
|
try {
|
||||||
const permalink: any = await getPermalink(message);
|
const permalink = await getPermalink(message);
|
||||||
|
if (permalink) {
|
||||||
Share.share({ message: permalink });
|
Share.share({ message: permalink });
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
logEvent(events.ROOM_MSG_ACTION_SHARE_F);
|
logEvent(events.ROOM_MSG_ACTION_SHARE_F);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQuote = (message: any) => {
|
const handleQuote = (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_QUOTE);
|
logEvent(events.ROOM_MSG_ACTION_QUOTE);
|
||||||
replyInit(message, false);
|
replyInit(message, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStar = async (message: any) => {
|
const handleStar = async (message: TAnyMessageModel) => {
|
||||||
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
||||||
try {
|
try {
|
||||||
await RocketChat.toggleStarMessage(message.id, message.starred);
|
await RocketChat.toggleStarMessage(message.id, message.starred as boolean); // TODO: reevaluate `message.starred` type on IMessage
|
||||||
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
|
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_STAR_F);
|
logEvent(events.ROOM_MSG_ACTION_STAR_F);
|
||||||
|
@ -234,20 +242,21 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePin = async (message: any) => {
|
const handlePin = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_PIN);
|
logEvent(events.ROOM_MSG_ACTION_PIN);
|
||||||
try {
|
try {
|
||||||
await RocketChat.togglePinMessage(message.id, message.pinned);
|
await RocketChat.togglePinMessage(message.id, message.pinned as boolean); // TODO: reevaluate `message.pinned` type on IMessage
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_PIN_F);
|
logEvent(events.ROOM_MSG_ACTION_PIN_F);
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReaction = (shortname: any, message: any) => {
|
const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
||||||
if (shortname) {
|
if (shortname) {
|
||||||
onReactionPress(shortname, message.id);
|
// TODO: evaluate unification with IEmoji
|
||||||
|
onReactionPress(shortname as any, message.id);
|
||||||
} else {
|
} else {
|
||||||
reactionInit(message);
|
reactionInit(message);
|
||||||
}
|
}
|
||||||
|
@ -255,7 +264,7 @@ const MessageActions = React.memo(
|
||||||
hideActionSheet();
|
hideActionSheet();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReadReceipt = (message: any) => {
|
const handleReadReceipt = (message: TAnyMessageModel) => {
|
||||||
if (isMasterDetail) {
|
if (isMasterDetail) {
|
||||||
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
|
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
|
||||||
} else {
|
} else {
|
||||||
|
@ -290,7 +299,7 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReport = async (message: any) => {
|
const handleReport = async (message: TAnyMessageModel) => {
|
||||||
logEvent(events.ROOM_MSG_ACTION_REPORT);
|
logEvent(events.ROOM_MSG_ACTION_REPORT);
|
||||||
try {
|
try {
|
||||||
await RocketChat.reportMessage(message.id);
|
await RocketChat.reportMessage(message.id);
|
||||||
|
@ -301,14 +310,14 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (message: any) => {
|
const handleDelete = (message: TAnyMessageModel) => {
|
||||||
showConfirmationAlert({
|
showConfirmationAlert({
|
||||||
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
|
message: I18n.t('You_will_not_be_able_to_recover_this_message'),
|
||||||
confirmationText: I18n.t('Delete'),
|
confirmationText: I18n.t('Delete'),
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
logEvent(events.ROOM_MSG_ACTION_DELETE);
|
logEvent(events.ROOM_MSG_ACTION_DELETE);
|
||||||
await RocketChat.deleteMessage(message.id, message.subscription.id);
|
await RocketChat.deleteMessage(message.id, message.subscription ? message.subscription.id : '');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
|
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -318,7 +327,7 @@ const MessageActions = React.memo(
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOptions = (message: TAnyMessageModel) => {
|
const getOptions = (message: TAnyMessageModel) => {
|
||||||
let options: any = [];
|
let options: TActionSheetOptionsItem[] = [];
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
if (!isReadOnly) {
|
if (!isReadOnly) {
|
||||||
|
@ -462,16 +471,15 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
const mapStateToProps = (state: any) => ({
|
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
Message_AllowDeleting: state.settings.Message_AllowDeleting,
|
Message_AllowDeleting: state.settings.Message_AllowDeleting as boolean,
|
||||||
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
|
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes as number,
|
||||||
Message_AllowEditing: state.settings.Message_AllowEditing,
|
Message_AllowEditing: state.settings.Message_AllowEditing as boolean,
|
||||||
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes,
|
Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes as number,
|
||||||
Message_AllowPinning: state.settings.Message_AllowPinning,
|
Message_AllowPinning: state.settings.Message_AllowPinning as boolean,
|
||||||
Message_AllowStarring: state.settings.Message_AllowStarring,
|
Message_AllowStarring: state.settings.Message_AllowStarring as boolean,
|
||||||
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users,
|
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users as boolean,
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
editMessagePermission: state.permissions['edit-message'],
|
editMessagePermission: state.permissions['edit-message'],
|
||||||
deleteMessagePermission: state.permissions['delete-message'],
|
deleteMessagePermission: state.permissions['delete-message'],
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity } from 'react-native';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
|
||||||
|
|
||||||
import styles from '../styles';
|
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import MessageboxContext from '../Context';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
import ActivityIndicator from '../../ActivityIndicator';
|
import ActivityIndicator from '../../ActivityIndicator';
|
||||||
|
import MessageboxContext from '../Context';
|
||||||
|
import styles from '../styles';
|
||||||
|
|
||||||
interface IMessageBoxCommandsPreviewItem {
|
interface IMessageBoxCommandsPreviewItem {
|
||||||
item: {
|
item: {
|
||||||
|
@ -14,13 +15,13 @@ interface IMessageBoxCommandsPreviewItem {
|
||||||
id: string;
|
id: string;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
theme?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
|
const Item = ({ item }: IMessageBoxCommandsPreviewItem) => {
|
||||||
const context = useContext(MessageboxContext);
|
const context = useContext(MessageboxContext);
|
||||||
const { onPressCommandPreview } = context;
|
const { onPressCommandPreview } = context;
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
@ -37,7 +38,7 @@ const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => {
|
||||||
{loading ? <ActivityIndicator /> : null}
|
{loading ? <ActivityIndicator /> : null}
|
||||||
</FastImage>
|
</FastImage>
|
||||||
) : (
|
) : (
|
||||||
<CustomIcon name='attach' size={36} color={themes[theme!].actionTintColor} />
|
<CustomIcon name='attach' size={36} color={themes[theme].actionTintColor} />
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
|
import { dequal } from 'dequal';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FlatList } from 'react-native';
|
import { FlatList } from 'react-native';
|
||||||
import { dequal } from 'dequal';
|
|
||||||
|
|
||||||
import Item from './Item';
|
|
||||||
import styles from '../styles';
|
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import { withTheme } from '../../../theme';
|
|
||||||
import { IPreviewItem } from '../../../definitions';
|
import { IPreviewItem } from '../../../definitions';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import styles from '../styles';
|
||||||
|
import Item from './Item';
|
||||||
|
|
||||||
interface IMessageBoxCommandsPreview {
|
interface IMessageBoxCommandsPreview {
|
||||||
commandPreview: IPreviewItem[];
|
commandPreview: IPreviewItem[];
|
||||||
showCommandPreview: boolean;
|
showCommandPreview: boolean;
|
||||||
theme?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommandsPreview = React.memo(
|
const CommandsPreview = React.memo(
|
||||||
({ theme, commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
|
({ commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => {
|
||||||
if (!showCommandPreview) {
|
if (!showCommandPreview) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
testID='commandbox-container'
|
testID='commandbox-container'
|
||||||
style={[styles.mentionList, { backgroundColor: themes[theme!].messageboxBackground }]}
|
style={[styles.mentionList, { backgroundColor: themes[theme].messageboxBackground }]}
|
||||||
data={commandPreview}
|
data={commandPreview}
|
||||||
renderItem={({ item }) => <Item item={item} theme={theme} />}
|
renderItem={({ item }) => <Item item={item} />}
|
||||||
keyExtractor={(item: any) => item.id}
|
keyExtractor={(item: any) => item.id}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
horizontal
|
horizontal
|
||||||
|
@ -33,9 +33,6 @@ const CommandsPreview = React.memo(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prevProps, nextProps) => {
|
(prevProps, nextProps) => {
|
||||||
if (prevProps.theme !== nextProps.theme) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
|
if (prevProps.showCommandPreview !== nextProps.showCommandPreview) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -46,4 +43,4 @@ const CommandsPreview = React.memo(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default withTheme(CommandsPreview);
|
export default CommandsPreview;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
// @ts-ignore
|
const MessageboxContext = React.createContext<any>(null);
|
||||||
const MessageboxContext = React.createContext<any>();
|
|
||||||
export default MessageboxContext;
|
export default MessageboxContext;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import EmojiPicker from '../EmojiPicker';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
|
import { IEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
interface IMessageBoxEmojiKeyboard {
|
interface IMessageBoxEmojiKeyboard {
|
||||||
theme: string;
|
theme: string;
|
||||||
|
@ -21,7 +22,7 @@ export default class EmojiKeyboard extends React.PureComponent<IMessageBoxEmojiK
|
||||||
this.baseUrl = state.share.server.server || state.server.server;
|
this.baseUrl = state.share.server.server || state.server.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected = (emoji: any) => {
|
onEmojiSelected = (emoji: IEmoji) => {
|
||||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import React from 'react';
|
||||||
import { CancelEditingButton, ToggleEmojiButton } from './buttons';
|
import { CancelEditingButton, ToggleEmojiButton } from './buttons';
|
||||||
|
|
||||||
interface IMessageBoxLeftButtons {
|
interface IMessageBoxLeftButtons {
|
||||||
theme: string;
|
|
||||||
showEmojiKeyboard: boolean;
|
showEmojiKeyboard: boolean;
|
||||||
openEmoji(): void;
|
openEmoji(): void;
|
||||||
closeEmoji(): void;
|
closeEmoji(): void;
|
||||||
|
@ -11,13 +10,11 @@ interface IMessageBoxLeftButtons {
|
||||||
editCancel(): void;
|
editCancel(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LeftButtons = React.memo(
|
const LeftButtons = React.memo(({ showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => {
|
||||||
({ theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => {
|
|
||||||
if (editing) {
|
if (editing) {
|
||||||
return <CancelEditingButton onPress={editCancel} theme={theme} />;
|
return <CancelEditingButton onPress={editCancel} />;
|
||||||
}
|
}
|
||||||
return <ToggleEmojiButton show={showEmojiKeyboard} open={openEmoji} close={closeEmoji} theme={theme} />;
|
return <ToggleEmojiButton show={showEmojiKeyboard} open={openEmoji} close={closeEmoji} />;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default LeftButtons;
|
export default LeftButtons;
|
||||||
|
|
|
@ -5,23 +5,20 @@ import { ActionsButton, CancelEditingButton } from './buttons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IMessageBoxLeftButtons {
|
interface IMessageBoxLeftButtons {
|
||||||
theme: string;
|
|
||||||
showMessageBoxActions(): void;
|
showMessageBoxActions(): void;
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
editCancel(): void;
|
editCancel(): void;
|
||||||
isActionsEnabled: boolean;
|
isActionsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LeftButtons = React.memo(
|
const LeftButtons = React.memo(({ showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
|
||||||
({ theme, showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => {
|
|
||||||
if (editing) {
|
if (editing) {
|
||||||
return <CancelEditingButton onPress={editCancel} theme={theme} />;
|
return <CancelEditingButton onPress={editCancel} />;
|
||||||
}
|
}
|
||||||
if (isActionsEnabled) {
|
if (isActionsEnabled) {
|
||||||
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
|
return <ActionsButton onPress={showMessageBoxActions} />;
|
||||||
}
|
}
|
||||||
return <View style={styles.buttonsWhitespace} />;
|
return <View style={styles.buttonsWhitespace} />;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default LeftButtons;
|
export default LeftButtons;
|
||||||
|
|
|
@ -4,16 +4,18 @@ import { Text, TouchableOpacity } from 'react-native';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IMessageBoxFixedMentionItem {
|
interface IMessageBoxFixedMentionItem {
|
||||||
item: {
|
item: {
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
onPress: Function;
|
onPress: Function;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FixedMentionItem = ({ item, onPress, theme }: IMessageBoxFixedMentionItem) => (
|
const FixedMentionItem = ({ item, onPress }: IMessageBoxFixedMentionItem) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.mentionItem,
|
styles.mentionItem,
|
||||||
|
@ -29,5 +31,6 @@ const FixedMentionItem = ({ item, onPress, theme }: IMessageBoxFixedMentionItem)
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default FixedMentionItem;
|
export default FixedMentionItem;
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
|
||||||
import styles from '../styles';
|
|
||||||
import MessageboxContext from '../Context';
|
|
||||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
|
||||||
import { IEmoji } from '../../../definitions/IEmoji';
|
import { IEmoji } from '../../../definitions/IEmoji';
|
||||||
|
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
||||||
|
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||||
|
import MessageboxContext from '../Context';
|
||||||
|
import styles from '../styles';
|
||||||
|
|
||||||
interface IMessageBoxMentionEmoji {
|
interface IMessageBoxMentionEmoji {
|
||||||
item: IEmoji;
|
item: IEmoji;
|
||||||
|
@ -22,8 +21,4 @@ const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
|
||||||
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
||||||
};
|
};
|
||||||
|
|
||||||
MentionEmoji.propTypes = {
|
|
||||||
item: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MentionEmoji;
|
export default MentionEmoji;
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { View, Text, ActivityIndicator, TouchableOpacity } from 'react-native';
|
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
|
|
||||||
import styles from '../styles';
|
|
||||||
import sharedStyles from '../../../views/Styles';
|
|
||||||
import I18n from '../../../i18n';
|
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import I18n from '../../../i18n';
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import sharedStyles from '../../../views/Styles';
|
||||||
|
import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
|
||||||
import MessageboxContext from '../Context';
|
import MessageboxContext from '../Context';
|
||||||
|
import styles from '../styles';
|
||||||
|
|
||||||
const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
|
interface IMentionHeaderList {
|
||||||
|
trackingType: string;
|
||||||
|
hasMentions: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MentionHeaderList = ({ trackingType, hasMentions, loading }: IMentionHeaderList) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const context = useContext(MessageboxContext);
|
const context = useContext(MessageboxContext);
|
||||||
const { onPressNoMatchCanned } = context;
|
const { onPressNoMatchCanned } = context;
|
||||||
|
|
||||||
|
@ -39,11 +46,4 @@ const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
MentionHeaderList.propTypes = {
|
|
||||||
trackingType: PropTypes.string,
|
|
||||||
hasMentions: PropTypes.bool,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
loading: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MentionHeaderList;
|
export default MentionHeaderList;
|
|
@ -1,14 +1,15 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Text, TouchableOpacity } from 'react-native';
|
import { Text, TouchableOpacity } from 'react-native';
|
||||||
|
|
||||||
import styles from '../styles';
|
|
||||||
import Avatar from '../../Avatar';
|
|
||||||
import MessageboxContext from '../Context';
|
|
||||||
import FixedMentionItem from './FixedMentionItem';
|
|
||||||
import MentionEmoji from './MentionEmoji';
|
|
||||||
import { MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
|
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import { IEmoji } from '../../../definitions/IEmoji';
|
import { IEmoji } from '../../../definitions/IEmoji';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import Avatar from '../../Avatar';
|
||||||
|
import { MENTIONS_TRACKING_TYPE_CANNED, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_EMOJIS } from '../constants';
|
||||||
|
import MessageboxContext from '../Context';
|
||||||
|
import styles from '../styles';
|
||||||
|
import FixedMentionItem from './FixedMentionItem';
|
||||||
|
import MentionEmoji from './MentionEmoji';
|
||||||
|
|
||||||
interface IMessageBoxMentionItem {
|
interface IMessageBoxMentionItem {
|
||||||
item: {
|
item: {
|
||||||
|
@ -21,11 +22,48 @@ interface IMessageBoxMentionItem {
|
||||||
text: string;
|
text: string;
|
||||||
} & IEmoji;
|
} & IEmoji;
|
||||||
trackingType: string;
|
trackingType: string;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
|
const MentionItemContent = React.memo(({ trackingType, item }: IMessageBoxMentionItem) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
switch (trackingType) {
|
||||||
|
case MENTIONS_TRACKING_TYPE_EMOJIS:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MentionEmoji item={item} />
|
||||||
|
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{item.name || item}:</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
|
||||||
|
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
case MENTIONS_TRACKING_TYPE_CANNED:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text style={[styles.cannedItem, { color: themes[theme].titleText }]}>!{item.shortcut}</Text>
|
||||||
|
<Text numberOfLines={1} style={[styles.cannedMentionText, { color: themes[theme].auxiliaryTintColor }]}>
|
||||||
|
{item.text}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Avatar style={styles.avatar} text={item.username || item.name} size={30} type={item.t} />
|
||||||
|
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.username || item.name || item}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const MentionItem = ({ item, trackingType }: IMessageBoxMentionItem) => {
|
||||||
const context = useContext(MessageboxContext);
|
const context = useContext(MessageboxContext);
|
||||||
|
const { theme } = useTheme();
|
||||||
const { onPressMention } = context;
|
const { onPressMention } = context;
|
||||||
|
|
||||||
const defineTestID = (type: string) => {
|
const defineTestID = (type: string) => {
|
||||||
|
@ -44,43 +82,7 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
|
||||||
const testID = defineTestID(trackingType);
|
const testID = defineTestID(trackingType);
|
||||||
|
|
||||||
if (item.username === 'all' || item.username === 'here') {
|
if (item.username === 'all' || item.username === 'here') {
|
||||||
return <FixedMentionItem item={item} onPress={onPressMention} theme={theme} />;
|
return <FixedMentionItem item={item} onPress={onPressMention} />;
|
||||||
}
|
|
||||||
|
|
||||||
let content = (
|
|
||||||
<>
|
|
||||||
<Avatar style={styles.avatar} text={item.username || item.name} size={30} type={item.t} />
|
|
||||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.username || item.name || item}</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (trackingType === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
|
||||||
content = (
|
|
||||||
<>
|
|
||||||
<MentionEmoji item={item} />
|
|
||||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>:{item.name || item}:</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) {
|
|
||||||
content = (
|
|
||||||
<>
|
|
||||||
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
|
|
||||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) {
|
|
||||||
content = (
|
|
||||||
<>
|
|
||||||
<Text style={[styles.cannedItem, { color: themes[theme].titleText }]}>!{item.shortcut}</Text>
|
|
||||||
<Text numberOfLines={1} style={[styles.cannedMentionText, { color: themes[theme].auxiliaryTintColor }]}>
|
|
||||||
{item.text}
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -94,7 +96,7 @@ const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => {
|
||||||
]}
|
]}
|
||||||
onPress={() => onPressMention(item)}
|
onPress={() => onPressMention(item)}
|
||||||
testID={testID}>
|
testID={testID}>
|
||||||
{content}
|
<MentionItemContent item={item} trackingType={trackingType} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,29 +6,30 @@ import MentionHeaderList from './MentionHeaderList';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import MentionItem from './MentionItem';
|
import MentionItem from './MentionItem';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
|
||||||
interface IMessageBoxMentions {
|
interface IMessageBoxMentions {
|
||||||
mentions: any[];
|
mentions: any[];
|
||||||
trackingType: string;
|
trackingType: string;
|
||||||
theme: string;
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Mentions = React.memo(
|
const Mentions = React.memo(
|
||||||
({ mentions, trackingType, theme, loading }: IMessageBoxMentions) => {
|
({ mentions, trackingType, loading }: IMessageBoxMentions) => {
|
||||||
if (!trackingType) {
|
if (!trackingType) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<View testID='messagebox-container'>
|
<View testID='messagebox-container'>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
style={[styles.mentionList, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||||
ListHeaderComponent={() => (
|
ListHeaderComponent={() => (
|
||||||
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} theme={theme} loading={loading} />
|
<MentionHeaderList trackingType={trackingType} hasMentions={mentions.length > 0} loading={loading} />
|
||||||
)}
|
)}
|
||||||
data={mentions}
|
data={mentions}
|
||||||
extraData={mentions}
|
extraData={mentions}
|
||||||
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} theme={theme} />}
|
renderItem={({ item }) => <MentionItem item={item} trackingType={trackingType} />}
|
||||||
keyExtractor={item => item.rid || item.name || item.command || item.shortcut || item}
|
keyExtractor={item => item.rid || item.name || item.command || item.shortcut || item}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
/>
|
/>
|
||||||
|
@ -39,9 +40,6 @@ const Mentions = React.memo(
|
||||||
if (prevProps.loading !== nextProps.loading) {
|
if (prevProps.loading !== nextProps.loading) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (prevProps.theme !== nextProps.theme) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prevProps.trackingType !== nextProps.trackingType) {
|
if (prevProps.trackingType !== nextProps.trackingType) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,23 +47,17 @@ const RECORDING_MODE = {
|
||||||
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX
|
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = function (seconds: any) {
|
const formatTime = function (time: number) {
|
||||||
let minutes: any = Math.floor(seconds / 60);
|
const minutes = Math.floor(time / 60);
|
||||||
seconds %= 60;
|
const seconds = time % 60;
|
||||||
if (minutes < 10) {
|
const min = minutes < 10 ? `0${minutes}` : minutes;
|
||||||
minutes = `0${minutes}`;
|
const sec = seconds < 10 ? `0${seconds}` : seconds;
|
||||||
}
|
return `${min}:${sec}`;
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = `0${seconds}`;
|
|
||||||
}
|
|
||||||
return `${minutes}:${seconds}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAudioProps, any> {
|
export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAudioProps, any> {
|
||||||
private isRecorderBusy: boolean;
|
private isRecorderBusy: boolean;
|
||||||
|
private recording!: Audio.Recording;
|
||||||
private recording: any;
|
|
||||||
|
|
||||||
private LastDuration: number;
|
private LastDuration: number;
|
||||||
|
|
||||||
constructor(props: IMessageBoxRecordAudioProps) {
|
constructor(props: IMessageBoxRecordAudioProps) {
|
||||||
|
@ -112,7 +106,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onRecordingStatusUpdate = (status: any) => {
|
onRecordingStatusUpdate = (status: Audio.RecordingStatus) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isRecording: status.isRecording,
|
isRecording: status.isRecording,
|
||||||
recordingDurationMillis: status.durationMillis
|
recordingDurationMillis: status.durationMillis
|
||||||
|
@ -157,7 +151,7 @@ export default class RecordAudio extends React.PureComponent<IMessageBoxRecordAu
|
||||||
await this.recording.stopAndUnloadAsync();
|
await this.recording.stopAndUnloadAsync();
|
||||||
|
|
||||||
const fileURI = this.recording.getURI();
|
const fileURI = this.recording.getURI();
|
||||||
const fileData = await getInfoAsync(fileURI);
|
const fileData = await getInfoAsync(fileURI as string);
|
||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
name: `${Date.now()}.m4a`,
|
name: `${Date.now()}.m4a`,
|
||||||
mime: 'audio/aac',
|
mime: 'audio/aac',
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { IMessage } from '../../definitions/IMessage';
|
import { IMessage } from '../../definitions/IMessage';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { IApplicationState } from '../../definitions';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -49,16 +51,15 @@ interface IMessageBoxReplyPreview {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
username: string;
|
username: string;
|
||||||
getCustomEmoji: Function;
|
getCustomEmoji: Function;
|
||||||
theme: string;
|
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReplyPreview = React.memo(
|
const ReplyPreview = React.memo(
|
||||||
({ message, Message_TimeFormat, replying, close, theme, useRealName }: IMessageBoxReplyPreview) => {
|
({ message, Message_TimeFormat, replying, close, useRealName }: IMessageBoxReplyPreview) => {
|
||||||
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 }]}>
|
||||||
|
@ -75,16 +76,14 @@ const ReplyPreview = React.memo(
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prevProps: any, nextProps: any) =>
|
(prevProps: IMessageBoxReplyPreview, nextProps: IMessageBoxReplyPreview) =>
|
||||||
prevProps.replying === nextProps.replying &&
|
prevProps.replying === nextProps.replying && prevProps.message.id === nextProps.message.id
|
||||||
prevProps.theme === nextProps.theme &&
|
|
||||||
prevProps.message.id === nextProps.message.id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapStateToProps = (state: any) => ({
|
const mapStateToProps = (state: IApplicationState) => ({
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat as string,
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
||||||
useRealName: state.settings.UI_Use_Real_Name
|
useRealName: state.settings.UI_Use_Real_Name as boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(ReplyPreview);
|
export default connect(mapStateToProps)(ReplyPreview);
|
||||||
|
|
|
@ -5,24 +5,20 @@ import { ActionsButton, SendButton } from './buttons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
interface IMessageBoxRightButtons {
|
interface IMessageBoxRightButtons {
|
||||||
theme: string;
|
|
||||||
showSend: boolean;
|
showSend: boolean;
|
||||||
submit(): void;
|
submit(): void;
|
||||||
showMessageBoxActions(): void;
|
showMessageBoxActions(): void;
|
||||||
isActionsEnabled: boolean;
|
isActionsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RightButtons = React.memo(
|
const RightButtons = React.memo(({ showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => {
|
||||||
({ theme, showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => {
|
|
||||||
if (showSend) {
|
if (showSend) {
|
||||||
return <SendButton onPress={submit} theme={theme} />;
|
return <SendButton onPress={submit} />;
|
||||||
}
|
}
|
||||||
if (isActionsEnabled) {
|
if (isActionsEnabled) {
|
||||||
return <ActionsButton onPress={showMessageBoxActions} theme={theme} />;
|
return <ActionsButton onPress={showMessageBoxActions} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <View style={styles.buttonsWhitespace} />;
|
return <View style={styles.buttonsWhitespace} />;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default RightButtons;
|
export default RightButtons;
|
||||||
|
|
|
@ -3,16 +3,15 @@ import React from 'react';
|
||||||
import { SendButton } from './buttons';
|
import { SendButton } from './buttons';
|
||||||
|
|
||||||
interface IMessageBoxRightButtons {
|
interface IMessageBoxRightButtons {
|
||||||
theme: string;
|
|
||||||
showSend: boolean;
|
showSend: boolean;
|
||||||
submit(): void;
|
submit(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RightButtons = React.memo(({ theme, showSend, submit }: IMessageBoxRightButtons) => {
|
const RightButtons = ({ showSend, submit }: IMessageBoxRightButtons) => {
|
||||||
if (showSend) {
|
if (showSend) {
|
||||||
return <SendButton theme={theme} onPress={submit} />;
|
return <SendButton onPress={submit} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
};
|
||||||
|
|
||||||
export default RightButtons;
|
export default RightButtons;
|
||||||
|
|
|
@ -3,12 +3,11 @@ import React from 'react';
|
||||||
import BaseButton from './BaseButton';
|
import BaseButton from './BaseButton';
|
||||||
|
|
||||||
interface IActionsButton {
|
interface IActionsButton {
|
||||||
theme: string;
|
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActionsButton = React.memo(({ theme, onPress }: IActionsButton) => (
|
const ActionsButton = ({ onPress }: IActionsButton) => (
|
||||||
<BaseButton onPress={onPress} testID='messagebox-actions' accessibilityLabel='Message_actions' icon='add' theme={theme} />
|
<BaseButton onPress={onPress} testID='messagebox-actions' accessibilityLabel='Message_actions' icon='add' />
|
||||||
));
|
);
|
||||||
|
|
||||||
export default ActionsButton;
|
export default ActionsButton;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { themes } from '../../../constants/colors';
|
|
||||||
import { CustomIcon } from '../../../lib/Icons';
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import I18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
import { CustomIcon } from '../../../lib/Icons';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import { themes } from '../../../constants/colors';
|
||||||
|
|
||||||
interface IBaseButton {
|
interface IBaseButton {
|
||||||
theme: string;
|
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
testID: string;
|
testID: string;
|
||||||
accessibilityLabel: string;
|
accessibilityLabel: string;
|
||||||
|
@ -15,16 +15,18 @@ interface IBaseButton {
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseButton = React.memo(({ onPress, testID, accessibilityLabel, icon, theme, color }: Partial<IBaseButton>) => (
|
const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBaseButton>) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<BorderlessButton
|
<BorderlessButton
|
||||||
onPress={onPress}
|
{...props}
|
||||||
style={styles.actionButton}
|
style={styles.actionButton}
|
||||||
testID={testID}
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
accessibilityLabel={I18n.t(accessibilityLabel)}
|
accessibilityLabel={i18n.t(accessibilityLabel)}
|
||||||
accessibilityTraits='button'>
|
accessibilityTraits='button'>
|
||||||
<CustomIcon name={icon} size={24} color={color ?? themes[theme!].auxiliaryTintColor} />
|
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
));
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default BaseButton;
|
export default BaseButton;
|
||||||
|
|
|
@ -3,18 +3,11 @@ import React from 'react';
|
||||||
import BaseButton from './BaseButton';
|
import BaseButton from './BaseButton';
|
||||||
|
|
||||||
interface ICancelEditingButton {
|
interface ICancelEditingButton {
|
||||||
theme: string;
|
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CancelEditingButton = React.memo(({ theme, onPress }: ICancelEditingButton) => (
|
const CancelEditingButton = ({ onPress }: ICancelEditingButton) => (
|
||||||
<BaseButton
|
<BaseButton onPress={onPress} testID='messagebox-cancel-editing' accessibilityLabel='Cancel_editing' icon='close' />
|
||||||
onPress={onPress}
|
);
|
||||||
testID='messagebox-cancel-editing'
|
|
||||||
accessibilityLabel='Cancel_editing'
|
|
||||||
icon='close'
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
export default CancelEditingButton;
|
export default CancelEditingButton;
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import BaseButton from './BaseButton';
|
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import BaseButton from './BaseButton';
|
||||||
|
|
||||||
interface ISendButton {
|
interface ISendButton {
|
||||||
theme: string;
|
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SendButton = React.memo(({ theme, onPress }: ISendButton) => (
|
const SendButton = ({ onPress }: ISendButton) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<BaseButton
|
<BaseButton
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
testID='messagebox-send-message'
|
testID='messagebox-send-message'
|
||||||
accessibilityLabel='Send_message'
|
accessibilityLabel='Send_message'
|
||||||
icon='send-filled'
|
icon='send-filled'
|
||||||
theme={theme}
|
|
||||||
color={themes[theme].tintColor}
|
color={themes[theme].tintColor}
|
||||||
/>
|
/>
|
||||||
));
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default SendButton;
|
export default SendButton;
|
||||||
|
|
|
@ -3,33 +3,18 @@ import React from 'react';
|
||||||
import BaseButton from './BaseButton';
|
import BaseButton from './BaseButton';
|
||||||
|
|
||||||
interface IToggleEmojiButton {
|
interface IToggleEmojiButton {
|
||||||
theme: string;
|
|
||||||
show: boolean;
|
show: boolean;
|
||||||
open(): void;
|
open(): void;
|
||||||
close(): void;
|
close(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToggleEmojiButton = React.memo(({ theme, show, open, close }: IToggleEmojiButton) => {
|
const ToggleEmojiButton = ({ show, open, close }: IToggleEmojiButton) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
return (
|
return (
|
||||||
<BaseButton
|
<BaseButton onPress={close} testID='messagebox-close-emoji' accessibilityLabel='Close_emoji_selector' icon='keyboard' />
|
||||||
onPress={close}
|
|
||||||
testID='messagebox-close-emoji'
|
|
||||||
accessibilityLabel='Close_emoji_selector'
|
|
||||||
icon='keyboard'
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return <BaseButton onPress={open} testID='messagebox-open-emoji' accessibilityLabel='Open_emoji_selector' icon='emoji' />;
|
||||||
<BaseButton
|
};
|
||||||
onPress={open}
|
|
||||||
testID='messagebox-open-emoji'
|
|
||||||
accessibilityLabel='Open_emoji_selector'
|
|
||||||
icon='emoji'
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ToggleEmojiButton;
|
export default ToggleEmojiButton;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||||
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
|
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
||||||
import ImagePicker, { Image, ImageOrVideo } from 'react-native-image-crop-picker';
|
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import DocumentPicker from 'react-native-document-picker';
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
@ -50,7 +50,8 @@ import { sanitizeLikeString } from '../../lib/database/utils';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { IMessage } from '../../definitions/IMessage';
|
import { IMessage } from '../../definitions/IMessage';
|
||||||
import { forceJpgExtension } from './forceJpgExtension';
|
import { forceJpgExtension } from './forceJpgExtension';
|
||||||
import { IPreviewItem, IUser } from '../../definitions';
|
import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions';
|
||||||
|
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
|
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
require('./EmojiKeyboard');
|
require('./EmojiKeyboard');
|
||||||
|
@ -63,18 +64,18 @@ const imagePickerConfig = {
|
||||||
forceJpg: true
|
forceJpg: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const libraryPickerConfig = {
|
const libraryPickerConfig: Options = {
|
||||||
multiple: true,
|
multiple: true,
|
||||||
compressVideoPreset: 'Passthrough',
|
compressVideoPreset: 'Passthrough',
|
||||||
mediaType: 'any',
|
mediaType: 'any',
|
||||||
forceJpg: true
|
forceJpg: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const videoPickerConfig = {
|
const videoPickerConfig: Options = {
|
||||||
mediaType: 'video'
|
mediaType: 'video'
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IMessageBoxProps {
|
export interface IMessageBoxProps extends IBaseScreen<MasterDetailInsideStackParamList, any> {
|
||||||
rid: string;
|
rid: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
message: IMessage;
|
message: IMessage;
|
||||||
|
@ -97,7 +98,6 @@ export interface IMessageBoxProps {
|
||||||
theme: string;
|
theme: string;
|
||||||
replyCancel(): void;
|
replyCancel(): void;
|
||||||
showSend: boolean;
|
showSend: boolean;
|
||||||
navigation: any;
|
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
showActionSheet: Function;
|
showActionSheet: Function;
|
||||||
|
@ -118,7 +118,7 @@ interface IMessageBoxState {
|
||||||
commandPreview: IPreviewItem[];
|
commandPreview: IPreviewItem[];
|
||||||
showCommandPreview: boolean;
|
showCommandPreview: boolean;
|
||||||
command: {
|
command: {
|
||||||
appId?: any;
|
appId?: string;
|
||||||
};
|
};
|
||||||
tshow: boolean;
|
tshow: boolean;
|
||||||
mentionLoading: boolean;
|
mentionLoading: boolean;
|
||||||
|
@ -132,17 +132,15 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
|
||||||
private focused: boolean;
|
private focused: boolean;
|
||||||
|
|
||||||
private options: any;
|
private imagePickerConfig: Options;
|
||||||
|
|
||||||
private imagePickerConfig: any;
|
private libraryPickerConfig: Options;
|
||||||
|
|
||||||
private libraryPickerConfig: any;
|
private videoPickerConfig: Options;
|
||||||
|
|
||||||
private videoPickerConfig: any;
|
private room!: TSubscriptionModel;
|
||||||
|
|
||||||
private room: any;
|
private thread!: TThreadModel;
|
||||||
|
|
||||||
private thread: any;
|
|
||||||
|
|
||||||
private unsubscribeFocus: any;
|
private unsubscribeFocus: any;
|
||||||
|
|
||||||
|
@ -713,7 +711,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
chooseFromLibrary = async () => {
|
chooseFromLibrary = async () => {
|
||||||
logEvent(events.ROOM_BOX_ACTION_LIBRARY);
|
logEvent(events.ROOM_BOX_ACTION_LIBRARY);
|
||||||
try {
|
try {
|
||||||
let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as ImageOrVideo[];
|
// The type can be video or photo, however the lib understands that it is just one of them.
|
||||||
|
let attachments = (await ImagePicker.openPicker(this.libraryPickerConfig)) as unknown as ImageOrVideo[];
|
||||||
attachments = attachments.map(att => forceJpgExtension(att));
|
attachments = attachments.map(att => forceJpgExtension(att));
|
||||||
this.openShareView(attachments);
|
this.openShareView(attachments);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -757,12 +756,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
openShareView = (attachments: any) => {
|
openShareView = (attachments: any) => {
|
||||||
const { message, replyCancel, replyWithMention } = this.props;
|
const { message, replyCancel, replyWithMention } = this.props;
|
||||||
// Start a thread with an attachment
|
// Start a thread with an attachment
|
||||||
let { thread } = this;
|
let value: TThreadModel | IMessage = this.thread;
|
||||||
if (replyWithMention) {
|
if (replyWithMention) {
|
||||||
thread = message;
|
value = message;
|
||||||
replyCancel();
|
replyCancel();
|
||||||
}
|
}
|
||||||
Navigation.navigate('ShareView', { room: this.room, thread, attachments });
|
Navigation.navigate('ShareView', { room: this.room, value, attachments });
|
||||||
};
|
};
|
||||||
|
|
||||||
createDiscussion = () => {
|
createDiscussion = () => {
|
||||||
|
@ -1058,7 +1057,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
const commandsPreviewAndMentions = !recording ? (
|
const commandsPreviewAndMentions = !recording ? (
|
||||||
<>
|
<>
|
||||||
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
|
<CommandsPreview commandPreview={commandPreview} showCommandPreview={showCommandPreview} />
|
||||||
<Mentions mentions={mentions} trackingType={trackingType} theme={theme} loading={mentionLoading} />
|
<Mentions mentions={mentions} trackingType={trackingType} loading={mentionLoading} />
|
||||||
</>
|
</>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
@ -1069,7 +1068,6 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
username={user.username}
|
username={user.username}
|
||||||
replying={replying}
|
replying={replying}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
theme={theme}
|
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
|
|
@ -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 '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import { withTheme } from '../theme';
|
import { 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';
|
||||||
|
@ -61,38 +61,37 @@ const styles = StyleSheet.create({
|
||||||
const standardEmojiStyle = { fontSize: 20 };
|
const standardEmojiStyle = { fontSize: 20 };
|
||||||
const customEmojiStyle = { width: 20, height: 20 };
|
const customEmojiStyle = { width: 20, height: 20 };
|
||||||
|
|
||||||
interface IItem {
|
interface ISharedFields {
|
||||||
|
user?: Pick<ILoggedUser, 'username'>;
|
||||||
|
baseUrl: string;
|
||||||
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IItem extends ISharedFields {
|
||||||
item: {
|
item: {
|
||||||
usernames: any;
|
usernames: string[];
|
||||||
emoji: string;
|
emoji: string;
|
||||||
};
|
};
|
||||||
user?: Pick<ILoggedUser, 'username'>;
|
|
||||||
baseUrl?: string;
|
|
||||||
getCustomEmoji?: TGetCustomEmoji;
|
|
||||||
theme?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IModalContent {
|
interface IModalContent extends ISharedFields {
|
||||||
message?: TMessageModel;
|
message?: TMessageModel;
|
||||||
onClose: Function;
|
onClose: () => void;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IReactionsModal {
|
interface IReactionsModal extends ISharedFields {
|
||||||
message?: any;
|
message?: TMessageModel;
|
||||||
user?: Pick<ILoggedUser, 'username'>;
|
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
baseUrl: string;
|
|
||||||
getCustomEmoji?: TGetCustomEmoji;
|
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem) => {
|
const Item = React.memo(({ item, user, baseUrl, getCustomEmoji }: IItem) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const count = item.usernames.length;
|
const count = item.usernames.length;
|
||||||
let usernames = item.usernames
|
let usernames = item.usernames
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
.map((username: any) => (username === user?.username ? I18n.t('you') : username))
|
.map((username: string) => (username === user?.username ? I18n.t('you') : username))
|
||||||
.join(', ');
|
.join(', ');
|
||||||
if (count > 3) {
|
if (count > 3) {
|
||||||
usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`;
|
usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`;
|
||||||
|
@ -106,15 +105,15 @@ const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem)
|
||||||
content={item.emoji}
|
content={item.emoji}
|
||||||
standardEmojiStyle={standardEmojiStyle}
|
standardEmojiStyle={standardEmojiStyle}
|
||||||
customEmojiStyle={customEmojiStyle}
|
customEmojiStyle={customEmojiStyle}
|
||||||
baseUrl={baseUrl!}
|
baseUrl={baseUrl}
|
||||||
getCustomEmoji={getCustomEmoji!}
|
getCustomEmoji={getCustomEmoji}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.peopleItemContainer}>
|
<View style={styles.peopleItemContainer}>
|
||||||
<Text style={[styles.reactCount, { color: themes[theme!].buttonText }]}>
|
<Text style={[styles.reactCount, { color: themes[theme].buttonText }]}>
|
||||||
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
|
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.peopleReacted, { color: themes[theme!].buttonText }]}>{usernames}</Text>
|
<Text style={[styles.peopleReacted, { color: themes[theme].buttonText }]}>{usernames}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -143,7 +142,9 @@ const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent)
|
||||||
});
|
});
|
||||||
|
|
||||||
const ReactionsModal = React.memo(
|
const ReactionsModal = React.memo(
|
||||||
({ isVisible, onClose, theme, ...props }: IReactionsModal) => (
|
({ isVisible, onClose, ...props }: IReactionsModal) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
onBackdropPress={onClose}
|
onBackdropPress={onClose}
|
||||||
|
@ -153,8 +154,9 @@ const ReactionsModal = React.memo(
|
||||||
swipeDirection={['up', 'left', 'right', 'down']}>
|
swipeDirection={['up', 'left', 'right', 'down']}>
|
||||||
<ModalContent onClose={onClose} theme={theme} {...props} />
|
<ModalContent onClose={onClose} theme={theme} {...props} />
|
||||||
</Modal>
|
</Modal>
|
||||||
),
|
);
|
||||||
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.theme === nextProps.theme
|
},
|
||||||
|
(prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactionsModal.displayName = 'ReactionsModal';
|
ReactionsModal.displayName = 'ReactionsModal';
|
||||||
|
|
|
@ -6,8 +6,8 @@ import sharedStyles from '../../views/Styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { MarkdownPreview } from '../markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import RoomTypeIcon from '../RoomTypeIcon';
|
import RoomTypeIcon from '../RoomTypeIcon';
|
||||||
import { withTheme } from '../../theme';
|
|
||||||
import { TUserStatus } from '../../definitions';
|
import { TUserStatus } from '../../definitions';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const HIT_SLOP = {
|
const HIT_SLOP = {
|
||||||
top: 5,
|
top: 5,
|
||||||
|
@ -44,9 +44,8 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
type TRoomHeaderSubTitle = {
|
type TRoomHeaderSubTitle = {
|
||||||
usersTyping: [];
|
usersTyping: [];
|
||||||
theme: string;
|
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
renderFunc: any;
|
renderFunc?: () => React.ReactElement;
|
||||||
scale: number;
|
scale: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,7 +54,6 @@ type TRoomHeaderHeaderTitle = {
|
||||||
tmid: string;
|
tmid: string;
|
||||||
prid: string;
|
prid: string;
|
||||||
scale: number;
|
scale: number;
|
||||||
theme: string;
|
|
||||||
testID: string;
|
testID: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,7 +75,8 @@ interface IRoomHeader {
|
||||||
testID: string;
|
testID: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }: TRoomHeaderSubTitle) => {
|
const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, scale }: TRoomHeaderSubTitle) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const fontSize = getSubTitleSize(scale);
|
const fontSize = getSubTitleSize(scale);
|
||||||
// typing
|
// typing
|
||||||
if (usersTyping.length) {
|
if (usersTyping.length) {
|
||||||
|
@ -108,7 +107,8 @@ const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const HeaderTitle = React.memo(({ title, tmid, prid, scale, theme, testID }: TRoomHeaderHeaderTitle) => {
|
const HeaderTitle = React.memo(({ title, tmid, prid, scale, testID }: TRoomHeaderHeaderTitle) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor };
|
const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor };
|
||||||
if (!tmid && !prid) {
|
if (!tmid && !prid) {
|
||||||
return (
|
return (
|
||||||
|
@ -133,12 +133,12 @@ const Header = React.memo(
|
||||||
prid,
|
prid,
|
||||||
tmid,
|
tmid,
|
||||||
onPress,
|
onPress,
|
||||||
theme,
|
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
teamMain,
|
teamMain,
|
||||||
testID,
|
testID,
|
||||||
usersTyping = []
|
usersTyping = []
|
||||||
}: IRoomHeader) => {
|
}: IRoomHeader) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const portrait = height > width;
|
const portrait = height > width;
|
||||||
let scale = 1;
|
let scale = 1;
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ const Header = React.memo(
|
||||||
renderFunc = () => (
|
renderFunc = () => (
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
|
<RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />
|
||||||
<Text style={[styles.subtitle, { color: themes[theme!].auxiliaryText }]} numberOfLines={1}>
|
<Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
|
||||||
{parentTitle}
|
{parentTitle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -168,25 +168,18 @@ const Header = React.memo(
|
||||||
accessibilityLabel={title}
|
accessibilityLabel={title}
|
||||||
onPress={handleOnPress}
|
onPress={handleOnPress}
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
// @ts-ignore
|
disabled={!!tmid}
|
||||||
disabled={tmid}
|
|
||||||
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} />
|
||||||
)}
|
)}
|
||||||
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} theme={theme!} testID={testID} />
|
<HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} testID={testID} />
|
||||||
</View>
|
</View>
|
||||||
<SubTitle
|
<SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} renderFunc={renderFunc} scale={scale} />
|
||||||
usersTyping={tmid ? [] : usersTyping}
|
|
||||||
subtitle={subtitle}
|
|
||||||
theme={theme!}
|
|
||||||
renderFunc={renderFunc}
|
|
||||||
scale={scale}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default withTheme(Header);
|
export default Header;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet, ViewProps } from 'react-native';
|
||||||
import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context';
|
import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import { withTheme } from '../theme';
|
import { useTheme } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
view: {
|
view: {
|
||||||
|
@ -11,22 +11,24 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ISafeAreaView {
|
type SupportedChildren = React.ReactElement | React.ReactElement[] | null;
|
||||||
testID?: string;
|
type TSafeAreaViewChildren = SupportedChildren | SupportedChildren[];
|
||||||
theme?: string;
|
|
||||||
|
interface ISafeAreaView extends ViewProps {
|
||||||
vertical?: boolean;
|
vertical?: boolean;
|
||||||
style?: object;
|
children: TSafeAreaViewChildren;
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SafeAreaView = React.memo(({ style, children, testID, theme, vertical = true, ...props }: ISafeAreaView) => (
|
const SafeAreaView = React.memo(({ style, children, vertical = true, ...props }: ISafeAreaView) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<SafeAreaContext
|
<SafeAreaContext
|
||||||
style={[styles.view, { backgroundColor: themes[theme!].auxiliaryBackground }, style]}
|
style={[styles.view, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
|
||||||
edges={vertical ? ['right', 'left'] : undefined}
|
edges={vertical ? ['right', 'left'] : undefined}
|
||||||
testID={testID}
|
|
||||||
{...props}>
|
{...props}>
|
||||||
{children}
|
{children}
|
||||||
</SafeAreaContext>
|
</SafeAreaContext>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default withTheme(SafeAreaView);
|
export default SafeAreaView;
|
||||||
|
|
|
@ -47,11 +47,11 @@ const styles = StyleSheet.create({
|
||||||
interface ISearchBox extends TextInputProps {
|
interface ISearchBox extends TextInputProps {
|
||||||
value?: string;
|
value?: string;
|
||||||
hasCancel?: boolean;
|
hasCancel?: boolean;
|
||||||
onCancelPress?: Function;
|
onCancelPress?: () => void;
|
||||||
inputRef?: React.Ref<RNTextInput>;
|
inputRef?: React.Ref<RNTextInput>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CancelButton = ({ onCancelPress }: { onCancelPress?: Function }) => {
|
const CancelButton = ({ onCancelPress }: { onCancelPress?: () => void }) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<Touchable onPress={onCancelPress} style={styles.cancel}>
|
<Touchable onPress={onCancelPress} style={styles.cancel}>
|
||||||
|
@ -84,7 +84,7 @@ const SearchBox = ({ hasCancel, onCancelPress, inputRef, ...props }: ISearchBox)
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{hasCancel ? <CancelButton onCancelPress={onCancelPress} /> : null}
|
{hasCancel && onCancelPress ? <CancelButton onCancelPress={onCancelPress} /> : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,8 +4,10 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { IActions } from './interfaces';
|
import { IActions } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
export const Actions = ({ blockId, appId, elements, parser, theme }: IActions) => {
|
export const Actions = ({ blockId, appId, elements, parser }: IActions) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
|
const [showMoreVisible, setShowMoreVisible] = useState(() => elements && elements.length > 5);
|
||||||
const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements;
|
const renderedElements = showMoreVisible ? elements?.slice(0, 5) : elements;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -11,6 +11,7 @@ import { themes } from '../../constants/colors';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../utils/deviceInfo';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { IDatePicker } from './interfaces';
|
import { IDatePicker } from './interfaces';
|
||||||
|
|
||||||
|
@ -36,14 +37,17 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DatePicker = ({ element, language, action, context, theme, loading, value, error }: IDatePicker) => {
|
export const DatePicker = ({ element, language, action, context, loading, value, error }: IDatePicker) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const [show, onShow] = useState(false);
|
const [show, onShow] = useState(false);
|
||||||
const initial_date = element?.initial_date;
|
const initial_date = element?.initial_date;
|
||||||
const placeholder = element?.placeholder;
|
const placeholder = element?.placeholder;
|
||||||
|
|
||||||
const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
|
const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
|
||||||
|
|
||||||
const onChange = ({ nativeEvent: { timestamp } }: any, date: any) => {
|
// timestamp as number exists in Event
|
||||||
|
// @ts-ignore
|
||||||
|
const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => {
|
||||||
const newDate = date || new Date(timestamp);
|
const newDate = date || new Date(timestamp);
|
||||||
onChangeDate(newDate);
|
onChangeDate(newDate);
|
||||||
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
||||||
|
@ -52,7 +56,9 @@ export const DatePicker = ({ element, language, action, context, theme, loading,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let button = <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />;
|
let button = placeholder ? (
|
||||||
|
<Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />
|
||||||
|
) : null;
|
||||||
|
|
||||||
if (context === BLOCK_CONTEXT.FORM) {
|
if (context === BLOCK_CONTEXT.FORM) {
|
||||||
button = (
|
button = (
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
import ImageContainer from '../message/Image';
|
import ImageContainer from '../message/Image';
|
||||||
import Navigation from '../../lib/Navigation';
|
import Navigation from '../../lib/Navigation';
|
||||||
import { IThumb, IImage, IElement } from './interfaces';
|
import { IThumb, IImage, IElement } from './interfaces';
|
||||||
import { TThemeMode } from '../../definitions/ITheme';
|
import { IAttachment } from '../../definitions';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
image: {
|
image: {
|
||||||
|
@ -27,23 +27,22 @@ export const Thumb = ({ element, size = 88 }: IThumb) => (
|
||||||
<FastImage style={[{ width: size, height: size }, styles.image]} source={{ uri: element?.imageUrl }} />
|
<FastImage style={[{ width: size, height: size }, styles.image]} source={{ uri: element?.imageUrl }} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Media = ({ element, theme }: IImage) => {
|
export const Media = ({ element }: IImage) => {
|
||||||
const showAttachment = (attachment: any) => Navigation.navigate('AttachmentView', { attachment });
|
const showAttachment = (attachment: IAttachment) => Navigation.navigate('AttachmentView', { attachment });
|
||||||
const imageUrl = element?.imageUrl ?? '';
|
const imageUrl = element?.imageUrl ?? '';
|
||||||
// @ts-ignore
|
|
||||||
// TODO: delete ts-ignore after refactor Markdown and ImageContainer
|
return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} />;
|
||||||
return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} theme={theme} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const genericImage = (theme: TThemeMode, element: IElement, context?: number) => {
|
const genericImage = (element: IElement, context?: number) => {
|
||||||
switch (context) {
|
switch (context) {
|
||||||
case BLOCK_CONTEXT.SECTION:
|
case BLOCK_CONTEXT.SECTION:
|
||||||
return <Thumb element={element} />;
|
return <Thumb element={element} />;
|
||||||
case BLOCK_CONTEXT.CONTEXT:
|
case BLOCK_CONTEXT.CONTEXT:
|
||||||
return <ThumbContext element={element} />;
|
return <ThumbContext element={element} />;
|
||||||
default:
|
default:
|
||||||
return <Media element={element} theme={theme} />;
|
return <Media element={element} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Image = ({ element, context, theme }: IImage) => genericImage(theme, element, context);
|
export const Image = ({ element, context }: IImage) => genericImage(element, context);
|
||||||
|
|
|
@ -7,26 +7,23 @@ import { themes } from '../../../constants/colors';
|
||||||
import { textParser } from '../utils';
|
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 '.';
|
||||||
|
|
||||||
interface IChip {
|
interface IChip {
|
||||||
item: {
|
item: IItemData;
|
||||||
value: string;
|
onSelect: (item: IItemData) => void;
|
||||||
imageUrl: string;
|
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
onSelect: Function;
|
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IChips {
|
interface IChips {
|
||||||
items: [];
|
items: IItemData[];
|
||||||
onSelect: Function;
|
onSelect: (item: IItemData) => void;
|
||||||
style?: object;
|
style?: object;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyExtractor = (item: any) => item.value.toString();
|
const keyExtractor = (item: IItemData) => item.value.toString();
|
||||||
|
|
||||||
const Chip = ({ item, onSelect, style, theme }: IChip) => (
|
const Chip = ({ item, onSelect, style, theme }: IChip) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
|
|
|
@ -9,10 +9,10 @@ import styles from './styles';
|
||||||
|
|
||||||
interface IInput {
|
interface IInput {
|
||||||
children?: JSX.Element;
|
children?: JSX.Element;
|
||||||
onPress: Function;
|
onPress: () => void;
|
||||||
theme: string;
|
theme: string;
|
||||||
inputStyle?: object;
|
inputStyle?: object;
|
||||||
disabled?: boolean | object;
|
disabled?: boolean | null;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
innerInputStyle?: object;
|
innerInputStyle?: object;
|
||||||
|
|
|
@ -8,34 +8,31 @@ import * as List from '../../List';
|
||||||
import { textParser } from '../utils';
|
import { textParser } from '../utils';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { IItemData } from '.';
|
||||||
|
|
||||||
interface IItem {
|
interface IItem {
|
||||||
item: {
|
item: IItemData;
|
||||||
value: { name: string };
|
selected?: string;
|
||||||
text: { text: string };
|
|
||||||
imageUrl: string;
|
|
||||||
};
|
|
||||||
selected: any;
|
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IItems {
|
interface IItems {
|
||||||
items: [];
|
items: IItemData[];
|
||||||
selected: [];
|
selected: string[];
|
||||||
onSelect: Function;
|
onSelect: Function;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyExtractor = (item: any) => 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.name || item.text.text.toLowerCase();
|
const itemName = item.value || item.text.text.toLowerCase();
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
testID={`multi-select-item-${itemName}`}
|
testID={`multi-select-item-${itemName}`}
|
||||||
key={item}
|
key={itemName}
|
||||||
onPress={() => onSelect(item)}
|
onPress={() => onSelect(item)}
|
||||||
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}>
|
style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Animated, Easing, KeyboardAvoidingView, Modal, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
import {
|
||||||
|
Animated,
|
||||||
|
Easing,
|
||||||
|
KeyboardAvoidingView,
|
||||||
|
Modal,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
TextStyle
|
||||||
|
} from 'react-native';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import Button from '../../Button';
|
import Button from '../../Button';
|
||||||
|
@ -8,26 +18,31 @@ import { textParser } from '../utils';
|
||||||
import { themes } from '../../../constants/colors';
|
import { themes } from '../../../constants/colors';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
import { isIOS } from '../../../utils/deviceInfo';
|
import { isIOS } from '../../../utils/deviceInfo';
|
||||||
|
import { useTheme } from '../../../theme';
|
||||||
|
import { BlockContext, IText } from '../interfaces';
|
||||||
import Chips from './Chips';
|
import Chips from './Chips';
|
||||||
import Items from './Items';
|
import Items from './Items';
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
|
export interface IItemData {
|
||||||
|
value: string;
|
||||||
|
text: { text: string };
|
||||||
|
imageUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface IMultiSelect {
|
interface IMultiSelect {
|
||||||
options: any[];
|
options?: IItemData[];
|
||||||
onChange: Function;
|
onChange: Function;
|
||||||
placeholder: {
|
placeholder?: IText;
|
||||||
text: string;
|
context?: BlockContext;
|
||||||
};
|
|
||||||
context?: number;
|
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
multiselect?: boolean;
|
multiselect?: boolean;
|
||||||
onSearch?: () => void;
|
onSearch?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
inputStyle?: object;
|
inputStyle?: TextStyle;
|
||||||
value?: any[];
|
value?: any[];
|
||||||
disabled?: boolean | object;
|
disabled?: boolean | null;
|
||||||
theme: string;
|
|
||||||
innerInputStyle?: object;
|
innerInputStyle?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,9 +69,9 @@ export const MultiSelect = React.memo(
|
||||||
onClose = () => {},
|
onClose = () => {},
|
||||||
disabled,
|
disabled,
|
||||||
inputStyle,
|
inputStyle,
|
||||||
theme,
|
|
||||||
innerInputStyle
|
innerInputStyle
|
||||||
}: IMultiSelect) => {
|
}: IMultiSelect) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const [selected, select] = useState<any>(Array.isArray(values) ? values : []);
|
const [selected, select] = useState<any>(Array.isArray(values) ? values : []);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, onSearchChange] = useState('');
|
const [search, onSearchChange] = useState('');
|
||||||
|
@ -95,7 +110,7 @@ export const MultiSelect = React.memo(
|
||||||
}).start(() => setShowContent(false));
|
}).start(() => setShowContent(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (item: any) => {
|
const onSelect = (item: IItemData) => {
|
||||||
const {
|
const {
|
||||||
value,
|
value,
|
||||||
text: { text }
|
text: { text }
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Touchable from 'react-native-platform-touchable';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
import { BUTTON_HIT_SLOP } from '../message/utils';
|
import { BUTTON_HIT_SLOP } from '../message/utils';
|
||||||
import * as List from '../List';
|
import * as List from '../List';
|
||||||
import { IOption, IOptions, IOverflow } from './interfaces';
|
import { IOption, IOptions, IOverflow } from './interfaces';
|
||||||
|
@ -43,9 +44,10 @@ const Options = ({ options, onOptionPress, parser, theme }: IOptions) => (
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const touchable: { [key: string]: any } = {};
|
const touchable: { [key: string]: Touchable | null } = {};
|
||||||
|
|
||||||
export const Overflow = ({ element, loading, action, parser, theme }: IOverflow) => {
|
export const Overflow = ({ element, loading, action, parser }: IOverflow) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const options = element?.options || [];
|
const options = element?.options || [];
|
||||||
const blockId = element?.blockId || '';
|
const blockId = element?.blockId || '';
|
||||||
const [show, onShow] = useState(false);
|
const [show, onShow] = useState(false);
|
||||||
|
@ -58,7 +60,7 @@ export const Overflow = ({ element, loading, action, parser, theme }: IOverflow)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Touchable
|
<Touchable
|
||||||
ref={(ref: any) => (touchable[blockId] = ref)}
|
ref={ref => (touchable[blockId] = ref)}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
onPress={() => onShow(!show)}
|
onPress={() => onShow(!show)}
|
||||||
hitSlop={BUTTON_HIT_SLOP}
|
hitSlop={BUTTON_HIT_SLOP}
|
||||||
|
@ -71,6 +73,7 @@ export const Overflow = ({ element, loading, action, parser, theme }: IOverflow)
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<Popover
|
<Popover
|
||||||
isVisible={show}
|
isVisible={show}
|
||||||
|
// fromView exists in Popover Component
|
||||||
/* @ts-ignore*/
|
/* @ts-ignore*/
|
||||||
fromView={touchable[blockId]}
|
fromView={touchable[blockId]}
|
||||||
onRequestClose={() => onShow(false)}>
|
onRequestClose={() => onShow(false)}>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { IAccessoryComponent, IFields, ISection } from './interfaces';
|
import { IAccessoryComponent, IFields, ISection } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
content: {
|
content: {
|
||||||
|
@ -37,10 +38,14 @@ const Fields = ({ fields, parser, theme }: IFields) => (
|
||||||
|
|
||||||
const accessoriesRight = ['image', 'overflow'];
|
const accessoriesRight = ['image', 'overflow'];
|
||||||
|
|
||||||
export const Section = ({ blockId, appId, text, fields, accessory, parser, theme }: ISection) => (
|
export const Section = ({ blockId, appId, text, fields, accessory, parser }: ISection) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
<View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}>
|
<View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}>
|
||||||
{text ? <View style={styles.text}>{parser.text(text)}</View> : null}
|
{text ? <View style={styles.text}>{parser.text(text)}</View> : null}
|
||||||
{fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null}
|
{fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null}
|
||||||
{accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null}
|
{accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import { textParser } from './utils';
|
import { textParser } from './utils';
|
||||||
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
import { isAndroid, isIOS } from '../../utils/deviceInfo';
|
||||||
import ActivityIndicator from '../ActivityIndicator';
|
import ActivityIndicator from '../ActivityIndicator';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
import { IText, Option } from './interfaces';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
iosPadding: {
|
iosPadding: {
|
||||||
|
@ -34,19 +36,16 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ISelect {
|
interface ISelect {
|
||||||
options: {
|
options?: Option[];
|
||||||
text: string;
|
placeholder?: IText;
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
placeholder: string;
|
|
||||||
onChange: Function;
|
onChange: Function;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
disabled: boolean;
|
disabled?: boolean;
|
||||||
value: [];
|
value: [];
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue, theme }: ISelect) => {
|
export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue }: ISelect) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue);
|
const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue);
|
||||||
const items = options.map(option => ({ label: textParser([option.text]), value: option.value }));
|
const items = options.map(option => ({ label: textParser([option.text]), value: option.value }));
|
||||||
const pickerStyle = {
|
const pickerStyle = {
|
||||||
|
@ -80,6 +79,7 @@ export const Select = ({ options = [], placeholder, onChange, loading, disabled,
|
||||||
}}
|
}}
|
||||||
Icon={Icon}
|
Icon={Icon}
|
||||||
textInputProps={{
|
textInputProps={{
|
||||||
|
// style property was Omitted in lib, but can be used normally
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText }
|
style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText }
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { Input } from './Input';
|
||||||
import { DatePicker } from './DatePicker';
|
import { DatePicker } from './DatePicker';
|
||||||
import { Overflow } from './Overflow';
|
import { Overflow } from './Overflow';
|
||||||
import { ThemeContext } from '../../theme';
|
import { ThemeContext } from '../../theme';
|
||||||
import { BlockContext, IButton, IInputIndex, IParser, IText } from './interfaces';
|
import { BlockContext, IActions, IButton, IElement, IInputIndex, IParser, ISection, IText } from './interfaces';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
input: {
|
input: {
|
||||||
|
@ -78,35 +78,28 @@ class MessageParser extends UiKitParserMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
divider() {
|
divider() {
|
||||||
const { theme } = useContext(ThemeContext);
|
return <Divider />;
|
||||||
// @ts-ignore
|
|
||||||
return <Divider theme={theme} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section(args: any) {
|
section(args: ISection) {
|
||||||
const { theme } = useContext(ThemeContext);
|
return <Section {...args} parser={this.current} />;
|
||||||
return <Section {...args} theme={theme} parser={this} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actions(args: any) {
|
actions(args: IActions) {
|
||||||
const { theme } = useContext(ThemeContext);
|
return <Actions {...args} parser={this.current} />;
|
||||||
return <Actions {...args} theme={theme} parser={this} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
overflow(element: any, context: any) {
|
overflow(element: IElement, context: BlockContext) {
|
||||||
const [{ loading }, action]: any = useBlockContext(element, context);
|
const [{ loading }, action] = useBlockContext(element, context);
|
||||||
const { theme }: any = useContext(ThemeContext);
|
return <Overflow element={element} context={context} loading={loading} action={action} parser={this.current} />;
|
||||||
return <Overflow element={element} context={context} loading={loading} action={action} theme={theme} parser={this.current} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
datePicker(element: any, context: any) {
|
datePicker(element: IElement, context: BlockContext) {
|
||||||
const [{ loading, value, error, language }, action]: any = useBlockContext(element, context);
|
const [{ loading, value, error, language }, action] = useBlockContext(element, context);
|
||||||
const { theme }: any = useContext(ThemeContext);
|
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
element={element}
|
element={element}
|
||||||
language={language}
|
language={language}
|
||||||
theme={theme}
|
|
||||||
value={value}
|
value={value}
|
||||||
action={action}
|
action={action}
|
||||||
context={context}
|
context={context}
|
||||||
|
@ -116,9 +109,8 @@ class MessageParser extends UiKitParserMessage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
image(element: any, context: any) {
|
image(element: IElement, context: BlockContext) {
|
||||||
const { theme }: any = useContext(ThemeContext);
|
return <Image element={element} context={context} />;
|
||||||
return <Image element={element} theme={theme} context={context} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context(args: any) {
|
context(args: any) {
|
||||||
|
@ -126,24 +118,19 @@ class MessageParser extends UiKitParserMessage {
|
||||||
return <Context {...args} theme={theme} parser={this} />;
|
return <Context {...args} theme={theme} parser={this} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
multiStaticSelect(element: any, context: any) {
|
multiStaticSelect(element: IElement, context: BlockContext) {
|
||||||
const [{ loading, value }, action]: any = useBlockContext(element, context);
|
const [{ loading, value }, action] = useBlockContext(element, context);
|
||||||
const { theme } = useContext(ThemeContext);
|
return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} multiselect />;
|
||||||
return (
|
|
||||||
<MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} multiselect />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
staticSelect(element: any, context: any) {
|
staticSelect(element: IElement, context: BlockContext) {
|
||||||
const [{ loading, value }, action]: any = useBlockContext(element, context);
|
const [{ loading, value }, action] = useBlockContext(element, context);
|
||||||
const { theme } = useContext(ThemeContext);
|
return <Select {...element} value={value} onChange={action} loading={loading} />;
|
||||||
return <Select {...element} theme={theme} value={value} onChange={action} loading={loading} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectInput(element: any, context: any) {
|
selectInput(element: IElement, context: BlockContext) {
|
||||||
const [{ loading, value }, action]: any = useBlockContext(element, context);
|
const [{ loading, value }, action] = useBlockContext(element, context);
|
||||||
const { theme } = useContext(ThemeContext);
|
return <MultiSelect {...element} value={value} onChange={action} context={context} loading={loading} />;
|
||||||
return <MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} />;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +147,8 @@ class ModalParser extends UiKitParserModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
input({ element, blockId, appId, label, description, hint }: IInputIndex, context: number) {
|
input({ element, blockId, appId, label, description, hint }: IInputIndex, context: number) {
|
||||||
const [{ error }]: any = useBlockContext({ ...element, appId, blockId }, context);
|
const [{ error }] = useBlockContext({ ...element, appId, blockId }, context);
|
||||||
const { theme }: any = useContext(ThemeContext);
|
const { theme } = useContext(ThemeContext);
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
parser={this.current}
|
parser={this.current}
|
||||||
|
@ -175,17 +162,15 @@ class ModalParser extends UiKitParserModal {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
image(element: any, context: any) {
|
image(element: IElement, context: BlockContext) {
|
||||||
const { theme }: any = useContext(ThemeContext);
|
return <Image element={element} context={context} />;
|
||||||
return <Image element={element} theme={theme} context={context} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plainInput(element: any, context: any) {
|
plainInput(element: IElement, context: BlockContext) {
|
||||||
const [{ loading, value, error }, action]: any = useBlockContext(element, context);
|
const [{ loading, value, error }, action] = useBlockContext(element, context);
|
||||||
const { theme } = useContext(ThemeContext);
|
const { theme } = useContext(ThemeContext);
|
||||||
const { multiline, actionId, placeholder } = element;
|
const { multiline, actionId, placeholder } = element;
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
|
||||||
<TextInput
|
<TextInput
|
||||||
key={actionId}
|
key={actionId}
|
||||||
placeholder={plainText(placeholder)}
|
placeholder={plainText(placeholder)}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { TThemeMode } from '../../definitions/ITheme';
|
|
||||||
|
|
||||||
export enum ElementTypes {
|
export enum ElementTypes {
|
||||||
IMAGE = 'image',
|
IMAGE = 'image',
|
||||||
BUTTON = 'button',
|
BUTTON = 'button',
|
||||||
|
@ -87,10 +85,11 @@ export interface IElement {
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
blockId?: string;
|
blockId?: string;
|
||||||
|
multiline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IText {
|
export interface IText {
|
||||||
type: ElementTypes;
|
type?: ElementTypes;
|
||||||
text: string;
|
text: string;
|
||||||
emoji?: boolean;
|
emoji?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -98,12 +97,15 @@ export interface IText {
|
||||||
export interface Option {
|
export interface Option {
|
||||||
text: IText;
|
text: IText;
|
||||||
value: string;
|
value: string;
|
||||||
|
imageUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IButton {
|
export interface IButton {
|
||||||
type: ElementTypes;
|
type: ElementTypes;
|
||||||
text: IText;
|
text: IText;
|
||||||
actionId: string;
|
actionId: string;
|
||||||
|
blockId: string;
|
||||||
|
appId: string;
|
||||||
value?: any;
|
value?: any;
|
||||||
style?: any;
|
style?: any;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +179,6 @@ export interface IParser {
|
||||||
}
|
}
|
||||||
export interface IActions extends Block {
|
export interface IActions extends Block {
|
||||||
parser?: IParser;
|
parser?: IParser;
|
||||||
theme: TThemeMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IContext extends Block {
|
export interface IContext extends Block {
|
||||||
|
@ -191,7 +192,6 @@ export interface IDatePicker extends Partial<Block> {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
value: string;
|
value: string;
|
||||||
error: string;
|
error: string;
|
||||||
theme: TThemeMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IInput extends Partial<Block> {
|
export interface IInput extends Partial<Block> {
|
||||||
|
@ -199,7 +199,7 @@ export interface IInput extends Partial<Block> {
|
||||||
description: string;
|
description: string;
|
||||||
error: string;
|
error: string;
|
||||||
hint: string;
|
hint: string;
|
||||||
theme: TThemeMode;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IInputIndex {
|
export interface IInputIndex {
|
||||||
|
@ -217,8 +217,7 @@ export interface IThumb {
|
||||||
}
|
}
|
||||||
export interface IImage {
|
export interface IImage {
|
||||||
element: IElement;
|
element: IElement;
|
||||||
theme: TThemeMode;
|
context?: BlockContext;
|
||||||
context?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UiKit/Overflow
|
// UiKit/Overflow
|
||||||
|
@ -226,14 +225,13 @@ export interface IOverflow extends Partial<Block> {
|
||||||
action: Function;
|
action: Function;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
parser: IParser;
|
parser: IParser;
|
||||||
theme: TThemeMode;
|
|
||||||
context: number;
|
context: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropsOption {
|
interface PropsOption {
|
||||||
onOptionPress: Function;
|
onOptionPress: Function;
|
||||||
parser: IParser;
|
parser: IParser;
|
||||||
theme: TThemeMode;
|
theme: string;
|
||||||
}
|
}
|
||||||
export interface IOptions extends PropsOption {
|
export interface IOptions extends PropsOption {
|
||||||
options: Option[];
|
options: Option[];
|
||||||
|
@ -262,12 +260,11 @@ export interface ISection {
|
||||||
text?: IText;
|
text?: IText;
|
||||||
accessory?: IAccessory;
|
accessory?: IAccessory;
|
||||||
parser: IParser;
|
parser: IParser;
|
||||||
theme: TThemeMode;
|
|
||||||
fields?: any[];
|
fields?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFields {
|
export interface IFields {
|
||||||
parser: IParser;
|
parser: IParser;
|
||||||
theme: TThemeMode;
|
theme: string;
|
||||||
fields: any[];
|
fields: any[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||||
|
|
||||||
import { BlockContext } from './interfaces';
|
import { BlockContext, IText } from './interfaces';
|
||||||
|
|
||||||
export const textParser = ([{ text }]: any) => text;
|
export const textParser = ([{ text }]: IText[]) => text;
|
||||||
|
|
||||||
export const defaultContext: any = {
|
export const defaultContext: any = {
|
||||||
action: (...args: any) => console.log(args),
|
action: (...args: any) => console.log(args),
|
||||||
|
@ -27,7 +27,14 @@ type TFunctionReturn = (value: any) => Promise<void>;
|
||||||
|
|
||||||
type TReturn = [TObjectReturn, TFunctionReturn];
|
type TReturn = [TObjectReturn, TFunctionReturn];
|
||||||
|
|
||||||
export const useBlockContext = ({ blockId, actionId, appId, initialValue }: any, context: BlockContext): TReturn => {
|
interface IUseBlockContext {
|
||||||
|
blockId?: string;
|
||||||
|
actionId: string;
|
||||||
|
appId?: string;
|
||||||
|
initialValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBlockContext = ({ blockId, actionId, appId, initialValue }: IUseBlockContext, context: BlockContext): TReturn => {
|
||||||
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
|
const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext);
|
||||||
const { value = initialValue } = values[actionId] || {};
|
const { value = initialValue } = values[actionId] || {};
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
|
||||||
|
|
||||||
const handlePress = () => {
|
const handlePress = () => {
|
||||||
const index = channels?.findIndex(channel => channel.name === hashtag);
|
const index = channels?.findIndex(channel => channel.name === hashtag);
|
||||||
if (index && navToRoomInfo) {
|
if (typeof index !== 'undefined' && navToRoomInfo) {
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'c',
|
t: 'c',
|
||||||
rid: channels?.[index]._id
|
rid: channels?.[index]._id
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Clipboard, Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export interface IUserMention {
|
export interface IUserMention {
|
||||||
_id: string;
|
_id: string;
|
||||||
username: string;
|
username?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Text, Clipboard } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import { Link as LinkProps } from '@rocket.chat/message-parser';
|
import { Link as LinkProps } from '@rocket.chat/message-parser';
|
||||||
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
|
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import I18n from '../../../i18n';
|
import I18n from '../../../i18n';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useContext } from 'react';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
import { IMessageAttachments, IMessageAttachedActions } from './interfaces';
|
import { IMessageAttachments } from './interfaces';
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
import Audio from './Audio';
|
import Audio from './Audio';
|
||||||
import Video from './Video';
|
import Video from './Video';
|
||||||
|
@ -13,30 +13,49 @@ import MessageContext from './Context';
|
||||||
import { useTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import { IAttachment } from '../../definitions';
|
import { IAttachment } from '../../definitions';
|
||||||
import CollapsibleQuote from './Components/CollapsibleQuote';
|
import CollapsibleQuote from './Components/CollapsibleQuote';
|
||||||
|
import openLink from '../../utils/openLink';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
|
export type TElement = {
|
||||||
|
type: string;
|
||||||
|
msg?: string;
|
||||||
|
url?: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
||||||
if (!attachment.actions) {
|
if (!attachment.actions) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { onAnswerButtonPress } = useContext(MessageContext);
|
const { onAnswerButtonPress } = useContext(MessageContext);
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => {
|
const attachedButtons = attachment.actions.map((element: TElement) => {
|
||||||
if (element.type === 'button') {
|
const onPress = () => {
|
||||||
return <Button theme={theme} onPress={() => onAnswerButtonPress(element.msg)} title={element.text} />;
|
if (element.msg) {
|
||||||
|
onAnswerButtonPress(element.msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.url) {
|
||||||
|
openLink(element.url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (element.type === 'button') {
|
||||||
|
return <Button theme={theme} onPress={onPress} title={element.text} />;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={styles.text}>{attachment.text}</Text>
|
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{attachment.text}</Text>
|
||||||
{attachedButtons}
|
{attachedButtons}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Attachments = React.memo(
|
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
||||||
// @ts-ignore
|
|
||||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
||||||
if (!attachments || attachments.length === 0) {
|
if (!attachments || attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -44,7 +63,7 @@ const Attachments = React.memo(
|
||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
return 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 (
|
||||||
<Image
|
<Image
|
||||||
|
@ -54,7 +73,6 @@ const Attachments = React.memo(
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
style={style}
|
style={style}
|
||||||
isReply={isReply}
|
isReply={isReply}
|
||||||
theme={theme}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -74,7 +92,6 @@ const Attachments = React.memo(
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
style={style}
|
style={style}
|
||||||
isReply={isReply}
|
isReply={isReply}
|
||||||
theme={theme}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -90,6 +107,7 @@ const Attachments = React.memo(
|
||||||
|
|
||||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
|
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
|
||||||
});
|
});
|
||||||
|
return <>{attachmentsElements}</>;
|
||||||
},
|
},
|
||||||
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments)
|
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
|
||||||
import { Audio } from 'expo-av';
|
import { Audio, AVPlaybackStatus } from 'expo-av';
|
||||||
import Slider from '@react-native-community/slider';
|
import Slider from '@react-native-community/slider';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||||
|
import { Sound } from 'expo-av/build/Audio/Sound';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
|
@ -23,7 +24,7 @@ interface IButton {
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
theme: string;
|
theme: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress: Function;
|
onPress: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageAudioProps {
|
interface IMessageAudioProps {
|
||||||
|
@ -108,7 +109,7 @@ Button.displayName = 'MessageAudioButton';
|
||||||
class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioState> {
|
class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioState> {
|
||||||
static contextType = MessageContext;
|
static contextType = MessageContext;
|
||||||
|
|
||||||
private sound: any;
|
private sound: Sound;
|
||||||
|
|
||||||
constructor(props: IMessageAudioProps) {
|
constructor(props: IMessageAudioProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -141,7 +142,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: any, nextState: any) {
|
shouldComponentUpdate(nextProps: IMessageAudioProps, nextState: IMessageAudioState) {
|
||||||
const { currentTime, duration, paused, loading } = this.state;
|
const { currentTime, duration, paused, loading } = this.state;
|
||||||
const { file, theme } = this.props;
|
const { file, theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -182,7 +183,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPlaybackStatusUpdate = (status: any) => {
|
onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
||||||
if (status) {
|
if (status) {
|
||||||
this.onLoad(status);
|
this.onLoad(status);
|
||||||
this.onProgress(status);
|
this.onProgress(status);
|
||||||
|
@ -190,20 +191,25 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoad = (data: any) => {
|
onLoad = (data: AVPlaybackStatus) => {
|
||||||
|
if (data.isLoaded && data.durationMillis) {
|
||||||
const duration = data.durationMillis / 1000;
|
const duration = data.durationMillis / 1000;
|
||||||
this.setState({ duration: duration > 0 ? duration : 0 });
|
this.setState({ duration: duration > 0 ? duration : 0 });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onProgress = (data: any) => {
|
onProgress = (data: AVPlaybackStatus) => {
|
||||||
|
if (data.isLoaded) {
|
||||||
const { duration } = this.state;
|
const { duration } = this.state;
|
||||||
const currentTime = data.positionMillis / 1000;
|
const currentTime = data.positionMillis / 1000;
|
||||||
if (currentTime <= duration) {
|
if (currentTime <= duration) {
|
||||||
this.setState({ currentTime });
|
this.setState({ currentTime });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onEnd = async (data: any) => {
|
onEnd = async (data: AVPlaybackStatus) => {
|
||||||
|
if (data.isLoaded) {
|
||||||
if (data.didJustFinish) {
|
if (data.didJustFinish) {
|
||||||
try {
|
try {
|
||||||
await this.sound.stopAsync();
|
await this.sound.stopAsync();
|
||||||
|
@ -212,6 +218,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get duration() {
|
get duration() {
|
||||||
|
@ -238,7 +245,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onValueChange = async (value: any) => {
|
onValueChange = async (value: number) => {
|
||||||
try {
|
try {
|
||||||
this.setState({ currentTime: value });
|
this.setState({ currentTime: value });
|
||||||
await this.sound.setPositionAsync(value * 1000);
|
await this.sound.setPositionAsync(value * 1000);
|
||||||
|
|
|
@ -8,7 +8,8 @@ const Blocks = React.memo(({ blocks, id: mid, rid, blockAction }: IMessageBlocks
|
||||||
const appId = blocks[0]?.appId || '';
|
const appId = blocks[0]?.appId || '';
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
messageBlockWithContext({
|
messageBlockWithContext({
|
||||||
action: async ({ actionId, value, blockId }: any) => {
|
action: async ({ actionId, value, blockId }: { actionId: string; value: string; blockId: string }) => {
|
||||||
|
if (blockAction) {
|
||||||
await blockAction({
|
await blockAction({
|
||||||
actionId,
|
actionId,
|
||||||
appId,
|
appId,
|
||||||
|
@ -17,6 +18,7 @@ const Blocks = React.memo(({ blocks, id: mid, rid, blockAction }: IMessageBlocks
|
||||||
rid,
|
rid,
|
||||||
mid
|
mid
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
appId,
|
appId,
|
||||||
rid
|
rid
|
||||||
|
|
|
@ -9,10 +9,13 @@ import I18n from '../../i18n';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { IMessageBroadcast } from './interfaces';
|
import { IMessageBroadcast } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const Broadcast = React.memo(({ author, broadcast, theme }: IMessageBroadcast) => {
|
const Broadcast = React.memo(({ author, broadcast }: IMessageBroadcast) => {
|
||||||
const { user, replyBroadcast } = useContext(MessageContext);
|
const { user, replyBroadcast } = useContext(MessageContext);
|
||||||
const isOwn = author._id === user.id;
|
const { theme } = useTheme();
|
||||||
|
const isOwn = author?._id === user.id;
|
||||||
|
|
||||||
if (broadcast && !isOwn) {
|
if (broadcast && !isOwn) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
|
|
|
@ -8,8 +8,11 @@ import I18n from '../../i18n';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { IMessageCallButton } from './interfaces';
|
import { IMessageCallButton } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const CallButton = React.memo(({ theme, callJitsi }: IMessageCallButton) => (
|
const CallButton = React.memo(({ callJitsi }: IMessageCallButton) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
return (
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={callJitsi}
|
onPress={callJitsi}
|
||||||
|
@ -22,7 +25,8 @@ const CallButton = React.memo(({ theme, callJitsi }: IMessageCallButton) => (
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
</View>
|
</View>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
CallButton.displayName = 'CallButton';
|
CallButton.displayName = 'CallButton';
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,6 @@ import React from 'react';
|
||||||
import MessageContext from '../../Context';
|
import MessageContext from '../../Context';
|
||||||
import CollapsibleQuote from '.';
|
import CollapsibleQuote from '.';
|
||||||
|
|
||||||
// For some reason a general mock didn't work, I have to do a search
|
|
||||||
jest.mock('react-native-mmkv-storage', () => ({
|
|
||||||
Loader: jest.fn().mockImplementation(() => ({
|
|
||||||
setProcessingMode: jest.fn().mockImplementation(() => ({
|
|
||||||
withEncryption: jest.fn().mockImplementation(() => ({
|
|
||||||
initialize: jest.fn()
|
|
||||||
}))
|
|
||||||
}))
|
|
||||||
})),
|
|
||||||
create: jest.fn(),
|
|
||||||
MODES: { MULTI_PROCESS: '' }
|
|
||||||
}));
|
|
||||||
|
|
||||||
const testAttachment = {
|
const testAttachment = {
|
||||||
ts: '1970-01-01T00:00:00.000Z',
|
ts: '1970-01-01T00:00:00.000Z',
|
||||||
title: 'Engineering (9 today)',
|
title: 'Engineering (9 today)',
|
||||||
|
|
|
@ -10,17 +10,19 @@ import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import Encrypted from './Encrypted';
|
import Encrypted from './Encrypted';
|
||||||
import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants';
|
import { E2E_MESSAGE_TYPE } from '../../lib/constants';
|
||||||
import { IMessageContent } from './interfaces';
|
import { IMessageContent } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const Content = React.memo(
|
const Content = React.memo(
|
||||||
(props: IMessageContent) => {
|
(props: IMessageContent) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
if (props.isInfo) {
|
if (props.isInfo) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const infoMessage = getInfoMessage({ ...props });
|
const infoMessage = getInfoMessage({ ...props });
|
||||||
|
|
||||||
const renderMessageContent = (
|
const renderMessageContent = (
|
||||||
<Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]} accessibilityLabel={infoMessage}>
|
<Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]} accessibilityLabel={infoMessage}>
|
||||||
{infoMessage}
|
{infoMessage}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -36,14 +38,12 @@ const Content = React.memo(
|
||||||
return renderMessageContent;
|
return renderMessageContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPreview: any = props.tmid && !props.isThreadRoom;
|
const isPreview = props.tmid && !props.isThreadRoom;
|
||||||
let content = null;
|
let content = null;
|
||||||
|
|
||||||
if (props.isEncrypted) {
|
if (props.isEncrypted) {
|
||||||
content = (
|
content = (
|
||||||
<Text
|
<Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]} accessibilityLabel={I18n.t('Encrypted_message')}>
|
||||||
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
|
|
||||||
accessibilityLabel={I18n.t('Encrypted_message')}>
|
|
||||||
{I18n.t('Encrypted_message')}
|
{I18n.t('Encrypted_message')}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -65,7 +65,7 @@ const Content = React.memo(
|
||||||
navToRoomInfo={props.navToRoomInfo}
|
navToRoomInfo={props.navToRoomInfo}
|
||||||
tmid={props.tmid}
|
tmid={props.tmid}
|
||||||
useRealName={props.useRealName}
|
useRealName={props.useRealName}
|
||||||
theme={props.theme}
|
theme={theme}
|
||||||
onLinkPress={onLinkPress}
|
onLinkPress={onLinkPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -76,13 +76,13 @@ const Content = React.memo(
|
||||||
content = (
|
content = (
|
||||||
<View style={styles.flex}>
|
<View style={styles.flex}>
|
||||||
<View style={styles.contentContainer}>{content}</View>
|
<View style={styles.contentContainer}>{content}</View>
|
||||||
<Encrypted type={props.type} theme={props.theme} />
|
<Encrypted type={props.type} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.isIgnored) {
|
if (props.isIgnored) {
|
||||||
content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
|
content = <Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <View style={props.isTemp && styles.temp}>{content}</View>;
|
return <View style={props.isTemp && styles.temp}>{content}</View>;
|
||||||
|
@ -97,9 +97,6 @@ const Content = React.memo(
|
||||||
if (prevProps.type !== nextProps.type) {
|
if (prevProps.type !== nextProps.type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (prevProps.theme !== nextProps.theme) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prevProps.isEncrypted !== nextProps.isEncrypted) {
|
if (prevProps.isEncrypted !== nextProps.isEncrypted) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,12 @@ import { DISCUSSION } from './constants';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { formatDateThreads } from '../../utils/room';
|
import { formatDateThreads } from '../../utils/room';
|
||||||
import { IMessageDiscussion } from './interfaces';
|
import { IMessage } from '../../definitions';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const Discussion = React.memo(
|
const Discussion = React.memo(
|
||||||
({ msg, dcount, dlm, theme }: IMessageDiscussion) => {
|
({ msg, dcount, dlm }: Pick<IMessage, 'msg' | 'dcount' | 'dlm'>) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
let time;
|
let time;
|
||||||
if (dlm) {
|
if (dlm) {
|
||||||
time = formatDateThreads(dlm);
|
time = formatDateThreads(dlm);
|
||||||
|
@ -50,9 +52,6 @@ const Discussion = React.memo(
|
||||||
if (prevProps.dlm !== nextProps.dlm) {
|
if (prevProps.dlm !== nextProps.dlm) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (prevProps.theme !== nextProps.theme) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants';
|
import { E2E_MESSAGE_TYPE } from '../../lib/constants';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
interface IMessageEncrypted {
|
const Encrypted = React.memo(({ type }: { type: string }) => {
|
||||||
type: string;
|
const { theme } = useTheme();
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Encrypted = React.memo(({ type, theme }: IMessageEncrypted) => {
|
|
||||||
if (type !== E2E_MESSAGE_TYPE) {
|
if (type !== E2E_MESSAGE_TYPE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,32 +13,27 @@ import { themes } from '../../constants/colors';
|
||||||
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';
|
||||||
|
|
||||||
type TMessageButton = {
|
interface IMessageButton {
|
||||||
children: JSX.Element;
|
children: React.ReactElement;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onPress: Function;
|
onPress: () => void;
|
||||||
theme: string;
|
theme: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
type TMessageImage = {
|
|
||||||
img: string;
|
|
||||||
theme: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMessageImage {
|
interface IMessageImage {
|
||||||
file: IAttachment;
|
file: IAttachment;
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
showAttachment?: Function;
|
showAttachment?: (file: IAttachment) => void;
|
||||||
style?: StyleProp<TextStyle>[];
|
style?: StyleProp<TextStyle>[];
|
||||||
isReply?: boolean;
|
isReply?: boolean;
|
||||||
theme: string;
|
getCustomEmoji?: TGetCustomEmoji;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageProgress = createImageProgress(FastImage);
|
const ImageProgress = createImageProgress(FastImage);
|
||||||
|
|
||||||
const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButton) => (
|
const Button = React.memo(({ children, onPress, disabled, theme }: IMessageButton) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
|
@ -48,10 +43,10 @@ const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButto
|
||||||
</Touchable>
|
</Touchable>
|
||||||
));
|
));
|
||||||
|
|
||||||
export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
|
export const MessageImage = React.memo(({ imgUri, theme }: { imgUri: string; theme: string }) => (
|
||||||
<ImageProgress
|
<ImageProgress
|
||||||
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
||||||
source={{ uri: encodeURI(img) }}
|
source={{ uri: encodeURI(imgUri) }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
indicator={Progress.Pie}
|
indicator={Progress.Pie}
|
||||||
indicatorProps={{
|
indicatorProps={{
|
||||||
|
@ -61,9 +56,11 @@ export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
|
||||||
));
|
));
|
||||||
|
|
||||||
const ImageContainer = React.memo(
|
const ImageContainer = React.memo(
|
||||||
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageImage) => {
|
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply }: IMessageImage) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||||
|
|
||||||
if (!img) {
|
if (!img) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +85,7 @@ const ImageContainer = React.memo(
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<MessageImage img={img} theme={theme} />
|
<MessageImage imgUri={img} theme={theme} />
|
||||||
</View>
|
</View>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
@ -96,11 +93,11 @@ const ImageContainer = React.memo(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button disabled={isReply} theme={theme} onPress={onPress}>
|
<Button disabled={isReply} theme={theme} onPress={onPress}>
|
||||||
<MessageImage img={img} theme={theme} />
|
<MessageImage imgUri={img} theme={theme} />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme
|
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
|
||||||
);
|
);
|
||||||
|
|
||||||
ImageContainer.displayName = 'MessageImageContainer';
|
ImageContainer.displayName = 'MessageImageContainer';
|
||||||
|
|
|
@ -19,6 +19,7 @@ import ReadReceipt from './ReadReceipt';
|
||||||
import CallButton from './CallButton';
|
import CallButton from './CallButton';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
|
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const MessageInner = React.memo((props: IMessageInner) => {
|
const MessageInner = React.memo((props: IMessageInner) => {
|
||||||
const { attachments } = props;
|
const { attachments } = props;
|
||||||
|
@ -85,7 +86,6 @@ const Message = React.memo((props: IMessage) => {
|
||||||
<View style={[styles.container, props.style]}>
|
<View style={[styles.container, props.style]}>
|
||||||
{thread}
|
{thread}
|
||||||
<View style={styles.flex}>
|
<View style={styles.flex}>
|
||||||
{/* @ts-ignore */}
|
|
||||||
<MessageAvatar small {...props} />
|
<MessageAvatar small {...props} />
|
||||||
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
|
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
|
||||||
<Content {...props} />
|
<Content {...props} />
|
||||||
|
@ -98,12 +98,11 @@ const Message = React.memo((props: IMessage) => {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, props.style]}>
|
<View style={[styles.container, props.style]}>
|
||||||
<View style={styles.flex}>
|
<View style={styles.flex}>
|
||||||
{/* @ts-ignore */}
|
|
||||||
<MessageAvatar {...props} />
|
<MessageAvatar {...props} />
|
||||||
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
|
<View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}>
|
||||||
<MessageInner {...props} />
|
<MessageInner {...props} />
|
||||||
</View>
|
</View>
|
||||||
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} theme={props.theme} />
|
<ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread || false} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -119,12 +118,14 @@ const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { onPress, onLongPress } = useContext(MessageContext);
|
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[props.theme].headerBackground : null }}>
|
style={{ backgroundColor: props.highlighted ? themes[theme].headerBackground : null }}>
|
||||||
<View>
|
<View>
|
||||||
<Message {...props} />
|
<Message {...props} />
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -4,13 +4,13 @@ import Avatar from '../Avatar';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { IMessageAvatar } from './interfaces';
|
import { IMessageAvatar } from './interfaces';
|
||||||
|
import { SubscriptionType } from '../../definitions';
|
||||||
|
|
||||||
const MessageAvatar = React.memo(
|
const MessageAvatar = React.memo(({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji }: IMessageAvatar) => {
|
||||||
({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji, theme }: IMessageAvatar) => {
|
|
||||||
const { user } = useContext(MessageContext);
|
const { user } = useContext(MessageContext);
|
||||||
if (isHeader && author) {
|
if (isHeader && author) {
|
||||||
const navParam = {
|
const navParam = {
|
||||||
t: 'd',
|
t: SubscriptionType.DIRECT,
|
||||||
rid: author._id
|
rid: author._id
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
@ -23,13 +23,11 @@ const MessageAvatar = React.memo(
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
avatar={avatar}
|
avatar={avatar}
|
||||||
emoji={emoji}
|
emoji={emoji}
|
||||||
theme={theme}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
MessageAvatar.displayName = 'MessageAvatar';
|
MessageAvatar.displayName = 'MessageAvatar';
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,12 @@ import styles from './styles';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
interface IMessageError {
|
|
||||||
hasError: boolean;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MessageError = React.memo(
|
const MessageError = React.memo(
|
||||||
({ hasError, theme }: IMessageError) => {
|
({ hasError }: { hasError: boolean }) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +22,7 @@ const MessageError = React.memo(
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prevProps, nextProps) => prevProps.hasError === nextProps.hasError && prevProps.theme === nextProps.theme
|
(prevProps, nextProps) => prevProps.hasError === nextProps.hasError
|
||||||
);
|
);
|
||||||
|
|
||||||
MessageError.displayName = 'MessageError';
|
MessageError.displayName = 'MessageError';
|
||||||
|
|
|
@ -7,30 +7,28 @@ 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 '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
|
|
||||||
interface IMessageAddReaction {
|
interface IReaction {
|
||||||
theme: string;
|
_id: string;
|
||||||
|
emoji: string;
|
||||||
|
usernames: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageReaction {
|
interface IMessageReaction {
|
||||||
reaction: {
|
reaction: IReaction;
|
||||||
usernames: [];
|
|
||||||
emoji: object;
|
|
||||||
};
|
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
theme: string;
|
theme: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMessageReactions {
|
interface IMessageReactions {
|
||||||
reactions?: object[];
|
reactions?: IReaction[];
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddReaction = React.memo(({ theme }: IMessageAddReaction) => {
|
const AddReaction = React.memo(({ theme }: { theme: string }) => {
|
||||||
const { reactionInit } = useContext(MessageContext);
|
const { reactionInit } = useContext(MessageContext);
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
|
@ -49,7 +47,7 @@ const AddReaction = React.memo(({ theme }: IMessageAddReaction) => {
|
||||||
|
|
||||||
const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReaction) => {
|
const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReaction) => {
|
||||||
const { onReactionPress, onReactionLongPress, baseUrl, user } = useContext(MessageContext);
|
const { onReactionPress, onReactionLongPress, baseUrl, user } = useContext(MessageContext);
|
||||||
const reacted = reaction.usernames.findIndex((item: IMessageReaction) => item === user.username) !== -1;
|
const reacted = reaction.usernames.findIndex((item: string) => item === user.username) !== -1;
|
||||||
return (
|
return (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => onReactionPress(reaction.emoji)}
|
onPress={() => onReactionPress(reaction.emoji)}
|
||||||
|
@ -76,13 +74,15 @@ const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReacti
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const Reactions = React.memo(({ reactions, getCustomEmoji, theme }: IMessageReactions) => {
|
const Reactions = React.memo(({ reactions, getCustomEmoji }: IMessageReactions) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!Array.isArray(reactions) || reactions.length === 0) {
|
if (!Array.isArray(reactions) || reactions.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={styles.reactionsContainer}>
|
<View style={styles.reactionsContainer}>
|
||||||
{reactions.map((reaction: any) => (
|
{reactions.map(reaction => (
|
||||||
<Reaction key={reaction.emoji} reaction={reaction} getCustomEmoji={getCustomEmoji} theme={theme} />
|
<Reaction key={reaction.emoji} reaction={reaction} getCustomEmoji={getCustomEmoji} theme={theme} />
|
||||||
))}
|
))}
|
||||||
<AddReaction theme={theme} />
|
<AddReaction theme={theme} />
|
||||||
|
@ -94,4 +94,4 @@ Reaction.displayName = 'MessageReaction';
|
||||||
Reactions.displayName = 'MessageReactions';
|
Reactions.displayName = 'MessageReactions';
|
||||||
AddReaction.displayName = 'MessageAddReaction';
|
AddReaction.displayName = 'MessageAddReaction';
|
||||||
|
|
||||||
export default withTheme(Reactions);
|
export default Reactions;
|
||||||
|
|
|
@ -3,14 +3,10 @@ import React from 'react';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
interface IMessageReadReceipt {
|
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => {
|
||||||
isReadReceiptEnabled: boolean;
|
const { theme } = useTheme();
|
||||||
unread: boolean;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread, theme }: IMessageReadReceipt) => {
|
|
||||||
if (isReadReceiptEnabled && !unread && unread !== null) {
|
if (isReadReceiptEnabled && !unread && unread !== null) {
|
||||||
return <CustomIcon name='check' color={themes[theme].tintColor} size={15} style={styles.readReceipt} />;
|
return <CustomIcon name='check' color={themes[theme].tintColor} size={15} style={styles.readReceipt} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,18 @@ import { themes } from '../../constants/colors';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { MarkdownPreview } from '../markdown';
|
import { MarkdownPreview } from '../markdown';
|
||||||
import { IMessageRepliedThread } from './interfaces';
|
import { IMessageRepliedThread } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
|
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted }: IMessageRepliedThread) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme }: IMessageRepliedThread) => {
|
|
||||||
if (!tmid || !isHeader) {
|
if (!tmid || !isHeader) {
|
||||||
return null;
|
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 fetch = async () => {
|
||||||
const threadName = await fetchThreadName(tmid, id);
|
const threadName = fetchThreadName ? await fetchThreadName(tmid, id) : '';
|
||||||
setMsg(threadName);
|
setMsg(threadName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ const styles = StyleSheet.create({
|
||||||
marginBottom: 4
|
marginBottom: 4
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
// @ts-ignore
|
// @ts-ignore TODO - check with the team, change this to undefined
|
||||||
width: null,
|
width: null,
|
||||||
height: 200,
|
height: 200,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
@ -93,24 +93,6 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IMessageTitle {
|
|
||||||
attachment: IAttachment;
|
|
||||||
timeFormat?: string;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMessageDescription {
|
|
||||||
attachment: IAttachment;
|
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMessageFields {
|
|
||||||
attachment: IAttachment;
|
|
||||||
theme: string;
|
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMessageReply {
|
interface IMessageReply {
|
||||||
attachment: IAttachment;
|
attachment: IAttachment;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
|
@ -118,7 +100,7 @@ interface IMessageReply {
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
|
const Title = React.memo(({ attachment, timeFormat, theme }: { attachment: IAttachment; timeFormat?: string; theme: string }) => {
|
||||||
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
|
const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
|
||||||
return (
|
return (
|
||||||
<View style={styles.authorContainer}>
|
<View style={styles.authorContainer}>
|
||||||
|
@ -132,7 +114,7 @@ const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const Description = React.memo(
|
const Description = React.memo(
|
||||||
({ attachment, getCustomEmoji, theme }: IMessageDescription) => {
|
({ attachment, getCustomEmoji, theme }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji; theme: string }) => {
|
||||||
const text = attachment.text || attachment.title;
|
const text = attachment.text || attachment.title;
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -164,7 +146,7 @@ const Description = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
const UrlImage = React.memo(
|
||||||
({ image }: any) => {
|
({ image }: { image?: string }) => {
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -176,7 +158,7 @@ const UrlImage = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const Fields = React.memo(
|
const Fields = React.memo(
|
||||||
({ attachment, theme, getCustomEmoji }: IMessageFields) => {
|
({ attachment, theme, getCustomEmoji }: { attachment: IAttachment; theme: string; getCustomEmoji: TGetCustomEmoji }) => {
|
||||||
if (!attachment.fields) {
|
if (!attachment.fields) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -206,12 +188,12 @@ const Fields = React.memo(
|
||||||
const Reply = React.memo(
|
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();
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
|
|
|
@ -7,9 +7,12 @@ import MessageContext from './Context';
|
||||||
import ThreadDetails from '../ThreadDetails';
|
import ThreadDetails from '../ThreadDetails';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { IMessageThread } from './interfaces';
|
import { IMessageThread } from './interfaces';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const Thread = React.memo(
|
const Thread = React.memo(
|
||||||
({ msg, tcount, tlm, isThreadRoom, theme, id }: IMessageThread) => {
|
({ msg, tcount, tlm, isThreadRoom, id }: IMessageThread) => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!tlm || isThreadRoom || tcount === 0) {
|
if (!tlm || isThreadRoom || tcount === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -38,9 +41,6 @@ const Thread = React.memo(
|
||||||
if (prevProps.tcount !== nextProps.tcount) {
|
if (prevProps.tcount !== nextProps.tcount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (prevProps.theme !== nextProps.theme) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Clipboard, StyleSheet, Text, View } from 'react-native';
|
import { StyleSheet, Text, View } from 'react-native';
|
||||||
|
import Clipboard from '@react-native-clipboard/clipboard';
|
||||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
|
|
||||||
|
@ -7,11 +8,12 @@ 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 '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { 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';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
|
import { IUrl } from '../../definitions';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -49,29 +51,6 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IMessageUrlContent {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMessageUrl {
|
|
||||||
url: {
|
|
||||||
ignoreParse: boolean;
|
|
||||||
url: string;
|
|
||||||
image: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
};
|
|
||||||
index: number;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMessageUrls {
|
|
||||||
urls?: any;
|
|
||||||
theme?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UrlImage = React.memo(
|
const UrlImage = React.memo(
|
||||||
({ image }: { image: string }) => {
|
({ image }: { image: string }) => {
|
||||||
if (!image) {
|
if (!image) {
|
||||||
|
@ -85,7 +64,7 @@ const UrlImage = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const UrlContent = React.memo(
|
const UrlContent = React.memo(
|
||||||
({ title, description, theme }: IMessageUrlContent) => (
|
({ title, description, theme }: { title: string; description: string; theme: string }) => (
|
||||||
<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}>
|
||||||
|
@ -114,7 +93,7 @@ const UrlContent = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const Url = React.memo(
|
const Url = React.memo(
|
||||||
({ url, index, theme }: IMessageUrl) => {
|
({ url, index, theme }: { url: IUrl; index: number; theme: string }) => {
|
||||||
if (!url || url?.ignoreParse) {
|
if (!url || url?.ignoreParse) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -151,14 +130,17 @@ const Url = React.memo(
|
||||||
);
|
);
|
||||||
|
|
||||||
const Urls = React.memo(
|
const Urls = React.memo(
|
||||||
({ urls, theme }: IMessageUrls) => {
|
// TODO - didn't work - (React.ReactElement | null)[] | React.ReactElement | null
|
||||||
|
({ urls }: { urls?: IUrl[] }): any => {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!urls || urls.length === 0) {
|
if (!urls || urls.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return urls.map((url: any, index: number) => <Url url={url} key={url.url} index={index} theme={theme!} />);
|
return urls.map((url: IUrl, index: number) => <Url url={url} key={url.url} index={index} theme={theme} />);
|
||||||
},
|
},
|
||||||
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme
|
(oldProps, newProps) => dequal(oldProps.urls, newProps.urls)
|
||||||
);
|
);
|
||||||
|
|
||||||
UrlImage.displayName = 'MessageUrlImage';
|
UrlImage.displayName = 'MessageUrlImage';
|
||||||
|
|
|
@ -3,12 +3,14 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { useTheme } from '../../theme';
|
||||||
import MessageError from './MessageError';
|
import MessageError from './MessageError';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import messageStyles from './styles';
|
import messageStyles from './styles';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
|
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
|
||||||
|
import { SubscriptionType } from '../../definitions';
|
||||||
|
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -49,15 +51,15 @@ interface IMessageUser {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
ts?: Date;
|
ts?: Date;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
theme: string;
|
navToRoomInfo?: (navParam: IRoomInfoParam) => void;
|
||||||
navToRoomInfo?: Function;
|
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const User = React.memo(
|
const User = React.memo(
|
||||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, type, ...props }: IMessageUser) => {
|
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, ...props }: IMessageUser) => {
|
||||||
if (isHeader || hasError) {
|
if (isHeader || hasError) {
|
||||||
const { user } = useContext(MessageContext);
|
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>
|
||||||
|
@ -65,8 +67,8 @@ const User = React.memo(
|
||||||
const time = moment(ts).format(timeFormat);
|
const time = moment(ts).format(timeFormat);
|
||||||
const onUserPress = () => {
|
const onUserPress = () => {
|
||||||
navToRoomInfo?.({
|
navToRoomInfo?.({
|
||||||
t: 'd',
|
t: SubscriptionType.DIRECT,
|
||||||
rid: author?._id
|
rid: author?._id || ''
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const isDisabled = author?._id === user.id;
|
const isDisabled = author?._id === user.id;
|
||||||
|
@ -83,7 +85,7 @@ const User = React.memo(
|
||||||
<Text
|
<Text
|
||||||
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
|
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
|
||||||
onPress={onUserPress}
|
onPress={onUserPress}
|
||||||
// @ts-ignore
|
// @ts-ignore // TODO - check this prop
|
||||||
disabled={isDisabled}>
|
disabled={isDisabled}>
|
||||||
{textContent}
|
{textContent}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -98,7 +100,7 @@ const User = React.memo(
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text>
|
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text>
|
||||||
{hasError && <MessageError hasError={hasError} theme={theme} {...props} />}
|
{hasError ? <MessageError hasError={hasError} {...props} /> : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -108,4 +110,4 @@ const User = React.memo(
|
||||||
|
|
||||||
User.displayName = 'MessageUser';
|
User.displayName = 'MessageUser';
|
||||||
|
|
||||||
export default withTheme(User);
|
export default User;
|
||||||
|
|
|
@ -16,9 +16,10 @@ import I18n from '../../i18n';
|
||||||
import { IAttachment } from '../../definitions/IAttachment';
|
import { IAttachment } from '../../definitions/IAttachment';
|
||||||
import RCActivityIndicator from '../ActivityIndicator';
|
import RCActivityIndicator from '../ActivityIndicator';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
|
import { useTheme } from '../../theme';
|
||||||
|
|
||||||
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
|
||||||
const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1;
|
const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
|
@ -33,23 +34,24 @@ const styles = StyleSheet.create({
|
||||||
|
|
||||||
interface IMessageVideo {
|
interface IMessageVideo {
|
||||||
file: IAttachment;
|
file: IAttachment;
|
||||||
showAttachment?: Function;
|
showAttachment?: (file: IAttachment) => void;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
style?: StyleProp<TextStyle>[];
|
style?: StyleProp<TextStyle>[];
|
||||||
isReply?: boolean;
|
isReply?: boolean;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Video = React.memo(
|
const Video = React.memo(
|
||||||
({ file, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageVideo) => {
|
({ file, showAttachment, getCustomEmoji, style, isReply }: IMessageVideo) => {
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPress = async () => {
|
const onPress = async () => {
|
||||||
if (isTypeSupported(file.video_type) && showAttachment) {
|
if (file.video_type && isTypeSupported(file.video_type) && showAttachment) {
|
||||||
return showAttachment(file);
|
return showAttachment(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +95,7 @@ const Video = React.memo(
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme
|
(prevProps, nextProps) => dequal(prevProps.file, nextProps.file)
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Video;
|
export default Video;
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Keyboard, ViewStyle } from 'react-native';
|
import { Keyboard, ViewStyle } from 'react-native';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import Message from './Message';
|
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 { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
|
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/constants';
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
import { withTheme } from '../../theme';
|
import { useTheme, withTheme } from '../../theme';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { TAnyMessageModel } from '../../definitions';
|
import { IAttachment, TAnyMessageModel } from '../../definitions';
|
||||||
|
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||||
|
|
||||||
interface IMessageContainerProps {
|
interface IMessageContainerProps {
|
||||||
item: TAnyMessageModel;
|
item: TAnyMessageModel;
|
||||||
|
@ -20,7 +22,7 @@ interface IMessageContainerProps {
|
||||||
token: string;
|
token: string;
|
||||||
};
|
};
|
||||||
msg?: string;
|
msg?: string;
|
||||||
rid?: string;
|
rid: string;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
style?: ViewStyle;
|
style?: ViewStyle;
|
||||||
archived?: boolean;
|
archived?: boolean;
|
||||||
|
@ -37,44 +39,35 @@ interface IMessageContainerProps {
|
||||||
isIgnored?: boolean;
|
isIgnored?: boolean;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
onLongPress?: Function;
|
onLongPress?: (item: TAnyMessageModel) => void;
|
||||||
onReactionPress?: Function;
|
onReactionPress?: (emoji: string, id: string) => void;
|
||||||
onEncryptedPress?: Function;
|
onEncryptedPress?: () => void;
|
||||||
onDiscussionPress?: Function;
|
onDiscussionPress?: (item: TAnyMessageModel) => void;
|
||||||
onThreadPress?: Function;
|
onThreadPress?: (item: TAnyMessageModel) => void;
|
||||||
errorActionsShow?: Function;
|
errorActionsShow?: (item: TAnyMessageModel) => void;
|
||||||
replyBroadcast?: Function;
|
replyBroadcast?: (item: TAnyMessageModel) => void;
|
||||||
reactionInit?: Function;
|
reactionInit?: (item: TAnyMessageModel) => void;
|
||||||
fetchThreadName?: Function;
|
fetchThreadName?: (tmid: string, id: string) => Promise<string | undefined>;
|
||||||
showAttachment?: Function;
|
showAttachment: (file: IAttachment) => void;
|
||||||
onReactionLongPress?: Function;
|
onReactionLongPress?: (item: TAnyMessageModel) => void;
|
||||||
navToRoomInfo?: Function;
|
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||||
callJitsi?: Function;
|
callJitsi?: () => void;
|
||||||
blockAction?: Function;
|
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||||
onAnswerButtonPress?: Function;
|
onAnswerButtonPress?: (message: string, tmid?: string, tshow?: boolean) => void;
|
||||||
theme?: string;
|
|
||||||
threadBadgeColor?: string;
|
threadBadgeColor?: string;
|
||||||
toggleFollowThread?: Function;
|
toggleFollowThread?: (isFollowingThread: boolean, tmid?: string) => Promise<void>;
|
||||||
jumpToMessage?: Function;
|
jumpToMessage?: (link: string) => void;
|
||||||
onPress?: Function;
|
onPress?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageContainer extends React.Component<IMessageContainerProps> {
|
interface IMessageContainerState {
|
||||||
|
isManualUnignored: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageContainer extends React.Component<IMessageContainerProps, IMessageContainerState> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
getCustomEmoji: () => null,
|
getCustomEmoji: () => null,
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
onReactionPress: () => {},
|
|
||||||
onEncryptedPress: () => {},
|
|
||||||
onDiscussionPress: () => {},
|
|
||||||
onThreadPress: () => {},
|
|
||||||
onAnswerButtonPress: () => {},
|
|
||||||
errorActionsShow: () => {},
|
|
||||||
replyBroadcast: () => {},
|
|
||||||
reactionInit: () => {},
|
|
||||||
fetchThreadName: () => {},
|
|
||||||
showAttachment: () => {},
|
|
||||||
onReactionLongPress: () => {},
|
|
||||||
navToRoomInfo: () => {},
|
|
||||||
callJitsi: () => {},
|
callJitsi: () => {},
|
||||||
blockAction: () => {},
|
blockAction: () => {},
|
||||||
archived: false,
|
archived: false,
|
||||||
|
@ -85,7 +78,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
|
|
||||||
state = { isManualUnignored: false };
|
state = { isManualUnignored: false };
|
||||||
|
|
||||||
private subscription: any;
|
private subscription?: Subscription;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
|
@ -97,12 +90,9 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: any, nextState: any) {
|
shouldComponentUpdate(nextProps: IMessageContainerProps, nextState: IMessageContainerState) {
|
||||||
const { isManualUnignored } = this.state;
|
const { isManualUnignored } = this.state;
|
||||||
const { theme, threadBadgeColor, isIgnored, highlighted } = this.props;
|
const { threadBadgeColor, isIgnored, highlighted } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextProps.highlighted !== highlighted) {
|
if (nextProps.highlighted !== highlighted) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -169,7 +159,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onReactionPress = (emoji: any) => {
|
onReactionPress = (emoji: string) => {
|
||||||
const { onReactionPress, item } = this.props;
|
const { onReactionPress, item } = this.props;
|
||||||
if (onReactionPress) {
|
if (onReactionPress) {
|
||||||
onReactionPress(emoji, item.id);
|
onReactionPress(emoji, item.id);
|
||||||
|
@ -228,7 +218,7 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
previousItem.u.username === item.u.username &&
|
previousItem.u.username === item.u.username &&
|
||||||
!(previousItem.groupable === false || item.groupable === false || broadcast === true) &&
|
!(previousItem.groupable === false || item.groupable === false || broadcast === true) &&
|
||||||
// @ts-ignore TODO: IMessage vs IMessageFromServer non-sense
|
// @ts-ignore TODO: IMessage vs IMessageFromServer non-sense
|
||||||
item.ts - previousItem.ts < Message_GroupingPeriod! * 1000 &&
|
item.ts - previousItem.ts < Message_GroupingPeriod * 1000 &&
|
||||||
previousItem.tmid === item.tmid
|
previousItem.tmid === item.tmid
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -303,10 +293,11 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onLinkPress = (link: string): void => {
|
onLinkPress = (link: string): void => {
|
||||||
const { item, theme, jumpToMessage } = this.props;
|
const { theme } = useTheme();
|
||||||
const isMessageLink = item?.attachments?.findIndex((att: any) => att?.message_link === link) !== -1;
|
const { item, jumpToMessage } = this.props;
|
||||||
if (isMessageLink) {
|
const isMessageLink = item?.attachments?.findIndex((att: IAttachment) => att?.message_link === link) !== -1;
|
||||||
return jumpToMessage!(link);
|
if (isMessageLink && jumpToMessage) {
|
||||||
|
return jumpToMessage(link);
|
||||||
}
|
}
|
||||||
openLink(link, theme);
|
openLink(link, theme);
|
||||||
};
|
};
|
||||||
|
@ -332,7 +323,6 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
callJitsi,
|
callJitsi,
|
||||||
blockAction,
|
blockAction,
|
||||||
rid,
|
rid,
|
||||||
theme,
|
|
||||||
threadBadgeColor,
|
threadBadgeColor,
|
||||||
toggleFollowThread,
|
toggleFollowThread,
|
||||||
jumpToMessage,
|
jumpToMessage,
|
||||||
|
@ -371,8 +361,8 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
let message = msg;
|
let message = msg;
|
||||||
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
|
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
|
||||||
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
|
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
|
||||||
if (autoTranslateRoom && autoTranslateMessage) {
|
if (autoTranslateRoom && autoTranslateMessage && autoTranslateLanguage) {
|
||||||
message = getMessageTranslation(item, autoTranslateLanguage!) || message;
|
message = getMessageTranslation(item, autoTranslateLanguage) || message;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -396,14 +386,15 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
toggleFollowThread,
|
toggleFollowThread,
|
||||||
replies
|
replies
|
||||||
}}>
|
}}>
|
||||||
|
{/* @ts-ignore*/}
|
||||||
<Message
|
<Message
|
||||||
id={id}
|
id={id}
|
||||||
msg={message}
|
msg={message}
|
||||||
md={md}
|
md={md}
|
||||||
rid={rid!}
|
rid={rid}
|
||||||
author={u}
|
author={u}
|
||||||
ts={ts}
|
ts={ts}
|
||||||
type={t as any}
|
type={t}
|
||||||
attachments={attachments}
|
attachments={attachments}
|
||||||
blocks={blocks}
|
blocks={blocks}
|
||||||
urls={urls}
|
urls={urls}
|
||||||
|
@ -413,23 +404,20 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
emoji={emoji}
|
emoji={emoji}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
style={style}
|
style={style}
|
||||||
archived={archived!}
|
archived={archived}
|
||||||
broadcast={broadcast!}
|
broadcast={broadcast}
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
isReadReceiptEnabled={isReadReceiptEnabled!}
|
isReadReceiptEnabled={isReadReceiptEnabled}
|
||||||
unread={unread}
|
unread={unread}
|
||||||
role={role}
|
role={role}
|
||||||
drid={drid}
|
drid={drid}
|
||||||
dcount={dcount}
|
dcount={dcount}
|
||||||
// @ts-ignore
|
|
||||||
dlm={dlm}
|
dlm={dlm}
|
||||||
tmid={tmid}
|
tmid={tmid}
|
||||||
tcount={tcount}
|
tcount={tcount}
|
||||||
// @ts-ignore
|
|
||||||
tlm={tlm}
|
tlm={tlm}
|
||||||
tmsg={tmsg}
|
tmsg={tmsg}
|
||||||
fetchThreadName={fetchThreadName!}
|
fetchThreadName={fetchThreadName}
|
||||||
// @ts-ignore
|
|
||||||
mentions={mentions}
|
mentions={mentions}
|
||||||
channels={channels}
|
channels={channels}
|
||||||
isIgnored={this.isIgnored}
|
isIgnored={this.isIgnored}
|
||||||
|
@ -442,13 +430,12 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
|
||||||
isTemp={this.isTemp}
|
isTemp={this.isTemp}
|
||||||
isEncrypted={this.isEncrypted}
|
isEncrypted={this.isEncrypted}
|
||||||
hasError={this.hasError}
|
hasError={this.hasError}
|
||||||
showAttachment={showAttachment!}
|
showAttachment={showAttachment}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
navToRoomInfo={navToRoomInfo!}
|
navToRoomInfo={navToRoomInfo}
|
||||||
callJitsi={callJitsi!}
|
callJitsi={callJitsi}
|
||||||
blockAction={blockAction!}
|
blockAction={blockAction}
|
||||||
theme={theme as string}
|
highlighted={highlighted}
|
||||||
highlighted={highlighted!}
|
|
||||||
/>
|
/>
|
||||||
</MessageContext.Provider>
|
</MessageContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,64 +1,45 @@
|
||||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
import { StyleProp, TextStyle } from 'react-native';
|
import { StyleProp, TextStyle } from 'react-native';
|
||||||
|
import { ImageStyle } from '@rocket.chat/react-native-fast-image';
|
||||||
|
|
||||||
import { IUserChannel, IUserMention } from '../markdown/interfaces';
|
import { IUserChannel } from '../markdown/interfaces';
|
||||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||||
import { IAttachment } from '../../definitions';
|
import { IAttachment, IThread, IUrl, IUserMention, IUserMessage, MessageType, TAnyMessageModel } from '../../definitions';
|
||||||
|
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||||
export type TMessageType = 'discussion-created' | 'jitsi_call_started';
|
|
||||||
|
|
||||||
export interface IMessageAttachments {
|
export interface IMessageAttachments {
|
||||||
attachments?: IAttachment[];
|
attachments?: IAttachment[];
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
style?: StyleProp<TextStyle>[];
|
style?: StyleProp<TextStyle>[];
|
||||||
isReply?: boolean;
|
isReply?: boolean;
|
||||||
showAttachment?: Function;
|
showAttachment?: (file: IAttachment) => void;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageAttachedActions {
|
|
||||||
attachment: IAttachment;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMessageAvatar {
|
export interface IMessageAvatar {
|
||||||
isHeader: boolean;
|
isHeader: boolean;
|
||||||
avatar: string;
|
avatar?: string;
|
||||||
emoji: string;
|
emoji?: string;
|
||||||
author: {
|
author?: IUserMessage;
|
||||||
username: string;
|
|
||||||
_id: string;
|
|
||||||
};
|
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
navToRoomInfo: Function;
|
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageBlocks {
|
export interface IMessageBlocks {
|
||||||
blocks: any;
|
blocks: { appId?: string }[];
|
||||||
id: string;
|
id: string;
|
||||||
rid: string;
|
rid: string;
|
||||||
blockAction: Function;
|
blockAction?: (params: { actionId: string; appId: string; value: string; blockId: string; rid: string; mid: string }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageBroadcast {
|
export interface IMessageBroadcast {
|
||||||
author: {
|
author?: IUserMessage;
|
||||||
_id: string;
|
broadcast?: boolean;
|
||||||
};
|
|
||||||
broadcast: boolean;
|
|
||||||
theme: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageCallButton {
|
export interface IMessageCallButton {
|
||||||
theme: string;
|
callJitsi?: () => void;
|
||||||
callJitsi: Function;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUser {
|
|
||||||
id: string;
|
|
||||||
username: string;
|
|
||||||
token: string;
|
|
||||||
name: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageContent {
|
export interface IMessageContent {
|
||||||
|
@ -69,40 +50,27 @@ export interface IMessageContent {
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
md?: MarkdownAST;
|
md?: MarkdownAST;
|
||||||
theme: string;
|
|
||||||
isEdited: boolean;
|
isEdited: boolean;
|
||||||
isEncrypted: boolean;
|
isEncrypted: boolean;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
channels?: IUserChannel[];
|
channels?: IUserChannel[];
|
||||||
mentions?: IUserMention[];
|
mentions?: IUserMention[];
|
||||||
navToRoomInfo?: Function;
|
navToRoomInfo: (navParam: IRoomInfoParam) => void;
|
||||||
useRealName?: boolean;
|
useRealName?: boolean;
|
||||||
isIgnored: boolean;
|
isIgnored: boolean;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageDiscussion {
|
|
||||||
msg?: string;
|
|
||||||
dcount?: number;
|
|
||||||
dlm?: Date;
|
|
||||||
theme: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMessageEmoji {
|
export interface IMessageEmoji {
|
||||||
content: any;
|
content: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
standardEmojiStyle: object;
|
standardEmojiStyle: { fontSize: number };
|
||||||
customEmojiStyle: object;
|
customEmojiStyle: StyleProp<ImageStyle>;
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageThread {
|
export interface IMessageThread extends Pick<IThread, 'msg' | 'tcount' | 'tlm' | 'id'> {
|
||||||
msg?: string;
|
|
||||||
tcount?: number | null;
|
|
||||||
theme: string;
|
|
||||||
tlm?: Date;
|
|
||||||
isThreadRoom: boolean;
|
isThreadRoom: boolean;
|
||||||
id: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageTouchable {
|
export interface IMessageTouchable {
|
||||||
|
@ -110,40 +78,35 @@ export interface IMessageTouchable {
|
||||||
isInfo: boolean;
|
isInfo: boolean;
|
||||||
isThreadReply: boolean;
|
isThreadReply: boolean;
|
||||||
isTemp: boolean;
|
isTemp: boolean;
|
||||||
archived: boolean;
|
archived?: boolean;
|
||||||
highlighted: boolean;
|
highlighted?: boolean;
|
||||||
theme: string;
|
ts?: string | Date;
|
||||||
ts?: any;
|
urls?: IUrl[];
|
||||||
urls?: any;
|
|
||||||
reactions?: any;
|
reactions?: any;
|
||||||
alias?: any;
|
alias?: string;
|
||||||
role?: any;
|
role?: string;
|
||||||
drid?: any;
|
drid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageRepliedThread {
|
export interface IMessageRepliedThread extends Pick<IThread, 'tmid' | 'tmsg' | 'id'> {
|
||||||
tmid?: string;
|
|
||||||
tmsg?: string;
|
|
||||||
id: string;
|
|
||||||
isHeader: boolean;
|
isHeader: boolean;
|
||||||
theme: string;
|
fetchThreadName?: (tmid: string, id: string) => Promise<string | undefined>;
|
||||||
fetchThreadName: Function;
|
|
||||||
isEncrypted: boolean;
|
isEncrypted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageInner
|
export interface IMessageInner
|
||||||
extends IMessageDiscussion,
|
extends IMessageContent,
|
||||||
IMessageContent,
|
|
||||||
IMessageCallButton,
|
IMessageCallButton,
|
||||||
IMessageBlocks,
|
IMessageBlocks,
|
||||||
IMessageThread,
|
IMessageThread,
|
||||||
IMessageAttachments,
|
IMessageAttachments,
|
||||||
IMessageBroadcast {
|
IMessageBroadcast {
|
||||||
type: TMessageType;
|
type: MessageType;
|
||||||
blocks: [];
|
blocks: [];
|
||||||
|
urls?: IUrl[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessage extends IMessageRepliedThread, IMessageInner {
|
export interface IMessage extends IMessageRepliedThread, IMessageInner, IMessageAvatar {
|
||||||
isThreadReply: boolean;
|
isThreadReply: boolean;
|
||||||
isThreadSequential: boolean;
|
isThreadSequential: boolean;
|
||||||
isInfo: boolean;
|
isInfo: boolean;
|
||||||
|
@ -151,9 +114,11 @@ export interface IMessage extends IMessageRepliedThread, IMessageInner {
|
||||||
isHeader: boolean;
|
isHeader: boolean;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
style: any;
|
style: any;
|
||||||
onLongPress: Function;
|
// style: ViewStyle;
|
||||||
isReadReceiptEnabled: boolean;
|
onLongPress?: (item: TAnyMessageModel) => void;
|
||||||
|
isReadReceiptEnabled?: boolean;
|
||||||
unread?: boolean;
|
unread?: boolean;
|
||||||
theme: string;
|
|
||||||
isIgnored: boolean;
|
isIgnored: boolean;
|
||||||
|
dcount: number | undefined;
|
||||||
|
dlm: string | Date | undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { IUser } from './IUser';
|
||||||
|
|
||||||
export interface IAttachment {
|
export interface IAttachment {
|
||||||
ts?: string | Date;
|
ts?: string | Date;
|
||||||
title: string;
|
title?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
title_link?: string;
|
title_link?: string;
|
||||||
|
@ -19,7 +19,7 @@ export interface IAttachment {
|
||||||
image_size?: number;
|
image_size?: number;
|
||||||
author_name?: string;
|
author_name?: string;
|
||||||
author_icon?: string;
|
author_icon?: string;
|
||||||
actions?: [];
|
actions?: { type: string; msg: string; text: string }[];
|
||||||
message_link?: string;
|
message_link?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
short?: boolean;
|
short?: boolean;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
// TODO: evaluate unification with IEmoji
|
||||||
export interface IEmoji {
|
export interface IEmoji {
|
||||||
content: any;
|
content?: string;
|
||||||
name: string;
|
name?: string;
|
||||||
extension: any;
|
extension?: string;
|
||||||
isCustom: boolean;
|
isCustom?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomEmoji {
|
export interface ICustomEmoji {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Model from '@nozbe/watermelondb/Model';
|
import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
|
// TODO: evaluate unification with IEmoji
|
||||||
export interface IFrequentlyUsedEmoji {
|
export interface IFrequentlyUsedEmoji {
|
||||||
content?: string;
|
content?: string;
|
||||||
extension?: string;
|
extension?: string;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { IAttachment } from './IAttachment';
|
||||||
import { IReaction } from './IReaction';
|
import { IReaction } from './IReaction';
|
||||||
import { TThreadMessageModel } from './IThreadMessage';
|
import { TThreadMessageModel } from './IThreadMessage';
|
||||||
import { TThreadModel } from './IThread';
|
import { TThreadModel } from './IThread';
|
||||||
import { IUrlFromServer } from './IUrl';
|
import { IUrl, IUrlFromServer } from './IUrl';
|
||||||
|
|
||||||
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj' | MessageTypeLoad;
|
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj' | MessageTypeLoad;
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ export interface IMessageFromServer {
|
||||||
ts: string | Date; // wm date issue
|
ts: string | Date; // wm date issue
|
||||||
u: IUserMessage;
|
u: IUserMessage;
|
||||||
_updatedAt: string | Date;
|
_updatedAt: string | Date;
|
||||||
urls?: IUrlFromServer[];
|
urls?: IUrl[];
|
||||||
mentions?: IUserMention[];
|
mentions?: IUserMention[];
|
||||||
channels?: IUserChannel[];
|
channels?: IUserChannel[];
|
||||||
md?: MarkdownAST;
|
md?: MarkdownAST;
|
||||||
|
@ -111,7 +111,7 @@ export interface ILoadMoreMessage {
|
||||||
|
|
||||||
export interface IMessage extends IMessageFromServer {
|
export interface IMessage extends IMessageFromServer {
|
||||||
id: string;
|
id: string;
|
||||||
t?: MessageType;
|
t: MessageType;
|
||||||
alias?: string;
|
alias?: string;
|
||||||
parseUrls?: boolean;
|
parseUrls?: boolean;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
|
|
@ -106,6 +106,7 @@ export interface ISubscription {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TSubscriptionModel = ISubscription & Model;
|
export type TSubscriptionModel = ISubscription & Model;
|
||||||
|
export type TSubscription = TSubscriptionModel | ISubscription;
|
||||||
|
|
||||||
// https://github.com/RocketChat/Rocket.Chat/blob/a88a96fcadd925b678ff27ada37075e029f78b5e/definition/ISubscription.ts#L8
|
// https://github.com/RocketChat/Rocket.Chat/blob/a88a96fcadd925b678ff27ada37075e029f78b5e/definition/ISubscription.ts#L8
|
||||||
export interface IServerSubscription extends IRocketChatRecord {
|
export interface IServerSubscription extends IRocketChatRecord {
|
||||||
|
|
|
@ -45,5 +45,4 @@ export interface IUrl extends IUrlFromServer {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
image: string;
|
image: string;
|
||||||
url: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ 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';
|
||||||
|
|
||||||
export * from './IAttachment';
|
export * from './IAttachment';
|
||||||
export * from './INotification';
|
export * from './INotification';
|
||||||
export * from './IPreferences';
|
export * from './IPreferences';
|
||||||
|
@ -32,8 +34,10 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
|
||||||
navigation: StackNavigationProp<T, S>;
|
navigation: StackNavigationProp<T, S>;
|
||||||
route: RouteProp<T, S>;
|
route: RouteProp<T, S>;
|
||||||
dispatch: Dispatch;
|
dispatch: Dispatch;
|
||||||
theme: string;
|
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
|
// TODO: remove after migrating all Class components
|
||||||
|
theme: string;
|
||||||
|
colors: TColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './redux';
|
export * from './redux';
|
||||||
|
|
|
@ -49,6 +49,9 @@ export type UsersEndpoints = {
|
||||||
'users.resetAvatar': {
|
'users.resetAvatar': {
|
||||||
POST: (params: { userId: string }) => {};
|
POST: (params: { userId: string }) => {};
|
||||||
};
|
};
|
||||||
|
'users.removeOtherTokens': {
|
||||||
|
POST: (params: { userId: string }) => {};
|
||||||
|
};
|
||||||
'users.getPreferences': {
|
'users.getPreferences': {
|
||||||
GET: (params: { userId: IUser['_id'] }) => {
|
GET: (params: { userId: IUser['_id'] }) => {
|
||||||
preferences: INotificationPreferences;
|
preferences: INotificationPreferences;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { TNavigationOptions } from './definitions/navigationTypes';
|
||||||
export interface IDimensionsContextProps {
|
export interface IDimensionsContextProps {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
scale?: number;
|
scale: number;
|
||||||
fontScale: number;
|
fontScale: number;
|
||||||
setDimensions?: ({
|
setDimensions?: ({
|
||||||
width,
|
width,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import UnreadBadge from '../../../presentation/UnreadBadge';
|
||||||
import RocketChat from '../../../lib/rocketchat';
|
import RocketChat from '../../../lib/rocketchat';
|
||||||
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib';
|
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib';
|
||||||
import { IUser } from '../../../definitions/IUser';
|
import { IUser } from '../../../definitions/IUser';
|
||||||
|
import Touch from '../../../utils/touch';
|
||||||
|
|
||||||
interface IOmnichannelStatus {
|
interface IOmnichannelStatus {
|
||||||
searching: boolean;
|
searching: boolean;
|
||||||
|
@ -48,7 +49,9 @@ const OmnichannelStatus = memo(({ searching, goQueue, queueSize, inquiryEnabled,
|
||||||
right={() => (
|
right={() => (
|
||||||
<View style={styles.omnichannelRightContainer}>
|
<View style={styles.omnichannelRightContainer}>
|
||||||
{inquiryEnabled ? <UnreadBadge style={styles.queueIcon} unread={queueSize} /> : null}
|
{inquiryEnabled ? <UnreadBadge style={styles.queueIcon} unread={queueSize} /> : null}
|
||||||
|
<Touch theme={theme} onPress={toggleLivechat}>
|
||||||
<Switch value={status} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleLivechat} />
|
<Switch value={status} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleLivechat} />
|
||||||
|
</Touch>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,7 +3,6 @@ declare module 'commonmark';
|
||||||
declare module 'commonmark-react-renderer';
|
declare module 'commonmark-react-renderer';
|
||||||
declare module 'remove-markdown';
|
declare module 'remove-markdown';
|
||||||
declare module 'react-native-image-progress';
|
declare module 'react-native-image-progress';
|
||||||
declare module 'react-native-platform-touchable';
|
|
||||||
declare module 'react-native-ui-lib/keyboard';
|
declare module 'react-native-ui-lib/keyboard';
|
||||||
declare module '@rocket.chat/ui-kit';
|
declare module '@rocket.chat/ui-kit';
|
||||||
declare module '@rocket.chat/sdk';
|
declare module '@rocket.chat/sdk';
|
||||||
|
|
|
@ -14,7 +14,7 @@ import parseQuery from './lib/methods/helpers/parseQuery';
|
||||||
import { initializePushNotifications, onNotification } from './notifications/push';
|
import { initializePushNotifications, onNotification } from './notifications/push';
|
||||||
import store from './lib/createStore';
|
import store from './lib/createStore';
|
||||||
import { toggleAnalyticsEventsReport, toggleCrashErrorsReport } from './utils/log';
|
import { toggleAnalyticsEventsReport, toggleCrashErrorsReport } from './utils/log';
|
||||||
import { ThemeContext } from './theme';
|
import { ThemeContext, TSupportedThemes } from './theme';
|
||||||
import { DimensionsContext } from './dimensions';
|
import { DimensionsContext } from './dimensions';
|
||||||
import RocketChat from './lib/rocketchat';
|
import RocketChat from './lib/rocketchat';
|
||||||
import { MIN_WIDTH_MASTER_DETAIL_LAYOUT } from './constants/tablet';
|
import { MIN_WIDTH_MASTER_DETAIL_LAYOUT } from './constants/tablet';
|
||||||
|
@ -32,7 +32,7 @@ import { isFDroidBuild } from './constants/environment';
|
||||||
import { IThemePreference } from './definitions/ITheme';
|
import { IThemePreference } from './definitions/ITheme';
|
||||||
import { ICommand } from './definitions/ICommand';
|
import { ICommand } from './definitions/ICommand';
|
||||||
import { initStore } from './lib/auxStore';
|
import { initStore } from './lib/auxStore';
|
||||||
import { themes } from './constants/colors';
|
import { colors, themes } from './constants/colors';
|
||||||
|
|
||||||
RNScreens.enableScreens();
|
RNScreens.enableScreens();
|
||||||
initStore(store);
|
initStore(store);
|
||||||
|
@ -45,7 +45,7 @@ interface IDimensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
theme: string;
|
theme: TSupportedThemes;
|
||||||
themePreferences: IThemePreference;
|
themePreferences: IThemePreference;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -215,7 +215,8 @@ export default class Root extends React.Component<{}, IState> {
|
||||||
value={{
|
value={{
|
||||||
theme,
|
theme,
|
||||||
themePreferences,
|
themePreferences,
|
||||||
setTheme: this.setTheme
|
setTheme: this.setTheme,
|
||||||
|
colors: colors[theme]
|
||||||
}}>
|
}}>
|
||||||
<DimensionsContext.Provider
|
<DimensionsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
|
@ -14,3 +14,8 @@ export const E2E_ROOM_TYPES: Record<string, string> = {
|
||||||
d: 'd',
|
d: 'd',
|
||||||
p: 'p'
|
p: 'p'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
||||||
|
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||||
|
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
|
||||||
|
export const MIN_ROCKETCHAT_VERSION = '0.70.0';
|
|
@ -17,7 +17,7 @@ import {
|
||||||
E2E_PUBLIC_KEY,
|
E2E_PUBLIC_KEY,
|
||||||
E2E_RANDOM_PASSWORD_KEY,
|
E2E_RANDOM_PASSWORD_KEY,
|
||||||
E2E_STATUS
|
E2E_STATUS
|
||||||
} from './constants';
|
} from '../constants';
|
||||||
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
|
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
|
||||||
import { EncryptionRoom } from './index';
|
import { EncryptionRoom } from './index';
|
||||||
import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
|
import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Deferred from '../../utils/deferred';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import database from '../database';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { E2E_MESSAGE_TYPE, E2E_STATUS } from './constants';
|
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../constants';
|
||||||
import {
|
import {
|
||||||
b64ToBuffer,
|
b64ToBuffer,
|
||||||
bufferToB64,
|
bufferToB64,
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
import random from '../../utils/random';
|
import { ITriggerAction, IUserInteraction, ModalActions } from '../../containers/UIKit/interfaces';
|
||||||
|
import { TRocketChat } from '../../definitions/IRocketChat';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import fetch from '../../utils/fetch';
|
import fetch from '../../utils/fetch';
|
||||||
|
import random from '../../utils/random';
|
||||||
import Navigation from '../Navigation';
|
import Navigation from '../Navigation';
|
||||||
import sdk from '../rocketchat/services/sdk';
|
import sdk from '../rocketchat/services/sdk';
|
||||||
import {
|
|
||||||
ActionTypes,
|
|
||||||
ITriggerAction,
|
|
||||||
ITriggerBlockAction,
|
|
||||||
ITriggerCancel,
|
|
||||||
ITriggerSubmitView,
|
|
||||||
IUserInteraction,
|
|
||||||
ModalActions
|
|
||||||
} from '../../containers/UIKit/interfaces';
|
|
||||||
import { TRocketChat } from '../../definitions/IRocketChat';
|
|
||||||
|
|
||||||
const triggersId = new Map();
|
const triggersId = new Map();
|
||||||
|
|
||||||
|
@ -139,18 +131,3 @@ export function triggerAction(
|
||||||
return reject();
|
return reject();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function triggerBlockAction(this: TRocketChat, options: ITriggerBlockAction) {
|
|
||||||
return triggerAction.call(this, { type: ActionTypes.ACTION, ...options });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function triggerSubmitView(this: TRocketChat, { viewId, ...options }: ITriggerSubmitView) {
|
|
||||||
const result = await triggerAction.call(this, { type: ActionTypes.SUBMIT, viewId, ...options });
|
|
||||||
if (!result || ModalActions.CLOSE === result) {
|
|
||||||
Navigation.back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function triggerCancel(this: TRocketChat, { view, ...options }: ITriggerCancel) {
|
|
||||||
return triggerAction.call(this, { type: ActionTypes.CLOSED, view, ...options });
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import database from '../../database';
|
import database from '../database';
|
||||||
|
|
||||||
export default async function clearCache({ server }: { server: string }): Promise<void> {
|
export default async function clearCache({ server }: { server: string }): Promise<void> {
|
||||||
try {
|
try {
|
|
@ -1,5 +1,5 @@
|
||||||
import { TSubscriptionModel } from '../../../definitions';
|
import { TSubscriptionModel } from '../../definitions';
|
||||||
import database from '../../database';
|
import database from '../database';
|
||||||
|
|
||||||
export default async function getRoom(rid: string): Promise<TSubscriptionModel> {
|
export default async function getRoom(rid: string): Promise<TSubscriptionModel> {
|
||||||
try {
|
try {
|
|
@ -4,7 +4,7 @@ import { MessageTypeLoad } from '../../constants/messageTypeLoad';
|
||||||
import { IMessage, TMessageModel } from '../../definitions';
|
import { IMessage, TMessageModel } from '../../definitions';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { getMessageById } from '../database/services/Message';
|
import { getMessageById } from '../database/services/Message';
|
||||||
import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType';
|
import roomTypeToApiType, { RoomTypes } from './roomTypeToApiType';
|
||||||
import sdk from '../rocketchat/services/sdk';
|
import sdk from '../rocketchat/services/sdk';
|
||||||
import { generateLoadMoreId } from '../utils';
|
import { generateLoadMoreId } from '../utils';
|
||||||
import updateMessages from './updateMessages';
|
import updateMessages from './updateMessages';
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { MessageTypeLoad } from '../../constants/messageTypeLoad';
|
||||||
import { generateLoadMoreId } from '../utils';
|
import { generateLoadMoreId } from '../utils';
|
||||||
import updateMessages from './updateMessages';
|
import updateMessages from './updateMessages';
|
||||||
import { TMessageModel } from '../../definitions';
|
import { TMessageModel } from '../../definitions';
|
||||||
import RocketChat from '../rocketchat';
|
import sdk from '../rocketchat/services/sdk';
|
||||||
|
|
||||||
const COUNT = 50;
|
const COUNT = 50;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ interface ILoadNextMessages {
|
||||||
export default function loadNextMessages(args: ILoadNextMessages): Promise<void> {
|
export default function loadNextMessages(args: ILoadNextMessages): Promise<void> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const data = await RocketChat.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
|
const data = await sdk.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
|
||||||
let messages = EJSON.fromJSONValue(data?.messages);
|
let messages = EJSON.fromJSONValue(data?.messages);
|
||||||
messages = orderBy(messages, 'ts');
|
messages = orderBy(messages, 'ts');
|
||||||
if (messages?.length) {
|
if (messages?.length) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue