Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Modernize overlay architecture and improve bottom sheet UX #1479

Merged
merged 8 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
7 changes: 2 additions & 5 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
import DebugButton from "@components/DebugButton";
import { BottomSheetModalProvider } from "@design-system/BottomSheet/BottomSheetModalProvider";
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { PortalProvider } from "@gorhom/portal";
import { useAppStateHandlers } from "@hooks/useAppStateHandlers";
import { PrivyProvider } from "@privy-io/expo";
import { queryClient } from "@queries/queryClient";
import { MaterialDarkTheme, MaterialLightTheme } from "@styles/colors";
import { QueryClientProvider } from "@tanstack/react-query";
import { useAppTheme, useThemeProvider } from "@theme/useAppTheme";

Check warning on line 13 in App.tsx

View workflow job for this annotation

GitHub Actions / lint

'useAppTheme' is defined but never used
import { useCoinbaseWalletListener } from "@utils/coinbaseWallet";
import { converseEventEmitter } from "@utils/events";
import logger from "@utils/logger";
Expand Down Expand Up @@ -77,10 +76,10 @@
}, []);

const showDebugMenu = useCallback(() => {
if (!debugRef.current || !(debugRef.current as any).showDebugMenu) {

Check warning on line 79 in App.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
return;
}
(debugRef.current as any).showDebugMenu();

Check warning on line 82 in App.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
}, []);

