From 79bc5397735308d29a41478b8069187e8660f4e4 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 8 Sep 2023 10:47:42 -0300 Subject: [PATCH] feat: Dynamic action sheet height (#5202) --- app/containers/ActionSheet/ActionSheet.tsx | 78 ++++++++++--------- .../ActionSheet/BottomSheetContent.tsx | 12 ++- app/containers/ActionSheet/Provider.tsx | 1 - app/containers/MessageActions/Header.tsx | 6 +- app/containers/MessageActions/index.tsx | 3 +- app/containers/UIKit/MultiSelect/index.tsx | 3 +- app/lib/hooks/useSnaps.ts | 22 ------ .../useVideoConf/StartACallActionSheet.tsx | 11 +-- app/lib/hooks/useVideoConf/index.tsx | 4 +- .../DeleteAccountActionSheetContent/index.tsx | 5 +- app/views/ProfileView/index.tsx | 6 +- app/views/RoomView/index.tsx | 2 +- 12 files changed, 61 insertions(+), 92 deletions(-) delete mode 100644 app/lib/hooks/useSnaps.ts diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx index a1785d7a3..f2e766f01 100644 --- a/app/containers/ActionSheet/ActionSheet.tsx +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -1,22 +1,16 @@ import { useBackHandler } from '@react-native-community/hooks'; import * as Haptics from 'expo-haptics'; import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react'; -import { Keyboard } from 'react-native'; -import { Easing } from 'react-native-reanimated'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Keyboard, useWindowDimensions } from 'react-native'; +import { Easing, useDerivedValue, useSharedValue } from 'react-native-reanimated'; import BottomSheet, { BottomSheetBackdrop } from '@gorhom/bottom-sheet'; -import { useDimensions, useOrientation } from '../../dimensions'; 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, { ITEM_HEIGHT } from './styles'; - -const HANDLE_HEIGHT = isIOS ? 40 : 56; -const MIN_SNAP_HEIGHT = 16; -const CANCEL_HEIGHT = 64; +import styles from './styles'; export const ACTION_SHEET_ANIMATION_DURATION = 250; @@ -32,33 +26,34 @@ const ActionSheet = React.memo( const bottomSheetRef = useRef(null); const [data, setData] = useState({} as TActionSheetOptions); const [isVisible, setVisible] = useState(false); - const { height } = useDimensions(); - const { isLandscape } = useOrientation(); - const insets = useSafeAreaInsets(); + const { width, height } = useWindowDimensions(); + const isLandscape = width > height; + const animatedContentHeight = useSharedValue(0); + const animatedHandleHeight = useSharedValue(0); + const animatedDataSnaps = useSharedValue([]); + const animatedSnapPoints = useDerivedValue(() => { + if (animatedDataSnaps.value?.length) { + return animatedDataSnaps.value; + } + const contentWithHandleHeight = animatedContentHeight.value + animatedHandleHeight.value; + // Bottom sheet requires a default value to work + if (contentWithHandleHeight === 0) { + return ['25%']; + } + return [contentWithHandleHeight]; + }, [data]); - const maxSnap = Math.min( - // Items height - ITEM_HEIGHT * (data?.options?.length || 0) + - // Handle height - HANDLE_HEIGHT + - // Custom header height - (data?.headerHeight || 0) + - // Insets bottom height (Notch devices) - insets.bottom + - // Cancel button height - (data?.hasCancel ? CANCEL_HEIGHT : 0), - height - MIN_SNAP_HEIGHT + const handleContentLayout = useCallback( + ({ + nativeEvent: { + layout: { height } + } + }) => { + animatedContentHeight.value = height; + }, + [animatedContentHeight] ); - /* - * if the action sheet cover more - * than 60% of the whole screen - * and it's not at the landscape mode - * we'll provide more one snap - * that point 50% of the whole screen - */ - const snaps = maxSnap > height * 0.6 && !isLandscape && !data.snaps ? [height * 0.5, maxSnap] : [maxSnap]; - const toggleVisible = () => setVisible(!isVisible); const hide = () => { @@ -67,6 +62,9 @@ const ActionSheet = React.memo( const show = (options: TActionSheetOptions) => { setData(options); + if (options.snaps?.length) { + animatedDataSnaps.value = options.snaps; + } toggleVisible(); }; @@ -104,6 +102,7 @@ const ActionSheet = React.memo( const onClose = () => { toggleVisible(); data?.onClose && data?.onClose(); + animatedDataSnaps.value = []; }; const renderBackdrop = useCallback( @@ -131,7 +130,10 @@ const ActionSheet = React.memo( {isVisible && ( - + )} diff --git a/app/containers/ActionSheet/BottomSheetContent.tsx b/app/containers/ActionSheet/BottomSheetContent.tsx index 8a83d8803..aa74953dc 100644 --- a/app/containers/ActionSheet/BottomSheetContent.tsx +++ b/app/containers/ActionSheet/BottomSheetContent.tsx @@ -1,6 +1,7 @@ -import { Text } from 'react-native'; +import { Text, ViewProps } from 'react-native'; import React from 'react'; import { BottomSheetView, BottomSheetFlatList } from '@gorhom/bottom-sheet'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import I18n from '../../i18n'; import { useTheme } from '../../theme'; @@ -15,10 +16,12 @@ interface IBottomSheetContentProps { options?: TActionSheetOptionsItem[]; hide: () => void; children?: React.ReactElement | null; + onLayout: ViewProps['onLayout']; } -const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: IBottomSheetContentProps) => { +const BottomSheetContent = React.memo(({ options, hasCancel, hide, children, onLayout }: IBottomSheetContentProps) => { const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const renderFooter = () => hasCancel ? ( @@ -42,18 +45,19 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I keyExtractor={item => item.title} bounces={true} renderItem={renderItem} - style={{ backgroundColor: colors.focusedBackground }} + style={{ backgroundColor: colors.focusedBackground, paddingBottom: bottom }} keyboardDismissMode='interactive' indicatorStyle='black' contentContainerStyle={styles.content} ItemSeparatorComponent={List.Separator} ListHeaderComponent={List.Separator} ListFooterComponent={renderFooter} + onLayout={onLayout} /> ); } return ( - + {children} ); diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx index 544fc180f..0293c7f21 100644 --- a/app/containers/ActionSheet/Provider.tsx +++ b/app/containers/ActionSheet/Provider.tsx @@ -15,7 +15,6 @@ export type TActionSheetOptionsItem = { export type TActionSheetOptions = { options?: TActionSheetOptionsItem[]; - headerHeight?: number; customHeader?: React.ReactElement | null; hasCancel?: boolean; type?: string; diff --git a/app/containers/MessageActions/Header.tsx b/app/containers/MessageActions/Header.tsx index f9c809e5e..eb25f77cb 100644 --- a/app/containers/MessageActions/Header.tsx +++ b/app/containers/MessageActions/Header.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { FlatList, StyleSheet, Text, View } from 'react-native'; +import { FlatList, StyleSheet, Text, View, useWindowDimensions } from 'react-native'; import { TSupportedThemes, useTheme } from '../../theme'; import { themes } from '../../lib/constants'; @@ -8,7 +8,6 @@ import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode'; import { addFrequentlyUsed } from '../../lib/methods'; import { useFrequentlyUsedEmoji } from '../../lib/hooks'; import CustomEmoji from '../EmojiPicker/CustomEmoji'; -import { useDimensions } from '../../dimensions'; import sharedStyles from '../../views/Styles'; import { IEmoji, TAnyMessageModel } from '../../definitions'; import Touch from '../Touch'; @@ -32,7 +31,6 @@ interface THeaderFooter { theme: TSupportedThemes; } -export const HEADER_HEIGHT = 36; const ITEM_SIZE = 36; const CONTAINER_MARGIN = 8; const ITEM_MARGIN = 8; @@ -86,7 +84,7 @@ const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => ( ); const Header = React.memo(({ handleReaction, message, isMasterDetail }: IHeader) => { - const { width, height } = useDimensions(); + const { width, height } = useWindowDimensions(); const { theme } = useTheme(); const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(true); const isLandscape = width > height; diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx index 9ee4be937..881246d2f 100644 --- a/app/containers/MessageActions/index.tsx +++ b/app/containers/MessageActions/index.tsx @@ -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, { HEADER_HEIGHT, IHeader } from './Header'; +import Header, { 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,7 +511,6 @@ const MessageActions = React.memo( await getPermissions(); showActionSheet({ options: getOptions(message), - headerHeight: HEADER_HEIGHT, customHeader: !isReadOnly || room.reactWhenReadOnly ? (
diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx index 87c0447fe..b1a669752 100644 --- a/app/containers/UIKit/MultiSelect/index.tsx +++ b/app/containers/UIKit/MultiSelect/index.tsx @@ -87,8 +87,7 @@ export const MultiSelect = React.memo( selectedItems={selected} /> ), - onClose, - headerHeight: 275 + onClose }); }; const onHide = () => { diff --git a/app/lib/hooks/useSnaps.ts b/app/lib/hooks/useSnaps.ts deleted file mode 100644 index 150457bd6..000000000 --- a/app/lib/hooks/useSnaps.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { StatusBar } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -import { isIOS, isTablet } from '../methods/helpers'; - -// Not sure if it's worth adding this here in the context of the actionSheet -/** - * Return the snaps based on the size you pass (aka: Size of action sheet) - * @param {number} componentSize size of the component that will be rendered in the action sheet - */ -export const useSnaps = (componentSize: number): number[] | string[] => { - const insets = useSafeAreaInsets(); - if (isIOS) { - const fixTabletInset = isTablet ? 2 : 1; - return [componentSize + (insets.bottom || insets.top) * fixTabletInset]; - } - let statusHeight = 0; - if (StatusBar.currentHeight) { - statusHeight = StatusBar.currentHeight; - } - return [componentSize + statusHeight]; -}; diff --git a/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx b/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx index 5459ed8d9..7326d03b8 100644 --- a/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx +++ b/app/lib/hooks/useVideoConf/StartACallActionSheet.tsx @@ -1,7 +1,6 @@ import { Camera, CameraType } from 'expo-camera'; import React, { useState } from 'react'; import { StyleSheet, View } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useDispatch } from 'react-redux'; import { useAppSelector } from '..'; @@ -13,7 +12,6 @@ import Ringer, { ERingerSounds } from '../../../containers/Ringer'; import i18n from '../../../i18n'; import { getUserSelector } from '../../../selectors/login'; import { useTheme } from '../../../theme'; -import { isIOS } from '../../methods/helpers'; import useUserData from '../useUserData'; export default function StartACallActionSheet({ rid }: { rid: string }): React.ReactElement { @@ -28,10 +26,6 @@ export default function StartACallActionSheet({ rid }: { rid: string }): React.R const user = useUserData(rid); - // fix safe area bottom padding on iOS - const insets = useSafeAreaInsets(); - const paddingBottom = isIOS && insets.bottom ? 8 : 0; - React.useEffect( () => () => { if (calling) { @@ -42,10 +36,7 @@ export default function StartACallActionSheet({ rid }: { rid: string }): React.R ); return ( - setContainerWidth(e.nativeEvent.layout.width / 2)} - > + setContainerWidth(e.nativeEvent.layout.width / 2)}> {calling ? : null} , - snaps + snaps: [480] }); if (!permission?.granted) { requestPermission(); diff --git a/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx b/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx index 1e7703fa9..cdbecc9fb 100644 --- a/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx +++ b/app/views/ProfileView/components/DeleteAccountActionSheetContent/index.tsx @@ -1,7 +1,6 @@ import { sha256 } from 'js-sha256'; import React from 'react'; import { Keyboard, Text } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useDispatch } from 'react-redux'; import { deleteAccount } from '../../../../actions/login'; @@ -18,7 +17,6 @@ import sharedStyles from '../../../Styles'; export function DeleteAccountActionSheetContent(): React.ReactElement { const { hideActionSheet, showActionSheet } = useActionSheet(); const dispatch = useDispatch(); - const insets = useSafeAreaInsets(); const { colors } = useTheme(); const handleDeleteAccount = async (password: string) => { @@ -40,8 +38,7 @@ export function DeleteAccountActionSheetContent(): React.ReactElement { removedRooms={removedRooms} password={sha256(password)} /> - ), - headerHeight: 225 + insets.bottom + ) }); }, 250); // timeout for hide effect } else if (error.data.errorType === 'error-invalid-password') { diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx index 190b5b3a9..a47f780ee 100644 --- a/app/views/ProfileView/index.tsx +++ b/app/views/ProfileView/index.tsx @@ -237,8 +237,7 @@ class ProfileView extends React.Component }} onCancel={this.props.hideActionSheet} /> - ), - headerHeight: 225 + ) }); return; } @@ -417,8 +416,7 @@ class ProfileView extends React.Component deleteOwnAccount = () => { logEvent(events.DELETE_OWN_ACCOUNT); this.props.showActionSheet({ - children: , - headerHeight: 225 + children: }); }; diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index d6774093f..7ca32dd21 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -854,7 +854,7 @@ class RoomView extends React.Component { children: ( ), - snaps: [400], + snaps: ['50%'], enableContentPanningGesture: false }); }, 100);