Skip to content

Commit

Permalink
context menu wip
Browse files Browse the repository at this point in the history
  • Loading branch information
saulmc committed Jun 25, 2024
1 parent cc66061 commit 680c1f9
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 85 deletions.
3 changes: 3 additions & 0 deletions components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -371,13 +371,16 @@ const useStyles = () => {
flex: 1,
justifyContent: "flex-end",
backgroundColor: backgroundColor(colorScheme),
overflow: "visible",
},
chatContent: {
backgroundColor: backgroundColor(colorScheme),
flex: 1,
overflow: "visible",
},
chat: {
backgroundColor: backgroundColor(colorScheme),
overflow: "visible",
},
inputBottomFiller: {
position: "absolute",
Expand Down
3 changes: 3 additions & 0 deletions components/Chat/Message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -469,16 +469,19 @@ const useStyles = () => {
messageRow: {
flexDirection: "row",
flexWrap: "wrap",
overflow: "visible",
},
messageSwipeable: {
width: "100%",
flexDirection: "row",
paddingHorizontal: Platform.OS === "android" ? 10 : 20,
overflow: "visible",
},
messageSwipeableChildren: {
width: "100%",
flexDirection: "row",
flexWrap: "wrap",
overflow: "visible",
},
statusContainer: {
marginLeft: "auto",
Expand Down
210 changes: 125 additions & 85 deletions components/Chat/Message/MessageActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
StyleSheet,
DimensionValue,
} from "react-native";
import ContextMenu from "react-native-context-menu-view";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Reanimated, {
import {
AnimatedStyle,
Easing,
ReduceMotion,
Expand All @@ -18,14 +19,13 @@ import Reanimated, {
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { SvgProps } from "react-native-svg";

import _MessageTail from "../../../assets/message-tail.svg";
import {
currentAccount,
useCurrentAccount,
useSettingsStore,
} from "../../../data/store/accountsStore";
import { useAppStore } from "../../../data/store/appStore";
import { XmtpConversation } from "../../../data/store/chatStore";
import { useFramesStore } from "../../../data/store/framesStore";
import { ReanimatedTouchableOpacity } from "../../../utils/animations";
Expand All @@ -52,31 +52,7 @@ import { consentToPeersOnProtocol } from "../../../utils/xmtpRN/conversations";
import EmojiPicker from "../../../vendor/rn-emoji-keyboard";
import { showActionSheetWithOptions } from "../../StateHandlers/ActionSheetStateHandler";
import { MessageToDisplay } from "./Message";

class MessageTailComponent extends React.Component<SvgProps> {
render() {
return <_MessageTail {...this.props} />;
}
}

const MessageTailAnimated =
Reanimated.createAnimatedComponent(MessageTailComponent);

const MessageTail = (props: any) => {
return (
<MessageTailAnimated
{...props}
fill={
// No tail needed if no background
props.hideBackground
? "transparent"
: props.fromMe
? myMessageBubbleColor(props.colorScheme)
: messageBubbleColor(props.colorScheme)
}
/>
);
};
import MessageTail from "./MessageTail";

type Props = {
children: React.ReactNode;
Expand Down Expand Up @@ -338,6 +314,67 @@ export default function ChatMessageActions({
};
}, [highlightMessage]);

const contextMenuItems = useMemo(() => {
const items = [];

if (canAddReaction) {
items.push({ title: "Add a reaction", systemIcon: "smiley" });
}
items.push({ title: "Reply", systemIcon: "arrowshape.turn.up.left" });
if (!isAttachment && !isTransaction) {
items.push({ title: "Copy message", systemIcon: "doc.on.doc" });
if (!message.fromMe) {
items.push({
title: "Report message",
systemIcon: "exclamationmark.triangle",
});
}
}

return items;
}, [canAddReaction, isAttachment, isTransaction, message.fromMe]);

const handleContextMenuAction = useCallback(
(event: { nativeEvent: { index: number } }) => {
const { index } = event.nativeEvent;
switch (contextMenuItems[index].title) {
case "Add a reaction":
showReactionModal();
break;
case "Reply":
triggerReplyToMessage();
break;
case "Copy message":
if (message.content) {
Clipboard.setString(message.content);
} else if (message.contentFallback) {
Clipboard.setString(message.contentFallback);
}
break;
case "Report message":
Alert.alert(
"Report this message",
"This message will be forwarded to Converse. The contact will not be informed.",
[
{ text: "Cancel", style: "cancel" },
{ text: "Report", onPress: report },
{ text: "Report and block", onPress: reportAndBlock },
]
);
break;
}
useAppStore.getState().setContextMenuShown(false);
},
[
contextMenuItems,
showReactionModal,
triggerReplyToMessage,
message,
report,
reportAndBlock,
]
);

// We use a mix of Gesture Detector AND TouchableOpacity
// because GestureDetector is better for dual tap but if
// we add the gesture detector for long press the long press
Expand All @@ -346,75 +383,78 @@ export default function ChatMessageActions({
return (
<>
<GestureDetector gesture={doubleTapGesture}>
<ReanimatedTouchableOpacity
activeOpacity={1}
style={[
styles.messageBubble,
message.fromMe ? styles.messageBubbleMe : undefined,
{
backgroundColor: hideBackground
? "transparent"
: initialBubbleBackgroundColor,
},
highlightingMessage ? animatedBackgroundStyle : undefined,
Platform.select({
default: {},
android: {
// Messages not from me
borderBottomLeftRadius:
!message.fromMe && message.hasNextMessageInSeries ? 2 : 18,
borderTopLeftRadius:
!message.fromMe && message.hasPreviousMessageInSeries
? 2
: 18,
// Messages from me
borderBottomRightRadius:
message.fromMe && message.hasNextMessageInSeries ? 2 : 18,
borderTopRightRadius:
message.fromMe && message.hasPreviousMessageInSeries ? 2 : 18,
},
}),
{
maxWidth: messageMaxWidth,
},
]}
onPress={() => {
if (isAttachment) {
// Transfering attachment opening intent to component
converseEventEmitter.emit(
`openAttachmentForMessage-${message.id}`
);
}
if (isTransaction) {
// Transfering event to component
converseEventEmitter.emit(
`showActionSheetForTxRef-${message.id}`
);
}
}}
onLongPress={showMessageActionSheet}
<ContextMenu
actions={contextMenuItems}
onPress={handleContextMenuAction}
onCancel={() => useAppStore.getState().setContextMenuShown(false)}
previewBackgroundColor={initialBubbleBackgroundColor}
style={[{ width: "100%" }, { overflow: "visible" }]}
>
{children}
<ReanimatedTouchableOpacity
activeOpacity={1}
style={[
styles.messageBubble,
message.fromMe ? styles.messageBubbleMe : undefined,
{
backgroundColor: hideBackground
? "transparent"
: initialBubbleBackgroundColor,
},
highlightingMessage ? animatedBackgroundStyle : undefined,
Platform.select({
default: {},
android: {
// Messages not from me
borderBottomLeftRadius:
!message.fromMe && message.hasNextMessageInSeries ? 2 : 18,
borderTopLeftRadius:
!message.fromMe && message.hasPreviousMessageInSeries
? 2
: 18,
// Messages from me
borderBottomRightRadius:
message.fromMe && message.hasNextMessageInSeries ? 2 : 18,
borderTopRightRadius:
message.fromMe && message.hasPreviousMessageInSeries
? 2
: 18,
},
}),
{
maxWidth: messageMaxWidth,
},
]}
onPress={() => {
if (isAttachment) {
// Transfering attachment opening intent to component
converseEventEmitter.emit(
`openAttachmentForMessage-${message.id}`
);
}
if (isTransaction) {
// Transfering event to component
converseEventEmitter.emit(
`showActionSheetForTxRef-${message.id}`
);
}
}}
onLongPress={() => useAppStore.getState().setContextMenuShown(true)}
>
{children}
</ReanimatedTouchableOpacity>
{!message.hasNextMessageInSeries &&
!isFrame &&
!isAttachment &&
!isTransaction &&
(Platform.OS === "ios" || Platform.OS === "web") && (
<MessageTail
style={[
styles.messageTail,
{
color: initialBubbleBackgroundColor,
},
highlightingMessage ? iosAnimatedTailStyle : undefined,
message.fromMe ? styles.messageTailMe : {},
]}
style={highlightingMessage ? iosAnimatedTailStyle : undefined}
fromMe={message.fromMe}
colorScheme={colorScheme}
hideBackground={hideBackground}
/>
)}
</ReanimatedTouchableOpacity>
</ContextMenu>
</GestureDetector>
{/* <View style={{width: 50, height: 20, backgroundColor: "red"}} />
<GestureDetector gesture={composedGesture}>{children}</GestureDetector> */}
Expand Down
64 changes: 64 additions & 0 deletions components/Chat/Message/MessageTail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from "react";
import { ColorSchemeName, StyleSheet, ViewStyle } from "react-native";
import Reanimated, { AnimatedStyleProp } from "react-native-reanimated";
import { SvgProps } from "react-native-svg";

import _MessageTail from "../../../assets/message-tail.svg";
import {
messageBubbleColor,
myMessageBubbleColor,
} from "../../../utils/colors";

class MessageTailComponent extends React.Component<SvgProps> {
render() {
return <_MessageTail {...this.props} />;
}
}

const MessageTailAnimated =
Reanimated.createAnimatedComponent(MessageTailComponent);

interface MessageTailProps {
fromMe: boolean;
colorScheme: ColorSchemeName;
hideBackground: boolean;
style?: AnimatedStyleProp<ViewStyle>;
}

const MessageTail: React.FC<MessageTailProps> = ({
fromMe,
colorScheme,
hideBackground,
style,
}) => {
return (
<MessageTailAnimated
style={[styles.messageTail, fromMe && styles.messageTailMe, style]}
fill={
hideBackground
? "transparent"
: fromMe
? myMessageBubbleColor(colorScheme)
: messageBubbleColor(colorScheme)
}
/>
);
};

const styles = StyleSheet.create({
messageTail: {
position: "absolute",
left: -5,
bottom: 0,
width: 14,
height: 21,
zIndex: -1,
},
messageTailMe: {
left: "auto",
right: -5,
transform: [{ scaleX: -1 }],
},
});

export default MessageTail;
6 changes: 6 additions & 0 deletions data/store/appStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type AppStoreType = {

actionSheetShown: boolean;
setActionSheetShown: (s: boolean) => void;

contextMenuShown: boolean;
setContextMenuShown: (s: boolean) => void;
};

export const useAppStore = create<AppStoreType>()(
Expand Down Expand Up @@ -84,6 +87,9 @@ export const useAppStore = create<AppStoreType>()(

actionSheetShown: false,
setActionSheetShown: (s: boolean) => set(() => ({ actionSheetShown: s })),

contextMenuShown: false,
setContextMenuShown: (s: boolean) => set(() => ({ contextMenuShown: s })),
}),
{
name: "store-app",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
"react-native-avoid-softinput": "^4.0.1",
"react-native-background-color": "^0.0.8",
"react-native-bootsplash": "^4.5.3",
"react-native-context-menu-view": "^1.16.0",
"react-native-device-info": "^10.9.0",
"react-native-fetch-api": "^3.0.0",
"react-native-fs": "^2.20.0",
Expand Down
Loading

0 comments on commit 680c1f9

Please sign in to comment.