useEffect(() => {
Expand Down Expand Up @@ -124,7 +123,7 @@
const AppKeyboardProvider =
Platform.OS === "ios" ? KeyboardProvider : React.Fragment;

export default function AppWithProviders() {

Check warning on line 126 in App.tsx

View workflow job for this annotation

GitHub Actions / lint

Prefer named exports
const colorScheme = useColorScheme();

const paperTheme = useMemo(() => {
Expand All @@ -142,12 +141,10 @@
<ActionSheetProvider>
<ThemeProvider value={{ themeScheme, setThemeContextOverride }}>
<PaperProvider theme={paperTheme}>
<GestureHandlerRootView style={{ flex: 1 }}>

Check warning on line 144 in App.tsx

View workflow job for this annotation

GitHub Actions / lint

Inline style: { flex: 1 }
<BottomSheetModalProvider>
<PortalProvider>
<App />
<Snackbars />
</PortalProvider>
<App />
<Snackbars />
lourou marked this conversation as resolved.
Show resolved Hide resolved
</BottomSheetModalProvider>
</GestureHandlerRootView>
</PaperProvider>
Expand Down
54 changes: 31 additions & 23 deletions components/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@ import {
ViewStyle,
useColorScheme,
useWindowDimensions,
Modal,
} from "react-native";
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
} from "react-native-gesture-handler";
import { Portal } from "react-native-paper";
import {
LinearTransition,
import Animated, {
interpolateColor,
runOnJS,
useAnimatedKeyboard,
useAnimatedStyle,
useSharedValue,
withTiming,
FadeIn,
FadeOut,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";

Expand Down Expand Up @@ -166,26 +167,33 @@ export const Drawer = forwardRef<DrawerRef, DrawerProps>(function Drawer(
}

return (
<DrawerContext.Provider value={{ closeDrawer: handleClose }}>
<Portal>
{/* A bit of a pain but have to wrap this in a gesture handler */}
<GestureHandlerRootView style={styles.gestureHandlerContainer}>
<TouchableWithoutFeedback onPress={handleClose}>
<AnimatedVStack style={[styles.backdrop, backgroundStyle]} />
</TouchableWithoutFeedback>
<GestureDetector gesture={composed}>
<AnimatedScrollView
style={[styles.trayContainer, animatedStyle, style]}
layout={LinearTransition.springify()}
alwaysBounceVertical={false}
>
{showHandle && <View style={styles.handle} />}
{children}
</AnimatedScrollView>
</GestureDetector>
</GestureHandlerRootView>
</Portal>
</DrawerContext.Provider>
<Modal
visible={visible}
transparent={true}
onRequestClose={onClose}
animationType="fade"
statusBarTranslucent={Platform.OS === "android"}
lourou marked this conversation as resolved.
Show resolved Hide resolved
supportedOrientations={["portrait", "landscape"]}
lourou marked this conversation as resolved.
Show resolved Hide resolved
>
<Animated.View style={StyleSheet.absoluteFill}>
<DrawerContext.Provider value={{ closeDrawer: handleClose }}>
<GestureHandlerRootView style={styles.gestureHandlerContainer}>
<TouchableWithoutFeedback onPress={handleClose}>
<AnimatedVStack style={[styles.backdrop, backgroundStyle]} />
</TouchableWithoutFeedback>
<GestureDetector gesture={composed}>
<AnimatedScrollView
style={[styles.trayContainer, animatedStyle, style]}
alwaysBounceVertical={false}
>
{showHandle && <View style={styles.handle} />}
{children}
</AnimatedScrollView>
</GestureDetector>
</GestureHandlerRootView>
</DrawerContext.Provider>
</Animated.View>
</Modal>
);
});

Expand Down
6 changes: 5 additions & 1 deletion design-system/BlurView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BlurViewProps, BlurView as RNBlurView } from "expo-blur";
import { memo } from "react";
import Animated, { AnimatedProps, FadeIn } from "react-native-reanimated";
import { useAppTheme } from "@theme/useAppTheme";

import { IVStackProps } from "./VStack";

Expand All @@ -13,12 +14,15 @@ export const BlurView = memo(function BlurView({
children,
isAbsolute,
style,
tint,
...rest
}: IBlurViewProps) {
const { theme } = useAppTheme();

return (
<AnimatedBlurView
intensity={80}
tint="dark"
tint={tint ?? (theme.isDark ? "dark" : "light")}
style={[
style,
isAbsolute && {
Expand Down
72 changes: 72 additions & 0 deletions design-system/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import BottomSheetBase, { BottomSheetProps } from "@gorhom/bottom-sheet";
import { BottomSheetMethods } from "@gorhom/bottom-sheet/lib/typescript/types";
import React, { forwardRef, memo, useMemo } from "react";
import { useAppTheme } from "@theme/useAppTheme";
import { BottomSheetBackdropOpacity } from "./BottomSheetBackdropOpacity";

export type IBottomSheetProps = BottomSheetProps & {
children: React.ReactNode;
};

export const BottomSheet = memo(
forwardRef<BottomSheetMethods, IBottomSheetProps>(
function BottomSheet(props, ref) {
const {
children,
backdropComponent = BottomSheetBackdropOpacity,
handleIndicatorStyle,
handleStyle,
backgroundStyle,
...rest
} = props;

const { theme } = useAppTheme();

const combinedHandleIndicatorStyle = useMemo(
() => [
{
backgroundColor: theme.colors.background.raised,
},
handleIndicatorStyle,
],
[theme.colors.background.raised, handleIndicatorStyle]
);

const combinedHandleStyle = useMemo(
() => [
{
backgroundColor: theme.colors.background.raised,
borderTopLeftRadius: theme.borderRadius.sm,
borderTopRightRadius: theme.borderRadius.sm,
},
handleStyle,
],
[theme.colors.background.raised, theme.borderRadius.sm, handleStyle]
);

const combinedBackgroundStyle = useMemo(
() => [
{
backgroundColor: theme.colors.background.raised,
},
backgroundStyle,
],
[theme.colors.background.raised, backgroundStyle]
);

return (
<BottomSheetBase
ref={ref}
enableDynamicSizing={false}
backdropComponent={backdropComponent}
handleIndicatorStyle={combinedHandleIndicatorStyle}
handleStyle={combinedHandleStyle}
backgroundStyle={combinedBackgroundStyle}
{...rest}
>
{children}
</BottomSheetBase>
);
}
)
);
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ import { useRemoveReactionOnMessage } from "@/features/conversation/hooks/use-re
import { messageIsFromCurrentAccountInboxId } from "@/features/conversation/utils/message-is-from-current-user";
import { useConversationQuery } from "@/queries/useConversationQuery";
import { calculateMenuHeight } from "@design-system/ContextMenu/ContextMenu.utils";
import { Portal } from "@gorhom/portal";
import { memo, useCallback } from "react";
import { StyleSheet } from "react-native";
import { StyleSheet, Modal, View, Platform } from "react-native";
import { MessageContextMenuAboveMessageReactions } from "./conversation-message-context-menu-above-message-reactions";
import { MessageContextMenuContainer } from "./conversation-message-context-menu-container";
import { useMessageContextMenuItems } from "./conversation-message-context-menu.utils";
import { useAppTheme } from "@theme/useAppTheme";
import Animated from "react-native-reanimated";

export const MESSAGE_CONTEXT_MENU_SPACE_BETWEEN_ABOVE_MESSAGE_REACTIONS_AND_MESSAGE = 16;

Expand All @@ -53,6 +54,7 @@ const Content = memo(function Content(props: {
>;
}) {
const { messageContextMenuData } = props;
const { theme } = useAppTheme();

const { messageId, itemRectX, itemRectY, itemRectHeight, itemRectWidth } =
messageContextMenuData;
Expand Down Expand Up @@ -124,11 +126,14 @@ const Content = memo(function Content(props: {

return (
<>
<Portal>
<ConversationStoreProvider
topic={topic}
conversationId={conversation!.id}
>
<Modal
visible={true}
transparent={true}
animationType="fade"
statusBarTranslucent={Platform.OS === "android"}
supportedOrientations={["portrait", "landscape"]}
>
<Animated.View style={StyleSheet.absoluteFill}>
lourou marked this conversation as resolved.
Show resolved Hide resolved
<MessageContextMenuBackdrop handlePressBackdrop={handlePressBackdrop}>
<AnimatedVStack style={StyleSheet.absoluteFill}>
{!!bySender && <MessageContextMenuReactors reactors={bySender} />}
Expand All @@ -151,7 +156,6 @@ const Content = memo(function Content(props: {
originY={itemRectHeight}
/>

{/* Replace with rowGap when we refactored menu items and not using rn-paper TableView */}
<VStack
style={{
height:
Expand All @@ -164,13 +168,9 @@ const Content = memo(function Content(props: {
nextMessage={undefined}
previousMessage={undefined}
>
{/* TODO: maybe make ConversationMessage more dumb to not need any context? */}
<ConversationMessage message={message} />
</MessageContextStoreProvider>

{/* Put back once we refactor the menu items */}
{/* <VStack style={{ height: 16 }}></VStack> */}

<MessageContextMenuItems
originX={fromMe ? itemRectX + itemRectWidth : itemRectX}
originY={itemRectHeight}
Expand All @@ -179,110 +179,9 @@ const Content = memo(function Content(props: {
</MessageContextMenuContainer>
</AnimatedVStack>
</MessageContextMenuBackdrop>
</ConversationStoreProvider>
</Portal>
</Animated.View>
</Modal>
<MessageContextMenuEmojiPicker onSelectReaction={handleSelectReaction} />
</>
);
});

/**
* Tried different approaches to implement native context menu, but it's not
* working as expected. Still missing some pieces from libraries to achieve what we want
*/

// import { MessageContextMenu } from "@/components/Chat/Message/MessageContextMenu";
// import ContextMenu from "react-native-context-menu-view";
// import * as ContextMenu from "zeego/context-menu";

{
/* <ContextMenuView
onMenuWillShow={() => {
console.log("onMenuWillShow");
}}
menuConfig={{
menuTitle: "Message Options",
menuItems: [
{
actionKey: "copy",
actionTitle: "Copy",
icon: {
type: "IMAGE_SYSTEM",
imageValue: {
systemName: "doc.on.doc",
},
},
},
{
actionKey: "delete",
actionTitle: "Delete",
menuAttributes: ["destructive"],
icon: {
type: "IMAGE_SYSTEM",
imageValue: {
systemName: "trash",
},
},
},
],
}}
auxiliaryPreviewConfig={{
transitionEntranceDelay: "RECOMMENDED",
anchorPosition: "top",
// alignmentHorizontal: "previewTrailing",
verticalAnchorPosition: "top",
// height: 600,
// preferredHeight: {
// mode: "percentRelativeToWindow",
// percent: 50,
// },
}}
previewConfig={{ previewType: "CUSTOM" }}
// renderPreview={() => (
// <BubbleContentContainer
// fromMe={fromMe}
// hasNextMessageInSeries={hasNextMessageInSeries}
// >
// <MessageText inverted={fromMe}>{textContent}</MessageText>
// </BubbleContentContainer>
// )}
isAuxiliaryPreviewEnabled={true}
renderPreview={() => (
// <VStack
// style={{
// ...debugBorder(),
// position: "absolute",
// top: 0,
// left: 0,
// width: 40,
// height: 40,
// }}
// ></VStack>
<BubbleContentContainer
fromMe={fromMe}
hasNextMessageInSeries={hasNextMessageInSeries}
>
<MessageText inverted={fromMe}>{textContent}</MessageText>
</BubbleContentContainer>
)}
renderAuxiliaryPreview={() => (
<HStack
style={{
...debugBorder(),
columnGap: 10,
padding: 10,
paddingHorizontal: 20,
backgroundColor: "white",
borderRadius: 10,
}}
>
<Text>😅</Text>
<Text>🤣</Text>
<Text>😂</Text>
<Text>🤩</Text>
<Text>🤗</Text>
<Text>🤔</Text>
</HStack>
)}
></ContextMenuView> */
}
Loading
Loading