diff --git a/NOTICE.txt b/NOTICE.txt
index 8453b77d5..c4a1b9f94 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -2962,28 +2962,6 @@ IN THE SOFTWARE.
"""
----
-
-## reanimated-bottom-sheet
-
-This product contains a modified version of 'reanimated-bottom-sheet' by Michał Osadnik.
-
-Highly configurable component imitating native bottom sheet behavior, with fully native 60 FPS animations!
-
-* HOMEPAGE:
- * https://github.com/osdnk/react-native-reanimated-bottom-sheet
-
-* LICENSE: MIT
-
-Copyright 2019 – present Michał Osadnik
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
---
## semver
diff --git a/app/components/announcement_banner/announcement_banner.tsx b/app/components/announcement_banner/announcement_banner.tsx
index 4008b5c86..08f4bcd17 100644
--- a/app/components/announcement_banner/announcement_banner.tsx
+++ b/app/components/announcement_banner/announcement_banner.tsx
@@ -9,6 +9,7 @@ import {
View,
} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {dismissAnnouncement} from '@actions/local/systems';
import CompassIcon from '@components/compass_icon';
@@ -17,6 +18,7 @@ import {ANNOUNCEMENT_BAR_HEIGHT} from '@constants/view';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {bottomSheet} from '@screens/navigation';
+import {bottomSheetSnapPoint} from '@utils/helpers';
import {getMarkdownTextStyles} from '@utils/markdown';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
@@ -82,6 +84,7 @@ const AnnouncementBanner = ({
const intl = useIntl();
const serverUrl = useServerUrl();
const height = useSharedValue(0);
+ const {bottom} = useSafeAreaInsets();
const theme = useTheme();
const [visible, setVisible] = useState(false);
const style = getStyle(theme);
@@ -100,19 +103,20 @@ const AnnouncementBanner = ({
defaultMessage: 'Announcement',
});
- let snapPoint = SNAP_POINT_WITHOUT_DISMISS;
- if (allowDismissal) {
- snapPoint += DISMISS_BUTTON_HEIGHT;
- }
+ const snapPoint = bottomSheetSnapPoint(
+ 1,
+ SNAP_POINT_WITHOUT_DISMISS + (allowDismissal ? DISMISS_BUTTON_HEIGHT : 0),
+ bottom,
+ );
bottomSheet({
closeButtonId: CLOSE_BUTTON_ID,
title,
renderContent,
- snapPoints: [snapPoint, 10],
+ snapPoints: [1, snapPoint],
theme,
});
- }, [theme.sidebarHeaderTextColor, intl.locale, renderContent, allowDismissal]);
+ }, [theme.sidebarHeaderTextColor, intl.locale, renderContent, allowDismissal, bottom]);
const handleDismiss = useCallback(() => {
dismissAnnouncement(serverUrl, bannerText);
diff --git a/app/components/announcement_banner/expanded_announcement_banner.tsx b/app/components/announcement_banner/expanded_announcement_banner.tsx
index 22de72d02..a34938b3c 100644
--- a/app/components/announcement_banner/expanded_announcement_banner.tsx
+++ b/app/components/announcement_banner/expanded_announcement_banner.tsx
@@ -1,11 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import {BottomSheetScrollView} from '@gorhom/bottom-sheet';
import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
-import {Text, View} from 'react-native';
+import {ScrollView, Text, View} from 'react-native';
import Button from 'react-native-button';
-import {ScrollView} from 'react-native-gesture-handler';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {dismissAnnouncement} from '@actions/local/systems';
@@ -84,6 +84,8 @@ const ExpandedAnnouncementBanner = ({
return [style.container, {marginBottom: insets.bottom + 10}];
}, [style, insets.bottom]);
+ const Scroll = useMemo(() => (isTablet ? ScrollView : BottomSheetScrollView), [isTablet]);
+
return (
{!isTablet && (
@@ -94,7 +96,7 @@ const ExpandedAnnouncementBanner = ({
})}
)}
-
-
+
+
);
}
+
+export default BottomSheetButton;
diff --git a/app/screens/bottom_sheet/content.tsx b/app/screens/bottom_sheet/content.tsx
index 8cd23947e..a4e76840c 100644
--- a/app/screens/bottom_sheet/content.tsx
+++ b/app/screens/bottom_sheet/content.tsx
@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
-import {GestureResponderEvent, Platform, Text, useWindowDimensions, View} from 'react-native';
+import {GestureResponderEvent, Text, useWindowDimensions, View} from 'react-native';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
@@ -33,7 +33,7 @@ export const TITLE_SEPARATOR_MARGIN_TABLET = 20;
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
- flex: 1,
+ flexGrow: 1,
},
titleContainer: {
marginTop: TITLE_MARGIN_TOP,
@@ -83,18 +83,15 @@ const BottomSheetContent = ({buttonText, buttonIcon, children, disableButton, on
{children}
>
{showButton && (
- <>
-
-
-
- >
- )}
+
+ )
+ }
);
};
diff --git a/app/screens/bottom_sheet/index.tsx b/app/screens/bottom_sheet/index.tsx
index 1c0d790a8..260acd2bc 100644
--- a/app/screens/bottom_sheet/index.tsx
+++ b/app/screens/bottom_sheet/index.tsx
@@ -1,13 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {ReactNode, useCallback, useEffect, useRef} from 'react';
-import {DeviceEventEmitter, Keyboard, StyleSheet, View} from 'react-native';
-import {State, TapGestureHandler} from 'react-native-gesture-handler';
-import {Navigation as RNN} from 'react-native-navigation';
-import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
-import RNBottomSheet from 'reanimated-bottom-sheet';
+import BottomSheetM, {BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetFooterProps} from '@gorhom/bottom-sheet';
+import React, {ReactNode, useCallback, useEffect, useMemo, useRef} from 'react';
+import {DeviceEventEmitter, Keyboard, View} from 'react-native';
+import useNavButtonPressed from '@app/hooks/navigation_button_pressed';
import {Events} from '@constants';
import {useTheme} from '@context/theme';
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
@@ -18,36 +16,94 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import Indicator from './indicator';
-type SlideUpPanelProps = {
+import type {WithSpringConfig} from 'react-native-reanimated';
+
+export {default as BottomSheetButton, BUTTON_HEIGHT} from './button';
+export {default as BottomSheetContent, TITLE_HEIGHT} from './content';
+
+type Props = {
closeButtonId?: string;
componentId: string;
initialSnapIndex?: number;
+ footerComponent?: React.FC;
renderContent: () => ReactNode;
snapPoints?: Array;
testID?: string;
}
-export const PADDING_TOP_MOBILE = 20;
-export const PADDING_TOP_TABLET = 8;
+const PADDING_TOP_MOBILE = 20;
+const PADDING_TOP_TABLET = 8;
-const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderContent, snapPoints = ['90%', '50%', 50], testID}: SlideUpPanelProps) => {
- const sheetRef = useRef(null);
+export const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
+ return {
+ bottomSheet: {
+ borderTopStartRadius: 24,
+ borderTopEndRadius: 24,
+ shadowOffset: {
+ width: 0,
+ height: 8,
+ },
+ shadowOpacity: 0.12,
+ shadowRadius: 24,
+ shadowColor: '#000',
+ elevation: 24,
+ },
+ bottomSheetBackground: {
+ backgroundColor: theme.centerChannelBg,
+ borderColor: changeOpacity(theme.centerChannelColor, 0.16),
+ },
+ content: {
+ flex: 1,
+ paddingHorizontal: 20,
+ paddingTop: PADDING_TOP_MOBILE,
+ },
+ contentTablet: {
+ paddingTop: PADDING_TOP_TABLET,
+ },
+ separator: {
+ height: 1,
+ borderTopWidth: 1,
+ borderColor: changeOpacity(theme.centerChannelColor, 0.08),
+ },
+ };
+});
+
+export const animatedConfig: Omit = {
+ damping: 50,
+ mass: 0.3,
+ stiffness: 121.6,
+ overshootClamping: true,
+ restSpeedThreshold: 0.3,
+ restDisplacementThreshold: 0.3,
+};
+
+const BottomSheet = ({
+ closeButtonId,
+ componentId,
+ initialSnapIndex = 1,
+ footerComponent,
+ renderContent,
+ snapPoints = [1, '50%', '90%'],
+ testID,
+}: Props) => {
+ const sheetRef = useRef(null);
const isTablet = useIsTablet();
const theme = useTheme();
- const firstRun = useRef(isTablet);
- const lastSnap = snapPoints.length - 1;
- const backdropOpacity = useSharedValue(0);
+ const styles = getStyleSheet(theme);
+
+ const bottomSheetBackgroundStyle = useMemo(() => [
+ styles.bottomSheetBackground,
+ {borderWidth: isTablet ? 0 : 1},
+ ], [isTablet, styles]);
const close = useCallback(() => {
- if (firstRun.current) {
- dismissModal({componentId});
- }
+ dismissModal({componentId});
}, [componentId]);
useEffect(() => {
const listener = DeviceEventEmitter.addListener(Events.CLOSE_BOTTOM_SHEET, () => {
if (sheetRef.current) {
- sheetRef.current.snapTo(lastSnap);
+ sheetRef.current.close();
} else {
close();
}
@@ -58,84 +114,40 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
const handleClose = useCallback(() => {
if (sheetRef.current) {
- sheetRef.current.snapTo(1);
+ sheetRef.current.close();
} else {
close();
}
}, []);
- const handleCloseEnd = useCallback(() => {
- if (firstRun.current) {
- backdropOpacity.value = 0;
- setTimeout(close, 250);
+ const handleDismissIfNeeded = useCallback((index: number) => {
+ if (index <= 0) {
+ close();
}
}, []);
- const handleOpenStart = useCallback(() => {
- backdropOpacity.value = 1;
- }, []);
-
useAndroidHardwareBackHandler(componentId, handleClose);
+ useNavButtonPressed(closeButtonId || '', componentId, close, [close]);
useEffect(() => {
hapticFeedback();
Keyboard.dismiss();
- sheetRef.current?.snapTo(initialSnapIndex);
- backdropOpacity.value = 1;
- const t = setTimeout(() => {
- firstRun.current = true;
- }, 100);
-
- return () => clearTimeout(t);
}, []);
- useEffect(() => {
- const navigationEvents = RNN.events().registerNavigationButtonPressedListener(({buttonId}) => {
- if (closeButtonId && buttonId === closeButtonId) {
- close();
- }
- });
-
- return () => navigationEvents.remove();
- }, [close]);
-
- const backdropStyle = useAnimatedStyle(() => ({
- opacity: withTiming(backdropOpacity.value, {duration: 250, easing: Easing.inOut(Easing.linear)}),
- backgroundColor: 'rgba(0, 0, 0, 0.6)',
- }));
-
- const renderBackdrop = () => {
+ const renderBackdrop = useCallback((props: BottomSheetBackdropProps) => {
return (
- {
- if (event.nativeEvent.state === State.END && event.nativeEvent.oldState === State.ACTIVE) {
- sheetRef.current?.snapTo(lastSnap);
- }
- }}
- testID={`${testID}.backdrop`}
- >
-
-
+
);
- };
+ }, []);
const renderContainerContent = () => (
{renderContent()}
@@ -143,7 +155,6 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
);
if (isTablet) {
- const styles = getStyleSheet(theme);
return (
<>
@@ -153,32 +164,22 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
}
return (
- <>
-
- {renderBackdrop()}
- >
+
+ {renderContainerContent()}
+
);
};
-const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
- return {
- separator: {
- height: 1,
- borderTopWidth: 1,
- borderColor: changeOpacity(theme.centerChannelColor, 0.08),
- },
- };
-});
-
export default BottomSheet;
diff --git a/app/screens/bottom_sheet/indicator.tsx b/app/screens/bottom_sheet/indicator.tsx
index 0534a1289..a34bc769e 100644
--- a/app/screens/bottom_sheet/indicator.tsx
+++ b/app/screens/bottom_sheet/indicator.tsx
@@ -6,9 +6,9 @@ import {Animated, StyleSheet, View} from 'react-native';
const styles = StyleSheet.create({
dragIndicatorContainer: {
- marginVertical: 10,
alignItems: 'center',
justifyContent: 'center',
+ top: -15,
},
dragIndicator: {
backgroundColor: 'white',
diff --git a/app/screens/browse_channels/channel_dropdown.tsx b/app/screens/browse_channels/channel_dropdown.tsx
index 161c8b281..3be4fda24 100644
--- a/app/screens/browse_channels/channel_dropdown.tsx
+++ b/app/screens/browse_channels/channel_dropdown.tsx
@@ -48,7 +48,7 @@ export default function ChannelDropdown({
sharedChannelsEnabled,
}: Props) {
const intl = useIntl();
- const insets = useSafeAreaInsets();
+ const {bottom} = useSafeAreaInsets();
const theme = useTheme();
const style = getStyleFromTheme(theme);
@@ -73,11 +73,11 @@ export default function ChannelDropdown({
items += 1;
}
- const itemsSnap = bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom) + TITLE_HEIGHT;
+ const itemsSnap = bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + TITLE_HEIGHT;
bottomSheet({
title: intl.formatMessage({id: 'browse_channels.dropdownTitle', defaultMessage: 'Show'}),
renderContent,
- snapPoints: [itemsSnap, 10],
+ snapPoints: [1, itemsSnap],
closeButtonId: 'close',
theme,
});
diff --git a/app/screens/channel/header/header.tsx b/app/screens/channel/header/header.tsx
index 305fad889..d01e81232 100644
--- a/app/screens/channel/header/header.tsx
+++ b/app/screens/channel/header/header.tsx
@@ -4,14 +4,16 @@
import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, Platform, Text, View} from 'react-native';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
+import {bottomSheetSnapPoint} from '@app/utils/helpers';
+import {CHANNEL_ACTIONS_OPTIONS_HEIGHT} from '@components/channel_actions/channel_actions';
import CompassIcon from '@components/compass_icon';
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
import NavigationHeader from '@components/navigation_header';
import {ITEM_HEIGHT} from '@components/option_item';
import RoundedHeaderContext from '@components/rounded_header_context';
import {General, Screens} from '@constants';
-import {QUICK_OPTIONS_HEIGHT} from '@constants/view';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import {useDefaultHeaderHeight} from '@hooks/header';
@@ -22,7 +24,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import OtherMentionsBadge from './other_mentions_badge';
-import QuickActions from './quick_actions';
+import QuickActions, {MARGIN, SEPARATOR_HEIGHT} from './quick_actions';
import type {HeaderRightButton} from '@components/navigation_header/header';
@@ -70,6 +72,7 @@ const ChannelHeader = ({
}: ChannelProps) => {
const intl = useIntl();
const isTablet = useIsTablet();
+ const {bottom} = useSafeAreaInsets();
const theme = useTheme();
const styles = getStyleSheet(theme);
const defaultHeight = useDefaultHeaderHeight();
@@ -132,7 +135,8 @@ const ChannelHeader = ({
}
// When calls is enabled, we need space to move the "Copy Link" from a button to an option
- const height = QUICK_OPTIONS_HEIGHT + (callsAvailable && !isDMorGM ? ITEM_HEIGHT : 0);
+ const items = callsAvailable && !isDMorGM ? 3 : 2;
+ const height = CHANNEL_ACTIONS_OPTIONS_HEIGHT + SEPARATOR_HEIGHT + MARGIN + (items * ITEM_HEIGHT);
const renderContent = () => {
return (
@@ -147,11 +151,11 @@ const ChannelHeader = ({
bottomSheet({
title: '',
renderContent,
- snapPoints: [height, 10],
+ snapPoints: [1, bottomSheetSnapPoint(1, height, bottom)],
theme,
closeButtonId: 'close-channel-quick-actions',
});
- }, [channelId, isDMorGM, isTablet, onTitlePress, theme, callsAvailable]);
+ }, [bottom, channelId, isDMorGM, isTablet, onTitlePress, theme, callsAvailable]);
const rightButtons: HeaderRightButton[] = useMemo(() => ([
diff --git a/app/screens/channel/header/quick_actions/index.tsx b/app/screens/channel/header/quick_actions/index.tsx
index a9b8be185..716e09a21 100644
--- a/app/screens/channel/header/quick_actions/index.tsx
+++ b/app/screens/channel/header/quick_actions/index.tsx
@@ -8,7 +8,6 @@ import ChannelActions from '@components/channel_actions';
import CopyChannelLinkOption from '@components/channel_actions/copy_channel_link_option';
import InfoBox from '@components/channel_actions/info_box';
import LeaveChannelLabel from '@components/channel_actions/leave_channel_label';
-import {QUICK_OPTIONS_HEIGHT} from '@constants/view';
import {useTheme} from '@context/theme';
import {dismissBottomSheet} from '@screens/navigation';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@@ -19,20 +18,23 @@ type Props = {
isDMorGM: boolean;
}
+export const SEPARATOR_HEIGHT = 17;
+export const MARGIN = 8;
+
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
- minHeight: QUICK_OPTIONS_HEIGHT,
+ flex: 1,
},
line: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
height: 1,
- marginVertical: 8,
+ marginVertical: MARGIN,
},
wrapper: {
- marginBottom: 8,
+ marginBottom: MARGIN,
},
separator: {
- width: 8,
+ width: MARGIN,
},
}));
diff --git a/app/screens/edit_profile/components/profile_image_picker.tsx b/app/screens/edit_profile/components/profile_image_picker.tsx
index b49780f49..7aead7a2e 100644
--- a/app/screens/edit_profile/components/profile_image_picker.tsx
+++ b/app/screens/edit_profile/components/profile_image_picker.tsx
@@ -14,6 +14,7 @@ import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import NetworkManager from '@managers/network_manager';
+import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
import PanelItem from '@screens/edit_profile/components/panel_item';
import {bottomSheet} from '@screens/navigation';
import PickerUtil from '@utils/file/file_picker';
@@ -121,13 +122,12 @@ const ProfileImagePicker = ({
);
};
- const snapPointsCount = canRemovePicture ? 5 : 4;
- const snapPoint = bottomSheetSnapPoint(snapPointsCount, ITEM_HEIGHT, bottom);
+ const snapPoint = bottomSheetSnapPoint(4, ITEM_HEIGHT, bottom) + TITLE_HEIGHT;
return bottomSheet({
closeButtonId: 'close-edit-profile',
renderContent,
- snapPoints: [snapPoint, 10],
+ snapPoints: [1, snapPoint],
title: 'Change profile photo',
theme,
});
diff --git a/app/screens/home/account/components/options/user_presence/index.tsx b/app/screens/home/account/components/options/user_presence/index.tsx
index 96e0fe2be..23e7df7c6 100644
--- a/app/screens/home/account/components/options/user_presence/index.tsx
+++ b/app/screens/home/account/components/options/user_presence/index.tsx
@@ -129,7 +129,7 @@ const UserStatus = ({currentUser}: Props) => {
bottomSheet({
closeButtonId: 'close-set-user-status',
renderContent,
- snapPoints: [(snapPoint + TITLE_HEIGHT), 10],
+ snapPoints: [1, (snapPoint + TITLE_HEIGHT)],
title: intl.formatMessage({id: 'user_status.title', defaultMessage: 'Status'}),
theme,
});
diff --git a/app/screens/home/channel_list/categories_list/header/header.tsx b/app/screens/home/channel_list/categories_list/header/header.tsx
index 8cf942240..ea94719e4 100644
--- a/app/screens/home/channel_list/categories_list/header/header.tsx
+++ b/app/screens/home/channel_list/categories_list/header/header.tsx
@@ -105,7 +105,7 @@ const ChannelListHeader = ({
const theme = useTheme();
const isTablet = useIsTablet();
const intl = useIntl();
- const insets = useSafeAreaInsets();
+ const {bottom} = useSafeAreaInsets();
const serverDisplayName = useServerDisplayName();
const marginLeft = useSharedValue(iconPad ? 50 : 0);
const styles = getStyles(theme);
@@ -150,11 +150,11 @@ const ChannelListHeader = ({
bottomSheet({
closeButtonId,
renderContent,
- snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom) + (separators * SEPARATOR_HEIGHT), 10],
+ snapPoints: [1, bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + (separators * SEPARATOR_HEIGHT)],
theme,
title: intl.formatMessage({id: 'home.header.plus_menu', defaultMessage: 'Options'}),
});
- }, [intl, insets, isTablet, theme]);
+ }, [intl, bottom, isTablet, theme]);
const onPushAlertPress = useCallback(() => {
if (pushProxyStatus === PUSH_PROXY_STATUS_NOT_AVAILABLE) {
diff --git a/app/screens/home/channel_list/servers/index.tsx b/app/screens/home/channel_list/servers/index.tsx
index 2a29d3ff5..4675f6db9 100644
--- a/app/screens/home/channel_list/servers/index.tsx
+++ b/app/screens/home/channel_list/servers/index.tsx
@@ -1,9 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import {BottomSheetProps} from '@gorhom/bottom-sheet';
import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {StyleSheet} from 'react-native';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
import ServerIcon from '@components/server_icon';
import {useServerUrl} from '@context/server';
@@ -11,14 +13,21 @@ import {useTheme} from '@context/theme';
import {subscribeAllServers} from '@database/subscription/servers';
import {subscribeUnreadAndMentionsByServer, UnreadObserverArgs} from '@database/subscription/unreads';
import {useIsTablet} from '@hooks/device';
+import {BUTTON_HEIGHT, TITLE_HEIGHT} from '@screens/bottom_sheet';
import {bottomSheet} from '@screens/navigation';
+import {bottomSheetSnapPoint} from '@utils/helpers';
import {sortServersByDisplayName} from '@utils/server';
-import ServerList from './servers_list';
+import ServerList, {AddServerButton} from './servers_list';
import type ServersModel from '@typings/database/models/app/servers';
import type {UnreadMessages, UnreadSubscription} from '@typings/database/subscriptions';
+export type ServersRef = {
+ openServers: () => void;
+}
+
+export const SERVER_ITEM_HEIGHT = 72;
const subscriptions: Map = new Map();
const styles = StyleSheet.create({
@@ -34,16 +43,13 @@ const styles = StyleSheet.create({
},
});
-export type ServersRef = {
- openServers: () => void;
-}
-
-const Servers = React.forwardRef((props, ref) => {
+const Servers = React.forwardRef((_, ref) => {
const intl = useIntl();
const [total, setTotal] = useState({mentions: 0, unread: false});
const registeredServers = useRef();
const currentServerUrl = useServerUrl();
const isTablet = useIsTablet();
+ const {bottom} = useSafeAreaInsets();
const theme = useTheme();
const updateTotal = () => {
@@ -111,21 +117,25 @@ const Servers = React.forwardRef((props, ref) => {
);
};
- const snapPoints = ['50%', 10];
- if (registeredServers.current.length > 3) {
- snapPoints[0] = '90%';
+ const snapPoints: BottomSheetProps['snapPoints'] = [
+ 1,
+ bottomSheetSnapPoint(Math.min(2.5, registeredServers.current.length), 72, bottom) + TITLE_HEIGHT + BUTTON_HEIGHT,
+ ];
+ if (registeredServers.current.length > 1) {
+ snapPoints.push('90%');
}
const closeButtonId = 'close-your-servers';
bottomSheet({
closeButtonId,
renderContent,
+ footerComponent: AddServerButton,
snapPoints,
theme,
title: intl.formatMessage({id: 'your.servers', defaultMessage: 'Your servers'}),
});
}
- }, [isTablet, theme]);
+ }, [bottom, isTablet, theme]);
useImperativeHandle(ref, () => ({
openServers: onPress,
diff --git a/app/screens/home/channel_list/servers/servers_list/add_server_button.tsx b/app/screens/home/channel_list/servers/servers_list/add_server_button.tsx
new file mode 100644
index 000000000..649e69460
--- /dev/null
+++ b/app/screens/home/channel_list/servers/servers_list/add_server_button.tsx
@@ -0,0 +1,32 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {BottomSheetFooter, BottomSheetFooterProps} from '@gorhom/bottom-sheet';
+import React, {useCallback} from 'react';
+import {useIntl} from 'react-intl';
+
+import {useTheme} from '@context/theme';
+import {BottomSheetButton} from '@screens/bottom_sheet';
+import {addNewServer} from '@utils/server';
+
+const AddServerButton = (props: BottomSheetFooterProps) => {
+ const theme = useTheme();
+ const {formatMessage} = useIntl();
+
+ const onAddServer = useCallback(async () => {
+ addNewServer(theme);
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default AddServerButton;
diff --git a/app/screens/home/channel_list/servers/servers_list/index.tsx b/app/screens/home/channel_list/servers/servers_list/index.tsx
index 5e645186b..045dfe9d6 100644
--- a/app/screens/home/channel_list/servers/servers_list/index.tsx
+++ b/app/screens/home/channel_list/servers/servers_list/index.tsx
@@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React, {useCallback} from 'react';
+import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
+import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
-import {ListRenderItemInfo, StyleSheet, View} from 'react-native';
-import {FlatList} from 'react-native-gesture-handler';
+import {FlatList, ListRenderItemInfo, StyleSheet, View} from 'react-native';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
@@ -16,13 +16,15 @@ import ServerItem from './server_item';
import type ServersModel from '@typings/database/models/app/servers';
+export {default as AddServerButton} from './add_server_button';
+
type Props = {
servers: ServersModel[];
}
const styles = StyleSheet.create({
container: {
- flex: 1,
+ flexGrow: 1,
},
contentContainer: {
marginVertical: 4,
@@ -51,18 +53,20 @@ const ServerList = ({servers}: Props) => {
);
}, []);
+ const List = useMemo(() => (isTablet ? FlatList : BottomSheetFlatList), [isTablet]);
+
return (
-
);
diff --git a/app/screens/home/search/results/file_options/mobile_options.tsx b/app/screens/home/search/results/file_options/mobile_options.tsx
index a2a0c4f3f..cb8d305ea 100644
--- a/app/screens/home/search/results/file_options/mobile_options.tsx
+++ b/app/screens/home/search/results/file_options/mobile_options.tsx
@@ -40,7 +40,8 @@ export const showMobileOptionsBottomSheet = ({
closeButtonId: 'close-search-file-options',
renderContent,
snapPoints: [
- bottomSheetSnapPoint(numOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT, 10,
+ 1,
+ bottomSheetSnapPoint(numOptions, ITEM_HEIGHT, insets.bottom) + HEADER_HEIGHT,
],
theme,
title: '',
diff --git a/app/screens/home/search/results/header.tsx b/app/screens/home/search/results/header.tsx
index 8f5a5d5f7..0ba4fd47f 100644
--- a/app/screens/home/search/results/header.tsx
+++ b/app/screens/home/search/results/header.tsx
@@ -93,12 +93,13 @@ const Header = ({
const snapPoints = useMemo(() => {
return [
+ 1,
bottomSheetSnapPoint(
NUMBER_FILTER_ITEMS,
FILTER_ITEM_HEIGHT,
bottom,
) + TITLE_HEIGHT + DIVIDERS_HEIGHT + (isTablet ? TITLE_SEPARATOR_MARGIN_TABLET : TITLE_SEPARATOR_MARGIN),
- 10];
+ ];
}, []);
const handleFilterPress = useCallback(() => {
diff --git a/app/screens/home/search/team_picker_icon.tsx b/app/screens/home/search/team_picker_icon.tsx
index be14b9e00..19c6cdd7a 100644
--- a/app/screens/home/search/team_picker_icon.tsx
+++ b/app/screens/home/search/team_picker_icon.tsx
@@ -3,23 +3,28 @@
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
-import {View, useWindowDimensions} from 'react-native';
+import {View} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import CompassIcon from '@components/compass_icon';
+import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
import TeamIcon from '@components/team_sidebar/team_list/team_item/team_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useTheme} from '@context/theme';
+import {TITLE_HEIGHT} from '@screens/bottom_sheet';
import {bottomSheet} from '@screens/navigation';
+import {bottomSheetSnapPoint} from '@utils/helpers';
import {preventDoubleTap} from '@utils/tap';
-import {getTeamsSnapHeight} from '@utils/team_list';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
-import SelectTeamSlideUp from './select_team_slideup';
+import BottomSheetTeamList from './bottom_sheet_team_list';
+import type {BottomSheetProps} from '@gorhom/bottom-sheet';
import type TeamModel from '@typings/database/models/servers/team';
const MENU_DOWN_ICON_SIZE = 24;
+const NO_TEAMS_HEIGHT = 392;
+
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
teamContainer: {
@@ -52,9 +57,8 @@ type Props = {
const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}: Props) => {
const intl = useIntl();
const theme = useTheme();
- const dimensions = useWindowDimensions();
const styles = getStyleFromTheme(theme);
- const insets = useSafeAreaInsets();
+ const {bottom} = useSafeAreaInsets();
const selectedTeam = teams.find((t) => t.id === teamId);
@@ -63,7 +67,7 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
const handleTeamChange = useCallback(preventDoubleTap(() => {
const renderContent = () => {
return (
- 3) {
+ snapPoints.push('90%');
+ }
+
bottomSheet({
closeButtonId: 'close-team_list',
renderContent,
- snapPoints: [height, 10],
+ snapPoints,
theme,
title,
});
- }), [theme, setTeamId, teamId, teams]);
+ }), [theme, setTeamId, teamId, teams, bottom]);
return (
<>
diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts
index 8c77176e1..9be856a49 100644
--- a/app/screens/navigation.ts
+++ b/app/screens/navigation.ts
@@ -17,6 +17,7 @@ import NavigationStore from '@store/navigation_store';
import {appearanceControlledScreens, mergeNavigationOptions} from '@utils/navigation';
import {changeOpacity, setNavigatorStyles} from '@utils/theme';
+import type {BottomSheetFooterProps} from '@gorhom/bottom-sheet';
import type {LaunchProps} from '@typings/launch';
import type {NavButtons} from '@typings/screens/navigation';
@@ -738,13 +739,14 @@ export async function dismissOverlay(componentId: string) {
type BottomSheetArgs = {
closeButtonId: string;
initialSnapIndex?: number;
+ footerComponent?: React.FC;
renderContent: () => JSX.Element;
snapPoints: Array;
theme: Theme;
title: string;
}
-export async function bottomSheet({title, renderContent, snapPoints, initialSnapIndex = 0, theme, closeButtonId}: BottomSheetArgs) {
+export async function bottomSheet({title, renderContent, footerComponent, snapPoints, initialSnapIndex = 1, theme, closeButtonId}: BottomSheetArgs) {
const {isSplitView} = await isRunningInSplitView();
const isTablet = Device.IS_TABLET && !isSplitView;
@@ -753,12 +755,14 @@ export async function bottomSheet({title, renderContent, snapPoints, initialSnap
closeButtonId,
initialSnapIndex,
renderContent,
+ footerComponent,
snapPoints,
}, bottomSheetModalOptions(theme, closeButtonId));
} else {
showModalOverCurrentContext(Screens.BOTTOM_SHEET, {
initialSnapIndex,
renderContent,
+ footerComponent,
snapPoints,
}, bottomSheetModalOptions(theme));
}
diff --git a/app/screens/post_options/post_options.tsx b/app/screens/post_options/post_options.tsx
index f783c2eb0..38e316ac8 100644
--- a/app/screens/post_options/post_options.tsx
+++ b/app/screens/post_options/post_options.tsx
@@ -1,15 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import {BottomSheetProps, BottomSheetScrollView} from '@gorhom/bottom-sheet';
import {useManagedConfig} from '@mattermost/react-native-emm';
import React, {useMemo} from 'react';
+import {ScrollView} from 'react-native';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {CopyPermalinkOption, FollowThreadOption, ReplyOption, SaveOption} from '@components/common_post_options';
import {ITEM_HEIGHT} from '@components/option_item';
import {Screens} from '@constants';
+import {REACTION_PICKER_HEIGHT, REACTION_PICKER_MARGIN} from '@constants/reaction_picker';
+import {useIsTablet} from '@hooks/device';
import useNavButtonPressed from '@hooks/navigation_button_pressed';
import BottomSheet from '@screens/bottom_sheet';
import {dismissBottomSheet} from '@screens/navigation';
+import {bottomSheetSnapPoint} from '@utils/helpers';
import {isSystemMessage} from '@utils/post';
import AppBindingsPostOptions from './options/app_bindings_post_option';
@@ -48,6 +54,9 @@ const PostOptions = ({
sourceScreen, post, thread, bindings, serverUrl,
}: PostOptionsProps) => {
const managedConfig = useManagedConfig();
+ const {bottom} = useSafeAreaInsets();
+ const isTablet = useIsTablet();
+ const Scroll = useMemo(() => (isTablet ? ScrollView : BottomSheetScrollView), [isTablet]);
const close = () => {
return dismissBottomSheet(Screens.POST_OPTIONS);
@@ -63,17 +72,31 @@ const PostOptions = ({
const shouldRenderFollow = !(sourceScreen !== Screens.CHANNEL || !thread);
const shouldShowBindings = bindings.length > 0 && !isSystemPost;
- const snapPoints = [
+ const snapPoints = useMemo(() => {
+ const items: BottomSheetProps['snapPoints'] = [1];
+ const optionsCount = [
+ canCopyPermalink, canCopyText, canDelete, canEdit,
+ canMarkAsUnread, canPin, canReply, !isSystemPost, shouldRenderFollow,
+ ].reduce((acc, v) => {
+ return v ? acc + 1 : acc;
+ }, 0) + (shouldShowBindings ? 0.5 : 0);
+
+ items.push(bottomSheetSnapPoint(optionsCount, ITEM_HEIGHT, bottom) + (canAddReaction ? REACTION_PICKER_HEIGHT + REACTION_PICKER_MARGIN : 0));
+
+ if (shouldShowBindings) {
+ items.push('90%');
+ }
+
+ return items;
+ }, [
canAddReaction, canCopyPermalink, canCopyText,
- canDelete, canEdit, shouldRenderFollow,
- canMarkAsUnread, canPin, canReply, !isSystemPost,
- ].reduce((acc, v) => {
- return v ? acc + 1 : acc;
- }, 0);
+ canDelete, canEdit, shouldRenderFollow, shouldShowBindings,
+ canMarkAsUnread, canPin, canReply, isSystemPost, bottom,
+ ]);
const renderContent = () => {
return (
- <>
+
{canAddReaction &&
}
- >
+
);
};
- const finalSnapPoints = useMemo(() => {
- const additionalSnapPoints = 2;
-
- const lowerSnapPoints = snapPoints + additionalSnapPoints;
- if (!shouldShowBindings) {
- return [lowerSnapPoints * ITEM_HEIGHT, 10];
- }
-
- const upperSnapPoints = lowerSnapPoints + bindings.length;
- return [upperSnapPoints * ITEM_HEIGHT, lowerSnapPoints * ITEM_HEIGHT, 10];
- }, [snapPoints, shouldShowBindings, bindings.length]);
-
- const initialSnapIndex = shouldShowBindings ? 1 : 0;
-
return (
);
diff --git a/app/screens/reactions/reactions.tsx b/app/screens/reactions/reactions.tsx
index 1227ef5e5..00798c62d 100644
--- a/app/screens/reactions/reactions.tsx
+++ b/app/screens/reactions/reactions.tsx
@@ -4,6 +4,7 @@
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {Screens} from '@constants';
+import {useIsTablet} from '@hooks/device';
import BottomSheet from '@screens/bottom_sheet';
import {getEmojiFirstAlias} from '@utils/emoji/helpers';
@@ -20,8 +21,10 @@ type Props = {
}
const Reactions = ({initialEmoji, location, reactions}: Props) => {
+ const isTablet = useIsTablet();
const [sortedReactions, setSortedReactions] = useState(Array.from(new Set(reactions?.map((r) => getEmojiFirstAlias(r.emojiName)))));
const [index, setIndex] = useState(sortedReactions.indexOf(initialEmoji));
+
const reactionsByName = useMemo(() => {
return reactions?.reduce((acc, reaction) => {
const emojiAlias = getEmojiFirstAlias(reaction.emojiName);
@@ -59,6 +62,7 @@ const Reactions = ({initialEmoji, location, reactions}: Props) => {
key={emojiAlias}
location={location}
reactions={reactionsByName.get(emojiAlias)!}
+ type={isTablet ? 'FlatList' : 'BottomSheetFlatList'}
/>
>
);
@@ -81,7 +85,7 @@ const Reactions = ({initialEmoji, location, reactions}: Props) => {
closeButtonId='close-post-reactions'
componentId={Screens.REACTIONS}
initialSnapIndex={1}
- snapPoints={['90%', '50%', 10]}
+ snapPoints={[1, '50%', '90%']}
testID='reactions'
/>
);
diff --git a/app/screens/reactions/reactors_list/index.tsx b/app/screens/reactions/reactors_list/index.tsx
index 34006c352..882ef6cbf 100644
--- a/app/screens/reactions/reactors_list/index.tsx
+++ b/app/screens/reactions/reactors_list/index.tsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
+import {BottomSheetFlatList} from '@gorhom/bottom-sheet';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {ListRenderItemInfo, NativeScrollEvent, NativeSyntheticEvent, PanResponder} from 'react-native';
import {FlatList} from 'react-native-gesture-handler';
@@ -15,9 +16,10 @@ import type ReactionModel from '@typings/database/models/servers/reaction';
type Props = {
location: string;
reactions: ReactionModel[];
+ type?: BottomSheetList;
}
-const ReactorsList = ({location, reactions}: Props) => {
+const ReactorsList = ({location, reactions, type = 'FlatList'}: Props) => {
const serverUrl = useServerUrl();
const [enabled, setEnabled] = useState(false);
const [direction, setDirection] = useState<'down' | 'up'>('down');
@@ -56,6 +58,17 @@ const ReactorsList = ({location, reactions}: Props) => {
fetchUsersByIds(serverUrl, userIds);
}, []);
+ if (type === 'BottomSheetFlatList') {
+ return (
+
+ );
+ }
+
return (
);
diff --git a/app/screens/user_profile/user_profile.tsx b/app/screens/user_profile/user_profile.tsx
index cd87a85a8..e0f1eaf78 100644
--- a/app/screens/user_profile/user_profile.tsx
+++ b/app/screens/user_profile/user_profile.tsx
@@ -12,6 +12,7 @@ import {Screens} from '@constants';
import {useServerUrl} from '@context/server';
import {getLocaleFromLanguage} from '@i18n';
import BottomSheet from '@screens/bottom_sheet';
+import {bottomSheetSnapPoint} from '@utils/helpers';
import {getUserCustomStatus, getUserTimezone, isCustomStatusExpired} from '@utils/user';
import UserProfileCustomStatus from './custom_status';
@@ -45,7 +46,6 @@ const TITLE_HEIGHT = 118;
const OPTIONS_HEIGHT = 82;
const SINGLE_OPTION_HEIGHT = 68;
const LABEL_HEIGHT = 58;
-const EXTRA_HEIGHT = 60;
const UserProfile = ({
channelId, closeButtonId, currentUserId, enablePostIconOverride, enablePostUsernameOverride,
@@ -55,14 +55,14 @@ const UserProfile = ({
}: Props) => {
const {formatMessage, locale} = useIntl();
const serverUrl = useServerUrl();
- const insets = useSafeAreaInsets();
+ const {bottom} = useSafeAreaInsets();
const channelContext = [Screens.CHANNEL, Screens.THREAD].includes(location);
const showOptions: OptionsType = channelContext && !user.isBot ? 'all' : 'message';
const override = Boolean(userIconOverride || usernameOverride);
const timezone = getUserTimezone(user);
const customStatus = getUserCustomStatus(user);
- const showCustomStatus = isCustomStatusEnabled && Boolean(customStatus) && !user.isBot && !isCustomStatusExpired(user);
let localTime: string|undefined;
+
if (timezone) {
moment.locale(getLocaleFromLanguage(locale).toLowerCase());
let format = 'H:mm';
@@ -73,36 +73,42 @@ const UserProfile = ({
localTime = mtz.tz(Date.now(), timezone).format(format);
}
+ const showCustomStatus = isCustomStatusEnabled && Boolean(customStatus) && !user.isBot && !isCustomStatusExpired(user);
+ const showUserProfileOptions = (!isDirectMessage || !channelContext) && !override;
+ const showNickname = Boolean(user.nickname) && !override && !user.isBot;
+ const showPosition = Boolean(user.position) && !override && !user.isBot;
+ const showLocalTime = Boolean(localTime) && !override && !user.isBot;
+
const snapPoints = useMemo(() => {
- let initial = TITLE_HEIGHT;
- if ((!isDirectMessage || !channelContext) && !override) {
- initial += showOptions === 'all' ? OPTIONS_HEIGHT : SINGLE_OPTION_HEIGHT;
+ let title = TITLE_HEIGHT;
+ if (showUserProfileOptions) {
+ title += showOptions === 'all' ? OPTIONS_HEIGHT : SINGLE_OPTION_HEIGHT;
}
let labels = 0;
- if (!override && !user.isBot) {
- if (showCustomStatus) {
- labels += 1;
- }
-
- if (user.nickname) {
- labels += 1;
- }
-
- if (user.position) {
- labels += 1;
- }
-
- if (localTime) {
- labels += 1;
- }
- initial += (labels * LABEL_HEIGHT);
+ if (showCustomStatus) {
+ labels += 1;
}
- return [initial + insets.bottom + EXTRA_HEIGHT, 10];
+ if (showNickname) {
+ labels += 1;
+ }
+
+ if (showPosition) {
+ labels += 1;
+ }
+
+ if (showLocalTime) {
+ labels += 1;
+ }
+
+ return [
+ 1,
+ bottomSheetSnapPoint(labels, LABEL_HEIGHT, bottom) + title,
+ ];
}, [
- isChannelAdmin, isDirectMessage, isSystemAdmin,
- isTeamAdmin, user, localTime, insets.bottom, override,
+ showUserProfileOptions, showCustomStatus, showNickname,
+ showPosition, showLocalTime, bottom,
]);
useEffect(() => {
@@ -125,7 +131,7 @@ const UserProfile = ({
userIconOverride={userIconOverride}
usernameOverride={usernameOverride}
/>
- {(!isDirectMessage || !channelContext) && !override &&
+ {showUserProfileOptions &&
}
{showCustomStatus && }
- {Boolean(user.nickname) && !override && !user.isBot &&
+ {showNickname &&
}
- {Boolean(user.position) && !override && !user.isBot &&
+ {showPosition &&
}
- {Boolean(localTime) && !override && !user.isBot &&
+ {showLocalTime &&
diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts
index e3115ca66..82031fd19 100644
--- a/app/utils/helpers.ts
+++ b/app/utils/helpers.ts
@@ -6,7 +6,7 @@ import {NativeModules, Platform} from 'react-native';
import {Device} from '@constants';
import {CUSTOM_STATUS_TIME_PICKER_INTERVALS_IN_MINUTES} from '@constants/custom_status';
-import {IOS_STATUS_BAR_HEIGHT} from '@constants/view';
+import {STATUS_BAR_HEIGHT} from '@constants/view';
const {MattermostManaged} = NativeModules;
const isRunningInSplitView = MattermostManaged.isRunningInSplitView;
@@ -141,12 +141,9 @@ 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) {
- let bottom = bottomInset;
- if (!bottom && Platform.OS === 'ios') {
- bottom = IOS_STATUS_BAR_HEIGHT;
- }
- return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (bottom * 2.5);
+export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset: number) {
+ const bottom = Platform.select({ios: bottomInset, default: 0}) + STATUS_BAR_HEIGHT;
+ return (itemsCount * itemHeight) + bottom;
}
export function hasTrailingSpaces(term: string) {
diff --git a/app/utils/team_list/index.ts b/app/utils/team_list/index.ts
deleted file mode 100644
index 30cbdf10d..000000000
--- a/app/utils/team_list/index.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-import {ScaledSize} from 'react-native';
-import {EdgeInsets} from 'react-native-safe-area-context';
-
-import {ITEM_HEIGHT} from '@components/team_list/team_list_item/team_list_item';
-import {PADDING_TOP_MOBILE} from '@screens/bottom_sheet';
-import {TITLE_HEIGHT, TITLE_SEPARATOR_MARGIN} from '@screens/bottom_sheet/content';
-import {bottomSheetSnapPoint} from '@utils/helpers';
-
-import type TeamModel from '@typings/database/models/servers/team';
-
-type TeamsSnapProps = {
- teams: TeamModel[];
- dimensions: ScaledSize;
- insets: EdgeInsets;
-}
-
-const NO_TEAMS_HEIGHT = 392;
-export const getTeamsSnapHeight = ({dimensions, teams, insets}: TeamsSnapProps) => {
- let height = NO_TEAMS_HEIGHT;
- if (teams.length) {
- const itemsHeight = bottomSheetSnapPoint(teams.length, ITEM_HEIGHT, 0);
- const heightWithHeader = PADDING_TOP_MOBILE +
- TITLE_HEIGHT + (TITLE_SEPARATOR_MARGIN * 2) +
- itemsHeight + insets.bottom;
- const maxHeight = Math.round((dimensions.height * 0.9));
- height = Math.min(maxHeight, heightWithHeader);
- }
- return height;
-};
diff --git a/package-lock.json b/package-lock.json
index 4dc877e32..862d47ec0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"@formatjs/intl-numberformat": "8.3.3",
"@formatjs/intl-pluralrules": "5.1.8",
"@formatjs/intl-relativetimeformat": "11.1.8",
+ "@gorhom/bottom-sheet": "4.4.5",
"@mattermost/compass-icons": "0.1.35",
"@mattermost/react-native-emm": "1.3.3",
"@mattermost/react-native-network-client": "1.0.2",
@@ -94,7 +95,6 @@
"react-native-webview": "11.26.0",
"react-syntax-highlighter": "15.5.0",
"readable-stream": "3.6.0",
- "reanimated-bottom-sheet": "1.0.0-alpha.22",
"semver": "7.3.8",
"serialize-error": "11.0.0",
"shallow-equals": "1.0.0",
@@ -2349,6 +2349,33 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@gorhom/bottom-sheet": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-4.4.5.tgz",
+ "integrity": "sha512-Z5Z20wshLUB8lIdtMKoJaRnjd64wBR/q8EeVPThrg+skrcBwBPHfUwZJ2srB0rEszA/01ejSJy/ixyd7Ra7vUA==",
+ "dependencies": {
+ "@gorhom/portal": "1.0.14",
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*",
+ "react-native-gesture-handler": ">=1.10.1",
+ "react-native-reanimated": ">=2.2.0"
+ }
+ },
+ "node_modules/@gorhom/portal": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
+ "integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
+ "dependencies": {
+ "nanoid": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -19082,17 +19109,6 @@
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
},
- "node_modules/reanimated-bottom-sheet": {
- "version": "1.0.0-alpha.22",
- "resolved": "https://registry.npmjs.org/reanimated-bottom-sheet/-/reanimated-bottom-sheet-1.0.0-alpha.22.tgz",
- "integrity": "sha512-NxecCn+2iA4YzkFuRK5/b86GHHS2OhZ9VRgiM4q18AC20YE/psRilqxzXCKBEvkOjP5AaAvY0yfE7EkEFBjTvw==",
- "peerDependencies": {
- "react": "*",
- "react-native": "*",
- "react-native-gesture-handler": "*",
- "react-native-reanimated": "*"
- }
- },
"node_modules/recast": {
"version": "0.20.5",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz",
@@ -23595,6 +23611,23 @@
"tslib": "^2.4.0"
}
},
+ "@gorhom/bottom-sheet": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-4.4.5.tgz",
+ "integrity": "sha512-Z5Z20wshLUB8lIdtMKoJaRnjd64wBR/q8EeVPThrg+skrcBwBPHfUwZJ2srB0rEszA/01ejSJy/ixyd7Ra7vUA==",
+ "requires": {
+ "@gorhom/portal": "1.0.14",
+ "invariant": "^2.2.4"
+ }
+ },
+ "@gorhom/portal": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/@gorhom/portal/-/portal-1.0.14.tgz",
+ "integrity": "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==",
+ "requires": {
+ "nanoid": "^3.3.1"
+ }
+ },
"@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -36257,12 +36290,6 @@
"resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
"integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
},
- "reanimated-bottom-sheet": {
- "version": "1.0.0-alpha.22",
- "resolved": "https://registry.npmjs.org/reanimated-bottom-sheet/-/reanimated-bottom-sheet-1.0.0-alpha.22.tgz",
- "integrity": "sha512-NxecCn+2iA4YzkFuRK5/b86GHHS2OhZ9VRgiM4q18AC20YE/psRilqxzXCKBEvkOjP5AaAvY0yfE7EkEFBjTvw==",
- "requires": {}
- },
"recast": {
"version": "0.20.5",
"resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz",
diff --git a/package.json b/package.json
index aa07ea7dd..2840c28c2 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@formatjs/intl-numberformat": "8.3.3",
"@formatjs/intl-pluralrules": "5.1.8",
"@formatjs/intl-relativetimeformat": "11.1.8",
+ "@gorhom/bottom-sheet": "4.4.5",
"@mattermost/compass-icons": "0.1.35",
"@mattermost/react-native-emm": "1.3.3",
"@mattermost/react-native-network-client": "1.0.2",
@@ -91,7 +92,6 @@
"react-native-webview": "11.26.0",
"react-syntax-highlighter": "15.5.0",
"readable-stream": "3.6.0",
- "reanimated-bottom-sheet": "1.0.0-alpha.22",
"semver": "7.3.8",
"serialize-error": "11.0.0",
"shallow-equals": "1.0.0",
diff --git a/types/components/bottom_sheet.d.ts b/types/components/bottom_sheet.d.ts
new file mode 100644
index 000000000..5db104ee2
--- /dev/null
+++ b/types/components/bottom_sheet.d.ts
@@ -0,0 +1,4 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+type BottomSheetList = 'FlatList' | 'BottomSheetFlatList';