Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Expensify/App into feat/#Ex…
Browse files Browse the repository at this point in the history
…pensify#23220-bidirectional-pagination
  • Loading branch information
perunt committed Sep 6, 2023
2 parents 42fe76a + 1223e1e commit 3df7a19
Show file tree
Hide file tree
Showing 45 changed files with 845 additions and 332 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/DesignDoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ labels: Daily, NewFeature
- [ ] Confirm that the doc has the minimum necessary number of reviews before proceeding
- [ ] Email `[email protected]` one last time to let them know the Design Doc is moving into the implementation phase
- [ ] Implement the changes
- [ ] Add regression tests so that QA can test your feature with every deploy ([instructions](https://stackoverflowteams.com/c/expensify/questions/363))
- [ ] Send out a follow up email to `[email protected]` once everything has been implemented and do a **Project Wrap-Up** retrospective that provides:
- Summary of what we accomplished with this project
- What went well?
Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1001036300
versionName "1.3.63-0"
versionCode 1001036400
versionName "1.3.64-0"
}

flavorDimensions "default"
Expand Down
4 changes: 2 additions & 2 deletions ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.3.63</string>
<string>1.3.64</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
Expand All @@ -40,7 +40,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.3.63.0</string>
<string>1.3.64.0</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
Expand Down
4 changes: 2 additions & 2 deletions ios/NewExpensifyTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.3.63</string>
<string>1.3.64</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.3.63.0</string>
<string>1.3.64.0</string>
</dict>
</plist>
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
"version": "1.3.63-0",
"version": "1.3.64-0",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
2 changes: 1 addition & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ const CONST = {
},
RECEIPT: {
ICON_SIZE: 164,
PERMISSION_AUTHORIZED: 'authorized',
PERMISSION_GRANTED: 'granted',
HAND_ICON_HEIGHT: 152,
HAND_ICON_WIDTH: 200,
SHUTTER_SIZE: 90,
Expand Down
6 changes: 4 additions & 2 deletions src/components/DownloadAppModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ const defaultProps = {
};

function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) {
const [shouldShowBanner, setshouldShowBanner] = useState(Browser.isMobile() && isAuthenticated && showDownloadAppBanner);
const [shouldShowBanner, setShouldShowBanner] = useState(Browser.isMobile() && isAuthenticated && showDownloadAppBanner);

const {translate} = useLocalize();

const handleCloseBanner = () => {
setShowDownloadAppModal(false);
setshouldShowBanner(false);
setShouldShowBanner(false);
};

let link = '';
Expand All @@ -44,6 +44,8 @@ function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) {
}

const handleOpenAppStore = () => {
setShowDownloadAppModal(false);
setShouldShowBanner(false);
Link.openExternalLink(link, true);
};

Expand Down
50 changes: 9 additions & 41 deletions src/components/EmojiPicker/EmojiPickerMenu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ class EmojiPickerMenu extends Component {

this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300);
this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this);
this.scrollToHighlightedIndex = this.scrollToHighlightedIndex.bind(this);
this.setupEventHandlers = this.setupEventHandlers.bind(this);
this.cleanupEventHandlers = this.cleanupEventHandlers.bind(this);
this.renderItem = this.renderItem.bind(this);
Expand All @@ -76,7 +75,6 @@ class EmojiPickerMenu extends Component {
this.getItemLayout = this.getItemLayout.bind(this);
this.scrollToHeader = this.scrollToHeader.bind(this);

this.currentScrollOffset = 0;
this.firstNonHeaderIndex = 0;

const {filteredEmojis, headerEmojis, headerRowIndices} = this.getEmojisAndHeaderRowIndices();
Expand Down Expand Up @@ -299,9 +297,9 @@ class EmojiPickerMenu extends Component {
return;
}

// Blur the input and change the highlight type to keyboard
// Blur the input, change the highlight type to keyboard, and disable pointer events
this.searchInput.blur();
this.setState({isUsingKeyboardMovement: true});
this.setState({isUsingKeyboardMovement: true, arePointerEventsDisabled: true});

// We only want to hightlight the Emoji if none was highlighted already
// If we already have a highlighted Emoji, lets just skip the first navigation
Expand All @@ -311,10 +309,9 @@ class EmojiPickerMenu extends Component {
}

// If nothing is highlighted and an arrow key is pressed
// select the first emoji
// select the first emoji, apply keyboard movement styles, and disable pointer events
if (this.state.highlightedIndex === -1) {
this.setState({highlightedIndex: this.firstNonHeaderIndex});
this.scrollToHighlightedIndex();
this.setState({highlightedIndex: this.firstNonHeaderIndex, isUsingKeyboardMovement: true, arePointerEventsDisabled: true});
return;
}

Expand Down Expand Up @@ -368,10 +365,9 @@ class EmojiPickerMenu extends Component {
break;
}

// Actually highlight the new emoji, apply keyboard movement styles, and scroll to it if the index was changed
// Actually highlight the new emoji, apply keyboard movement styles, and disable pointer events
if (newIndex !== this.state.highlightedIndex) {
this.setState({highlightedIndex: newIndex, isUsingKeyboardMovement: true});
this.scrollToHighlightedIndex();
this.setState({highlightedIndex: newIndex, isUsingKeyboardMovement: true, arePointerEventsDisabled: true});
}
}

