Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fix…
Browse files Browse the repository at this point in the history
…/46416-rbr-display
  • Loading branch information
rezkiy37 committed Aug 8, 2024
2 parents 6d86ef9 + d7b0aba commit 6c4661e
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/components/FeatureTrainingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function FeatureTrainingModal({
url={videoURL}
videoPlayerStyle={[styles.onboardingVideoPlayer, {aspectRatio}]}
onVideoLoaded={setAspectRatio}
controlsStatus={CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW}
controlsStatus={CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE}
shouldUseControlsBottomMargin={false}
shouldPlay
isLooping
Expand Down
15 changes: 9 additions & 6 deletions src/components/Lottie/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {View} from 'react-native';
import type DotLottieAnimation from '@components/LottieAnimations/types';
import useAppState from '@hooks/useAppState';
import useNetwork from '@hooks/useNetwork';
import useSplashScreen from '@hooks/useSplashScreen';
import useThemeStyles from '@hooks/useThemeStyles';

type Props = {
Expand All @@ -14,6 +15,7 @@ type Props = {

function Lottie({source, webStyle, ...props}: Props, ref: ForwardedRef<LottieView>) {
const appState = useAppState();
const {isSplashHidden} = useSplashScreen();
const styles = useThemeStyles();
const [isError, setIsError] = React.useState(false);

Expand All @@ -27,14 +29,15 @@ function Lottie({source, webStyle, ...props}: Props, ref: ForwardedRef<LottieVie

const aspectRatioStyle = styles.aspectRatioLottie(source);

// If the image fails to load or app is in background state, we'll just render an empty view
// using the fallback in case of a Lottie error or appState.isBackground to prevent
// memory leak, see issue: https://github.com/Expensify/App/issues/36645
if (isError || appState.isBackground) {
// If the image fails to load, app is in background state, animation file isn't ready, or the splash screen isn't hidden yet,
// we'll just render an empty view as the fallback to prevent
// 1. memory leak, see issue: https://github.com/Expensify/App/issues/36645
// 2. heavy rendering, see issue: https://github.com/Expensify/App/issues/34696
if (isError || appState.isBackground || !animationFile || !isSplashHidden) {
return <View style={[aspectRatioStyle, props.style]} />;
}

return animationFile ? (
return (
<LottieView
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
Expand All @@ -44,7 +47,7 @@ function Lottie({source, webStyle, ...props}: Props, ref: ForwardedRef<LottieVie
webStyle={{...aspectRatioStyle, ...webStyle}}
onAnimationFailure={() => setIsError(true)}
/>
) : null;
);
}

Lottie.displayName = 'Lottie';
Expand Down
61 changes: 56 additions & 5 deletions src/components/VideoPlayer/BaseVideoPlayer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable no-underscore-dangle */
import type {AVPlaybackStatus, VideoFullscreenUpdateEvent} from 'expo-av';
import {ResizeMode, Video, VideoFullscreenUpdate} from 'expo-av';
import {debounce} from 'lodash';
import type {MutableRefObject} from 'react';
import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {GestureResponderEvent} from 'react-native';
import {View} from 'react-native';
import {runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import AttachmentOfflineIndicator from '@components/AttachmentOfflineIndicator';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Hoverable from '@components/Hoverable';
Expand Down Expand Up @@ -73,6 +75,11 @@ function BaseVideoPlayer({
const [sourceURL] = useState(VideoUtils.addSkipTimeTagToURL(url.includes('blob:') || url.includes('file:///') ? url : addEncryptedAuthTokenToURL(url), 0.001));
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0});
const [controlStatusState, setControlStatusState] = useState(controlsStatus);
const controlsOpacity = useSharedValue(1);
const controlsAnimatedStyle = useAnimatedStyle(() => ({
opacity: controlsOpacity.value,
}));

const videoPlayerRef = useRef<VideoWithOnFullScreenUpdate | null>(null);
const videoPlayerElementParentRef = useRef<View | HTMLDivElement | null>(null);
Expand All @@ -96,6 +103,46 @@ function BaseVideoPlayer({
}
}, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url, videoResumeTryNumberRef]);

const hideControl = useCallback(() => {
// eslint-disable-next-line react-compiler/react-compiler
controlsOpacity.value = withTiming(0, {duration: 500}, () => runOnJS(setControlStatusState)(CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE));
}, [controlsOpacity]);
const debouncedHideControl = useMemo(() => debounce(hideControl, 1500), [hideControl]);

useEffect(() => {
if (canUseTouchScreen) {
return;
}
// If the device cannot use touch screen, always set the control status as 'show'.
// Then if user hover over the video, controls is shown.
setControlStatusState(CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW);
}, [canUseTouchScreen]);

useEffect(() => {
// We only auto hide the control if the device can use touch screen.
if (!canUseTouchScreen) {
return;
}
if (controlStatusState !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW) {
return;
}
if (!isPlaying || isPopoverVisible) {
debouncedHideControl.cancel();
return;
}

debouncedHideControl();
}, [isPlaying, debouncedHideControl, controlStatusState, isPopoverVisible, canUseTouchScreen]);

const toggleControl = useCallback(() => {
if (controlStatusState === CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW) {
hideControl();
return;
}
setControlStatusState(CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW);
controlsOpacity.value = 1;
}, [controlStatusState, controlsOpacity, hideControl]);

const showPopoverMenu = (event?: GestureResponderEvent | KeyboardEvent) => {
videoPopoverMenuPlayerRef.current = videoPlayerRef.current;
videoPlayerRef.current?.getStatusAsync().then((status) => {
Expand Down Expand Up @@ -311,7 +358,11 @@ function BaseVideoPlayer({
if (isFullScreenRef.current) {
return;
}
togglePlayCurrentVideo();
if (!canUseTouchScreen) {
togglePlayCurrentVideo();
return;
}
toggleControl();
}}
style={[styles.flex1, styles.noSelect]}
>
Expand Down Expand Up @@ -373,17 +424,17 @@ function BaseVideoPlayer({
</PressableWithoutFeedback>
{((isLoading && !isOffline) || isBuffering) && <FullScreenLoadingIndicator style={[styles.opacity1, styles.bgTransparent]} />}
{isLoading && !isBuffering && <AttachmentOfflineIndicator isPreview={isPreview} />}
{controlsStatus !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && (
{controlStatusState !== CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE && !isLoading && (isPopoverVisible || isHovered || canUseTouchScreen) && (
<VideoPlayerControls
duration={duration}
position={position}
url={url}
videoPlayerRef={videoPlayerRef}
isPlaying={isPlaying}
small={shouldUseSmallVideoControls}
style={videoControlsStyle}
style={[videoControlsStyle, controlsAnimatedStyle]}
togglePlayCurrentVideo={togglePlayCurrentVideo}
controlsStatus={controlsStatus}
controlsStatus={controlStatusState}
showPopoverMenu={showPopoverMenu}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedSt
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
import type {RootStackParamList, State} from '@libs/Navigation/types';
import {isCentralPaneName} from '@libs/NavigationUtils';
import {getCurrentSearchParams} from '@libs/SearchUtils';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils';
import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar';
Expand Down Expand Up @@ -81,6 +82,21 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
Navigation.navigate(route);
}, [activeWorkspaceID, selectedTab]);

const navigateToSearch = useCallback(() => {
if (selectedTab === SCREENS.SEARCH.BOTTOM_TAB) {
return;
}
interceptAnonymousUser(() => {
const currentSearchParams = getCurrentSearchParams();
if (currentSearchParams) {
const {q, ...rest} = currentSearchParams;
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: q, ...rest}));
return;
}
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: CONST.SEARCH.TAB.EXPENSE.ALL}));
});
}, [selectedTab]);

return (
<View style={styles.bottomTabBarContainer}>
<Tooltip text={translate('common.inbox')}>
Expand All @@ -106,12 +122,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
</Tooltip>
<Tooltip text={translate('common.search')}>
<PressableWithFeedback
onPress={() => {
if (selectedTab === SCREENS.SEARCH.BOTTOM_TAB) {
return;
}
interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: CONST.SEARCH.TAB.EXPENSE.ALL})));
}}
onPress={navigateToSearch}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.search')}
wrapperStyle={styles.flex1}
Expand Down
13 changes: 11 additions & 2 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1145,12 +1145,21 @@ function isSystemChat(report: OnyxEntry<Report>): boolean {
return getChatType(report) === CONST.REPORT.CHAT_TYPE.SYSTEM;
}

const CONCIERGE_ACCOUNT_ID_STRING = CONST.ACCOUNT_ID.CONCIERGE.toString();
/**
* Only returns true if this is our main 1:1 DM report with Concierge.
*/
function isConciergeChatReport(report: OnyxInputOrEntry<Report>): boolean {
const participantAccountIDs = Object.keys(report?.participants ?? {}).filter((accountID) => Number(accountID) !== currentUserAccountID);
return participantAccountIDs.length === 1 && Number(participantAccountIDs[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isThread(report);
if (!report?.participants || isThread(report)) {
return false;
}

const participantAccountIDs = new Set(Object.keys(report.participants));
if (participantAccountIDs.size !== 2) {
return false;
}

return participantAccountIDs.has(CONCIERGE_ACCOUNT_ID_STRING);
}

function findSelfDMReportID(): string | undefined {
Expand Down
20 changes: 16 additions & 4 deletions src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import TransactionListItem from '@components/SelectionList/Search/TransactionLis
import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import SCREENS from '@src/SCREENS';
import type {SearchAdvancedFiltersForm} from '@src/types/form';
import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm';
import type * as OnyxTypes from '@src/types/onyx';
import type SearchResults from '@src/types/onyx/SearchResults';
import type {SearchAccountDetails, SearchDataTypes, SearchPersonalDetails, SearchTransaction, SearchTypeToItemMap, SectionsType} from '@src/types/onyx/SearchResults';
import DateUtils from './DateUtils';
import getTopmostCentralPaneRoute from './Navigation/getTopmostCentralPaneRoute';
import navigationRef from './Navigation/navigationRef';
import type {AuthScreensParamList, RootStackParamList, State} from './Navigation/types';
import type {AuthScreensParamList, BottomTabNavigatorParamList, RootStackParamList, State} from './Navigation/types';
import * as searchParser from './SearchParser/searchParser';
import * as TransactionUtils from './TransactionUtils';
import * as UserUtils from './UserUtils';
Expand Down Expand Up @@ -318,8 +318,20 @@ function getSortedReportData(data: ReportListItemType[]) {
}

function getCurrentSearchParams() {
const topmostCentralPaneRoute = getTopmostCentralPaneRoute(navigationRef.getRootState() as State<RootStackParamList>);
return topmostCentralPaneRoute?.params as AuthScreensParamList['Search_Central_Pane'];
const rootState = navigationRef.getRootState() as State<RootStackParamList>;

const lastSearchCentralPaneRoute = rootState.routes.filter((route) => route.name === SCREENS.SEARCH.CENTRAL_PANE).at(-1);
const lastSearchBottomTabRoute = rootState.routes[0].state?.routes.filter((route) => route.name === SCREENS.SEARCH.BOTTOM_TAB).at(-1);

if (lastSearchCentralPaneRoute) {
return lastSearchCentralPaneRoute.params as AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE];
}

if (lastSearchBottomTabRoute) {
const {policyID, ...rest} = lastSearchBottomTabRoute.params as BottomTabNavigatorParamList[typeof SCREENS.SEARCH.BOTTOM_TAB];
const params: AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE] = {policyIDs: policyID, ...rest};
return params;
}
}

function isSearchResultsEmpty(searchResults: SearchResults) {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function buildOptimisticTransaction(
created: created || DateUtils.getDBTime(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
receipt: receipt?.source ? {source: receipt.source, state: receipt.state ?? CONST.IOU.RECEIPT_STATE.SCANREADY} : {},
filename: receipt?.source ?? receipt?.name ?? filename,
filename: (receipt?.source ?? receipt?.name ?? filename).toString(),
category,
tag,
taxCode,
Expand Down
10 changes: 0 additions & 10 deletions src/pages/signin/SignInPageLayout/SignInHeroImage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React, {useMemo} from 'react';
import {View} from 'react-native';
import Lottie from '@components/Lottie';
import LottieAnimations from '@components/LottieAnimations';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSplashScreen from '@hooks/useSplashScreen';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';

Expand All @@ -24,14 +22,6 @@ function SignInHeroImage() {
};
}, [shouldUseNarrowLayout, isMediumScreenWidth]);

const {isSplashHidden} = useSplashScreen();
// Prevents rendering of the Lottie animation until the splash screen is hidden
// by returning an empty view of the same size as the animation.
// See issue: https://github.com/Expensify/App/issues/34696
if (!isSplashHidden) {
return <View style={[styles.alignSelfCenter, imageSize]} />;
}

return (
<Lottie
source={LottieAnimations.Hands}
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ describe('ReportUtils', () => {
});

const report: Report = {
...LHNTestUtils.getFakeReport([CONST.ACCOUNT_ID.CONCIERGE]),
...LHNTestUtils.getFakeReport([accountID, CONST.ACCOUNT_ID.CONCIERGE]),
};

expect(ReportUtils.isChatUsedForOnboarding(report)).toBeTruthy();
Expand Down

0 comments on commit 6c4661e

Please sign in to comment.