diff --git a/README.md b/README.md index c31a423233e..c3080f22b77 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ This is a persistent storage solution wrapped in a Pub/Sub library. In general t - Onyx stores and retrieves data from persistent storage - Data is stored as key/value pairs, where the value can be anything from a single piece of data to a complex object -- Collections of data are usually not stored as a single key (eg. an array with multiple objects), but as individual keys+ID (eg. `report_1234`, `report_4567`, etc.). Store collections as individual keys when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `SidebarLink.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action. +- Collections of data are usually not stored as a single key (eg. an array with multiple objects), but as individual keys+ID (eg. `report_1234`, `report_4567`, etc.). Store collections as individual keys when a component will bind directly to one of those keys. For example: reports are stored as individual keys because `ChatLinkRow.js` binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action. - Onyx allows other code to subscribe to changes in data, and then publishes change events whenever data is changed - Anything needing to read Onyx data needs to: 1. Know what key the data is stored in (for web, you can find this by looking in the JS console > Application > local storage) diff --git a/src/pages/home/sidebar/ChatLinkRow.js b/src/pages/home/sidebar/ChatLinkRow.js new file mode 100644 index 00000000000..b1384fd76fa --- /dev/null +++ b/src/pages/home/sidebar/ChatLinkRow.js @@ -0,0 +1,131 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Image, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import styles from '../../../styles/StyleSheet'; +import ChatSwitcherOptionPropTypes from './ChatSwitcherOptionPropTypes'; +import ROUTES from '../../../ROUTES'; +import PressableLink from '../../../components/PressableLink'; + +const propTypes = { + // Option to allow the user to choose from can be type 'report' or 'user' + option: ChatSwitcherOptionPropTypes.isRequired, + + // Whether this option is currently in focus so we can modify its style + optionIsFocused: PropTypes.bool.isRequired, + + // A function that is called when an option is selected. Selected option is passed as a param + onSelectRow: PropTypes.func.isRequired, + + // Callback that adds a user to the pending list of Group DM users + onAddToGroup: PropTypes.func, + + // A flag to indicate whether this comes from the Chat Switcher so we can display the group button + isChatSwitcher: PropTypes.bool, +}; + +const defaultProps = { + onAddToGroup: () => {}, + isChatSwitcher: false, +}; + +const ChatLinkRow = ({ + option, + optionIsFocused, + onSelectRow, + onAddToGroup, + isChatSwitcher, +}) => { + const isUserRow = option.type === 'user'; + const textStyle = optionIsFocused + ? styles.sidebarLinkActiveText + : styles.sidebarLinkText; + const textUnreadStyle = option.isUnread + ? [textStyle, styles.sidebarLinkTextUnread] : [textStyle]; + return ( + + onSelectRow(option)} + to={ROUTES.getReportRoute(option.reportID)} + style={styles.textDecorationNoLine} + > + onSelectRow(option)} + style={[ + styles.flexGrow1, + styles.chatSwitcherItemAvatarNameWrapper, + ]} + > + + { + option.icon + && ( + + + + ) + } + + {option.text === option.alternateText ? ( + + {option.alternateText} + + ) : ( + <> + + {option.text} + + + {option.alternateText} + + + )} + + + + + {isUserRow && isChatSwitcher && ( + + onAddToGroup(option)} + > + + Add + + + + )} + + ); +}; + +ChatLinkRow.propTypes = propTypes; +ChatLinkRow.defaultProps = defaultProps; +ChatLinkRow.displayName = 'ChatLinkRow'; + +export default ChatLinkRow; diff --git a/src/pages/home/sidebar/ChatSwitcherList.js b/src/pages/home/sidebar/ChatSwitcherList.js index ed9319f0439..004597a2a42 100644 --- a/src/pages/home/sidebar/ChatSwitcherList.js +++ b/src/pages/home/sidebar/ChatSwitcherList.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {View, FlatList} from 'react-native'; import styles from '../../../styles/StyleSheet'; import ChatSwitcherOptionPropTypes from './ChatSwitcherOptionPropTypes'; -import ChatSwitcherRow from './ChatSwitcherRow'; +import ChatLinkRow from './ChatLinkRow'; import KeyboardSpacer from '../../../components/KeyboardSpacer'; const propTypes = { @@ -35,11 +35,12 @@ const ChatSwitcherList = ({ data={options} keyExtractor={option => (option.type === 'user' ? option.alternateText : String(option.reportID))} renderItem={({item, index}) => ( - )} extraData={focusedIndex} diff --git a/src/pages/home/sidebar/ChatSwitcherRow.js b/src/pages/home/sidebar/ChatSwitcherRow.js deleted file mode 100644 index 82afb56590f..00000000000 --- a/src/pages/home/sidebar/ChatSwitcherRow.js +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - Image, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import styles from '../../../styles/StyleSheet'; -import ChatSwitcherOptionPropTypes from './ChatSwitcherOptionPropTypes'; - -const propTypes = { - // Option to allow the user to choose from can be type 'report' or 'user' - option: ChatSwitcherOptionPropTypes.isRequired, - - // Whether this option is currently in focus so we can modify its style - optionIsFocused: PropTypes.bool.isRequired, - - // A function that is called when an option is selected. Selected option is passed as a param - onSelectRow: PropTypes.func.isRequired, - - // Callback that adds a user to the pending list of Group DM users - onAddToGroup: PropTypes.func.isRequired, -}; - -const ChatSwitcherRow = ({ - option, - optionIsFocused, - onSelectRow, - onAddToGroup, -}) => { - const isUserRow = option.type === 'user'; - const textStyle = optionIsFocused - ? styles.sidebarLinkActiveText - : styles.sidebarLinkText; - const textUnreadStyle = option.isUnread - ? [textStyle, styles.sidebarLinkTextUnread] : [textStyle]; - return ( - - onSelectRow(option)} - style={[ - styles.flexGrow1, - styles.chatSwitcherItemAvatarNameWrapper, - ]} - > - - { - option.icon - && ( - - - - ) - } - - {option.text === option.alternateText ? ( - - {option.alternateText} - - ) : ( - <> - - {option.text} - - - {option.alternateText} - - - )} - - - - {isUserRow && ( - - onAddToGroup(option)} - > - - Add - - - - )} - - ); -}; - -ChatSwitcherRow.propTypes = propTypes; -ChatSwitcherRow.displayName = 'ChatSwitcherRow'; - -export default ChatSwitcherRow; diff --git a/src/pages/home/sidebar/SidebarLink.js b/src/pages/home/sidebar/SidebarLink.js deleted file mode 100644 index 00ac1888c1d..00000000000 --- a/src/pages/home/sidebar/SidebarLink.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {View} from 'react-native'; -import Text from '../../../components/Text'; -import styles from '../../../styles/StyleSheet'; -import PressableLink from '../../../components/PressableLink'; -import ROUTES from '../../../ROUTES'; - -const propTypes = { - // The ID of the report for this link - reportID: PropTypes.number.isRequired, - - // The name of the report to use as the text for this link - reportName: PropTypes.string, - - // Toggles the hamburger menu open and closed - onLinkClick: PropTypes.func.isRequired, - - // Does the report for this link have unread comments? - isUnread: PropTypes.bool, - - // Whether this is the report currently in view - isActiveReport: PropTypes.bool.isRequired, -}; - -const defaultProps = { - isUnread: false, - reportName: '', -}; - -const SidebarLink = (props) => { - const linkWrapperActiveStyle = props.isActiveReport && styles.sidebarLinkWrapperActive; - const linkActiveStyle = props.isActiveReport ? styles.sidebarLinkActive : null; - const textActiveStyle = props.isActiveReport ? styles.sidebarLinkActiveText : styles.sidebarLinkText; - const textActiveUnreadStyle = props.isUnread - ? [textActiveStyle, styles.sidebarLinkTextUnread] : [textActiveStyle]; - - return ( - - - - - - {props.reportName} - - - - - - ); -}; - -SidebarLink.displayName = 'SidebarLink'; -SidebarLink.propTypes = propTypes; -SidebarLink.defaultProps = defaultProps; - -export default SidebarLink; diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index 81bad1bc7ae..8734e8f2eae 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -3,15 +3,16 @@ import {View, ScrollView} from 'react-native'; import _ from 'underscore'; import PropTypes from 'prop-types'; import lodashOrderby from 'lodash.orderby'; +import get from 'lodash.get'; import {withOnyx} from 'react-native-onyx'; import styles from '../../../styles/StyleSheet'; import Text from '../../../components/Text'; -import SidebarLink from './SidebarLink'; import ONYXKEYS from '../../../ONYXKEYS'; import ChatSwitcherView from './ChatSwitcherView'; import SafeAreaInsetPropTypes from '../../SafeAreaInsetPropTypes'; import compose from '../../../libs/compose'; import {withRouter} from '../../../libs/Router'; +import ChatLinkRow from './ChatLinkRow'; const propTypes = { // These are from withRouter @@ -34,10 +35,13 @@ const propTypes = { })), isChatSwitcherActive: PropTypes.bool, + + personalDetails: PropTypes.object, }; const defaultProps = { reports: {}, isChatSwitcherActive: false, + personalDetails: {}, }; const SidebarLinks = (props) => { @@ -80,17 +84,25 @@ const SidebarLinks = (props) => { {/* A report will not have a report name if it hasn't been fetched from the server yet */} {/* so nothing is rendered */} - {_.map(reportsToDisplay, report => report.reportName && ( - 0} - onLinkClick={onLinkClick} - isActiveReport={report.reportID === reportIDInUrl} - isPinned={report.isPinned} - /> - ))} + {_.map(reportsToDisplay, (report) => { + const participantDetails = get(report, 'participants.length', 0) === 1 ? get(props.personalDetails, report.participants[0], '') : ''; + return report.reportName && ( + 0, + }} + onSelectRow={onLinkClick} + optionIsFocused={report.reportID === reportIDInUrl} + /> + ); + })} ); @@ -106,5 +118,8 @@ export default compose( reports: { key: ONYXKEYS.COLLECTION.REPORT, }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, }), )(SidebarLinks);