From 94b2a143139457aed40c9537019ec2361fb3f569 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 13 Apr 2022 10:11:31 -0400 Subject: [PATCH] bottomSheet snapPoint helper (#6169) --- .../channel_list/header/header.test.tsx | 13 +++--- app/components/channel_list/header/header.tsx | 5 ++- .../channel_list/header/plus_menu/item.tsx | 15 +++---- app/components/channel_list/index.test.tsx | 43 ++++++++----------- .../markdown/at_mention/at_mention.tsx | 7 ++- .../markdown/markdown_code_block/index.tsx | 7 ++- .../markdown/markdown_image/index.tsx | 7 ++- .../markdown/markdown_link/markdown_link.tsx | 7 ++- .../post_list/post/body/failed/index.tsx | 8 +++- .../components/profile_image_picker.tsx | 10 +++-- .../options/user_presence/index.tsx | 8 +++- app/utils/helpers.ts | 6 ++- test/intl-test-helper.tsx | 13 ++++-- 13 files changed, 86 insertions(+), 63 deletions(-) diff --git a/app/components/channel_list/header/header.test.tsx b/app/components/channel_list/header/header.test.tsx index 44e91f963..a9725eb2d 100644 --- a/app/components/channel_list/header/header.test.tsx +++ b/app/components/channel_list/header/header.test.tsx @@ -2,7 +2,6 @@ // See LICENSE.txt for license information. import React from 'react'; -import {SafeAreaProvider} from 'react-native-safe-area-context'; import {renderWithIntl} from '@test/intl-test-helper'; @@ -11,13 +10,11 @@ import Header from './header'; describe('components/channel_list/header', () => { it('Channel List Header Component should match snapshot', () => { const {toJSON} = renderWithIntl( - -
- , +
, ); expect(toJSON()).toMatchSnapshot(); diff --git a/app/components/channel_list/header/header.tsx b/app/components/channel_list/header/header.tsx index 991bb66e6..9f2a804a9 100644 --- a/app/components/channel_list/header/header.tsx +++ b/app/components/channel_list/header/header.tsx @@ -3,7 +3,7 @@ import React, {useCallback, useEffect} from 'react'; import {useIntl} from 'react-intl'; -import {Platform, Text, View} from 'react-native'; +import {Text, View} from 'react-native'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; @@ -14,6 +14,7 @@ import {useServerDisplayName} from '@context/server'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; import {bottomSheet} from '@screens/navigation'; +import {bottomSheetSnapPoint} from '@utils/helpers'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -101,7 +102,7 @@ const ChannelListHeader = ({canCreateChannels, canJoinChannels, displayName, ico bottomSheet({ closeButtonId, renderContent, - snapPoints: [((items + Platform.select({android: 1, default: 0})) * ITEM_HEIGHT) + (insets.bottom * 2), 10], + snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom), 10], theme, title: intl.formatMessage({id: 'home.header.plus_menu', defaultMessage: 'Options'}), }); diff --git a/app/components/channel_list/header/plus_menu/item.tsx b/app/components/channel_list/header/plus_menu/item.tsx index 6edddb949..e46b285eb 100644 --- a/app/components/channel_list/header/plus_menu/item.tsx +++ b/app/components/channel_list/header/plus_menu/item.tsx @@ -3,7 +3,6 @@ import React from 'react'; import {useIntl} from 'react-intl'; -import {View} from 'react-native'; import SlideUpPanelItem from '@components/slide_up_panel_item'; @@ -36,14 +35,12 @@ const PlusMenuItem = ({pickerAction, onPress}: PlusMenuItemProps) => { }; const itemType = menuItems[pickerAction]; return ( - - - + ); }; diff --git a/app/components/channel_list/index.test.tsx b/app/components/channel_list/index.test.tsx index dfae58f13..25c878c6b 100644 --- a/app/components/channel_list/index.test.tsx +++ b/app/components/channel_list/index.test.tsx @@ -3,7 +3,6 @@ import Database from '@nozbe/watermelondb/Database'; import React from 'react'; -import {SafeAreaProvider} from 'react-native-safe-area-context'; import {SYSTEM_IDENTIFIERS} from '@constants/database'; import ServerDataOperator from '@database/operator/server_data_operator'; @@ -31,14 +30,12 @@ describe('components/channel_list', () => { it('should render', () => { const wrapper = renderWithEverything( - - - , + , {database}, ); expect(wrapper.toJSON()).toBeTruthy(); @@ -51,14 +48,12 @@ describe('components/channel_list', () => { }); const wrapper = renderWithEverything( - - - , + , {database}, ); @@ -72,14 +67,12 @@ describe('components/channel_list', () => { it('should render channels error', () => { const wrapper = renderWithEverything( - - - , + , {database}, ); expect(wrapper.toJSON()).toMatchSnapshot(); diff --git a/app/components/markdown/at_mention/at_mention.tsx b/app/components/markdown/at_mention/at_mention.tsx index 132c33a54..0f55943e4 100644 --- a/app/components/markdown/at_mention/at_mention.tsx +++ b/app/components/markdown/at_mention/at_mention.tsx @@ -7,6 +7,7 @@ import Clipboard from '@react-native-community/clipboard'; import React, {useCallback, useMemo} from 'react'; import {useIntl} from 'react-intl'; import {GestureResponderEvent, StyleProp, StyleSheet, Text, TextStyle, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; import CompassIcon from '@components/compass_icon'; import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item'; @@ -14,6 +15,7 @@ import {MM_TABLES} from '@constants/database'; import {useTheme} from '@context/theme'; import UserModel from '@database/models/server/user'; import {bottomSheet, dismissBottomSheet, showModal} from '@screens/navigation'; +import {bottomSheetSnapPoint} from '@utils/helpers'; import {displayUsername, getUsersByUsername} from '@utils/user'; import type UserModelType from '@typings/database/models/servers/user'; @@ -54,6 +56,7 @@ const AtMention = ({ const intl = useIntl(); const managedConfig = useManagedConfig(); const theme = useTheme(); + const insets = useSafeAreaInsets(); const user = useMemo(() => { const usersByUsername = getUsersByUsername(users); let mn = mentionName.toLowerCase(); @@ -146,12 +149,12 @@ const AtMention = ({ bottomSheet({ closeButtonId: 'close-at-mention', renderContent, - snapPoints: [3 * ITEM_HEIGHT, 10], + snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10], title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}), theme, }); } - }, [managedConfig, intl, theme]); + }, [managedConfig, intl, theme, insets]); const mentionTextStyle = []; diff --git a/app/components/markdown/markdown_code_block/index.tsx b/app/components/markdown/markdown_code_block/index.tsx index 63e17b8ce..1321e49ec 100644 --- a/app/components/markdown/markdown_code_block/index.tsx +++ b/app/components/markdown/markdown_code_block/index.tsx @@ -6,6 +6,7 @@ import Clipboard from '@react-native-community/clipboard'; import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; import {Keyboard, StyleSheet, Text, TextStyle, TouchableOpacity, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; import FormattedText from '@components/formatted_text'; import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item'; @@ -13,6 +14,7 @@ import SyntaxHighlighter from '@components/syntax_highlight'; import {Screens} from '@constants'; import {useTheme} from '@context/theme'; import {bottomSheet, dismissBottomSheet, goToScreen} from '@screens/navigation'; +import {bottomSheetSnapPoint} from '@utils/helpers'; import {getHighlightLanguageFromNameOrAlias, getHighlightLanguageName} from '@utils/markdown'; import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -66,6 +68,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc const intl = useIntl(); const managedConfig = useManagedConfig(); const theme = useTheme(); + const insets = useSafeAreaInsets(); const style = getStyleSheet(theme); const handlePress = preventDoubleTap(() => { @@ -134,12 +137,12 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc bottomSheet({ closeButtonId: 'close-code-block', renderContent, - snapPoints: [3 * ITEM_HEIGHT, 10], + snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10], title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}), theme, }); } - }, [managedConfig, intl, theme]); + }, [managedConfig, intl, insets, theme]); const trimContent = (text: string) => { const lines = text.split('\n'); diff --git a/app/components/markdown/markdown_image/index.tsx b/app/components/markdown/markdown_image/index.tsx index 13b9acd61..b5e9d3f12 100644 --- a/app/components/markdown/markdown_image/index.tsx +++ b/app/components/markdown/markdown_image/index.tsx @@ -7,6 +7,7 @@ import React, {useCallback, useMemo, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; import {Alert, Platform, StyleProp, Text, TextStyle, TouchableWithoutFeedback, View} from 'react-native'; import Animated from 'react-native-reanimated'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {SvgUri} from 'react-native-svg'; import parseUrl from 'url-parse'; @@ -24,6 +25,7 @@ import {bottomSheet, dismissBottomSheet} from '@screens/navigation'; import {lookupMimeType} from '@utils/file'; import {fileToGalleryItem, openGalleryAtIndex} from '@utils/gallery'; import {generateId} from '@utils/general'; +import {bottomSheetSnapPoint} from '@utils/helpers'; import {calculateDimensions, getViewPortWidth, isGifTooLarge} from '@utils/images'; import {getMarkdownImageSize} from '@utils/markdown'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -69,6 +71,7 @@ const MarkdownImage = ({ }: MarkdownImageProps) => { const intl = useIntl(); const isTablet = useIsTablet(); + const insets = useSafeAreaInsets(); const theme = useTheme(); const style = getStyleSheet(theme); const managedConfig = useManagedConfig(); @@ -180,12 +183,12 @@ const MarkdownImage = ({ bottomSheet({ closeButtonId: 'close-mardown-image', renderContent, - snapPoints: [3 * ITEM_HEIGHT, 10], + snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10], title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}), theme, }); } - }, [managedConfig, intl, theme]); + }, [managedConfig, intl, insets, theme]); const handleOnError = useCallback(() => { setFailed(true); diff --git a/app/components/markdown/markdown_link/markdown_link.tsx b/app/components/markdown/markdown_link/markdown_link.tsx index 0d88c139e..fcec54183 100644 --- a/app/components/markdown/markdown_link/markdown_link.tsx +++ b/app/components/markdown/markdown_link/markdown_link.tsx @@ -6,6 +6,7 @@ import Clipboard from '@react-native-community/clipboard'; import React, {Children, ReactElement, useCallback} from 'react'; import {useIntl} from 'react-intl'; import {Alert, StyleSheet, Text, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; import urlParse from 'url-parse'; import {switchToChannelByName} from '@actions/remote/channel'; @@ -16,6 +17,7 @@ import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {bottomSheet, dismissAllModals, dismissBottomSheet, popToRoot} from '@screens/navigation'; import {errorBadChannel} from '@utils/draft'; +import {bottomSheetSnapPoint} from '@utils/helpers'; import {preventDoubleTap} from '@utils/tap'; import {matchDeepLink, normalizeProtocol, tryOpenURL} from '@utils/url'; @@ -36,6 +38,7 @@ const style = StyleSheet.create({ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteURL}: MarkdownLinkProps) => { const intl = useIntl(); + const insets = useSafeAreaInsets(); const managedConfig = useManagedConfig(); const serverUrl = useServerUrl(); const theme = useTheme(); @@ -147,12 +150,12 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU bottomSheet({ closeButtonId: 'close-mardown-link', renderContent, - snapPoints: [3 * ITEM_HEIGHT, 10], + snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10], title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}), theme, }); } - }, [managedConfig, intl, theme]); + }, [managedConfig, intl, insets, theme]); const renderChildren = experimentalNormalizeMarkdownLinks ? parseChildren() : children; diff --git a/app/components/post_list/post/body/failed/index.tsx b/app/components/post_list/post/body/failed/index.tsx index 82aa1194d..2675d9c29 100644 --- a/app/components/post_list/post/body/failed/index.tsx +++ b/app/components/post_list/post/body/failed/index.tsx @@ -4,12 +4,14 @@ import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; import {StyleSheet, TouchableOpacity, View} from 'react-native'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {removePost} from '@actions/local/post'; import CompassIcon from '@components/compass_icon'; import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item'; import {useServerUrl} from '@context/server'; import {bottomSheet, dismissBottomSheet} from '@screens/navigation'; +import {bottomSheetSnapPoint} from '@utils/helpers'; import type PostModel from '@typings/database/models/servers/post'; @@ -33,7 +35,9 @@ const retryPost = (serverUrl: string, post: PostModel) => post; const Failed = ({post, theme}: FailedProps) => { const intl = useIntl(); + const insets = useSafeAreaInsets(); const serverUrl = useServerUrl(); + const onPress = useCallback(() => { const renderContent = () => { return ( @@ -67,11 +71,11 @@ const Failed = ({post, theme}: FailedProps) => { bottomSheet({ closeButtonId: 'close-post-failed', renderContent, - snapPoints: [3 * ITEM_HEIGHT, 10], + snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10], title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}), theme, }); - }, []); + }, [insets]); return ( { const theme = useTheme(); const intl = useIntl(); + const insets = useSafeAreaInsets(); const serverUrl = useServerUrl(); const pictureUtils = useMemo(() => new PickerUtil(intl, uploadFiles), [uploadFiles, intl]); const canRemovePicture = hasPictureUrl(user, serverUrl); @@ -102,16 +105,17 @@ const ProfileImagePicker = ({ ); }; - const snapPoints = canRemovePicture ? 5 : 4; + const snapPointsCount = canRemovePicture ? 5 : 4; + const snapPoint = bottomSheetSnapPoint(snapPointsCount, ACTION_HEIGHT, insets.bottom); return bottomSheet({ closeButtonId: 'close-edit-profile', renderContent, - snapPoints: [(snapPoints * ACTION_HEIGHT), 10], + snapPoints: [snapPoint, 10], title: '', theme, }); - }), [canRemovePicture, onRemoveProfileImage, pictureUtils, theme]); + }), [canRemovePicture, onRemoveProfileImage, insets, pictureUtils, theme]); return ( { const intl = useIntl(); + const insets = useSafeAreaInsets(); const serverUrl = useServerUrl(); const handleSetStatus = useCallback(preventDoubleTap(() => { @@ -83,14 +86,15 @@ const UserStatus = ({currentUser, style, theme}: Props) => { ); }; + const snapPoint = bottomSheetSnapPoint(4, ITEM_HEIGHT, insets.bottom); bottomSheet({ closeButtonId: 'close-set-user-status', renderContent, - snapPoints: [(5 * ITEM_HEIGHT) + 10, 10], + snapPoints: [snapPoint, 10], title: intl.formatMessage({id: 'account.user_status.title', defaultMessage: 'User Presence'}), theme, }); - }), [theme]); + }), [theme, insets]); const updateStatus = useCallback((status: string) => { const userStatus = { diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts index 7bdd0d859..1a95d4c7d 100644 --- a/app/utils/helpers.ts +++ b/app/utils/helpers.ts @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import moment, {Moment} from 'moment-timezone'; -import {NativeModules} from 'react-native'; +import {NativeModules, Platform} from 'react-native'; import {Device} from '@constants'; import {CUSTOM_STATUS_TIME_PICKER_INTERVALS_IN_MINUTES} from '@constants/custom_status'; @@ -142,3 +142,7 @@ export async function isTablet() { } export const pluckUnique = (key: string) => (array: Array<{[key: string]: unknown}>) => Array.from(new Set(array.map((obj) => obj[key]))); + +export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0) { + return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (bottomInset * 2.5); +} diff --git a/test/intl-test-helper.tsx b/test/intl-test-helper.tsx index 3fc8f8fe8..970467fb3 100644 --- a/test/intl-test-helper.tsx +++ b/test/intl-test-helper.tsx @@ -5,6 +5,7 @@ import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider'; import {render} from '@testing-library/react-native'; import React, {ReactElement} from 'react'; import {createIntl, IntlProvider} from 'react-intl'; +import {SafeAreaProvider} from 'react-native-safe-area-context'; import {ThemeContext, getDefaultThemeByAppearance} from '@context/theme'; import {getTranslations} from '@i18n'; @@ -25,7 +26,9 @@ export function renderWithIntl(ui: ReactElement, {locale = 'en', ...renderOption locale={locale} messages={getTranslations(locale)} > - {children} + + {children} + ); } @@ -41,7 +44,9 @@ export function renderWithIntlAndTheme(ui: ReactElement, {locale = 'en', ...rend messages={getTranslations(locale)} > - {children} + + {children} + ); @@ -63,7 +68,9 @@ export function renderWithEverything(ui: ReactElement, {locale = 'en', database, messages={getTranslations(locale)} > - {children} + + {children} +