Skip to content

Commit

Permalink
Merge pull request #27190 from s-alves10/fix/issue-23908
Browse files Browse the repository at this point in the history
fix: issue 23908
  • Loading branch information
techievivek authored Sep 15, 2023
2 parents 1862aff + 4e9083f commit 2427a06
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 49 deletions.
4 changes: 3 additions & 1 deletion src/components/EmojiPicker/EmojiPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@ const EmojiPicker = forwardRef((props, ref) => {
*/
const isActive = (id) => Boolean(id) && id === activeID;

const clearActive = () => setActiveID(null);

const resetEmojiPopoverAnchor = () => (emojiPopoverAnchor.current = null);

useImperativeHandle(ref, () => ({showEmojiPicker, isActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor}));
useImperativeHandle(ref, () => ({showEmojiPicker, isActive, clearActive, hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor}));

useEffect(() => {
const emojiPopoverDimensionListener = Dimensions.addEventListener('change', () => {
Expand Down
9 changes: 8 additions & 1 deletion src/libs/actions/EmojiPickerAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ function isActive(id) {
return emojiPickerRef.current.isActive(id);
}

function clearActive() {
if (!emojiPickerRef.current) {
return;
}
return emojiPickerRef.current.clearActive();
}

function isEmojiPickerVisible() {
if (!emojiPickerRef.current) {
return;
Expand All @@ -59,4 +66,4 @@ function resetEmojiPopoverAnchor() {
return emojiPickerRef.current.resetEmojiPopoverAnchor();
}

export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, isEmojiPickerVisible, resetEmojiPopoverAnchor};
export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor};

This file was deleted.

This file was deleted.

5 changes: 5 additions & 0 deletions src/libs/setShouldShowComposeInputKeyboardAware/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as Composer from '../actions/Composer';

export default (shouldShow) => {
Composer.setShouldShowComposeInput(shouldShow);
};
26 changes: 26 additions & 0 deletions src/libs/setShouldShowComposeInputKeyboardAware/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Keyboard} from 'react-native';
import * as Composer from '../actions/Composer';

let keyboardDidHideListener = null;
export default (shouldShow) => {
if (keyboardDidHideListener) {
keyboardDidHideListener.remove();
keyboardDidHideListener = null;
}

if (!shouldShow) {
Composer.setShouldShowComposeInput(false);
return;
}

// If keyboard is already hidden, we should show composer immediately because keyboardDidHide event won't be called
if (!Keyboard.isVisible()) {
Composer.setShouldShowComposeInput(true);
return;
}

keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
Composer.setShouldShowComposeInput(true);
keyboardDidHideListener.remove();
});
};
6 changes: 3 additions & 3 deletions src/pages/home/report/ContextMenu/ContextMenuActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as ReportUtils from '../../../../libs/ReportUtils';
import * as ReportActionsUtils from '../../../../libs/ReportActionsUtils';
import * as PersonalDetailsUtils from '../../../../libs/PersonalDetailsUtils';
import ReportActionComposeFocusManager from '../../../../libs/ReportActionComposeFocusManager';
import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu';
import {hideContextMenu, showDeleteModal, clearActiveReportAction} from './ReportActionContextMenu';
import CONST from '../../../../CONST';
import getAttachmentDetails from '../../../../libs/fileDownload/getAttachmentDetails';
import fileDownload from '../../../../libs/fileDownload';
Expand Down Expand Up @@ -317,12 +317,12 @@ export default [
onPress: (closePopover, {reportID, reportAction}) => {
if (closePopover) {
// Hide popover, then call showDeleteConfirmModal
hideContextMenu(false, () => showDeleteModal(reportID, reportAction));
hideContextMenu(false, () => showDeleteModal(reportID, reportAction, true, clearActiveReportAction, clearActiveReportAction));
return;
}

// No popover to hide, call showDeleteConfirmModal immediately
showDeleteModal(reportID, reportAction);
showDeleteModal(reportID, reportAction, true, clearActiveReportAction, clearActiveReportAction);
},
getDescription: () => {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class PopoverReportActionContextMenu extends React.Component {
reportID: '0',
reportActionID: '0',
originalReportID: '0',
reportAction: {},
selection: '',
reportActionDraftMessage: '',
isPopoverVisible: false,
Expand Down Expand Up @@ -57,6 +58,7 @@ class PopoverReportActionContextMenu extends React.Component {
this.runAndResetOnPopoverHide = this.runAndResetOnPopoverHide.bind(this);
this.getContextMenuMeasuredLocation = this.getContextMenuMeasuredLocation.bind(this);
this.isActiveReportAction = this.isActiveReportAction.bind(this);
this.clearActiveReportAction = this.clearActiveReportAction.bind(this);

this.dimensionsEventListener = null;

Expand Down Expand Up @@ -113,7 +115,11 @@ class PopoverReportActionContextMenu extends React.Component {
* @return {Boolean}
*/
isActiveReportAction(actionID) {
return Boolean(actionID) && this.state.reportActionID === actionID;
return Boolean(actionID) && (this.state.reportActionID === actionID || this.state.reportAction.reportActionID === actionID);
}

clearActiveReportAction() {
this.setState({reportID: '0', reportAction: {}});
}

/**
Expand Down Expand Up @@ -332,10 +338,7 @@ class PopoverReportActionContextMenu extends React.Component {
shouldSetModalVisibility={this.state.shouldSetModalVisibilityForDeleteConfirmation}
onConfirm={this.confirmDeleteAndHideModal}
onCancel={this.hideDeleteModal}
onModalHide={() => {
this.setState({reportID: '0', reportAction: {}});
this.callbackWhenDeleteModalHide();
}}
onModalHide={this.callbackWhenDeleteModalHide}
prompt={this.props.translate('reportActionContextMenu.deleteConfirmation', {action: this.state.reportAction})}
confirmText={this.props.translate('common.delete')}
cancelText={this.props.translate('common.cancel')}
Expand Down
9 changes: 8 additions & 1 deletion src/pages/home/report/ContextMenu/ReportActionContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,11 @@ function isActiveReportAction(actionID) {
return contextMenuRef.current.isActiveReportAction(actionID);
}

export {contextMenuRef, showContextMenu, hideContextMenu, isActiveReportAction, showDeleteModal, hideDeleteModal};
function clearActiveReportAction() {
if (!contextMenuRef.current) {
return;
}
return contextMenuRef.current.clearActiveReportAction();
}

export {contextMenuRef, showContextMenu, hideContextMenu, isActiveReportAction, clearActiveReportAction, showDeleteModal, hideDeleteModal};
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ function ReportActionCompose({

const onBlur = useCallback((e) => {
setIsFocused(false);
suggestionsRef.current.resetSuggestions();
if (suggestionsRef.current) {
suggestionsRef.current.resetSuggestions();
}
if (e.relatedTarget && e.relatedTarget === actionButtonRef.current) {
isKeyboardVisibleWhenShowingModalRef.current = true;
}
Expand Down
6 changes: 6 additions & 0 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ function ReportActionItem(props) {
const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action);
const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID);

// When active action changes, we need to update the `isContextMenuActive` state
const isActiveReportActionForMenu = ReportActionContextMenu.isActiveReportAction(props.action.reportActionID);
useEffect(() => {
setIsContextMenuActive(isActiveReportActionForMenu);
}, [isActiveReportActionForMenu]);

const updateHiddenState = useCallback(
(isHiddenValue) => {
setIsHidden(isHiddenValue);
Expand Down
63 changes: 40 additions & 23 deletions src/pages/home/report/ReportActionItemMessageEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import containerComposeStyles from '../../../styles/containerComposeStyles';
import Composer from '../../../components/Composer';
import * as Report from '../../../libs/actions/Report';
import {withReportActionsDrafts} from '../../../components/OnyxProvider';
import openReportActionComposeViewWhenClosingMessageEdit from '../../../libs/openReportActionComposeViewWhenClosingMessageEdit';
import setShouldShowComposeInputKeyboardAware from '../../../libs/setShouldShowComposeInputKeyboardAware';
import ReportActionComposeFocusManager from '../../../libs/ReportActionComposeFocusManager';
import EmojiPickerButton from '../../../components/EmojiPicker/EmojiPickerButton';
import Icon from '../../../components/Icon';
Expand All @@ -28,7 +28,6 @@ import ExceededCommentLength from '../../../components/ExceededCommentLength';
import CONST from '../../../CONST';
import refPropTypes from '../../../components/refPropTypes';
import * as ComposerUtils from '../../../libs/ComposerUtils';
import * as ComposerActions from '../../../libs/actions/Composer';
import * as User from '../../../libs/actions/User';
import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback';
import getButtonState from '../../../libs/getButtonState';
Expand Down Expand Up @@ -84,8 +83,6 @@ const defaultProps = {
};

// native ids
const saveButtonID = 'saveButton';
const cancelButtonID = 'cancelButton';
const emojiButtonID = 'emojiButton';
const messageEditInput = 'messageEditInput';

Expand Down Expand Up @@ -130,6 +127,12 @@ function ReportActionItemMessageEdit(props) {
isFocusedRef.current = isFocused;
}, [isFocused]);

// We consider the report action active if it's focused, its emoji picker is open or its context menu is open
const isActive = useCallback(
() => isFocusedRef.current || EmojiPickerAction.isActive(props.action.reportActionID) || ReportActionContextMenu.isActiveReportAction(props.action.reportActionID),
[props.action.reportActionID],
);

useEffect(() => {
// For mobile Safari, updating the selection prop on an unfocused input will cause it to automatically gain focus
// and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style),
Expand All @@ -145,16 +148,23 @@ function ReportActionItemMessageEdit(props) {
}

return () => {
// Skip if this is not the focused message so the other edit composer stays focused.
// In small screen devices, when EmojiPicker is shown, the current edit message will lose focus, we need to check this case as well.
if (!isFocusedRef.current && !EmojiPickerAction.isActive(props.action.reportActionID)) {
// Skip if the current report action is not active
if (!isActive()) {
return;
}

if (EmojiPickerAction.isActive(props.action.reportActionID)) {
EmojiPickerAction.clearActive();
}
if (ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)) {
ReportActionContextMenu.clearActiveReportAction();
}

// Show the main composer when the focused message is deleted from another client
// to prevent the main composer stays hidden until we swtich to another chat.
ComposerActions.setShouldShowComposeInput(true);
setShouldShowComposeInputKeyboardAware(true);
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- this cleanup needs to be called only on unmount
}, [props.action.reportActionID]);

/**
Expand Down Expand Up @@ -227,9 +237,11 @@ function ReportActionItemMessageEdit(props) {
const deleteDraft = useCallback(() => {
debouncedSaveDraft.cancel();
Report.saveReportActionDraft(props.reportID, props.action.reportActionID, '');
ComposerActions.setShouldShowComposeInput(true);
ReportActionComposeFocusManager.clear();
ReportActionComposeFocusManager.focus();

if (isActive()) {
ReportActionComposeFocusManager.clear();
ReportActionComposeFocusManager.focus();
}

// Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report.
if (props.index === 0) {
Expand All @@ -238,7 +250,7 @@ function ReportActionItemMessageEdit(props) {
keyboardDidHideListener.remove();
});
}
}, [props.action.reportActionID, debouncedSaveDraft, props.index, props.reportID, reportScrollManager]);
}, [props.action.reportActionID, debouncedSaveDraft, props.index, props.reportID, reportScrollManager, isActive]);

/**
* Save the draft of the comment to be the new comment message. This will take the comment out of "edit mode" with
Expand Down Expand Up @@ -273,6 +285,7 @@ function ReportActionItemMessageEdit(props) {

// When user tries to save the empty message, it will delete it. Prompt the user to confirm deleting.
if (!trimmedNewDraft) {
textInputRef.current.blur();
ReportActionContextMenu.showDeleteModal(props.reportID, props.action, false, deleteDraft, () => InteractionManager.runAfterInteractions(() => textInputRef.current.focus()));
return;
}
Expand Down Expand Up @@ -325,14 +338,15 @@ function ReportActionItemMessageEdit(props) {
<PressableWithFeedback
onPress={deleteDraft}
style={styles.chatItemSubmitButton}
nativeID={cancelButtonID}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={translate('common.close')}
// disable dimming
hoverDimmingValue={1}
pressDimmingValue={1}
hoverStyle={StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.ACTIVE)}
pressStyle={StyleUtils.getButtonBackgroundColorStyle(CONST.BUTTON_STATES.PRESSED)}
// Keep focus on the composer when cancel button is clicked.
onMouseDown={(e) => e.preventDefault()}
>
{({hovered, pressed}) => (
<Icon
Expand Down Expand Up @@ -369,21 +383,23 @@ function ReportActionItemMessageEdit(props) {
onFocus={() => {
setIsFocused(true);
reportScrollManager.scrollToIndex({animated: true, index: props.index}, true);
ComposerActions.setShouldShowComposeInput(false);
setShouldShowComposeInputKeyboardAware(false);

// Clear active report action when another action gets focused
if (!EmojiPickerAction.isActive(props.action.reportActionID)) {
EmojiPickerAction.clearActive();
}
if (!ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)) {
ReportActionContextMenu.clearActiveReportAction();
}
}}
onBlur={(event) => {
setIsFocused(false);
const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id');

// Return to prevent re-render when save/cancel button is pressed which cancels the onPress event by re-rendering
if (_.contains([saveButtonID, cancelButtonID, emojiButtonID], relatedTargetId)) {
return;
}

if (messageEditInput === relatedTargetId) {
if (_.contains([messageEditInput, emojiButtonID], relatedTargetId)) {
return;
}
openReportActionComposeViewWhenClosingMessageEdit();
setShouldShowComposeInputKeyboardAware(true);
}}
selection={selection}
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
Expand All @@ -407,12 +423,13 @@ function ReportActionItemMessageEdit(props) {
<PressableWithFeedback
style={[styles.chatItemSubmitButton, hasExceededMaxCommentLength ? {} : styles.buttonSuccess]}
onPress={publishDraft}
nativeID={saveButtonID}
disabled={hasExceededMaxCommentLength}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={translate('common.saveChanges')}
hoverDimmingValue={1}
pressDimmingValue={0.2}
// Keep focus on the composer when send button is clicked.
onMouseDown={(e) => e.preventDefault()}
>
<Icon
src={Expensicons.Checkmark}
Expand Down

0 comments on commit 2427a06

Please sign in to comment.