diff --git a/src/components/CopySelectionHelper.js b/src/components/CopySelectionHelper.js
deleted file mode 100644
index 5f00bab3146b..000000000000
--- a/src/components/CopySelectionHelper.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import CONST from '../CONST';
-import KeyboardShortcut from '../libs/KeyboardShortcut';
-import Clipboard from '../libs/Clipboard';
-import SelectionScraper from '../libs/SelectionScraper';
-
-class CopySelectionHelper extends React.Component {
- componentDidMount() {
- const copyShortcutConfig = CONST.KEYBOARD_SHORTCUTS.COPY;
- this.unsubscribeCopyShortcut = KeyboardShortcut.subscribe(
- copyShortcutConfig.shortcutKey,
- this.copySelectionToClipboard,
- copyShortcutConfig.descriptionKey,
- copyShortcutConfig.modifiers,
- false,
- );
- }
-
- componentWillUnmount() {
- if (!this.unsubscribeCopyShortcut) {
- return;
- }
-
- this.unsubscribeCopyShortcut();
- }
-
- copySelectionToClipboard() {
- const selectionMarkdown = SelectionScraper.getAsMarkdown();
- Clipboard.setString(selectionMarkdown);
- }
-
- render() {
- return null;
- }
-}
-
-export default CopySelectionHelper;
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
deleted file mode 100644
index 2648769e0a57..000000000000
--- a/src/pages/home/report/ReportActionsList.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {ActivityIndicator, View} from 'react-native';
-import InvertedFlatList from '../../../components/InvertedFlatList';
-import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState';
-import compose from '../../../libs/compose';
-import * as ReportScrollManager from '../../../libs/ReportScrollManager';
-import styles from '../../../styles/styles';
-import themeColors from '../../../styles/themes/default';
-import * as ReportUtils from '../../../libs/ReportUtils';
-import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
-import {withPersonalDetails} from '../../../components/OnyxProvider';
-import ReportActionItem from './ReportActionItem';
-import variables from '../../../styles/variables';
-import participantPropTypes from '../../../components/participantPropTypes';
-import * as ReportActionsUtils from '../../../libs/ReportActionsUtils';
-import reportActionPropTypes from './reportActionPropTypes';
-
-const propTypes = {
- /** Personal details of all the users */
- personalDetails: PropTypes.objectOf(participantPropTypes),
-
- /** The report currently being looked at */
- report: PropTypes.shape({
- /** Number of actions unread */
- unreadActionCount: PropTypes.number,
-
- /** The largest sequenceNumber on this report */
- maxSequenceNumber: PropTypes.number,
-
- /** The current position of the new marker */
- newMarkerSequenceNumber: PropTypes.number,
-
- /** Whether there is an outstanding amount in IOU */
- hasOutstandingIOU: PropTypes.bool,
- }).isRequired,
-
- /** Sorted actions prepared for display */
- sortedReportActions: PropTypes.arrayOf(PropTypes.shape({
- /** Index of the action in the array */
- index: PropTypes.number,
-
- /** The action itself */
- action: PropTypes.shape(reportActionPropTypes),
- })).isRequired,
-
- /** The sequence number of the most recent IOU report connected with the shown report */
- mostRecentIOUReportSequenceNumber: PropTypes.number,
-
- /** Are we loading more report actions? */
- isLoadingReportActions: PropTypes.bool.isRequired,
-
- /** Callback executed on list layout */
- onLayout: PropTypes.func.isRequired,
-
- /** Callback executed on scroll */
- onScroll: PropTypes.func.isRequired,
-
- ...withDrawerPropTypes,
- ...windowDimensionsPropTypes,
-};
-
-const defaultProps = {
- personalDetails: {},
- mostRecentIOUReportSequenceNumber: undefined,
-};
-
-class ReportActionsList extends React.Component {
- constructor(props) {
- super(props);
- this.renderItem = this.renderItem.bind(this);
- this.renderCell = this.renderCell.bind(this);
- this.keyExtractor = this.keyExtractor.bind(this);
- }
-
- /**
- * Calculates the ideal number of report actions to render in the first render, based on the screen height and on
- * the height of the smallest report action possible.
- * @return {Number}
- */
- calculateInitialNumToRender() {
- const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom
- + variables.fontSizeNormalHeight;
- const availableHeight = this.props.windowHeight
- - (styles.chatFooter.minHeight + variables.contentHeaderHeight);
- return Math.ceil(availableHeight / minimumReportActionHeight);
- }
-
- /**
- * Create a unique key for Each Action in the FlatList.
- * We use a combination of sequenceNumber and clientID in case the clientID are the same - which
- * shouldn't happen, but might be possible in some rare cases.
- * @param {Object} item
- * @return {String}
- */
- keyExtractor(item) {
- return `${item.action.sequenceNumber}${item.action.clientID}`;
- }
-
- /**
- * Do not move this or make it an anonymous function it is a method
- * so it will not be recreated each time we render an item
- *
- * See: https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem
- *
- * @param {Object} args
- * @param {Object} args.item
- * @param {Number} args.index
- *
- * @returns {React.Component}
- */
- renderItem({
- item,
- index,
- }) {
- const shouldDisplayNewIndicator = this.props.report.newMarkerSequenceNumber > 0
- && item.action.sequenceNumber === this.props.report.newMarkerSequenceNumber;
- return (
-
- );
- }
-
- /**
- * This function overrides the CellRendererComponent (defaults to a plain View), giving each ReportActionItem a
- * higher z-index than the one below it. This prevents issues where the ReportActionContextMenu overlapping between
- * rows is hidden beneath other rows.
- *
- * @param {Object} index - The ReportAction item in the FlatList.
- * @param {Object|Array} style – The default styles of the CellRendererComponent provided by the CellRenderer.
- * @param {Object} props – All the other Props provided to the CellRendererComponent by default.
- * @returns {React.Component}
- */
- renderCell({item, style, ...props}) {
- const cellStyle = [
- style,
- {zIndex: item.action.sequenceNumber},
- ];
- // eslint-disable-next-line react/jsx-props-no-spreading
- return ;
- }
-
- render() {
- // Native mobile does not render updates flatlist the changes even though component did update called.
- // To notify there something changes we can use extraData prop to flatlist
- const extraData = (!this.props.isDrawerOpen && this.props.isSmallScreenWidth) ? this.props.report.newMarkerSequenceNumber : undefined;
- const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(this.props.personalDetails, this.props.report);
- return (
-
- : null}
- keyboardShouldPersistTaps="handled"
- onLayout={this.props.onLayout}
- onScroll={this.props.onScroll}
- extraData={extraData}
- />
- );
- }
-}
-
-ReportActionsList.propTypes = propTypes;
-ReportActionsList.defaultProps = defaultProps;
-
-export default compose(
- withDrawerState,
- withWindowDimensions,
- withPersonalDetails(),
-)(ReportActionsList);
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index 877868f0cd82..c6689dca247e 100755
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -1,18 +1,27 @@
import React from 'react';
import {
+ View,
Keyboard,
AppState,
+ ActivityIndicator,
} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import _ from 'underscore';
import lodashGet from 'lodash/get';
+import Clipboard from '../../../libs/Clipboard';
import * as Report from '../../../libs/actions/Report';
+import KeyboardShortcut from '../../../libs/KeyboardShortcut';
+import SelectionScraper from '../../../libs/SelectionScraper';
+import ReportActionItem from './ReportActionItem';
+import styles from '../../../styles/styles';
import reportActionPropTypes from './reportActionPropTypes';
+import InvertedFlatList from '../../../components/InvertedFlatList';
import * as CollectionUtils from '../../../libs/CollectionUtils';
import Visibility from '../../../libs/Visibility';
import Timing from '../../../libs/actions/Timing';
import CONST from '../../../CONST';
+import themeColors from '../../../styles/themes/default';
import compose from '../../../libs/compose';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import withDrawerState, {withDrawerPropTypes} from '../../../components/withDrawerState';
@@ -21,15 +30,16 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu';
import PopoverReportActionContextMenu from './ContextMenu/PopoverReportActionContextMenu';
+import variables from '../../../styles/variables';
import Performance from '../../../libs/Performance';
+import * as ReportUtils from '../../../libs/ReportUtils';
import ONYXKEYS from '../../../ONYXKEYS';
-import {withNetwork} from '../../../components/OnyxProvider';
+import {withNetwork, withPersonalDetails} from '../../../components/OnyxProvider';
+import participantPropTypes from '../../../components/participantPropTypes';
+import EmojiPicker from '../../../components/EmojiPicker/EmojiPicker';
import * as EmojiPickerAction from '../../../libs/actions/EmojiPickerAction';
import FloatingMessageCounter from './FloatingMessageCounter';
import networkPropTypes from '../../../components/networkPropTypes';
-import ReportActionsList from './ReportActionsList';
-import CopySelectionHelper from '../../../components/CopySelectionHelper';
-import EmojiPicker from '../../../components/EmojiPicker/EmojiPicker';
import * as ReportActionsUtils from '../../../libs/ReportActionsUtils';
const propTypes = {
@@ -68,6 +78,9 @@ const propTypes = {
/** Are we waiting for more report data? */
isLoadingReportData: PropTypes.bool,
+ /** Personal details of all the users */
+ personalDetails: PropTypes.objectOf(participantPropTypes),
+
/** Information about the network */
network: networkPropTypes.isRequired,
@@ -86,12 +99,19 @@ const defaultProps = {
session: {},
isLoadingReportActions: false,
isLoadingReportData: false,
+ personalDetails: {},
};
class ReportActionsView extends React.Component {
constructor(props) {
super(props);
+ this.renderItem = this.renderItem.bind(this);
+ this.renderCell = this.renderCell.bind(this);
+ this.scrollToBottomAndUpdateLastRead = this.scrollToBottomAndUpdateLastRead.bind(this);
+ this.onVisibilityChange = this.onVisibilityChange.bind(this);
+ this.recordTimeToMeasureItemLayout = this.recordTimeToMeasureItemLayout.bind(this);
+ this.loadMoreChats = this.loadMoreChats.bind(this);
this.appStateChangeListener = null;
this.didLayout = false;
@@ -104,25 +124,19 @@ class ReportActionsView extends React.Component {
this.currentScrollOffset = 0;
this.sortedReportActions = ReportActionsUtils.getSortedReportActions(props.reportActions);
this.mostRecentIOUReportSequenceNumber = ReportActionsUtils.getMostRecentIOUReportSequenceNumber(props.reportActions);
+ this.keyExtractor = this.keyExtractor.bind(this);
this.trackScroll = this.trackScroll.bind(this);
this.showFloatingMessageCounter = this.showFloatingMessageCounter.bind(this);
this.hideFloatingMessageCounter = this.hideFloatingMessageCounter.bind(this);
this.toggleFloatingMessageCounter = this.toggleFloatingMessageCounter.bind(this);
this.updateNewMarkerPosition = this.updateNewMarkerPosition.bind(this);
this.updateMessageCounterCount = this.updateMessageCounterCount.bind(this);
- this.recordTimeToMeasureItemLayout = this.recordTimeToMeasureItemLayout.bind(this);
this.scrollToBottomAndUpdateLastRead = this.scrollToBottomAndUpdateLastRead.bind(this);
this.updateNewMarkerAndMarkReadOnce = _.once(this.updateNewMarkerAndMarkRead.bind(this));
}
componentDidMount() {
- this.appStateChangeListener = AppState.addEventListener('change', () => {
- if (!Visibility.isVisible() || this.props.isDrawerOpen) {
- return;
- }
-
- Report.updateLastReadActionID(this.props.reportID);
- });
+ this.appStateChangeListener = AppState.addEventListener('change', this.onVisibilityChange);
// If the reportID is not found then we have either not loaded this chat or the user is unable to access it.
// We will attempt to fetch it and redirect if still not accessible.
@@ -142,6 +156,15 @@ class ReportActionsView extends React.Component {
}
this.fetchData();
+
+ const copyShortcutConfig = CONST.KEYBOARD_SHORTCUTS.COPY;
+ this.unsubscribeCopyShortcut = KeyboardShortcut.subscribe(
+ copyShortcutConfig.shortcutKey,
+ this.copySelectionToClipboard,
+ copyShortcutConfig.descriptionKey,
+ copyShortcutConfig.modifiers,
+ false,
+ );
}
shouldComponentUpdate(nextProps, nextState) {
@@ -206,28 +229,26 @@ class ReportActionsView extends React.Component {
const previousLastSequenceNumber = lodashGet(CollectionUtils.lastItem(prevProps.reportActions), 'sequenceNumber');
const currentLastSequenceNumber = lodashGet(CollectionUtils.lastItem(this.props.reportActions), 'sequenceNumber');
- // Record the max action when window is visible and the sidebar is not covering the report view on a small screen
- const isSidebarCoveringReportView = this.props.isSmallScreenWidth && this.props.isDrawerOpen;
- const shouldRecordMaxAction = Visibility.isVisible() && !isSidebarCoveringReportView;
-
- const sidebarClosed = prevProps.isDrawerOpen && !this.props.isDrawerOpen;
- const screenSizeIncreased = prevProps.isSmallScreenWidth && !this.props.isSmallScreenWidth;
- const reportBecomeVisible = sidebarClosed || screenSizeIncreased;
+ // Record the max action when window is visible except when Drawer is open on small screen
+ const shouldRecordMaxAction = Visibility.isVisible()
+ && (!this.props.isSmallScreenWidth || !this.props.isDrawerOpen);
if (previousLastSequenceNumber !== currentLastSequenceNumber) {
+ // If a new comment is added and it's from the current user scroll to the bottom otherwise
+ // leave the user positioned where they are now in the list.
const lastAction = CollectionUtils.lastItem(this.props.reportActions);
- const isLastActionFromCurrentUser = lodashGet(lastAction, 'actorEmail', '') === lodashGet(this.props.session, 'email', '');
- if (isLastActionFromCurrentUser) {
- // If a new comment is added and it's from the current user scroll to the bottom otherwise leave the user positioned where they are now in the list.
+ if (lastAction && (lastAction.actorEmail === this.props.session.email)) {
ReportScrollManager.scrollToBottom();
- } else {
+ }
+
+ if (lodashGet(lastAction, 'actorEmail', '') !== lodashGet(this.props.session, 'email', '')) {
// Only update the unread count when the floating message counter is visible
// Otherwise counter will be shown on scrolling up from the bottom even if user have read those messages
if (this.state.isFloatingMessageCounterVisible) {
this.updateMessageCounterCount(!shouldRecordMaxAction);
}
- // Show new floating message counter when there is a new message
+ // show new floating message counter when there is a new message
this.toggleFloatingMessageCounter();
}
@@ -236,10 +257,10 @@ class ReportActionsView extends React.Component {
if (shouldRecordMaxAction) {
Report.updateLastReadActionID(this.props.reportID);
}
- }
-
- // Update the new marker position and last read action when we are closing the sidebar or moving from a small to large screen size
- if (shouldRecordMaxAction && reportBecomeVisible) {
+ } else if (shouldRecordMaxAction && (
+ prevProps.isDrawerOpen !== this.props.isDrawerOpen
+ || prevProps.isSmallScreenWidth !== this.props.isSmallScreenWidth
+ )) {
this.updateNewMarkerPosition(this.props.report.unreadActionCount);
Report.updateLastReadActionID(this.props.reportID);
}
@@ -255,6 +276,38 @@ class ReportActionsView extends React.Component {
}
Report.unsubscribeFromReportChannel(this.props.reportID);
+
+ if (this.unsubscribeCopyShortcut) {
+ this.unsubscribeCopyShortcut();
+ }
+ }
+
+ /**
+ * Records the max action on app visibility change event.
+ */
+ onVisibilityChange() {
+ if (!Visibility.isVisible() || this.props.isDrawerOpen) {
+ return;
+ }
+
+ Report.updateLastReadActionID(this.props.reportID);
+ }
+
+ copySelectionToClipboard() {
+ const selectionMarkdown = SelectionScraper.getAsMarkdown();
+
+ Clipboard.setString(selectionMarkdown);
+ }
+
+ /**
+ * Create a unique key for Each Action in the FlatList.
+ * We use a combination of sequenceNumber and clientID in case the clientID are the same - which
+ * shouldn't happen, but might be possible in some rare cases.
+ * @param {Object} item
+ * @return {String}
+ */
+ keyExtractor(item) {
+ return `${item.action.sequenceNumber}${item.action.clientID}`;
}
fetchData() {
@@ -286,6 +339,19 @@ class ReportActionsView extends React.Component {
Report.fetchActionsWithLoadingState(this.props.reportID, offset);
}
+ /**
+ * Calculates the ideal number of report actions to render in the first render, based on the screen height and on
+ * the height of the smallest report action possible.
+ * @return {Number}
+ */
+ calculateInitialNumToRender() {
+ const minimumReportActionHeight = styles.chatItem.paddingTop + styles.chatItem.paddingBottom
+ + variables.fontSizeNormalHeight;
+ const availableHeight = this.props.windowHeight
+ - (styles.chatFooter.minHeight + variables.contentHeaderHeight);
+ return Math.ceil(availableHeight / minimumReportActionHeight);
+ }
+
/**
* This function is triggered from the ref callback for the scrollview. That way it can be scrolled once all the
* items have been rendered. If the number of actions has changed since it was last rendered, then
@@ -396,12 +462,67 @@ class ReportActionsView extends React.Component {
}
}
+ /**
+ * This function overrides the CellRendererComponent (defaults to a plain View), giving each ReportActionItem a
+ * higher z-index than the one below it. This prevents issues where the ReportActionContextMenu overlapping between
+ * rows is hidden beneath other rows.
+ *
+ * @param {Object} index - The ReportAction item in the FlatList.
+ * @param {Object|Array} style – The default styles of the CellRendererComponent provided by the CellRenderer.
+ * @param {Object} props – All the other Props provided to the CellRendererComponent by default.
+ * @returns {React.Component}
+ */
+ renderCell({item, style, ...props}) {
+ const cellStyle = [
+ style,
+ {zIndex: item.action.sequenceNumber},
+ ];
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return ;
+ }
+
+ /**
+ * Do not move this or make it an anonymous function it is a method
+ * so it will not be recreated each time we render an item
+ *
+ * See: https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem
+ *
+ * @param {Object} args
+ * @param {Object} args.item
+ * @param {Number} args.index
+ *
+ * @returns {React.Component}
+ */
+ renderItem({
+ item,
+ index,
+ }) {
+ const shouldDisplayNewIndicator = this.props.report.newMarkerSequenceNumber > 0
+ && item.action.sequenceNumber === this.props.report.newMarkerSequenceNumber;
+ return (
+
+ );
+ }
+
render() {
// Comments have not loaded at all yet do nothing
if (!_.size(this.props.reportActions)) {
return null;
}
+ // Native mobile does not render updates flatlist the changes even though component did update called.
+ // To notify there something changes we can use extraData prop to flatlist
+ const extraData = (!this.props.isDrawerOpen && this.props.isSmallScreenWidth) ? this.props.report.newMarkerSequenceNumber : undefined;
+ const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(this.props.personalDetails, this.props.report);
+
return (
<>
-
+ : null}
+ keyboardShouldPersistTaps="handled"
onLayout={this.recordTimeToMeasureItemLayout}
- sortedReportActions={this.sortedReportActions}
- mostRecentIOUReportSequenceNumber={this.mostRecentIOUReportSequenceNumber}
- isLoadingReportActions={this.props.isLoadingReportActions}
+ onScroll={this.trackScroll}
+ extraData={extraData}
/>
-
>
);
}
@@ -434,6 +568,7 @@ export default compose(
withWindowDimensions,
withDrawerState,
withLocalize,
+ withPersonalDetails(),
withNetwork(),
withOnyx({
isLoadingReportData: {