Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 6 additions & 37 deletions src/components/FeatureTrainingModal.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type {VideoReadyForDisplayEvent} from 'expo-av';
import type {ImageContentFit} from 'expo-image';
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
import {Image, InteractionManager, View} from 'react-native';
// eslint-disable-next-line no-restricted-imports
import type {ImageResizeMode, ImageSourcePropType, LayoutChangeEvent, ScrollView as RNScrollView, StyleProp, TextStyle, ViewStyle} from 'react-native';
import type {ImageResizeMode, ImageSourcePropType, StyleProp, TextStyle, ViewStyle} from 'react-native';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import type {MergeExclusive} from 'type-fest';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import {parseFSAttributes} from '@libs/Fullstory';
Expand Down Expand Up @@ -211,10 +209,6 @@ function FeatureTrainingModal({
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {isOffline} = useNetwork();
const hasHelpButtonBeenPressed = useRef(false);
const scrollViewRef = useRef<RNScrollView>(null);
const [containerHeight, setContainerHeight] = useState(0);
const [contentHeight, setContentHeight] = useState(0);
const insets = useSafeAreaInsets();

useEffect(() => {
InteractionManager.runAfterInteractions(() => {
Expand Down Expand Up @@ -361,18 +355,8 @@ function FeatureTrainingModal({
*/
useLayoutEffect(parseFSAttributes, []);

// Scrolls modal to the bottom when keyboard appears so the action buttons are visible.
useEffect(() => {
if (contentHeight <= containerHeight || onboardingIsMediumOrLargerScreenWidth || !shouldUseScrollView) {
return;
}
scrollViewRef.current?.scrollToEnd({animated: false});
}, [contentHeight, containerHeight, onboardingIsMediumOrLargerScreenWidth, shouldUseScrollView]);

const Wrapper = shouldUseScrollView ? ScrollView : View;

const wrapperStyles = useMemo(() => (shouldUseScrollView ? StyleUtils.getScrollableFeatureTrainingModalStyles(insets) : {}), [shouldUseScrollView, StyleUtils, insets]);

return (
<Modal
avoidKeyboard={avoidKeyboard}
Expand Down Expand Up @@ -402,13 +386,9 @@ function FeatureTrainingModal({
shouldUseReanimatedModal
>
<Wrapper
scrollsToTop={false}
style={[styles.mh100, onboardingIsMediumOrLargerScreenWidth && StyleUtils.getWidthStyle(width), wrapperStyles.style]}
contentContainerStyle={wrapperStyles.containerStyle}
style={[styles.mh100, onboardingIsMediumOrLargerScreenWidth && StyleUtils.getWidthStyle(width)]}
contentContainerStyle={shouldUseScrollView ? styles.pb5 : undefined}
keyboardShouldPersistTaps={shouldUseScrollView ? 'handled' : undefined}
ref={shouldUseScrollView ? scrollViewRef : undefined}
onLayout={shouldUseScrollView ? (e: LayoutChangeEvent) => setContainerHeight(e.nativeEvent.layout.height) : undefined}
onContentSizeChange={shouldUseScrollView ? (_w: number, h: number) => setContentHeight(h) : undefined}
fsClass={CONST.FULL_STORY.UNMASK}
testID={CONST.FULL_STORY.UNMASK}
>
Expand All @@ -417,20 +397,9 @@ function FeatureTrainingModal({
</View>
<View style={[styles.mt5, styles.mh5, contentOuterContainerStyles]}>
{!!title && !!description && (
<View
style={[
onboardingIsMediumOrLargerScreenWidth ? [styles.gap1, styles.mb8] : [shouldRenderHTMLDescription ? styles.mb5 : styles.mb10],
contentInnerContainerStyles,
]}
>
<View style={[onboardingIsMediumOrLargerScreenWidth ? [styles.gap1, styles.mb8] : [styles.mb10], contentInnerContainerStyles]}>
{typeof title === 'string' ? <Text style={[styles.textHeadlineH1, titleStyles]}>{title}</Text> : title}
{shouldRenderHTMLDescription ? (
<View style={styles.mb2}>
<RenderHTML html={description} />
</View>
) : (
<Text style={styles.textSupporting}>{description}</Text>
)}
{shouldRenderHTMLDescription ? <RenderHTML html={description} /> : <Text style={styles.textSupporting}>{description}</Text>}
{secondaryDescription.length > 0 && <Text style={[styles.textSupporting, styles.mt4]}>{secondaryDescription}</Text>}
{children}
</View>
Expand Down
33 changes: 2 additions & 31 deletions src/components/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import React, {forwardRef, useCallback, useContext, useEffect, useMemo, useRef} from 'react';
import {View} from 'react-native';
import type {LayoutChangeEvent} from 'react-native';
import type {ModalProps as ReactNativeModalProps} from 'react-native-modal';
import ReactNativeModal from 'react-native-modal';
import type {ValueOf} from 'type-fest';
Expand All @@ -18,7 +17,6 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import getPlatform from '@libs/getPlatform';
import NarrowPaneContext from '@libs/Navigation/AppNavigator/Navigators/NarrowPaneContext';
import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -111,8 +109,6 @@ function BaseModal(
const {sidePanelOffset} = useSidePanel();
const sidePanelStyle = shouldApplySidePanelOffset && !isSmallScreenWidth ? {paddingRight: sidePanelOffset.current} : undefined;
const keyboardStateContextValue = useKeyboardState();
const [modalOverlapsWithTopSafeArea, setModalOverlapsWithTopSafeArea] = useState(false);
const [modalHeight, setModalHeight] = useState(0);

const insets = useSafeAreaInsets();

Expand Down Expand Up @@ -207,29 +203,6 @@ function BaseModal(
ComposerFocusManager.setReadyToFocus(uniqueModalId);
};

// Checks if modal overlaps with topSafeArea. Used to offset tall bottom docked modals with keyboard.
useEffect(() => {
if (type !== CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED || getPlatform() === CONST.PLATFORM.WEB) {
return;
}
const {paddingTop} = StyleUtils.getPlatformSafeAreaPadding(insets);
const availableHeight = windowHeight - modalHeight - keyboardStateContextValue.keyboardActiveHeight - paddingTop;
setModalOverlapsWithTopSafeArea((keyboardStateContextValue.isKeyboardAnimatingRef.current || keyboardStateContextValue.isKeyboardActive) && Math.floor(availableHeight) <= 0);
}, [
StyleUtils,
insets,
keyboardStateContextValue.isKeyboardActive,
keyboardStateContextValue.isKeyboardAnimatingRef,
keyboardStateContextValue.keyboardActiveHeight,
modalHeight,
type,
windowHeight,
]);

const onViewLayout = (e: LayoutChangeEvent) => {
setModalHeight(e.nativeEvent.layout.height);
};

const {
modalStyle,
modalContainerStyle,
Expand All @@ -253,9 +226,8 @@ function BaseModal(
innerContainerStyle,
outerStyle,
shouldUseModalPaddingStyle,
modalOverlapsWithTopSafeArea,
),
[StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle, shouldUseModalPaddingStyle, modalOverlapsWithTopSafeArea],
[StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle, shouldUseModalPaddingStyle],
);

const modalPaddingStyles = useMemo(() => {
Expand Down Expand Up @@ -378,7 +350,6 @@ function BaseModal(
shouldPreventScroll={shouldPreventScrollOnFocus}
>
<View
onLayout={onViewLayout}
style={[styles.defaultModalContainer, modalContainerStyle, modalPaddingStyles, !isVisible && styles.pointerEventsNone]}
ref={ref}
>
Expand Down
16 changes: 4 additions & 12 deletions src/components/withKeyboardState.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {ReactElement, RefObject} from 'react';
import type {MutableRefObject, ReactElement} from 'react';
import React, {createContext, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {KeyboardEvents, useKeyboardHandler} from 'react-native-keyboard-controller';
import {runOnJS} from 'react-native-reanimated';
Expand All @@ -16,25 +16,20 @@ type KeyboardStateContextValue = {
/** Height of the keyboard in pixels */
keyboardHeight: number;

/** Future or present height of the keyboard in pixels. Available together with isKeyboardActive. */
keyboardActiveHeight: number;

/** Ref to check if the keyboard is animating */
isKeyboardAnimatingRef: RefObject<boolean>;
isKeyboardAnimatingRef: MutableRefObject<boolean>;
};

const KeyboardStateContext = createContext<KeyboardStateContextValue>({
isKeyboardShown: false,
isKeyboardActive: false,
keyboardHeight: 0,
keyboardActiveHeight: 0,
isKeyboardAnimatingRef: {current: false},
});

function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null {
const {bottom} = useSafeAreaInsets();
const [keyboardHeight, setKeyboardHeight] = useState(0);
const [keyboardActiveHeight, setKeyboardActiveHeight] = useState(0);
const isKeyboardAnimatingRef = useRef(false);
const [isKeyboardActive, setIsKeyboardActive] = useState(false);

Expand All @@ -47,13 +42,11 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null {
setKeyboardHeight(0);
setIsKeyboardActive(false);
});
const keyboardWillShowListener = KeyboardEvents.addListener('keyboardWillShow', (e) => {
const keyboardWillShowListener = KeyboardEvents.addListener('keyboardWillShow', () => {
setIsKeyboardActive(true);
setKeyboardActiveHeight(e.height);
});
const keyboardWillHideListener = KeyboardEvents.addListener('keyboardWillHide', () => {
setIsKeyboardActive(false);
setKeyboardActiveHeight(0);
});

return () => {
Expand Down Expand Up @@ -87,12 +80,11 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null {
const contextValue = useMemo(
() => ({
keyboardHeight,
keyboardActiveHeight,
isKeyboardShown: keyboardHeight !== 0,
isKeyboardAnimatingRef,
isKeyboardActive,
}),
[isKeyboardActive, keyboardActiveHeight, keyboardHeight],
[isKeyboardActive, keyboardHeight],
);
return <KeyboardStateContext.Provider value={contextValue}>{children}</KeyboardStateContext.Provider>;
}
Expand Down
19 changes: 2 additions & 17 deletions src/styles/utils/generators/ModalStyleUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {ViewStyle} from 'react-native';
import type {ModalProps} from 'react-native-modal';
import {isMobileSafari} from '@libs/Browser';
import type {ThemeStyles} from '@styles/index';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -43,20 +42,11 @@ type GetModalStylesStyleUtil = {
innerContainerStyle?: ViewStyle,
outerStyle?: ViewStyle,
shouldUseModalPaddingStyle?: boolean,
modalOverlapsWithTopSafeArea?: boolean,
) => GetModalStyles;
};

const createModalStyleUtils: StyleUtilGenerator<GetModalStylesStyleUtil> = ({theme, styles}) => ({
getModalStyles: (
type,
windowDimensions,
popoverAnchorPosition = {},
innerContainerStyle = {},
outerStyle = {},
shouldUseModalPaddingStyle = true,
modalOverlapsWithTopSafeArea = false,
): GetModalStyles => {
getModalStyles: (type, windowDimensions, popoverAnchorPosition = {}, innerContainerStyle = {}, outerStyle = {}, shouldUseModalPaddingStyle = true): GetModalStyles => {
const {windowWidth, isSmallScreenWidth} = windowDimensions;

let modalStyle: GetModalStyles['modalStyle'] = {
Expand Down Expand Up @@ -228,19 +218,14 @@ const createModalStyleUtils: StyleUtilGenerator<GetModalStylesStyleUtil> = ({the
justifyContent: 'center',
overflow: 'hidden',
boxShadow: theme.shadow,
// Workaround for Safari not supporting interactive-widget=resizes-content, sets max height of a container modal.
// This allows better scrolling experience after keyboard shows for modals with input, that are larger than remaining screen height.
// More info https://github.com/Expensify/App/pull/62799#issuecomment-2943136220.
...(isMobileSafari() ? {maxHeight: `${windowDimensions.windowHeight}px`} : {}),
};

if (shouldUseModalPaddingStyle) {
modalContainerStyle.paddingTop = variables.componentBorderRadiusLarge;
modalContainerStyle.paddingBottom = variables.componentBorderRadiusLarge;
}

shouldAddBottomSafeAreaPadding = innerContainerStyle.paddingBottom !== 0;
shouldAddTopSafeAreaMargin = modalOverlapsWithTopSafeArea;
shouldAddBottomSafeAreaPadding = true;
swipeDirection = undefined;
animationIn = 'slideInUp';
animationOut = 'slideOutDown';
Expand Down
22 changes: 1 addition & 21 deletions src/styles/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import type {EdgeInsets} from 'react-native-safe-area-context';
import type {ValueOf} from 'type-fest';
import type ImageSVGProps from '@components/ImageSVG/types';
import {isMobile, isMobileChrome} from '@libs/Browser';
import {isMobile} from '@libs/Browser';
import getPlatform from '@libs/getPlatform';
import {hashText} from '@libs/UserUtils';
// eslint-disable-next-line no-restricted-imports
Expand Down Expand Up @@ -1234,7 +1234,6 @@ const staticStyleUtils = {
getBackgroundAndBorderStyle,
getBackgroundColorStyle,
getBackgroundColorWithOpacityStyle,
getCombinedSpacing,
getPaddingLeft,
getPaddingRight,
getPaddingBottom,
Expand Down Expand Up @@ -1833,25 +1832,6 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
styleObj[key] = null;
return styleObj;
}, {} as Nullable<K>) as K,
getScrollableFeatureTrainingModalStyles: (
insets: EdgeInsets,
): {
style?: ViewStyle;
containerStyle?: ViewStyle;
} => {
const {paddingBottom: safeAreaPaddingBottom} = getPlatformSafeAreaPadding(insets);

const paddingBottom = getCombinedSpacing(styles.pb5.paddingBottom, safeAreaPaddingBottom, true);
// Forces scroll on modal when keyboard is open and the modal larger than remaining screen height.
return {
style: isMobileChrome()
? {
maxHeight: '100dvh',
}
: {},
containerStyle: {paddingBottom},
};
},
});

type StyleUtilsType = ReturnType<typeof createStyleUtils>;
Expand Down