Expand All @@ -381,36 +377,6 @@ class EmojiPickerMenu extends Component {
this.emojiList.scrollToOffset({offset: calculatedOffset, animated: true});
}

/**
* Calculates the required scroll offset (aka distance from top) and scrolls the FlatList to the highlighted emoji
* if any portion of it falls outside of the window.
* Doing this because scrollToIndex doesn't work as expected.
*/
scrollToHighlightedIndex() {
// Calculate the number of rows above the current row, then add 1 to include the current row
const numRows = Math.floor(this.state.highlightedIndex / CONST.EMOJI_NUM_PER_ROW) + 1;

// The scroll offsets at the top and bottom of the highlighted emoji
const offsetAtEmojiBottom = numRows * CONST.EMOJI_PICKER_HEADER_HEIGHT;
const offsetAtEmojiTop = offsetAtEmojiBottom - CONST.EMOJI_PICKER_ITEM_HEIGHT;

// Scroll to fit the entire highlighted emoji into the window if we need to
let targetOffset = this.currentScrollOffset;
if (offsetAtEmojiBottom - this.currentScrollOffset >= CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT) {
targetOffset = offsetAtEmojiBottom - CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT;
} else if (offsetAtEmojiTop - CONST.EMOJI_PICKER_HEADER_HEIGHT <= this.currentScrollOffset) {
// There is always a sticky header on the top, subtract the EMOJI_PICKER_HEADER_HEIGHT from offsetAtEmojiTop to get the correct scroll position.
targetOffset = offsetAtEmojiTop - CONST.EMOJI_PICKER_HEADER_HEIGHT;
}
if (targetOffset !== this.currentScrollOffset) {
// Disable pointer events so that onHover doesn't get triggered when the items move while we're scrolling
if (!this.state.arePointerEventsDisabled) {
this.setState({arePointerEventsDisabled: true});
}
this.emojiList.scrollToOffset({offset: targetOffset, animated: false});
}
}

