Unverified Commit b0580bf5 authored by Gerzon Z's avatar Gerzon Z Committed by GitHub
Browse files

[NEW] Redesign quoted messages (#3883)

parent 82acb917
......@@ -37,6 +37,7 @@ export const themes: any = {
infoText: '#6d6d72',
tintColor: '#1d74f5',
tintActive: '#549df9',
tintDisabled: '#88B4F5',
auxiliaryTintColor: '#6C727A',
actionTintColor: '#1d74f5',
separatorColor: '#cbcbcc',
......@@ -87,6 +88,7 @@ export const themes: any = {
infoText: '#6D6D72',
tintColor: '#1d74f5',
tintActive: '#549df9',
tintDisabled: '#88B4F5',
auxiliaryTintColor: '#f9f9f9',
actionTintColor: '#1d74f5',
separatorColor: '#2b2b2d',
......@@ -137,6 +139,7 @@ export const themes: any = {
infoText: '#6d6d72',
tintColor: '#1e9bfe',
tintActive: '#76b7fc',
tintDisabled: '#88B4F5', // TODO: Evaluate this with design team
auxiliaryTintColor: '#f9f9f9',
actionTintColor: '#1e9bfe',
separatorColor: '#272728',
......
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 styles from './styles';
......@@ -19,7 +19,7 @@ const Table = React.memo(({ children, numColumns, theme }: ITable) => {
const getTableWidth = () => numColumns * CELL_WIDTH;
const renderRows = (drawExtraBorders = true) => {
const tableStyle = [styles.table, { borderColor: themes[theme].borderColor }];
const tableStyle: ViewStyle[] = [styles.table, { borderColor: themes[theme].borderColor }];
if (drawExtraBorders) {
tableStyle.push(styles.tableExtraBorders);
}
......
import React from 'react';
import { Text, View } from 'react-native';
import { Text, View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles';
......@@ -14,7 +14,7 @@ interface ITableCell {
export const CELL_WIDTH = 100;
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) {
cellStyle.push(styles.cellRightBorder);
}
......
import React from 'react';
import { View } from 'react-native';
import { View, ViewStyle } from 'react-native';
import { themes } from '../../constants/colors';
import styles from './styles';
......@@ -11,7 +11,7 @@ interface 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) {
rowStyle.push(styles.rowBottomBorder);
}
......
......@@ -280,6 +280,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
renderHeading = ({ children, level }: any) => {
const { numberOfLines, theme } = this.props;
// @ts-ignore
const textStyle = styles[`heading${level}Text`];
return (
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}>
......
......@@ -7,7 +7,7 @@ const codeFontFamily = Platform.select({
android: { fontFamily: 'monospace' }
});
export default StyleSheet.create<any>({
export default StyleSheet.create({
container: {
alignItems: 'flex-start',
flexDirection: 'row'
......
......@@ -10,10 +10,16 @@ import Reply from './Reply';
import Button from '../Button';
import styles from './styles';
import MessageContext from './Context';
import { useTheme } from '../../theme';
import { IAttachment } from '../../definitions';
import CollapsibleQuote from './Components/CollapsibleQuote';
const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => {
const AttachedActions = ({ attachment }: IMessageAttachedActions) => {
if (!attachment.actions) {
return null;
}
const { onAnswerButtonPress } = useContext(MessageContext);
const { theme } = useTheme();
const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => {
if (element.type === 'button') {
......@@ -30,46 +36,62 @@ const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => {
};
const Attachments = React.memo(
({ attachments, timeFormat, showAttachment, getCustomEmoji, theme }: IMessageAttachments) => {
// @ts-ignore
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
if (!attachments || attachments.length === 0) {
return null;
}
return attachments.map((file: any, index: number) => {
if (file.image_url) {
const { theme } = useTheme();
return attachments.map((file: IAttachment, index: number) => {
if (file && file.image_url) {
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) {
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 (
<CollapsibleQuote key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />
);
}
return (
<Reply
key={index}
index={index}
attachment={file}
timeFormat={timeFormat}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
);
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
});
},
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme
(prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments)
);
Attachments.displayName = 'MessageAttachments';
......
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 Slider from '@react-native-community/slider';
import moment from 'moment';
......@@ -16,19 +16,20 @@ import MessageContext from './Context';
import ActivityIndicator from '../ActivityIndicator';
import { withDimensions } from '../../dimensions';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
interface IButton {
loading: boolean;
paused: boolean;
theme: string;
disabled?: boolean;
onPress: Function;
}
interface IMessageAudioProps {
file: {
audio_url: string;
description: string;
};
file: IAttachment;
isReply?: boolean;
style?: StyleProp<TextStyle>[];
theme: string;
getCustomEmoji: TGetCustomEmoji;
scale?: number;
......@@ -89,16 +90,21 @@ const sliderAnimationConfig = {
delay: 0
};
const Button = React.memo(({ loading, paused, onPress, theme }: IButton) => (
const Button = React.memo(({ loading, paused, onPress, disabled, theme }: IButton) => (
<Touchable
style={styles.playPauseButton}
disabled={disabled}
onPress={onPress}
hitSlop={BUTTON_HIT_SLOP}
background={Touchable.SelectableBackgroundBorderless()}>
{loading ? (
<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>
));
......@@ -128,7 +134,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
const { baseUrl, user } = this.context;
let url = file.audio_url;
if (!url.startsWith('http')) {
if (url && !url.startsWith('http')) {
url = `${baseUrl}${file.audio_url}`;
}
......@@ -249,7 +255,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
render() {
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 { baseUrl, user } = this.context;
......@@ -259,29 +265,37 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
return (
<>
<Markdown
msg={description}
style={[isReply && style]}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<View
style={[
styles.audioContainer,
{ 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
disabled={isReply}
style={styles.slider}
value={currentTime}
maximumValue={duration}
minimumValue={0}
// @ts-ignore
animateTransitions
animationConfig={sliderAnimationConfig}
thumbTintColor={isAndroid && themes[theme].tintColor}
thumbTintColor={isReply ? themes[theme].tintDisabled : isAndroid && themes[theme].tintColor}
minimumTrackTintColor={themes[theme].tintColor}
maximumTrackTintColor={themes[theme].auxiliaryText}
onValueChange={this.onValueChange}
/* @ts-ignore*/
thumbImage={isIOS && { uri: 'audio_thumb', scale }}
thumbImage={isIOS ? { uri: 'audio_thumb', scale } : undefined}
/>
<Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text>
</View>
<Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} />
</>
);
}
......
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 { dequal } from 'dequal';
import { createImageProgress } from 'react-native-image-progress';
......@@ -12,9 +12,11 @@ import { formatAttachmentUrl } from '../../lib/utils';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import { IAttachment } from '../../definitions';
type TMessageButton = {
children: JSX.Element;
disabled?: boolean;
onPress: Function;
theme: string;
};
......@@ -25,17 +27,23 @@ type TMessageImage = {
};
interface IMessageImage {
file: { image_url: string; description?: string };
file: IAttachment;
imageUrl?: string;
showAttachment: Function;
showAttachment?: Function;
style?: StyleProp<TextStyle>[];
isReply?: boolean;
theme: string;
getCustomEmoji: TGetCustomEmoji;
}
const ImageProgress = createImageProgress(FastImage);
const Button = React.memo(({ children, onPress, theme }: TMessageButton) => (
<Touchable onPress={onPress} style={styles.imageContainer} background={Touchable.Ripple(themes[theme].bannerBackground)}>
const Button = React.memo(({ children, onPress, disabled, theme }: TMessageButton) => (
<Touchable
disabled={disabled}
onPress={onPress}
style={styles.imageContainer}
background={Touchable.Ripple(themes[theme].bannerBackground)}>
{children}
</Touchable>
));
......@@ -53,34 +61,41 @@ export const MessageImage = React.memo(({ img, theme }: TMessageImage) => (
));
const ImageContainer = React.memo(
({ file, imageUrl, showAttachment, getCustomEmoji, theme }: IMessageImage) => {
({ file, imageUrl, showAttachment, getCustomEmoji, style, isReply, theme }: IMessageImage) => {
const { baseUrl, user } = useContext(MessageContext);
const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
if (!img) {
return null;
}
const onPress = () => showAttachment(file);
const onPress = () => {
if (!showAttachment) {
return;
}
return showAttachment(file);
};
if (file.description) {
return (
<Button theme={theme} onPress={onPress}>
<Button disabled={isReply} theme={theme} onPress={onPress}>
<View>
<MessageImage img={img} theme={theme} />
<Markdown
msg={file.description}
style={[isReply && style]}
baseUrl={baseUrl}
username={user.username}
getCustomEmoji={getCustomEmoji}
theme={theme}
/>
<MessageImage img={img} theme={theme} />
</View>
</Button>
);
}
return (
<Button theme={theme} onPress={onPress}>
<Button disabled={isReply} theme={theme} onPress={onPress}>
<MessageImage img={img} theme={theme} />
</Button>
);
......
......@@ -21,6 +21,9 @@ import { themes } from '../../constants/colors';
import { IMessage, IMessageInner, IMessageTouchable } from './interfaces';
const MessageInner = React.memo((props: IMessageInner) => {
const { attachments } = props;
const isCollapsible = attachments ? attachments[0] && attachments[0].collapsed : false;
if (props.type === 'discussion-created') {
return (
<>
......@@ -29,6 +32,7 @@ const MessageInner = React.memo((props: IMessageInner) => {
</>
);
}
if (props.type === 'jitsi_call_started') {
return (
<>
......@@ -38,6 +42,7 @@ const MessageInner = React.memo((props: IMessageInner) => {
</>
);
}
if (props.blocks && props.blocks.length) {
return (
<>
......@@ -48,11 +53,22 @@ const MessageInner = React.memo((props: IMessageInner) => {
</>
);
}
return (
<>
<User {...props} />
<Content {...props} />
<Attachments {...props} />
{isCollapsible ? (
<>
<Content {...props} />
<Attachments {...props} />
</>
) : (
<>
<Attachments {...props} />
<Content {...props} />
</>
)}
<Urls {...props} />
<Thread {...props} />
<Reactions {...props} />
......
import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import moment from 'moment';
import { transparentize } from 'color2k';
import { dequal } from 'dequal';
import FastImage from '@rocket.chat/react-native-fast-image';
......@@ -16,22 +15,24 @@ import { formatAttachmentUrl } from '../../lib/utils';
import { IAttachment } from '../../definitions/IAttachment';
import { TGetCustomEmoji } from '../../definitions/IEmoji';
import RCActivityIndicator from '../ActivityIndicator';
import Attachments from './Attachments';
import { useTheme } from '../../theme';
const styles = StyleSheet.create({
button: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginTop: 6,
marginVertical: 4,
alignSelf: 'flex-start',
borderWidth: 1,
borderRadius: 4
borderLeftWidth: 2
},
attachmentContainer: {
flex: 1,
borderRadius: 4,
flexDirection: 'column',
padding: 15
paddingVertical: 4,
paddingLeft: 8
},
backdrop: {
...StyleSheet.absoluteFillObject
......@@ -39,7 +40,8 @@ const styles = StyleSheet.create({
authorContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
alignItems: 'center',
marginBottom: 8
},
author: {
flex: 1,
......@@ -48,9 +50,8 @@ const styles = StyleSheet.create({
},
time: {
fontSize: 12,
marginLeft: 10,
...sharedStyles.textRegular,
fontWeight: '300'
marginLeft: 8,
...sharedStyles.textRegular
},
fieldsContainer: {
flex: 1,
......@@ -114,7 +115,6 @@ interface IMessageReply {
attachment: IAttachment;
timeFormat?: string;
index: number;
theme: string;
getCustomEmoji: TGetCustomEmoji;
}
......@@ -123,10 +123,10 @@ const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
return (
<View style={styles.authorContainer}>
{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}
{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>
);
});
......@@ -138,7 +138,16 @@ const Description = React.memo(
return null;
}
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) => {
if (prevProps.attachment.text !== nextProps.attachment.text) {
......@@ -195,12 +204,14 @@ const Fields = React.memo(
);
const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => {
({ attachment, timeFormat, index, getCustomEmoji }: IMessageReply) => {
const [loading, setLoading] = useState(false);
if (!attachment) {
return null;
}
const { theme } = useTheme();
const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
const onPress = async () => {
......@@ -221,14 +232,9 @@ const Reply = React.memo(
openLink(url, theme);
};
let { borderColor, chatComponentBackground: backgroundColor } = themes[theme];
try {
if (attachment.color) {
backgroundColor = transparentize(attachment.color, 0.8);
borderColor = attachment.color;
}
} catch (e) {
// fallback to default
let { borderColor } = themes[theme];
if (attachment.color) {
borderColor = attachment.color;
}
return (
......@@ -240,7 +246,6 @@ const Reply = React.memo(
index > 0 && styles.marginTop,
attachment.description && styles.marginBottom,
{
backgroundColor,
borderColor
}
]}
......@@ -248,6 +253,13 @@ const Reply = React.memo(
disabled={loading}>
<View style={styles.attachmentContainer}>
<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
/>