[NEW] Redesign quoted messages (#3883)

This commit is contained in:
Gerzon Z 2022-03-21 16:44:06 -04:00 committed by GitHub
parent 82acb917fc
commit b0580bf547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 246 additions and 143 deletions

View File

@ -37,6 +37,7 @@ export const themes: any = {
infoText: '#6d6d72', infoText: '#6d6d72',
tintColor: '#1d74f5', tintColor: '#1d74f5',
tintActive: '#549df9', tintActive: '#549df9',
tintDisabled: '#88B4F5',
auxiliaryTintColor: '#6C727A', auxiliaryTintColor: '#6C727A',
actionTintColor: '#1d74f5', actionTintColor: '#1d74f5',
separatorColor: '#cbcbcc', separatorColor: '#cbcbcc',
@ -87,6 +88,7 @@ export const themes: any = {
infoText: '#6D6D72', infoText: '#6D6D72',
tintColor: '#1d74f5', tintColor: '#1d74f5',
tintActive: '#549df9', tintActive: '#549df9',
tintDisabled: '#88B4F5',
auxiliaryTintColor: '#f9f9f9', auxiliaryTintColor: '#f9f9f9',
actionTintColor: '#1d74f5', actionTintColor: '#1d74f5',
separatorColor: '#2b2b2d', separatorColor: '#2b2b2d',
@ -137,6 +139,7 @@ export const themes: any = {
infoText: '#6d6d72', infoText: '#6d6d72',
tintColor: '#1e9bfe', tintColor: '#1e9bfe',
tintActive: '#76b7fc', tintActive: '#76b7fc',
tintDisabled: '#88B4F5', // TODO: Evaluate this with design team
auxiliaryTintColor: '#f9f9f9', auxiliaryTintColor: '#f9f9f9',
actionTintColor: '#1e9bfe', actionTintColor: '#1e9bfe',
separatorColor: '#272728', separatorColor: '#272728',

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { ScrollView, Text, TouchableOpacity, View, ViewStyle } from 'react-native';
import { CELL_WIDTH } from './TableCell'; import { CELL_WIDTH } from './TableCell';
import styles from './styles'; import styles from './styles';
@ -19,7 +19,7 @@ const Table = React.memo(({ children, numColumns, theme }: ITable) => {
const getTableWidth = () => numColumns * CELL_WIDTH; const getTableWidth = () => numColumns * CELL_WIDTH;
const renderRows = (drawExtraBorders = true) => { const renderRows = (drawExtraBorders = true) => {
const tableStyle = [styles.table, { borderColor: themes[theme].borderColor }]; const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }];
if (drawExtraBorders) { if (drawExtraBorders) {
tableStyle.push(styles.tableExtraBorders); tableStyle.push(styles.tableExtraBorders);
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Text, View } from 'react-native'; import { Text, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
@ -14,7 +14,7 @@ interface ITableCell {
export const CELL_WIDTH = 100; export const CELL_WIDTH = 100;
const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell) => { const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell) => {
const cellStyle = [styles.cell, { borderColor: themes[theme].borderColor }]; const cellStyle: ViewStyle[] = [styles.cell, { borderColor: themes[theme].borderColor }];
if (!isLastCell) { if (!isLastCell) {
cellStyle.push(styles.cellRightBorder); cellStyle.push(styles.cellRightBorder);
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { View } from 'react-native'; import { View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import styles from './styles'; import styles from './styles';
@ -11,7 +11,7 @@ interface ITableRow {
} }
const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => { const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => {
const rowStyle = [styles.row, { borderColor: themes[theme].borderColor }]; const rowStyle: ViewStyle[] = [styles.row, { borderColor: themes[theme].borderColor }];
if (!isLastRow) { if (!isLastRow) {
rowStyle.push(styles.rowBottomBorder); rowStyle.push(styles.rowBottomBorder);
} }

View File

@ -280,6 +280,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
renderHeading = ({ children, level }: any) => { renderHeading = ({ children, level }: any) => {
const { numberOfLines, theme } = this.props; const { numberOfLines, theme } = this.props;
// @ts-ignore
const textStyle = styles[`heading${level}Text`]; const textStyle = styles[`heading${level}Text`];
return ( return (
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}> <Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}>

View File

@ -7,7 +7,7 @@ const codeFontFamily = Platform.select({
android: { fontFamily: 'monospace' } android: { fontFamily: 'monospace' }
}); });
export default StyleSheet.create<any>({ export default StyleSheet.create({
container: { container: {
alignItems: 'flex-start', alignItems: 'flex-start',
flexDirection: 'row' flexDirection: 'row'

View File

@ -10,10 +10,16 @@ import Reply from './Reply';
import Button from '../Button'; import Button from '../Button';
import styles from './styles'; import styles from './styles';
import MessageContext from './Context'; import MessageContext from './Context';
import { useTheme } from '../../theme';
import { IAttachment } from '../../definitions';
import CollapsibleQuote from './Components/CollapsibleQuote'; import CollapsibleQuote from './Components/CollapsibleQuote';
const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => { const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
if (!attachment.actions) {
return null;
}
const { onAnswerButtonPress } = useContext(MessageContext); const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme();
const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => { const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => {
if (element.type === 'button') { if (element.type === 'button') {
@ -30,46 +36,62 @@ const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => {
}; };
const Attachments = React.memo( const Attachments = React.memo(
({ attachments, timeFormat, showAttachment, getCustomEmoji, theme }: IMessageAttachments) => { // @ts-ignore
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
if (!attachments || attachments.length === 0) { if (!attachments || attachments.length === 0) {
return null; return null;
} }
return attachments.map((file: any, index: number) => { const { theme } = useTheme();
if (file.image_url) {
return attachments.map((file: IAttachment, index: number) => {
if (file && file.image_url) {
return ( return (
<Image key={file.image_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} /> <Image
key={file.image_url}
file={file}
showAttachment={showAttachment}
getCustomEmoji={getCustomEmoji}
style={style}
isReply={isReply}
theme={theme}
/>
); );
} }
if (file.audio_url) {
return <Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} theme={theme} />; if (file && file.audio_url) {
return (
<Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} isReply={isReply} style={style} theme={theme} />
);
} }
if (file.video_url) { if (file.video_url) {
return ( return (
<Video key={file.video_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} /> <Video
key={file.video_url}
file={file}
showAttachment={showAttachment}
getCustomEmoji={getCustomEmoji}
style={style}
isReply={isReply}
theme={theme}
/>
); );
} }
if (file.actions && file.actions.length > 0) {
return <AttachedActions attachment={file} theme={theme} />; if (file && file.actions && file.actions.length > 0) {
return <AttachedActions attachment={file} />;
} }
if (file.title) if (file.title) {
return ( return (
<CollapsibleQuote key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} /> <CollapsibleQuote key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />
); );
}
return ( return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
<Reply
key={index}
index={index}
attachment={file}
timeFormat={timeFormat}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
);
}); });
}, },
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme (prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments)
); );
Attachments.displayName = 'MessageAttachments'; Attachments.displayName = 'MessageAttachments';

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Easing, StyleSheet, Text, View } from 'react-native'; import { Easing, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
import { Audio } from 'expo-av'; import { Audio } 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';
@ -16,19 +16,20 @@ import MessageContext from './Context';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
interface IButton { interface IButton {
loading: boolean; loading: boolean;
paused: boolean; paused: boolean;
theme: string; theme: string;
disabled?: boolean;
onPress: Function; onPress: Function;
} }
interface IMessageAudioProps { interface IMessageAudioProps {
file: { file: IAttachment;
audio_url: string; isReply?: boolean;
description: string; style?: StyleProp<TextStyle>[];
};
theme: string; theme: string;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
scale?: number; scale?: number;
@ -89,16 +90,21 @@ const sliderAnimationConfig = {
delay: 0 delay: 0
}; };
const Button = React.memo(({ loading, paused, onPress, theme }: IButton) => ( const Button = React.memo(({ loading, paused, onPress, disabled, theme }: IButton) => (
<Touchable <Touchable
style={styles.playPauseButton} style={styles.playPauseButton}
disabled={disabled}
onPress={onPress} onPress={onPress}
hitSlop={BUTTON_HIT_SLOP} hitSlop={BUTTON_HIT_SLOP}
background={Touchable.SelectableBackgroundBorderless()}> background={Touchable.SelectableBackgroundBorderless()}>
{loading ? ( {loading ? (
<ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} /> <ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} />
) : ( ) : (
<CustomIcon name={paused ? 'play-filled' : 'pause-filled'} size={36} color={themes[theme].tintColor} /> <CustomIcon
name={paused ? 'play-filled' : 'pause-filled'}
size={36}
color={disabled ? themes[theme].tintDisabled : themes[theme].tintColor}
/>
)} )}
</Touchable> </Touchable>
)); ));
@ -128,7 +134,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
const { baseUrl, user } = this.context; const { baseUrl, user } = this.context;
let url = file.audio_url; let url = file.audio_url;
if (!url.startsWith('http')) { if (url && !url.startsWith('http')) {
url = `${baseUrl}${file.audio_url}`; url = `${baseUrl}${file.audio_url}`;
} }
@ -249,7 +255,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
render() { render() {
const { loading, paused, currentTime, duration } = this.state; const { loading, paused, currentTime, duration } = this.state;
const { file, getCustomEmoji, theme, scale } = this.props; const { file, getCustomEmoji, theme, scale, isReply, style } = this.props;
const { description } = file; const { description } = file;
const { baseUrl, user } = this.context; const { baseUrl, user } = this.context;
@ -259,29 +265,37 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
return ( return (
<> <>
<Markdown
msg={description}
style={[isReply && style]}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<View <View
style={[ style={[
styles.audioContainer, styles.audioContainer,
{ backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor } { backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor }
]}> ]}>
<Button loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} /> <Button disabled={isReply} loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} />
<Slider <Slider
disabled={isReply}
style={styles.slider} style={styles.slider}
value={currentTime} value={currentTime}
maximumValue={duration} maximumValue={duration}
minimumValue={0} minimumValue={0}
// @ts-ignore
animateTransitions animateTransitions
animationConfig={sliderAnimationConfig} animationConfig={sliderAnimationConfig}
thumbTintColor={isAndroid && themes[theme].tintColor} thumbTintColor={isReply ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
minimumTrackTintColor={themes[theme].tintColor} minimumTrackTintColor={themes[theme].tintColor}
maximumTrackTintColor={themes[theme].auxiliaryText} maximumTrackTintColor={themes[theme].auxiliaryText}
onValueChange={this.onValueChange} onValueChange={this.onValueChange}
/* @ts-ignore*/ thumbImage={isIOS ? { uri: 'audio_thumb', scale } : undefined}
thumbImage={isIOS && { uri: 'audio_thumb', scale }}
/> />
<Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text> <Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text>
</View> </View>
<Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
</> </>
); );
} }

View File

@ -1,5 +1,5 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { View } from 'react-native'; import { StyleProp, TextStyle, View } from 'react-native';
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';
import { createImageProgress } from 'react-native-image-progress'; import { createImageProgress } from 'react-native-image-progress';
@ -12,9 +12,11 @@ import { formatAttachmentUrl } from '../../lib/utils';
import { themes } from '../../constants/colors'; 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';
type TMessageButton = { type TMessageButton = {
children: JSX.Element; children: JSX.Element;
disabled?: boolean;
onPress: Function; onPress: Function;
theme: string; theme: string;
}; };
@ -25,17 +27,23 @@ type TMessageImage = {
}; };
interface IMessageImage { interface IMessageImage {
file: { image_url: string; description?: string }; file: IAttachment;
imageUrl?: string; imageUrl?: string;
showAttachment: Function; showAttachment?: Function;
style?: StyleProp<TextStyle>[];
isReply?: boolean;
theme: string; theme: string;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
} }
const ImageProgress = createImageProgress(FastImage); const ImageProgress = createImageProgress(FastImage);
const Button = React.memo(({ children, onPress, theme }: TMessageButton) => ( const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButton) => (
<Touchable onPress={onPress} style={styles.imageContainer} background={Touchable.Ripple(themes[theme].bannerBackground)}> <Touchable
disabled={disabled}
onPress={onPress}
style={styles.imageContainer}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
{children} {children}
</Touchable> </Touchable>
)); ));
@ -53,34 +61,41 @@ export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
)); ));
const ImageContainer = React.memo( const ImageContainer = React.memo(
({ file, imageUrl, showAttachment, getCustomEmoji, theme }: IMessageImage) => { ({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageImage) => {
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;
} }
const onPress = () => showAttachment(file); const onPress = () => {
if (!showAttachment) {
return;
}
return showAttachment(file);
};
if (file.description) { if (file.description) {
return ( return (
<Button theme={theme} onPress={onPress}> <Button disabled={isReply} theme={theme} onPress={onPress}>
<View> <View>
<MessageImage img={img} theme={theme} />
<Markdown <Markdown
msg={file.description} msg={file.description}
style={[isReply && style]}
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
theme={theme} theme={theme}
/> />
<MessageImage img={img} theme={theme} />
</View> </View>
</Button> </Button>
); );
} }
return ( return (
<Button theme={theme} onPress={onPress}> <Button disabled={isReply} theme={theme} onPress={onPress}>
<MessageImage img={img} theme={theme} /> <MessageImage img={img} theme={theme} />
</Button> </Button>
); );

View File

@ -21,6 +21,9 @@ import { themes } from '../../constants/colors';
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces'; import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
const MessageInner = React.memo((props: IMessageInner) => { const MessageInner = React.memo((props: IMessageInner) => {
const { attachments } = props;
const isCollapsible = attachments ? attachments[0] && attachments[0].collapsed : false;
if (props.type === 'discussion-created') { if (props.type === 'discussion-created') {
return ( return (
<> <>
@ -29,6 +32,7 @@ const MessageInner = React.memo((props: IMessageInner) => {
</> </>
); );
} }
if (props.type === 'jitsi_call_started') { if (props.type === 'jitsi_call_started') {
return ( return (
<> <>
@ -38,6 +42,7 @@ const MessageInner = React.memo((props: IMessageInner) => {
</> </>
); );
} }
if (props.blocks && props.blocks.length) { if (props.blocks && props.blocks.length) {
return ( return (
<> <>
@ -48,11 +53,22 @@ const MessageInner = React.memo((props: IMessageInner) => {
</> </>
); );
} }
return ( return (
<> <>
<User {...props} /> <User {...props} />
<Content {...props} /> {isCollapsible ? (
<Attachments {...props} /> <>
<Content {...props} />
<Attachments {...props} />
</>
) : (
<>
<Attachments {...props} />
<Content {...props} />
</>
)}
<Urls {...props} /> <Urls {...props} />
<Thread {...props} /> <Thread {...props} />
<Reactions {...props} /> <Reactions {...props} />

View File

@ -1,7 +1,6 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import moment from 'moment'; import moment from 'moment';
import { transparentize } from 'color2k';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import FastImage from '@rocket.chat/react-native-fast-image'; import FastImage from '@rocket.chat/react-native-fast-image';
@ -16,22 +15,24 @@ import { formatAttachmentUrl } from '../../lib/utils';
import { IAttachment } from '../../definitions/IAttachment'; import { IAttachment } from '../../definitions/IAttachment';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions/IEmoji';
import RCActivityIndicator from '../ActivityIndicator'; import RCActivityIndicator from '../ActivityIndicator';
import Attachments from './Attachments';
import { useTheme } from '../../theme';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginTop: 6, marginVertical: 4,
alignSelf: 'flex-start', alignSelf: 'flex-start',
borderWidth: 1, borderLeftWidth: 2
borderRadius: 4
}, },
attachmentContainer: { attachmentContainer: {
flex: 1, flex: 1,
borderRadius: 4, borderRadius: 4,
flexDirection: 'column', flexDirection: 'column',
padding: 15 paddingVertical: 4,
paddingLeft: 8
}, },
backdrop: { backdrop: {
...StyleSheet.absoluteFillObject ...StyleSheet.absoluteFillObject
@ -39,7 +40,8 @@ const styles = StyleSheet.create({
authorContainer: { authorContainer: {
flex: 1, flex: 1,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center' alignItems: 'center',
marginBottom: 8
}, },
author: { author: {
flex: 1, flex: 1,
@ -48,9 +50,8 @@ const styles = StyleSheet.create({
}, },
time: { time: {
fontSize: 12, fontSize: 12,
marginLeft: 10, marginLeft: 8,
...sharedStyles.textRegular, ...sharedStyles.textRegular
fontWeight: '300'
}, },
fieldsContainer: { fieldsContainer: {
flex: 1, flex: 1,
@ -114,7 +115,6 @@ interface IMessageReply {
attachment: IAttachment; attachment: IAttachment;
timeFormat?: string; timeFormat?: string;
index: number; index: number;
theme: string;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
} }
@ -123,10 +123,10 @@ const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
return ( return (
<View style={styles.authorContainer}> <View style={styles.authorContainer}>
{attachment.author_name ? ( {attachment.author_name ? (
<Text style={[styles.author, { color: themes[theme].bodyText }]}>{attachment.author_name}</Text> <Text style={[styles.author, { color: themes[theme].auxiliaryTintColor }]}>{attachment.author_name}</Text>
) : null} ) : null}
{attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null} {attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null}
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> : null} {time ? <Text style={[styles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text> : null}
</View> </View>
); );
}); });
@ -138,7 +138,16 @@ const Description = React.memo(
return null; return null;
} }
const { baseUrl, user } = useContext(MessageContext); const { baseUrl, user } = useContext(MessageContext);
return <Markdown msg={text} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />; return (
<Markdown
msg={text}
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14 }]}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
);
}, },
(prevProps, nextProps) => { (prevProps, nextProps) => {
if (prevProps.attachment.text !== nextProps.attachment.text) { if (prevProps.attachment.text !== nextProps.attachment.text) {
@ -195,12 +204,14 @@ const Fields = React.memo(
); );
const Reply = React.memo( const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => { ({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
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 () => {
@ -221,14 +232,9 @@ const Reply = React.memo(
openLink(url, theme); openLink(url, theme);
}; };
let { borderColor, chatComponentBackground: backgroundColor } = themes[theme]; let { borderColor } = themes[theme];
try { if (attachment.color) {
if (attachment.color) { borderColor = attachment.color;
backgroundColor = transparentize(attachment.color, 0.8);
borderColor = attachment.color;
}
} catch (e) {
// fallback to default
} }
return ( return (
@ -240,7 +246,6 @@ const Reply = React.memo(
index > 0 && styles.marginTop, index > 0 && styles.marginTop,
attachment.description && styles.marginBottom, attachment.description && styles.marginBottom,
{ {
backgroundColor,
borderColor borderColor
} }
]} ]}
@ -248,6 +253,13 @@ const Reply = React.memo(
disabled={loading}> disabled={loading}>
<View style={styles.attachmentContainer}> <View style={styles.attachmentContainer}>
<Title attachment={attachment} timeFormat={timeFormat} theme={theme} /> <Title attachment={attachment} timeFormat={timeFormat} theme={theme} />
<Attachments
attachments={attachment.attachments}
getCustomEmoji={getCustomEmoji}
timeFormat={timeFormat}
style={[{ color: themes[theme].auxiliaryTintColor, fontSize: 14, marginBottom: 8 }]}
isReply
/>
<UrlImage image={attachment.thumb_url} /> <UrlImage image={attachment.thumb_url} />
<Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> <Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
<Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> <Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} />
@ -273,7 +285,7 @@ const Reply = React.memo(
</> </>
); );
}, },
(prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme (prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment)
); );
Reply.displayName = 'MessageReply'; Reply.displayName = 'MessageReply';

View File

@ -97,7 +97,7 @@ const User = React.memo(
{textContent} {textContent}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <Text style={[messageStyles.time, { color: themes[theme].auxiliaryTintColor }]}>{time}</Text>
{hasError && <MessageError hasError={hasError} theme={theme} {...props} />} {hasError && <MessageError hasError={hasError} theme={theme} {...props} />}
</View> </View>
); );

View File

@ -1,5 +1,5 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { StyleSheet } from 'react-native'; import { StyleProp, StyleSheet, TextStyle } from 'react-native';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import Touchable from './Touchable'; import Touchable from './Touchable';
@ -33,13 +33,15 @@ const styles = StyleSheet.create({
interface IMessageVideo { interface IMessageVideo {
file: IAttachment; file: IAttachment;
showAttachment: Function; showAttachment?: Function;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
style?: StyleProp<TextStyle>[];
isReply?: boolean;
theme: string; theme: string;
} }
const Video = React.memo( const Video = React.memo(
({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => { ({ file, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageVideo) => {
const { baseUrl, user } = useContext(MessageContext); const { baseUrl, user } = useContext(MessageContext);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -47,7 +49,7 @@ const Video = React.memo(
return null; return null;
} }
const onPress = async () => { const onPress = async () => {
if (isTypeSupported(file.video_type)) { if (isTypeSupported(file.video_type) && showAttachment) {
return showAttachment(file); return showAttachment(file);
} }
@ -73,19 +75,21 @@ const Video = React.memo(
return ( return (
<> <>
<Touchable
onPress={onPress}
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
{loading ? <RCActivityIndicator /> : <CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />}
</Touchable>
<Markdown <Markdown
msg={file.description} msg={file.description}
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}
style={[isReply && style]}
theme={theme} theme={theme}
/> />
<Touchable
disabled={isReply}
onPress={onPress}
style={[styles.button, { backgroundColor: themes[theme].videoBackground }]}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
{loading ? <RCActivityIndicator /> : <CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} />}
</Touchable>
</> </>
); );
}, },

View File

@ -1,24 +1,23 @@
import { MarkdownAST } from '@rocket.chat/message-parser'; import { MarkdownAST } from '@rocket.chat/message-parser';
import { StyleProp, TextStyle } from 'react-native';
import { IUserChannel, IUserMention } from '../markdown/interfaces'; import { IUserChannel, IUserMention } from '../markdown/interfaces';
import { TGetCustomEmoji } from '../../definitions/IEmoji'; import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
export type TMessageType = 'discussion-created' | 'jitsi_call_started'; export type TMessageType = 'discussion-created' | 'jitsi_call_started';
export interface IMessageAttachments { export interface IMessageAttachments {
attachments: any; attachments?: IAttachment[];
timeFormat?: string; timeFormat?: string;
showAttachment: Function; style?: StyleProp<TextStyle>[];
isReply?: boolean;
showAttachment?: Function;
getCustomEmoji: TGetCustomEmoji; getCustomEmoji: TGetCustomEmoji;
theme: string;
} }
export interface IMessageAttachedActions { export interface IMessageAttachedActions {
attachment: { attachment: IAttachment;
actions: [];
text: string;
};
theme: string;
} }
export interface IMessageAvatar { export interface IMessageAvatar {

View File

@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { isTablet } from '../../utils/deviceInfo'; import { isTablet } from '../../utils/deviceInfo';
export default StyleSheet.create<any>({ export default StyleSheet.create({
root: { root: {
flexDirection: 'row' flexDirection: 'row'
}, },
@ -28,7 +28,6 @@ export default StyleSheet.create<any>({
}, },
flex: { flex: {
flexDirection: 'row' flexDirection: 'row'
// flex: 1
}, },
temp: { opacity: 0.3 }, temp: { opacity: 0.3 },
marginTop: { marginTop: {
@ -100,7 +99,6 @@ export default StyleSheet.create<any>({
...sharedStyles.textSemibold ...sharedStyles.textSemibold
}, },
imageContainer: { imageContainer: {
// flex: 1,
flexDirection: 'column', flexDirection: 'column',
borderRadius: 4 borderRadius: 4
}, },
@ -141,7 +139,6 @@ export default StyleSheet.create<any>({
}, },
repliedThread: { repliedThread: {
flexDirection: 'row', flexDirection: 'row',
// flex: 1,
alignItems: 'center', alignItems: 'center',
marginTop: 6, marginTop: 6,
marginBottom: 12 marginBottom: 12

View File

@ -10,13 +10,16 @@ export interface IAttachment {
image_type?: string; image_type?: string;
video_url?: string; video_url?: string;
video_type?: string; video_type?: string;
audio_url?: string;
title_link_download?: boolean; title_link_download?: boolean;
attachments?: IAttachment[];
fields?: IAttachment[]; fields?: IAttachment[];
image_dimensions?: { width?: number; height?: number }; image_dimensions?: { width?: number; height?: number };
image_preview?: string; image_preview?: string;
image_size?: number; image_size?: number;
author_name?: string; author_name?: string;
author_icon?: string; author_icon?: string;
actions?: [];
message_link?: string; message_link?: string;
text?: string; text?: string;
short?: boolean; short?: boolean;
@ -24,7 +27,6 @@ export interface IAttachment {
author_link?: string; author_link?: string;
color?: string; color?: string;
thumb_url?: string; thumb_url?: string;
attachments?: any[];
collapsed?: boolean; collapsed?: boolean;
} }

View File

@ -1,7 +1,7 @@
import { coerce, gt, gte, lt, lte, SemVer } from 'semver'; import { coerce, gt, gte, lt, lte, SemVer } from 'semver';
export const formatAttachmentUrl = (attachmentUrl: string, userId: string, token: string, server: string): string => { export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: string, token: string, server: string): string => {
if (attachmentUrl.startsWith('http')) { if (attachmentUrl && attachmentUrl.startsWith('http')) {
if (attachmentUrl.includes('rc_token')) { if (attachmentUrl.includes('rc_token')) {
return encodeURI(attachmentUrl); return encodeURI(attachmentUrl);
} }

File diff suppressed because one or more lines are too long

View File

@ -485,6 +485,24 @@ stories.add('Message with reply', () => (
} }
]} ]}
/> />
<Message
msg='Looks cool!'
attachments={[
{
author_name: 'rocket.cat',
attachments: [
{
author_name: 'rocket.cat',
ts: date,
timeFormat: 'LT',
description: 'What you think about this one?',
image_url: 'https://octodex.github.com/images/yaktocat.png'
}
],
text: ''
}
]}
/>
</> </>
)); ));

File diff suppressed because one or more lines are too long