fix: dynamic action sheet taking whole screen (#5249)

* Fix dynamic action sheet taking whole screen

* Bring back maxSnap logic based on options

* Rollback enableContentPanningGesture

* Fix e2e tests
This commit is contained in:
Diego Mello 2023-10-03 09:57:49 -03:00 committed by GitHub
parent 37c58eb3ba
commit a1172f8bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 56 additions and 28 deletions

View File

@ -4,15 +4,18 @@ import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useR
import { Keyboard, useWindowDimensions } from 'react-native';
import { Easing, useDerivedValue, useSharedValue } from 'react-native-reanimated';
import BottomSheet, { BottomSheetBackdrop } from '@gorhom/bottom-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTheme } from '../../theme';
import { isIOS, isTablet } from '../../lib/methods/helpers';
import { Handle } from './Handle';
import { TActionSheetOptions } from './Provider';
import BottomSheetContent from './BottomSheetContent';
import styles from './styles';
import styles, { ITEM_HEIGHT } from './styles';
export const ACTION_SHEET_ANIMATION_DURATION = 250;
const HANDLE_HEIGHT = 28;
const CANCEL_HEIGHT = 64;
const ANIMATION_CONFIG = {
duration: ACTION_SHEET_ANIMATION_DURATION,
@ -23,11 +26,11 @@ const ANIMATION_CONFIG = {
const ActionSheet = React.memo(
forwardRef(({ children }: { children: React.ReactElement }, ref) => {
const { colors } = useTheme();
const { height: windowHeight } = useWindowDimensions();
const { bottom } = useSafeAreaInsets();
const bottomSheetRef = useRef<BottomSheet>(null);
const [data, setData] = useState<TActionSheetOptions>({} as TActionSheetOptions);
const [isVisible, setVisible] = useState(false);
const { width, height } = useWindowDimensions();
const isLandscape = width > height;
const animatedContentHeight = useSharedValue(0);
const animatedHandleHeight = useSharedValue(0);
const animatedDataSnaps = useSharedValue<TActionSheetOptions['snaps']>([]);
@ -49,11 +52,33 @@ const ActionSheet = React.memo(
layout: { height }
}
}) => {
animatedContentHeight.value = height;
/**
* This logic is only necessary to prevent the action sheet from
* occupying the entire screen when the dynamic content is too big.
*/
animatedContentHeight.value = Math.min(height, windowHeight * 0.8);
},
[animatedContentHeight]
[animatedContentHeight, windowHeight]
);
const maxSnap = Math.min(
(ITEM_HEIGHT + 0.5) * (data?.options?.length || 0) +
HANDLE_HEIGHT +
// Custom header height
(data?.headerHeight || 0) +
// Insets bottom height (Notch devices)
bottom +
// Cancel button height
(data?.hasCancel ? CANCEL_HEIGHT : 0),
windowHeight * 0.8
);
/*
* if the action sheet cover more than 60% of the screen height,
* we'll provide more one snap of 50%
*/
const snaps = maxSnap > windowHeight * 0.6 && !data.snaps ? ['50%', maxSnap] : [maxSnap];
const toggleVisible = () => setVisible(!isVisible);
const hide = () => {
@ -82,11 +107,6 @@ const ActionSheet = React.memo(
}
}, [isVisible]);
// Hides action sheet when orientation changes
useEffect(() => {
setVisible(false);
}, [isLandscape]);
useImperativeHandle(ref, () => ({
showActionSheet: show,
hideActionSheet: hide
@ -118,11 +138,11 @@ const ActionSheet = React.memo(
[]
);
const bottomSheet = isLandscape || isTablet ? styles.bottomSheet : {};
const bottomSheet = isTablet ? styles.bottomSheet : {};
// Must need this prop to avoid keyboard dismiss
// when is android tablet and the input text is focused
const androidTablet: any = isTablet && isLandscape && !isIOS ? { android_keyboardInputMode: 'adjustResize' } : {};
const androidTablet: any = isTablet && !isIOS ? { android_keyboardInputMode: 'adjustResize' } : {};
return (
<>
@ -130,10 +150,11 @@ const ActionSheet = React.memo(
{isVisible && (
<BottomSheet
ref={bottomSheetRef}
snapPoints={animatedSnapPoints}
// If data.options exist, we calculate snaps to be precise, otherwise we cal
snapPoints={data.options?.length ? snaps : animatedSnapPoints}
handleHeight={animatedHandleHeight}
// We need undefined to enable vertical swipe gesture inside the bottom sheet like in reaction picker
contentHeight={data.snaps?.length ? undefined : animatedContentHeight}
contentHeight={data.snaps?.length || data.options?.length ? undefined : animatedContentHeight}
animationConfigs={ANIMATION_CONFIG}
animateOnMount={true}
backdropComponent={renderBackdrop}

View File

@ -43,12 +43,12 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children, onL
data={options}
refreshing={false}
keyExtractor={item => item.title}
bounces={true}
bounces={false}
renderItem={renderItem}
style={{ backgroundColor: colors.focusedBackground, paddingBottom: bottom }}
style={{ backgroundColor: colors.focusedBackground }}
keyboardDismissMode='interactive'
indicatorStyle='black'
contentContainerStyle={styles.content}
contentContainerStyle={{ paddingBottom: bottom }}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={List.Separator}
ListFooterComponent={renderFooter}
@ -57,7 +57,7 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children, onL
);
}
return (
<BottomSheetView testID='action-sheet' style={[styles.contentContainer, { paddingBottom: bottom }]} onLayout={onLayout}>
<BottomSheetView testID='action-sheet' style={styles.contentContainer} onLayout={onLayout}>
{children}
</BottomSheetView>
);

View File

@ -8,7 +8,7 @@ import { useTheme } from '../../theme';
export const Handle = React.memo(() => {
const { theme } = useTheme();
return (
<View style={[styles.handle]} testID='action-sheet-handle'>
<View style={styles.handle} testID='action-sheet-handle'>
<View style={[styles.handleIndicator, { backgroundColor: themes[theme].auxiliaryText }]} />
</View>
);

View File

@ -15,10 +15,12 @@ export type TActionSheetOptionsItem = {
export type TActionSheetOptions = {
options?: TActionSheetOptionsItem[];
headerHeight?: number;
customHeader?: React.ReactElement | null;
hasCancel?: boolean;
type?: string;
// children can both use snaps or dynamic
children?: React.ReactElement | null;
/** Required if your action sheet needs vertical scroll */
snaps?: (string | number)[];
onClose?: () => void;
enableContentPanningGesture?: boolean;

View File

@ -19,9 +19,6 @@ export default StyleSheet.create({
separator: {
marginHorizontal: 16
},
content: {
paddingTop: 16
},
titleContainer: {
flex: 1
},

View File

@ -31,6 +31,7 @@ interface THeaderFooter {
theme: TSupportedThemes;
}
export const HEADER_HEIGHT = 54;
const ITEM_SIZE = 36;
const CONTAINER_MARGIN = 8;
const ITEM_MARGIN = 8;
@ -38,7 +39,8 @@ const ITEM_MARGIN = 8;
const styles = StyleSheet.create({
container: {
alignItems: 'center',
marginHorizontal: CONTAINER_MARGIN
marginHorizontal: CONTAINER_MARGIN,
paddingBottom: 16
},
headerItem: {
height: ITEM_SIZE,

View File

@ -13,7 +13,7 @@ import { LISTENER } from '../Toast';
import EventEmitter from '../../lib/methods/helpers/events';
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATION } from '../ActionSheet';
import Header, { IHeader } from './Header';
import Header, { HEADER_HEIGHT, IHeader } from './Header';
import events from '../../lib/methods/helpers/log/events';
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
import { getPermalinkMessage } from '../../lib/methods';
@ -511,6 +511,7 @@ const MessageActions = React.memo(
await getPermissions();
showActionSheet({
options: getOptions(message),
headerHeight: HEADER_HEIGHT,
customHeader:
!isReadOnly || room.reactWhenReadOnly ? (
<Header handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />

View File

@ -2,6 +2,7 @@ import { Camera, CameraType } from 'expo-camera';
import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useAppSelector } from '..';
import { cancelCall, initVideoCall } from '../../../actions/videoConf';
@ -19,6 +20,7 @@ export default function StartACallActionSheet({ rid }: { rid: string }): React.R
const [mic, setMic] = useState(true);
const [cam, setCam] = useState(false);
const [containerWidth, setContainerWidth] = useState(0);
const { bottom } = useSafeAreaInsets();
const username = useAppSelector(state => getUserSelector(state).username);
const calling = useAppSelector(state => state.videoConf.calling);
@ -36,7 +38,10 @@ export default function StartACallActionSheet({ rid }: { rid: string }): React.R
);
return (
<View style={style.actionSheetContainer} onLayout={e => setContainerWidth(e.nativeEvent.layout.width / 2)}>
<View
style={[style.actionSheetContainer, { paddingBottom: bottom }]}
onLayout={e => setContainerWidth(e.nativeEvent.layout.width / 2)}
>
{calling ? <Ringer ringer={ERingerSounds.DIALTONE} /> : null}
<CallHeader
title={calling && user.direct ? i18n.t('Calling') : i18n.t('Start_a_call')}

View File

@ -478,7 +478,7 @@ describe('Room screen', () => {
.toExist()
.withTimeout(2000);
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
await sleep(300); // wait for animation
await waitFor(element(by[textMatcher]('Delete')))
.toExist()

View File

@ -237,7 +237,7 @@ describe('Threads', () => {
.withTimeout(5000);
await element(by.id(`message-thread-button-${thread}`)).tap();
await tryTapping(element(by[textMatcher]('replied')).atIndex(0), 2000, true);
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
await sleep(300); // wait for animation
await element(by[textMatcher]('Delete')).atIndex(0).tap();
await element(by[textMatcher]('Delete').and(by.type(alertButtonType))).tap();