Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🍒 Cherry pick PR #3208 to staging 🍒 #3214

Merged
merged 41 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
830b95e
old Animation changes
parasharrajat May 16, 2021
c66c698
Fix issues with search page
jasperhuangg May 17, 2021
89a99f9
Fix comment
jasperhuangg May 17, 2021
1ed4111
Fix comment
jasperhuangg May 17, 2021
4afe449
Remove debug
jasperhuangg May 17, 2021
0db6f8c
Make prop required since it will always be included
jasperhuangg May 17, 2021
2d1f7c7
style
jasperhuangg May 18, 2021
c0c9043
Merge branch 'main' into jasper-searchPageFix
jasperhuangg May 18, 2021
c2629b9
Use set
jasperhuangg May 18, 2021
9053938
Update src/components/OptionsList.js
jasperhuangg May 18, 2021
9e203cd
Remove full display names changes
jasperhuangg May 18, 2021
ff7eb32
merge
jasperhuangg May 18, 2021
7908e9b
Remove leftover unused
jasperhuangg May 18, 2021
194653b
Remove leftover unused
jasperhuangg May 18, 2021
328cca6
Add back stuff messed up by merge
jasperhuangg May 18, 2021
fd39d88
Remove unneeded whitespace change
jasperhuangg May 18, 2021
e3d6424
Use participants set in search
jasperhuangg May 24, 2021
6863bd4
Merge branch 'main' of github.com:Expensify/Expensify.cash into jaspe…
jasperhuangg May 24, 2021
7a451c8
style
jasperhuangg May 24, 2021
06bf09c
fix line breaks
jasperhuangg May 25, 2021
e18549b
use _.each
jasperhuangg May 25, 2021
ec3f9d6
Rename participantsSet => participantNames
jasperhuangg May 25, 2021
77e5b53
Change participantNames to an Object
jasperhuangg May 26, 2021
4854d34
Merge branch 'main' into jasper-searchPageFix
jasperhuangg May 26, 2021
7699460
comment
jasperhuangg May 26, 2021
af978cd
Merge branch 'jasper-searchPageFix' of github.com:Expensify/Expensify…
jasperhuangg May 26, 2021
44647ff
Update comment
jasperhuangg May 27, 2021
8024c7a
simplify logic
jasperhuangg May 27, 2021
a6063b2
remove set usage
jasperhuangg May 27, 2021
b8a34a5
Fix jest unit tests
jasperhuangg May 27, 2021
cb31f89
remove unused comment
jasperhuangg May 27, 2021
3ff9eb5
fixed merge conflicts and pulled latest changes
parasharrajat May 27, 2021
bc695a5
fix: IOS spinner
parasharrajat May 27, 2021
d1177d8
Move logic to a separate function, use Set for clarity
jasperhuangg May 28, 2021
71c58ee
Remove export
jasperhuangg May 28, 2021
18190bb
Temporarily replace green with cyan
roryabraham May 28, 2021
7d23065
Merge branch 'main' into Rory-DummyPR-B
roryabraham May 28, 2021
5177fe9
fix bad merge
roryabraham May 28, 2021
bbbefbf
Merge pull request #2956 from parasharrajat/animations
May 28, 2021
0c8dd5a
Merge pull request #2958 from Expensify/jasper-searchPageFix
marcaaron May 28, 2021
d428542
Merge pull request #3208 from Expensify/Rory-DummyPR-B
deetergp May 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions src/components/ScreenWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SafeAreaInsetsContext} from 'react-native-safe-area-context';
import styles, {getSafeAreaPadding} from '../styles/styles';
import HeaderGap from './HeaderGap';
import KeyboardShortcut from '../libs/KeyboardShortcut';
import onScreenTransitionEnd from '../libs/onScreenTransitionEnd';

const propTypes = {
/** Array of additional styles to add */
Expand All @@ -24,9 +25,15 @@ const propTypes = {
/** Whether to include padding top */
includePaddingTop: PropTypes.bool,

/** react-navigation object that will allow us to goBack() */
// Called when navigated Screen's transition is finished.
onTransitionEnd: PropTypes.func,

// react-navigation navigation object available to screen components
navigation: PropTypes.shape({
/** Returns to the previous navigation state e.g. if this is inside a Modal we will dismiss it */
// Method to attach listner to Navigaton state.
addListener: PropTypes.func.isRequired,

// Returns to the previous navigation state e.g. if this is inside a Modal we will dismiss it
goBack: PropTypes.func,
}),
};
Expand All @@ -35,24 +42,39 @@ const defaultProps = {
style: [],
includePaddingBottom: true,
includePaddingTop: true,
onTransitionEnd: () => {},
navigation: {
addListener: () => {},
goBack: () => {},
},
};

class ScreenWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
didScreenTransitionEnd: false,
};
}

