[IMPROVEMENT] Threads layout tweaks (#2686)

* improvement: Thread Details

* fix: re-render Thread Messages Item

* fix: update snapshots

* improve: thread details component

* fix: cast replies length

* improvement: format date of threads

* improvement: thread details styles

* fix: wrap text

* tests: update snapshot

* improvement: use same date format for all dates

* Icon size 24

* Remove date

* Remove prop drill

* Badge position

* Badge container tweak

* Fix inline style

* Move ThreadDetails to containers

* Update stories

* Fix lint

* Remove wrong prop

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Djorkaeff Alexandre 2021-01-14 14:06:19 -03:00 committed by GitHub
parent 32b1b36e48
commit 4d13689503
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 3793 additions and 3481 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,103 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles';
import { withTheme } from '../theme';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
detailsContainer: {
flex: 1,
flexDirection: 'row'
},
detailContainer: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 8
},
detailText: {
fontSize: 10,
marginLeft: 2,
...sharedStyles.textSemibold
},
badgeContainer: {
flexDirection: 'row',
alignItems: 'center'
},
badge: {
width: 8,
height: 8,
borderRadius: 4,
marginRight: 8
}
});
const ThreadDetails = ({
item,
user,
badgeColor,
toggleFollowThread,
style,
theme
}) => {
let { tcount } = item;
if (tcount >= 1000) {
tcount = '+999';
} else if (tcount >= 100) {
tcount = '+99';
}
let replies = item?.replies?.length ?? 0;
if (replies >= 1000) {
replies = '+999';
} else if (replies >= 100) {
replies = '+99';
}
const isFollowing = item.replies?.find(u => u === user?.id);
return (
<View style={[styles.container, style]}>
<View style={styles.detailsContainer}>
<View style={styles.detailContainer}>
<CustomIcon name='threads' size={24} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{tcount}</Text>
</View>
<View style={styles.detailContainer}>
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{replies}</Text>
</View>
</View>
<View style={styles.badgeContainer}>
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null }
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
<CustomIcon
size={24}
name={isFollowing ? 'notification' : 'notification-disabled'}
color={themes[theme].auxiliaryTintColor}
/>
</Touchable>
</View>
</View>
);
};
ThreadDetails.propTypes = {
item: PropTypes.object,
user: PropTypes.object,
badgeColor: PropTypes.string,
toggleFollowThread: PropTypes.func,
style: PropTypes.object,
theme: PropTypes.string
};
export default withTheme(ThreadDetails);

View File