/**
* Filter the entire list of emojis to only emojis that have the search term in their keywords
*
Expand Down Expand Up @@ -530,6 +496,7 @@ class EmojiPickerMenu extends Component {
return (
<View
style={[styles.emojiPickerContainer, StyleUtils.getEmojiPickerStyle(this.props.isSmallScreenWidth)]}
// Disable pointer events so that onHover doesn't get triggered when the items move while we're scrolling
pointerEvents={this.state.arePointerEventsDisabled ? 'none' : 'auto'}
>
<View style={[styles.ph4, styles.pb3, styles.pt2]}>
Expand Down Expand Up @@ -566,10 +533,11 @@ class EmojiPickerMenu extends Component {
{overscrollBehaviorY: 'contain'},
// Set overflow to hidden to prevent elastic scrolling when there are not enough contents to scroll in FlatList
{overflowY: this.state.filteredEmojis.length > overflowLimit ? 'auto' : 'hidden'},
// Set scrollPaddingTop to consider sticky headers while scrolling
{scrollPaddingTop: isFiltered ? 0 : CONST.EMOJI_PICKER_ITEM_HEIGHT},
]}
extraData={[this.state.filteredEmojis, this.state.highlightedIndex, this.props.preferredSkinTone]}
stickyHeaderIndices={this.state.headerIndices}
onScroll={(e) => (this.currentScrollOffset = e.nativeEvent.contentOffset.y)}
getItemLayout={this.getItemLayout}
contentContainerStyle={styles.flexGrow1}
ListEmptyComponent={<Text style={[styles.textLabel, styles.colorMuted]}>{this.props.translate('common.noResultsFound')}</Text>}
Expand Down
10 changes: 8 additions & 2 deletions src/components/EmojiPicker/EmojiPickerMenuItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ class EmojiPickerMenuItem extends PureComponent {
super(props);

this.ref = null;
this.focusAndScroll = this.focusAndScroll.bind(this);
}

componentDidMount() {
if (!this.props.isFocused) {
return;
}
this.ref.focus();
this.focusAndScroll();
}

componentDidUpdate(prevProps) {
Expand All @@ -58,7 +59,12 @@ class EmojiPickerMenuItem extends PureComponent {
if (!this.props.isFocused) {
return;
}
this.ref.focus();
this.focusAndScroll();
}

focusAndScroll() {
this.ref.focus({preventScroll: true});
this.ref.scrollIntoView({block: 'nearest'});
}

render() {
Expand Down
19 changes: 18 additions & 1 deletion src/components/LHNOptionsList/OptionRowLHNData.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDe
import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultProps} from './OptionRowLHN';
import * as Report from '../../libs/actions/Report';
import * as UserUtils from '../../libs/UserUtils';
import * as ReportActionsUtils from '../../libs/ReportActionsUtils';
import * as TransactionUtils from '../../libs/TransactionUtils';

import participantPropTypes from '../participantPropTypes';
import CONST from '../../CONST';
import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes';
Expand Down Expand Up @@ -75,6 +78,7 @@ function OptionRowLHNData({
preferredLocale,
comment,
policies,
receiptTransactions,
parentReportActions,
...propsToForward
}) {
Expand All @@ -88,6 +92,14 @@ function OptionRowLHNData({
const parentReportAction = parentReportActions[fullReport.parentReportActionID];

const optionItemRef = useRef();

const linkedTransaction = useMemo(() => {
const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions);
const lastReportAction = _.first(sortedReportActions);
return TransactionUtils.getLinkedTransaction(lastReportAction);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fullReport.reportID, receiptTransactions, reportActions]);

const optionItem = useMemo(() => {
// Note: ideally we'd have this as a dependent selector in onyx!
const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy);
Expand All @@ -98,7 +110,7 @@ function OptionRowLHNData({
return item;
// Listen parentReportAction to update title of thread report when parentReportAction changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction]);
}, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]);

useEffect(() => {
if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) {
Expand Down Expand Up @@ -186,6 +198,11 @@ export default React.memo(
key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`,
canEvict: false,
},
// Ideally, we aim to access only the last transaction for the current report by listening to changes in reportActions.
// In some scenarios, a transaction might be created after reportActions have been modified.
// This can lead to situations where `lastTransaction` doesn't update and retains the previous value.
// However, performance overhead of this is minimized by using memos inside the component.
receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION},
}),
)(OptionRowLHNData),
);
4 changes: 3 additions & 1 deletion src/components/ReportActionItem/MoneyRequestPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ function MoneyRequestPreview(props) {
!_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT;
const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant;

const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || props.transaction.receiptFilename || '')] : [];

const getSettledMessage = () => {
switch (lodashGet(props.action, 'originalMessage.paymentType', '')) {
case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME:
Expand Down Expand Up @@ -231,7 +233,7 @@ function MoneyRequestPreview(props) {
<View style={[styles.moneyRequestPreviewBox, isScanning || props.isWhisper ? styles.reportPreviewBoxHoverBorder : undefined, ...props.containerStyles]}>
{hasReceipt && (
<ReportActionItemImages
images={[ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || '')]}
images={receiptImages}
isHovered={isScanning}
/>
)}
Expand Down
Loading

0 comments on commit 3df7a19

Please sign in to comment.