componentDidMount() {
this.unsubscribe = KeyboardShortcut.subscribe('Escape', () => {
this.unsubscribeEscapeKey = KeyboardShortcut.subscribe('Escape', () => {
this.props.navigation.goBack();
}, [], true);

this.unsubscribeTransitionEnd = onScreenTransitionEnd(this.props.navigation, () => {
this.setState({didScreenTransitionEnd: true});
this.props.onTransitionEnd();
});
}

componentWillUnmount() {
if (!this.unsubscribe) {
return;
if (this.unsubscribeEscapeKey) {
this.unsubscribeEscapeKey();
}
if (this.unsubscribeTransitionEnd) {
this.unsubscribeTransitionEnd();
}

this.unsubscribe();
}

render() {
Expand Down Expand Up @@ -80,7 +102,10 @@ class ScreenWrapper extends React.Component {
<HeaderGap />
{// If props.children is a function, call it to provide the insets to the children.
_.isFunction(this.props.children)
? this.props.children(insets)
? this.props.children({
insets,
didScreenTransitionEnd: this.state.didScreenTransitionEnd,
})
: this.props.children
}
</View>
Expand Down
8 changes: 5 additions & 3 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ Onyx.connect({

const RootStack = createCustomModalStackNavigator();

// When modal screen gets focused, update modal visibility in Onyx
// We want to delay the re-rendering for components(e.g. ReportActionCompose)
// that depends on modal visibility until Modal is completely closed or its transition has ended
// When modal screen is focused and animation transition is ended, update modal visibility in Onyx
// https://reactnavigation.org/docs/navigation-events/
const modalScreenListeners = {
focus: () => {
transitionEnd: () => {
setModalVisibility(true);
},
beforeRemove: () => {
Expand Down Expand Up @@ -160,7 +162,7 @@ class AuthScreens extends React.Component {
const modalScreenOptions = {
headerShown: false,
cardStyle: getNavigationModalCardStyle(this.props.isSmallScreenWidth),
cardStyleInterpolator: modalCardStyleInterpolator,
cardStyleInterpolator: props => modalCardStyleInterpolator(this.props.isSmallScreenWidth, props),
animationEnabled: true,
gestureDirection: 'horizontal',
cardOverlayEnabled: true,
Expand Down
18 changes: 11 additions & 7 deletions src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import {Animated} from 'react-native';
import variables from '../../../styles/variables';

export default ({
current: {progress},
inverted,
layouts: {
screen,
export default (
isSmallScreen,
{
current: {progress},
inverted,
layouts: {
screen,
},
},
}) => {
) => {
const translateX = Animated.multiply(progress.interpolate({
inputRange: [0, 1],
outputRange: [screen.width, 0],
outputRange: [isSmallScreen ? screen.width : variables.sideBarWidth, 0],
extrapolate: 'clamp',
}), inverted);

Expand Down
52 changes: 43 additions & 9 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,34 @@ function getPersonalDetailsForLogins(logins, personalDetails) {
});
}

/**
* Constructs a Set with all possible names (displayName, firstName, lastName, email) for all participants in a report,
* to be used in isSearchStringMatch.
*
* @param {Array<Object>} personalDetailList
* @return {Set<String>}
*/
function getParticipantNames(personalDetailList) {
// We use a Set because `Set.has(value)` on a Set of with n entries is up to n (or log(n)) times faster than
// `_.contains(Array, value)` for an Array with n members.
const participantNames = new Set();
_.each(personalDetailList, (participant) => {
if (participant.login) {
participantNames.add(participant.login.toLowerCase());
}
if (participant.firstName) {
participantNames.add(participant.firstName.toLowerCase());
}
if (participant.lastName) {
participantNames.add(participant.lastName.toLowerCase());
}
if (participant.displayName) {
participantNames.add(participant.displayName.toLowerCase());
}
});
return participantNames;
}

/**
* Returns a string with all relevant search terms
*
Expand All @@ -62,8 +90,8 @@ function getSearchText(report, personalDetailList) {
searchTerms.push(personalDetail.displayName);
searchTerms.push(personalDetail.login);
});

if (report) {
searchTerms.push(...report.reportName);
searchTerms.push(...report.reportName.split(',').map(name => name.trim()));
searchTerms.push(...report.participants);
}
Expand Down Expand Up @@ -140,14 +168,18 @@ Onyx.connect({
*
* @param {String} searchValue
* @param {String} searchText
* @param {Set<String>} participantNames
* @returns {Boolean}
*/
function isSearchStringMatch(searchValue, searchText) {
const searchWords = searchValue.split(' ');
function isSearchStringMatch(searchValue, searchText, participantNames) {
const searchWords = searchValue
.replace(/,/g, ' ')
.split(' ')
.map(word => word.trim());
return _.every(searchWords, (word) => {
const matchRegex = new RegExp(Str.escapeForRegExp(word), 'i');
const valueToSearch = searchText && searchText.replace(new RegExp(/&nbsp;/g), '');
return matchRegex.test(valueToSearch);
return matchRegex.test(valueToSearch) || participantNames.has(word);
});
}

Expand Down Expand Up @@ -250,8 +282,10 @@ function getOptions(reports, personalDetails, draftComments, activeReportID, {
continue;
}

// Finally check to see if this options is a match for the provided search string if we have one
if (searchValue && !isSearchStringMatch(searchValue, reportOption.searchText)) {
// Finally check to see if this option is a match for the provided search string if we have one
const {searchText, participantsList} = reportOption;
const participantNames = getParticipantNames(participantsList);
if (searchValue && !isSearchStringMatch(searchValue, searchText, participantNames)) {
continue;
}

Expand Down Expand Up @@ -284,11 +318,11 @@ function getOptions(reports, personalDetails, draftComments, activeReportID, {
))) {
return;
}

if (searchValue && !isSearchStringMatch(searchValue, personalDetailOption.searchText)) {
const {searchText, participantsList} = personalDetailOption;
const participantNames = getParticipantNames(participantsList);
if (searchValue && !isSearchStringMatch(searchValue, searchText, participantNames)) {
return;
}

personalDetailsOptions.push(personalDetailOption);
});
}
Expand Down
19 changes: 19 additions & 0 deletions src/libs/onScreenTransitionEnd/index.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Navigation's transitionEnd is not reliable on IOS thus we use InteractonManager
* Some information https://github.com/software-mansion/react-native-screens/issues/713
*/
import {InteractionManager} from 'react-native';

/**
* Call the callback after screen transiton has ended
*
* @param {Object} navigation Screen navigation prop
* @param {Function} callback Method to call
* @returns {Function}
*/
function onScreenTransitionEnd(navigation, callback) {
const handle = InteractionManager.runAfterInteractions(callback);
return () => handle.cancel();
}

export default onScreenTransitionEnd;
14 changes: 14 additions & 0 deletions src/libs/onScreenTransitionEnd/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Str from 'expensify-common/lib/str';

/**
* Call the callback after screen transiton has ended
*
* @param {Object} navigation Screen navigation prop
* @param {Function} callback Method to call
* @returns {Function}
*/
function onScreenTransitionEnd(navigation, callback) {
return navigation.addListener('transitionEnd', evt => Str.result(callback, evt));
}

export default onScreenTransitionEnd;
75 changes: 41 additions & 34 deletions src/pages/NewChatPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../components/wit
import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
import Navigation from '../libs/Navigation/Navigation';
import ScreenWrapper from '../components/ScreenWrapper';
import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
import withLocalize, {withLocalizePropTypes} from '../components/withLocalize';
import compose from '../libs/compose';

Expand Down Expand Up @@ -53,7 +54,6 @@ class NewChatPage extends Component {
super(props);

this.createNewChat = this.createNewChat.bind(this);

const {
recentReports,
personalDetails,
Expand Down Expand Up @@ -127,39 +127,46 @@ class NewChatPage extends Component {

return (
<ScreenWrapper>
<HeaderWithCloseButton
title={this.props.translate('sidebarScreen.newChat')}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<View style={[styles.flex1, styles.w100]}>
<OptionsSelector
sections={sections}
value={this.state.searchValue}
onSelectRow={this.createNewChat}
onChangeText={(searchValue = '') => {
const {
recentReports,
personalDetails,
userToInvite,
} = getNewChatOptions(
this.props.reports,
this.props.personalDetails,
searchValue,
);
this.setState({
searchValue,
recentReports,
userToInvite,
personalDetails,
});
}}
headerMessage={headerMessage}
disableArrowKeysActions
hideAdditionalOptionStates
forceTextUnreadStyle
/>
</View>
<KeyboardSpacer />
{({didScreenTransitionEnd}) => (
<>
<HeaderWithCloseButton
title={this.props.translate('sidebarScreen.newChat')}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<View style={[styles.flex1, styles.w100, styles.pRelative]}>
<FullScreenLoadingIndicator visible={!didScreenTransitionEnd} />
{didScreenTransitionEnd && (
<OptionsSelector
sections={sections}
value={this.state.searchValue}
onSelectRow={this.createNewChat}
onChangeText={(searchValue = '') => {
const {
recentReports,
personalDetails,
userToInvite,
} = getNewChatOptions(
this.props.reports,
this.props.personalDetails,
searchValue,
);
this.setState({
searchValue,
recentReports,
userToInvite,
personalDetails,
});
}}
headerMessage={headerMessage}
disableArrowKeysActions
hideAdditionalOptionStates
forceTextUnreadStyle
/>
)}
</View>
<KeyboardSpacer />
</>
)}
</ScreenWrapper>
);
}
Expand Down
Loading