@ -1,15 +1,12 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { View, Text } from 'react-native'; import { View, Text } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import { formatMessageCount } from './utils';
import styles from './styles'; import styles from './styles';
import { CustomIcon } from '../../lib/Icons';
import { THREAD } from './constants';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { formatDateThreads } from '../../utils/room';
import MessageContext from './Context'; import MessageContext from './Context';
import ThreadDetails from '../ThreadDetails';
import I18n from '../../i18n';
const Thread = React.memo(({ const Thread = React.memo(({
msg, tcount, tlm, isThreadRoom, theme, id msg, tcount, tlm, isThreadRoom, theme, id
@ -21,28 +18,26 @@ const Thread = React.memo(({
const { const {
threadBadgeColor, toggleFollowThread, user, replies threadBadgeColor, toggleFollowThread, user, replies
} = useContext(MessageContext); } = useContext(MessageContext);
const time = formatDateThreads(tlm);
const buttonText = formatMessageCount(tcount, THREAD);
const isFollowing = replies?.find(u => u === user.id);
return ( return (
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<View <View
style={[styles.button, { backgroundColor: themes[theme].tintColor }]} style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
testID={`message-thread-button-${ msg }`} testID={`message-thread-button-${ msg }`}
> >
<CustomIcon name='threads' size={16} style={[styles.buttonIcon, { color: themes[theme].buttonText }]} /> <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text>
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text>
</View> </View>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <ThreadDetails
{threadBadgeColor ? <View style={[styles.threadBadge, { backgroundColor: threadBadgeColor }]} /> : null} item={{
<Touchable onPress={() => toggleFollowThread(isFollowing, id)}> tcount,
<CustomIcon replies,
name={isFollowing ? 'notification' : 'notification-disabled'} tlm,
size={24} id
color={themes[theme].auxiliaryText} }}
style={styles.threadBell} user={user}
/> badgeColor={threadBadgeColor}
</Touchable> toggleFollowThread={toggleFollowThread}
style={styles.threadDetails}
/>
</View> </View>
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {

View File

@ -176,5 +176,9 @@ export default StyleSheet.create({
}, },
encrypted: { encrypted: {
justifyContent: 'center' justifyContent: 'center'
},
threadDetails: {
flex: 1,
marginLeft: 12
} }
}); });

View File

@ -1,15 +1,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native'; import { View, Text, StyleSheet } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import Markdown from '../../containers/markdown'; import Markdown from '../../containers/markdown';
import { CustomIcon } from '../../lib/Icons';
import { formatDateThreads, makeThreadName } from '../../utils/room'; import { formatDateThreads, makeThreadName } from '../../utils/room';
import ThreadDetails from '../../containers/ThreadDetails';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -38,34 +38,26 @@ const styles = StyleSheet.create({
avatar: { avatar: {
marginRight: 8 marginRight: 8
}, },
detailsContainer: { threadDetails: {
marginTop: 8, marginTop: 8
flexDirection: 'row'
},
detailContainer: {
marginRight: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
},
detailText: {
fontSize: 10,
marginLeft: 2,
...sharedStyles.textSemibold
},
badgeContainer: {
marginLeft: 8,
justifyContent: 'center'
}, },
badge: { badge: {
width: 12, width: 8,
height: 12, height: 8,
borderRadius: 6 borderRadius: 4,
marginHorizontal: 8,
alignSelf: 'center'
},
messageContainer: {
flexDirection: 'row'
},
markdown: {
flex: 1
} }
}); });
const Item = ({ const Item = ({
item, baseUrl, theme, useRealName, user, badgeColor, onPress item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread
}) => { }) => {
const username = (useRealName && item?.u?.name) || item?.u?.username; const username = (useRealName && item?.u?.name) || item?.u?.username;
let time; let time;
@ -73,13 +65,8 @@ const Item = ({
time = formatDateThreads(item.ts); time = formatDateThreads(item.ts);
} }
let tlm;
if (item?.tlm) {
tlm = formatDateThreads(item.tlm);
}
return ( return (
<Touch theme={theme} onPress={() => onPress(item)} testID={`thread-messages-view-${ item.msg }`} style={{ backgroundColor: themes[theme].backgroundColor }}> <Touchable onPress={() => onPress(item)} testID={`thread-messages-view-${ item.msg }`} style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}> <View style={styles.container}>
<Avatar <Avatar
style={styles.avatar} style={styles.avatar}
@ -96,33 +83,19 @@ const Item = ({
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>{username}</Text> <Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>{username}</Text>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View> </View>
<Markdown msg={makeThreadName(item)} baseUrl={baseUrl} username={username} theme={theme} numberOfLines={2} preview /> <View style={styles.messageContainer}>
<View style={styles.detailsContainer}> <Markdown msg={makeThreadName(item)} baseUrl={baseUrl} username={username} theme={theme} numberOfLines={2} style={[styles.markdown]} preview />
<View style={styles.detailContainer}> {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null }
<CustomIcon name='threads' size={20} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]}>{item?.tcount}</Text>
</View>
<View style={styles.detailContainer}>
<CustomIcon name='user' size={20} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]}>{item?.replies?.length}</Text>
</View>
<View style={styles.detailContainer}>
<CustomIcon name='clock' size={20} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]}>{tlm}</Text>
</View>
</View> </View>
<ThreadDetails
item={item}
user={user}
toggleFollowThread={toggleFollowThread}
style={styles.threadDetails}
/>
</View> </View>
{badgeColor
? (
<View style={styles.badgeContainer}>
<View style={[styles.badge, { backgroundColor: badgeColor }]} />
</View>
)
: null}
</View> </View>
</Touch> </Touchable>
); );
}; };
@ -133,7 +106,8 @@ Item.propTypes = {
useRealName: PropTypes.bool, useRealName: PropTypes.bool,
user: PropTypes.object, user: PropTypes.object,
badgeColor: PropTypes.string, badgeColor: PropTypes.string,
onPress: PropTypes.func onPress: PropTypes.func,
toggleFollowThread: PropTypes.func
}; };
export default withTheme(Item); export default withTheme(Item);

View File

@ -33,6 +33,8 @@ import { isIOS } from '../../utils/deviceInfo';
import { getBadgeColor, makeThreadName } from '../../utils/room'; import { getBadgeColor, makeThreadName } from '../../utils/room';
import { getHeaderTitlePosition } from '../../containers/Header'; import { getHeaderTitlePosition } from '../../containers/Header';
import SearchHeader from './SearchHeader'; import SearchHeader from './SearchHeader';
import EventEmitter from '../../utils/events';
import { LISTENER } from '../../containers/Toast';
const API_FETCH_COUNT = 50; const API_FETCH_COUNT = 50;
@ -410,6 +412,15 @@ class ThreadMessagesView extends React.Component {
this.setState({ currentFilter: filter, displayingThreads }); this.setState({ currentFilter: filter, displayingThreads });
} }
toggleFollowThread = async(isFollowingThread, tmid) => {
try {
await RocketChat.toggleFollowMessage(tmid, !isFollowingThread);
EventEmitter.emit(LISTENER, { message: isFollowingThread ? I18n.t('Unfollowed_thread') : I18n.t('Following_thread') });
} catch (e) {
log(e);
}
}
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { const {
user, navigation, baseUrl, useRealName user, navigation, baseUrl, useRealName
@ -426,6 +437,7 @@ class ThreadMessagesView extends React.Component {
badgeColor badgeColor
}} }}
onPress={this.onThreadPress} onPress={this.onThreadPress}
toggleFollowThread={this.toggleFollowThread}
/> />
); );
} }

View File

@ -469,11 +469,6 @@ export default ({ theme }) => {
tcount={1} tcount={1}
tlm={date} tlm={date}
/> />
<Message
msg='How are you?'
tcount={9999}
tlm={date}
/>
<Message <Message
msg="I'm fine!" msg="I'm fine!"
tmid='1' tmid='1'