@planningcenter/chat-react-native 3.4.1-rc.1 → 3.4.1-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/attachments/image_attachment.d.ts +6 -1
- package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/image_attachment.js +115 -6
- package/build/components/conversation/attachments/image_attachment.js.map +1 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +5 -1
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_attachments.d.ts +2 -0
- package/build/components/conversation/message_attachments.d.ts.map +1 -1
- package/build/components/conversation/message_attachments.js +4 -4
- package/build/components/conversation/message_attachments.js.map +1 -1
- package/package.json +2 -2
- package/src/components/conversation/attachments/image_attachment.tsx +210 -9
- package/src/components/conversation/message.tsx +6 -1
- package/src/components/conversation/message_attachments.tsx +15 -6
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export
|
|
2
|
+
export type MetaProps = {
|
|
3
|
+
authorName: string;
|
|
4
|
+
createdAt: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function ImageAttachment({ attachment, metaProps, }: {
|
|
3
7
|
attachment: any;
|
|
8
|
+
metaProps: MetaProps;
|
|
4
9
|
}): React.JSX.Element;
|
|
5
10
|
//# sourceMappingURL=image_attachment.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image_attachment.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"image_attachment.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAgC7D,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,UAAU,EACV,SAAS,GACV,EAAE;IACD,UAAU,EAAE,GAAG,CAAA;IACf,SAAS,EAAE,SAAS,CAAA;CACrB,qBAwDA"}
|
|
@@ -1,16 +1,81 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View,
|
|
3
|
-
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { Image, StyleSheet, Modal, Pressable, useWindowDimensions, SafeAreaView, View, Linking, } from 'react-native';
|
|
3
|
+
import { Gesture, GestureDetector, GestureHandlerRootView, } from 'react-native-gesture-handler';
|
|
4
|
+
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
5
|
+
import { tokens } from '../../../vendor/tapestry/tokens';
|
|
6
|
+
import { IconButton, Heading, Text } from '../../display';
|
|
7
|
+
import colorFunction from 'color';
|
|
8
|
+
import { formatDatePreview } from '../../../utils/date';
|
|
9
|
+
const PAN_THRESHOLD_PX = 300;
|
|
10
|
+
export function ImageAttachment({ attachment, metaProps, }) {
|
|
4
11
|
const styles = useStyles();
|
|
12
|
+
const [visible, setVisible] = useState(false);
|
|
13
|
+
// shared values run on the native UI thread and prevents clogging up the JS thread
|
|
14
|
+
const dismissY = useSharedValue(0);
|
|
15
|
+
const opacity = useSharedValue(1);
|
|
16
|
+
const resetAnimations = useCallback(() => {
|
|
17
|
+
dismissY.value = withTiming(0);
|
|
18
|
+
opacity.value = withTiming(1);
|
|
19
|
+
}, [dismissY, opacity]);
|
|
20
|
+
const handleCloseModal = useCallback(() => {
|
|
21
|
+
setVisible(false);
|
|
22
|
+
resetAnimations();
|
|
23
|
+
}, [setVisible, resetAnimations]);
|
|
24
|
+
const panGesture = Gesture.Pan()
|
|
25
|
+
.onUpdate(e => {
|
|
26
|
+
dismissY.value = e.translationY;
|
|
27
|
+
opacity.value = 1 - Math.abs(e.translationY) / PAN_THRESHOLD_PX;
|
|
28
|
+
})
|
|
29
|
+
.onEnd(() => {
|
|
30
|
+
runOnJS(handleCloseModal)(); // Ensures we can call a JS function
|
|
31
|
+
});
|
|
32
|
+
const animatedImageStyle = useAnimatedStyle(() => ({
|
|
33
|
+
transform: [{ translateY: dismissY.value }],
|
|
34
|
+
opacity: opacity.value,
|
|
35
|
+
}));
|
|
5
36
|
const { attributes } = attachment;
|
|
6
37
|
const { url, urlMedium, filename, metadata = {} } = attributes;
|
|
7
38
|
const width = metadata.width || 100;
|
|
8
39
|
const height = metadata.height || 100;
|
|
9
|
-
return (
|
|
10
|
-
<
|
|
11
|
-
|
|
40
|
+
return (<>
|
|
41
|
+
<Pressable style={styles.container} onPress={() => setVisible(true)}>
|
|
42
|
+
<Image source={{ uri: urlMedium || url }} style={[styles.image, { aspectRatio: width / height }]} accessibilityLabel={filename}/>
|
|
43
|
+
</Pressable>
|
|
44
|
+
<LightboxModal visible={visible} handleCloseModal={handleCloseModal} uri={urlMedium || url} metaProps={metaProps} panGesture={panGesture} animatedImageStyle={animatedImageStyle}/>
|
|
45
|
+
</>);
|
|
12
46
|
}
|
|
47
|
+
const LightboxModal = ({ uri, visible, handleCloseModal, metaProps, panGesture, animatedImageStyle, }) => {
|
|
48
|
+
const styles = useStyles();
|
|
49
|
+
const { authorName, createdAt } = metaProps;
|
|
50
|
+
const handleOpenInBrowser = () => {
|
|
51
|
+
Linking.openURL(uri);
|
|
52
|
+
};
|
|
53
|
+
return (<Modal visible={visible} transparent animationType="fade" onRequestClose={handleCloseModal}>
|
|
54
|
+
<SafeAreaView style={styles.modal}>
|
|
55
|
+
<GestureHandlerRootView>
|
|
56
|
+
<GestureDetector gesture={panGesture}>
|
|
57
|
+
<Animated.Image source={{ uri }} style={[styles.lightboxImage, animatedImageStyle]} resizeMode="contain"/>
|
|
58
|
+
</GestureDetector>
|
|
59
|
+
<View style={styles.actionToolbar} accessibilityRole="toolbar">
|
|
60
|
+
<View style={styles.actionToolbarTextMeta}>
|
|
61
|
+
<Heading variant="h3" style={styles.actionToolbarTitle} numberOfLines={1}>
|
|
62
|
+
{authorName}
|
|
63
|
+
</Heading>
|
|
64
|
+
<Text variant="tertiary" style={styles.actionToolbarSubtitle}>
|
|
65
|
+
{formatDatePreview(createdAt)}
|
|
66
|
+
</Text>
|
|
67
|
+
</View>
|
|
68
|
+
<IconButton onPress={handleOpenInBrowser} name="general.newWindow" accessibilityRole="link" accessibilityLabel="Open image in browser" accessibilityHint="Image can be downloaded and shared through the browser." style={styles.actionButton} iconStyle={styles.actionButtonIcon} size="lg"/>
|
|
69
|
+
<IconButton onPress={handleCloseModal} name="general.x" accessibilityLabel="Close image" style={styles.actionButton} iconStyle={styles.actionButtonIcon}/>
|
|
70
|
+
</View>
|
|
71
|
+
</GestureHandlerRootView>
|
|
72
|
+
</SafeAreaView>
|
|
73
|
+
</Modal>);
|
|
74
|
+
};
|
|
13
75
|
const useStyles = () => {
|
|
76
|
+
const { width: windowWidth } = useWindowDimensions();
|
|
77
|
+
const backgroundColor = tokens.colorNeutral7;
|
|
78
|
+
const transparentBackgroundColor = useMemo(() => colorFunction(backgroundColor).alpha(0.8).toString(), [backgroundColor]);
|
|
14
79
|
return StyleSheet.create({
|
|
15
80
|
container: {
|
|
16
81
|
maxWidth: '100%',
|
|
@@ -19,6 +84,50 @@ const useStyles = () => {
|
|
|
19
84
|
borderRadius: 8,
|
|
20
85
|
minWidth: 200,
|
|
21
86
|
},
|
|
87
|
+
modal: {
|
|
88
|
+
flex: 1,
|
|
89
|
+
backgroundColor,
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
},
|
|
93
|
+
lightboxImage: {
|
|
94
|
+
width: windowWidth,
|
|
95
|
+
height: '100%',
|
|
96
|
+
},
|
|
97
|
+
actionToolbar: {
|
|
98
|
+
width: '100%',
|
|
99
|
+
position: 'absolute',
|
|
100
|
+
top: 0,
|
|
101
|
+
flexDirection: 'row',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
gap: 20,
|
|
104
|
+
paddingHorizontal: 16,
|
|
105
|
+
paddingTop: 16,
|
|
106
|
+
paddingBottom: 8,
|
|
107
|
+
backgroundColor: transparentBackgroundColor,
|
|
108
|
+
},
|
|
109
|
+
actionToolbarTextMeta: {
|
|
110
|
+
flex: 1,
|
|
111
|
+
},
|
|
112
|
+
actionToolbarTitle: {
|
|
113
|
+
marginRight: 'auto',
|
|
114
|
+
flexShrink: 1,
|
|
115
|
+
color: tokens.colorNeutral88,
|
|
116
|
+
},
|
|
117
|
+
actionToolbarSubtitle: {
|
|
118
|
+
color: tokens.colorNeutral68,
|
|
119
|
+
},
|
|
120
|
+
actionButton: {
|
|
121
|
+
backgroundColor,
|
|
122
|
+
height: 40,
|
|
123
|
+
width: 40,
|
|
124
|
+
borderRadius: 50,
|
|
125
|
+
borderWidth: 1,
|
|
126
|
+
borderColor: tokens.colorNeutral24,
|
|
127
|
+
},
|
|
128
|
+
actionButtonIcon: {
|
|
129
|
+
color: tokens.colorNeutral88,
|
|
130
|
+
},
|
|
22
131
|
});
|
|
23
132
|
};
|
|
24
133
|
//# sourceMappingURL=image_attachment.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image_attachment.js","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEtD,MAAM,UAAU,eAAe,CAAC,EAAE,UAAU,EAAuB;IACjE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAAA;IACjC,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,UAAU,CAAA;IAC9D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,GAAG,CAAA;IACnC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAA;IACrC,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAC5B;MAAA,CAAC,KAAK,CACJ,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,IAAI,GAAG,EAAE,CAAC,CAClC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,KAAK,GAAG,MAAM,EAAE,CAAC,CAAC,CACvD,kBAAkB,CAAC,CAAC,QAAQ,CAAC,EAEjC;IAAA,EAAE,IAAI,CAAC,CACR,CAAA;AACH,CAAC;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,SAAS,EAAE;YACT,QAAQ,EAAE,MAAM;SACjB;QACD,KAAK,EAAE;YACL,YAAY,EAAE,CAAC;YACf,QAAQ,EAAE,GAAG;SACd;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import React from 'react'\nimport { View, Image, StyleSheet } from 'react-native'\n\nexport function ImageAttachment({ attachment }: { attachment: any }) {\n const styles = useStyles()\n const { attributes } = attachment\n const { url, urlMedium, filename, metadata = {} } = attributes\n const width = metadata.width || 100\n const height = metadata.height || 100\n return (\n <View style={styles.container}>\n <Image\n source={{ uri: urlMedium || url }}\n style={[styles.image, { aspectRatio: width / height }]}\n accessibilityLabel={filename}\n />\n </View>\n )\n}\n\nconst useStyles = () => {\n return StyleSheet.create({\n container: {\n maxWidth: '100%',\n },\n image: {\n borderRadius: 8,\n minWidth: 200,\n },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"image_attachment.js","sourceRoot":"","sources":["../../../../src/components/conversation/attachments/image_attachment.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useCallback, useMemo, useState } from 'react'\nimport {\n Image,\n StyleSheet,\n Modal,\n Pressable,\n useWindowDimensions,\n SafeAreaView,\n View,\n Linking,\n ImageStyle,\n} from 'react-native'\nimport {\n Gesture,\n GestureDetector,\n GestureHandlerRootView,\n type PanGesture,\n} from 'react-native-gesture-handler'\nimport Animated, {\n runOnJS,\n useAnimatedStyle,\n useSharedValue,\n withTiming,\n type AnimatedStyle,\n} from 'react-native-reanimated'\nimport { tokens } from '../../../vendor/tapestry/tokens'\nimport { IconButton, Heading, Text } from '../../display'\nimport colorFunction from 'color'\nimport { formatDatePreview } from '../../../utils/date'\n\nconst PAN_THRESHOLD_PX = 300\n\nexport type MetaProps = {\n authorName: string\n createdAt: string\n}\n\nexport function ImageAttachment({\n attachment,\n metaProps,\n}: {\n attachment: any\n metaProps: MetaProps\n}) {\n const styles = useStyles()\n const [visible, setVisible] = useState(false)\n\n // shared values run on the native UI thread and prevents clogging up the JS thread\n const dismissY = useSharedValue(0)\n const opacity = useSharedValue(1)\n\n const resetAnimations = useCallback(() => {\n dismissY.value = withTiming(0)\n opacity.value = withTiming(1)\n }, [dismissY, opacity])\n\n const handleCloseModal = useCallback(() => {\n setVisible(false)\n resetAnimations()\n }, [setVisible, resetAnimations])\n\n const panGesture = Gesture.Pan()\n .onUpdate(e => {\n dismissY.value = e.translationY\n opacity.value = 1 - Math.abs(e.translationY) / PAN_THRESHOLD_PX\n })\n .onEnd(() => {\n runOnJS(handleCloseModal)() // Ensures we can call a JS function\n })\n\n const animatedImageStyle = useAnimatedStyle(() => ({\n transform: [{ translateY: dismissY.value }],\n opacity: opacity.value,\n }))\n\n const { attributes } = attachment\n const { url, urlMedium, filename, metadata = {} } = attributes\n const width = metadata.width || 100\n const height = metadata.height || 100\n\n return (\n <>\n <Pressable style={styles.container} onPress={() => setVisible(true)}>\n <Image\n source={{ uri: urlMedium || url }}\n style={[styles.image, { aspectRatio: width / height }]}\n accessibilityLabel={filename}\n />\n </Pressable>\n <LightboxModal\n visible={visible}\n handleCloseModal={handleCloseModal}\n uri={urlMedium || url}\n metaProps={metaProps}\n panGesture={panGesture}\n animatedImageStyle={animatedImageStyle}\n />\n </>\n )\n}\n\ninterface LightboxModalProps {\n visible: boolean\n handleCloseModal: () => void\n uri: string\n metaProps: MetaProps\n panGesture: PanGesture\n animatedImageStyle: AnimatedStyle<ImageStyle>\n}\n\nconst LightboxModal = ({\n uri,\n visible,\n handleCloseModal,\n metaProps,\n panGesture,\n animatedImageStyle,\n}: LightboxModalProps) => {\n const styles = useStyles()\n\n const { authorName, createdAt } = metaProps\n\n const handleOpenInBrowser = () => {\n Linking.openURL(uri)\n }\n\n return (\n <Modal visible={visible} transparent animationType=\"fade\" onRequestClose={handleCloseModal}>\n <SafeAreaView style={styles.modal}>\n <GestureHandlerRootView>\n <GestureDetector gesture={panGesture}>\n <Animated.Image\n source={{ uri }}\n style={[styles.lightboxImage, animatedImageStyle]}\n resizeMode=\"contain\"\n />\n </GestureDetector>\n <View style={styles.actionToolbar} accessibilityRole=\"toolbar\">\n <View style={styles.actionToolbarTextMeta}>\n <Heading variant=\"h3\" style={styles.actionToolbarTitle} numberOfLines={1}>\n {authorName}\n </Heading>\n <Text variant=\"tertiary\" style={styles.actionToolbarSubtitle}>\n {formatDatePreview(createdAt)}\n </Text>\n </View>\n <IconButton\n onPress={handleOpenInBrowser}\n name=\"general.newWindow\"\n accessibilityRole=\"link\"\n accessibilityLabel=\"Open image in browser\"\n accessibilityHint=\"Image can be downloaded and shared through the browser.\"\n style={styles.actionButton}\n iconStyle={styles.actionButtonIcon}\n size=\"lg\"\n />\n <IconButton\n onPress={handleCloseModal}\n name=\"general.x\"\n accessibilityLabel=\"Close image\"\n style={styles.actionButton}\n iconStyle={styles.actionButtonIcon}\n />\n </View>\n </GestureHandlerRootView>\n </SafeAreaView>\n </Modal>\n )\n}\n\nconst useStyles = () => {\n const { width: windowWidth } = useWindowDimensions()\n const backgroundColor = tokens.colorNeutral7\n const transparentBackgroundColor = useMemo(\n () => colorFunction(backgroundColor).alpha(0.8).toString(),\n [backgroundColor]\n )\n\n return StyleSheet.create({\n container: {\n maxWidth: '100%',\n },\n image: {\n borderRadius: 8,\n minWidth: 200,\n },\n modal: {\n flex: 1,\n backgroundColor,\n justifyContent: 'center',\n alignItems: 'center',\n },\n lightboxImage: {\n width: windowWidth,\n height: '100%',\n },\n actionToolbar: {\n width: '100%',\n position: 'absolute',\n top: 0,\n flexDirection: 'row',\n alignItems: 'center',\n gap: 20,\n paddingHorizontal: 16,\n paddingTop: 16,\n paddingBottom: 8,\n backgroundColor: transparentBackgroundColor,\n },\n actionToolbarTextMeta: {\n flex: 1,\n },\n actionToolbarTitle: {\n marginRight: 'auto',\n flexShrink: 1,\n color: tokens.colorNeutral88,\n },\n actionToolbarSubtitle: {\n color: tokens.colorNeutral68,\n },\n actionButton: {\n backgroundColor,\n height: 40,\n width: 40,\n borderRadius: 50,\n borderWidth: 1,\n borderColor: tokens.colorNeutral24,\n },\n actionButtonIcon: {\n color: tokens.colorNeutral88,\n },\n })\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAM7C;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAM7C;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,qBAsD3E"}
|
|
@@ -29,6 +29,10 @@ export function Message(props) {
|
|
|
29
29
|
reaction_value: reaction.value,
|
|
30
30
|
});
|
|
31
31
|
};
|
|
32
|
+
const metaProps = {
|
|
33
|
+
authorName: props.author.name,
|
|
34
|
+
createdAt: props.createdAt,
|
|
35
|
+
};
|
|
32
36
|
return (<View style={styles.message}>
|
|
33
37
|
{!props.mine && (<View style={styles.messageAuthor}>
|
|
34
38
|
<Avatar size={'md'} sourceUri={props.author.avatar}/>
|
|
@@ -37,7 +41,7 @@ export function Message(props) {
|
|
|
37
41
|
{!props.mine && <Text variant="tertiary">{props.author.name}</Text>}
|
|
38
42
|
<PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
|
|
39
43
|
<ErrorBoundary>
|
|
40
|
-
<MessageAttachments attachments={props.attachments}/>
|
|
44
|
+
<MessageAttachments attachments={props.attachments} metaProps={metaProps}/>
|
|
41
45
|
</ErrorBoundary>
|
|
42
46
|
{text && (<View style={styles.messageText}>
|
|
43
47
|
<MessageMarkdown text={text}/>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,aAAa,MAAM,OAAO,CAAA;AACjC,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,gDAAgD,CAAA;AAChF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAGtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,aAAa,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEpD;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,KAAoD;IAC1E,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,cAAc,EAAE,GAAG,KAAK,CAAA;IACvD,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAA;IACtC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAA;IAClC,MAAM,kBAAkB,GAAG,GAAG,EAAE;QAC9B,UAAU,CAAC,QAAQ,CAAC,gBAAgB,EAAE;YACpC,UAAU,EAAE,KAAK,CAAC,EAAE;YACpB,eAAe;SAChB,CAAC,CAAA;IACJ,CAAC,CAAA;IACD,MAAM,mBAAmB,GAAG,CAAC,QAA+B,EAAE,EAAE;QAC9D,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE;YAC/B,UAAU,EAAE,KAAK,CAAC,EAAE;YACpB,eAAe;YACf,cAAc,EAAE,QAAQ,CAAC,KAAK;SAC/B,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAC1B;MAAA,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CACd,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAChC;UAAA,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EACrD;QAAA,EAAE,IAAI,CAAC,CACR,CACD;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CACjC;QAAA,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CACnE;QAAA,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,CAAC,CAAC,kBAAkB,CAAC,CAC9E;UAAA,CAAC,aAAa,CACZ;YAAA,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/components/conversation/message.tsx"],"names":[],"mappings":"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","sourcesContent":["import { PlatformPressable } from '@react-navigation/elements'\nimport { useNavigation } from '@react-navigation/native'\nimport colorFunction from 'color'\nimport React from 'react'\nimport { StyleSheet, View } from 'react-native'\nimport { MessageReaction } from '../../components/conversation/message_reaction'\nimport { Avatar, Text } from '../../components/display'\nimport { useTheme } from '../../hooks'\nimport { MessageResource } from '../../types'\nimport { ReactionCountResource } from '../../types/resources/reaction'\nimport { MessageAttachments } from './message_attachments'\nimport ErrorBoundary from '../page/error_boundary'\nimport { MessageMarkdown } from './message_markdown'\n\n/** Message\n * Component for display of a message within a conversation list\n */\nexport function Message(props: MessageResource & { conversation_id: number }) {\n const { text, conversation_id, reactionCounts } = props\n const styles = useMessageStyles(props)\n const navigation = useNavigation()\n const handleMessagePress = () => {\n navigation.navigate('MessageActions', {\n message_id: props.id,\n conversation_id,\n })\n }\n const handleReactionPress = (reaction: ReactionCountResource) => {\n navigation.navigate('Reactions', {\n message_id: props.id,\n conversation_id,\n reaction_value: reaction.value,\n })\n }\n\n const metaProps = {\n authorName: props.author.name,\n createdAt: props.createdAt,\n }\n\n return (\n <View style={styles.message}>\n {!props.mine && (\n <View style={styles.messageAuthor}>\n <Avatar size={'md'} sourceUri={props.author.avatar} />\n </View>\n )}\n <View style={styles.messageContent}>\n {!props.mine && <Text variant=\"tertiary\">{props.author.name}</Text>}\n <PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>\n <ErrorBoundary>\n <MessageAttachments attachments={props.attachments} metaProps={metaProps} />\n </ErrorBoundary>\n {text && (\n <View style={styles.messageText}>\n <MessageMarkdown text={text} />\n </View>\n )}\n </PlatformPressable>\n <View style={styles.messageReactions}>\n {reactionCounts.map(reaction => (\n <MessageReaction\n key={reaction.value}\n reaction={reaction}\n onPress={handleReactionPress}\n />\n ))}\n </View>\n </View>\n </View>\n )\n}\n\nconst useMessageStyles = ({ mine }: MessageResource) => {\n const { colors } = useTheme()\n const activeColor = colorFunction(colors.interaction).alpha(0.2).string()\n\n return StyleSheet.create({\n message: {\n gap: 8,\n flexDirection: mine ? 'row-reverse' : 'row',\n },\n messageContent: {\n gap: 8,\n flexShrink: 1,\n },\n messageAuthor: {\n flexDirection: 'row',\n gap: 8,\n },\n messageBubble: {\n backgroundColor: mine ? activeColor : colors.fillColorNeutral070,\n borderRadius: 8,\n maxWidth: 232,\n },\n messageText: {\n paddingVertical: 6,\n paddingHorizontal: 8,\n },\n messageReactions: {\n flexDirection: 'row',\n gap: 4,\n justifyContent: mine ? 'flex-end' : 'flex-start',\n },\n })\n}\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource';
|
|
3
|
+
import { type MetaProps } from './attachments/image_attachment';
|
|
3
4
|
export declare function MessageAttachments(props: {
|
|
4
5
|
attachments: DenormalizedAttachmentResource[];
|
|
6
|
+
metaProps: MetaProps;
|
|
5
7
|
}): React.JSX.Element | null;
|
|
6
8
|
//# sourceMappingURL=message_attachments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message_attachments.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message_attachments.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,8BAA8B,EAAE,MAAM,wDAAwD,CAAA;
|
|
1
|
+
{"version":3,"file":"message_attachments.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/message_attachments.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,8BAA8B,EAAE,MAAM,wDAAwD,CAAA;AAMvG,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,gCAAgC,CAAA;AAEhF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,WAAW,EAAE,8BAA8B,EAAE,CAAA;IAC7C,SAAS,EAAE,SAAS,CAAA;CACrB,4BA+BA"}
|
|
@@ -8,14 +8,14 @@ import { ExpandedLink } from './attachments/expanded_link';
|
|
|
8
8
|
import { ImageAttachment } from './attachments/image_attachment';
|
|
9
9
|
export function MessageAttachments(props) {
|
|
10
10
|
const styles = useStyles();
|
|
11
|
-
const { attachments } = props;
|
|
11
|
+
const { attachments, metaProps } = props;
|
|
12
12
|
if (!attachments || attachments.length === 0)
|
|
13
13
|
return null;
|
|
14
14
|
return (<View style={styles.attachmentsContainer}>
|
|
15
15
|
{attachments.map(attachment => {
|
|
16
16
|
switch (attachment.type) {
|
|
17
17
|
case 'MessageAttachment':
|
|
18
|
-
return <MessageAttachment key={attachment.id} attachment={attachment}
|
|
18
|
+
return (<MessageAttachment key={attachment.id} attachment={attachment} metaProps={metaProps}/>);
|
|
19
19
|
case 'giphy':
|
|
20
20
|
return (<GiphyAttachment key={attachment.id || attachment.titleLink} attachment={attachment}/>);
|
|
21
21
|
case 'ExpandedLink':
|
|
@@ -26,13 +26,13 @@ export function MessageAttachments(props) {
|
|
|
26
26
|
})}
|
|
27
27
|
</View>);
|
|
28
28
|
}
|
|
29
|
-
function MessageAttachment({ attachment }) {
|
|
29
|
+
function MessageAttachment({ attachment, metaProps }) {
|
|
30
30
|
const { attributes } = attachment;
|
|
31
31
|
const contentType = attributes?.contentType;
|
|
32
32
|
const basicType = contentType ? contentType.split('/')[0] : '';
|
|
33
33
|
switch (basicType) {
|
|
34
34
|
case 'image':
|
|
35
|
-
return <ImageAttachment attachment={attachment}/>;
|
|
35
|
+
return <ImageAttachment attachment={attachment} metaProps={metaProps}/>;
|
|
36
36
|
case 'video':
|
|
37
37
|
return <VideoAttachment attachment={attachment}/>;
|
|
38
38
|
case 'audio':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message_attachments.js","sourceRoot":"","sources":["../../../src/components/conversation/message_attachments.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"message_attachments.js","sourceRoot":"","sources":["../../../src/components/conversation/message_attachments.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAkB,MAAM,gCAAgC,CAAA;AAEhF,MAAM,UAAU,kBAAkB,CAAC,KAGlC;IACC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;IACxC,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACzD,OAAO,CACL,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,CACvC;MAAA,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE;YAC5B,QAAQ,UAAU,CAAC,IAAI,EAAE,CAAC;gBACxB,KAAK,mBAAmB;oBACtB,OAAO,CACL,CAAC,iBAAiB,CAChB,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CACnB,UAAU,CAAC,CAAC,UAAU,CAAC,CACvB,SAAS,CAAC,CAAC,SAAS,CAAC,EACrB,CACH,CAAA;gBACH,KAAK,OAAO;oBACV,OAAO,CACL,CAAC,eAAe,CACd,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,UAAU,CAAC,SAAS,CAAC,CAC3C,UAAU,CAAC,CAAC,UAAU,CAAC,EACvB,CACH,CAAA;gBACH,KAAK,cAAc;oBACjB,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,EAAG,CAAA;gBACrE;oBACE,OAAO,IAAI,CAAA;YACf,CAAC;QACH,CAAC,CAAC,CACJ;IAAA,EAAE,IAAI,CAAC,CACR,CAAA;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,UAAU,EAAE,SAAS,EAA6C;IAC7F,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAAA;IACjC,MAAM,WAAW,GAAG,UAAU,EAAE,WAAW,CAAA;IAC3C,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9D,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,OAAO;YACV,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,EAAG,CAAA;QAC1E,KAAK,OAAO;YACV,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,EAAG,CAAA;QACpD,KAAK,OAAO;YACV,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,EAAG,CAAA;QACpD;YACE,OAAO,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,EAAG,CAAA;IAC5D,CAAC;AACH,CAAC;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,oBAAoB,EAAE;YACpB,GAAG,EAAE,CAAC;YACN,OAAO,EAAE,CAAC;SACX;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import React from 'react'\nimport { View, StyleSheet } from 'react-native'\nimport { DenormalizedAttachmentResource } from '../../types/resources/denormalized_attachment_resource'\nimport { AudioAttachment } from './attachments/audio_attachment'\nimport { VideoAttachment } from './attachments/video_attachment'\nimport { GiphyAttachment } from './attachments/giphy_attachment'\nimport { GenericFileAttachment } from './attachments/generic_file_attachment'\nimport { ExpandedLink } from './attachments/expanded_link'\nimport { ImageAttachment, type MetaProps } from './attachments/image_attachment'\n\nexport function MessageAttachments(props: {\n attachments: DenormalizedAttachmentResource[]\n metaProps: MetaProps\n}) {\n const styles = useStyles()\n const { attachments, metaProps } = props\n if (!attachments || attachments.length === 0) return null\n return (\n <View style={styles.attachmentsContainer}>\n {attachments.map(attachment => {\n switch (attachment.type) {\n case 'MessageAttachment':\n return (\n <MessageAttachment\n key={attachment.id}\n attachment={attachment}\n metaProps={metaProps}\n />\n )\n case 'giphy':\n return (\n <GiphyAttachment\n key={attachment.id || attachment.titleLink}\n attachment={attachment}\n />\n )\n case 'ExpandedLink':\n return <ExpandedLink key={attachment.id} attachment={attachment} />\n default:\n return null\n }\n })}\n </View>\n )\n}\n\nfunction MessageAttachment({ attachment, metaProps }: { attachment: any; metaProps: MetaProps }) {\n const { attributes } = attachment\n const contentType = attributes?.contentType\n const basicType = contentType ? contentType.split('/')[0] : ''\n switch (basicType) {\n case 'image':\n return <ImageAttachment attachment={attachment} metaProps={metaProps} />\n case 'video':\n return <VideoAttachment attachment={attachment} />\n case 'audio':\n return <AudioAttachment attachment={attachment} />\n default:\n return <GenericFileAttachment attachment={attachment} />\n }\n}\n\nconst useStyles = () => {\n return StyleSheet.create({\n attachmentsContainer: {\n gap: 4,\n padding: 2,\n },\n })\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.4.1-rc.
|
|
3
|
+
"version": "3.4.1-rc.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -54,5 +54,5 @@
|
|
|
54
54
|
"prettier": "^3.4.2",
|
|
55
55
|
"typescript": "<5.6.0"
|
|
56
56
|
},
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "a9ddceace400b15a185d59d7466cef5b81582002"
|
|
58
58
|
}
|
|
@@ -1,24 +1,181 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import {
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Image,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Modal,
|
|
6
|
+
Pressable,
|
|
7
|
+
useWindowDimensions,
|
|
8
|
+
SafeAreaView,
|
|
9
|
+
View,
|
|
10
|
+
Linking,
|
|
11
|
+
ImageStyle,
|
|
12
|
+
} from 'react-native'
|
|
13
|
+
import {
|
|
14
|
+
Gesture,
|
|
15
|
+
GestureDetector,
|
|
16
|
+
GestureHandlerRootView,
|
|
17
|
+
type PanGesture,
|
|
18
|
+
} from 'react-native-gesture-handler'
|
|
19
|
+
import Animated, {
|
|
20
|
+
runOnJS,
|
|
21
|
+
useAnimatedStyle,
|
|
22
|
+
useSharedValue,
|
|
23
|
+
withTiming,
|
|
24
|
+
type AnimatedStyle,
|
|
25
|
+
} from 'react-native-reanimated'
|
|
26
|
+
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
27
|
+
import { IconButton, Heading, Text } from '../../display'
|
|
28
|
+
import colorFunction from 'color'
|
|
29
|
+
import { formatDatePreview } from '../../../utils/date'
|
|
3
30
|
|
|
4
|
-
|
|
31
|
+
const PAN_THRESHOLD_PX = 300
|
|
32
|
+
|
|
33
|
+
export type MetaProps = {
|
|
34
|
+
authorName: string
|
|
35
|
+
createdAt: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ImageAttachment({
|
|
39
|
+
attachment,
|
|
40
|
+
metaProps,
|
|
41
|
+
}: {
|
|
42
|
+
attachment: any
|
|
43
|
+
metaProps: MetaProps
|
|
44
|
+
}) {
|
|
5
45
|
const styles = useStyles()
|
|
46
|
+
const [visible, setVisible] = useState(false)
|
|
47
|
+
|
|
48
|
+
// shared values run on the native UI thread and prevents clogging up the JS thread
|
|
49
|
+
const dismissY = useSharedValue(0)
|
|
50
|
+
const opacity = useSharedValue(1)
|
|
51
|
+
|
|
52
|
+
const resetAnimations = useCallback(() => {
|
|
53
|
+
dismissY.value = withTiming(0)
|
|
54
|
+
opacity.value = withTiming(1)
|
|
55
|
+
}, [dismissY, opacity])
|
|
56
|
+
|
|
57
|
+
const handleCloseModal = useCallback(() => {
|
|
58
|
+
setVisible(false)
|
|
59
|
+
resetAnimations()
|
|
60
|
+
}, [setVisible, resetAnimations])
|
|
61
|
+
|
|
62
|
+
const panGesture = Gesture.Pan()
|
|
63
|
+
.onUpdate(e => {
|
|
64
|
+
dismissY.value = e.translationY
|
|
65
|
+
opacity.value = 1 - Math.abs(e.translationY) / PAN_THRESHOLD_PX
|
|
66
|
+
})
|
|
67
|
+
.onEnd(() => {
|
|
68
|
+
runOnJS(handleCloseModal)() // Ensures we can call a JS function
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const animatedImageStyle = useAnimatedStyle(() => ({
|
|
72
|
+
transform: [{ translateY: dismissY.value }],
|
|
73
|
+
opacity: opacity.value,
|
|
74
|
+
}))
|
|
75
|
+
|
|
6
76
|
const { attributes } = attachment
|
|
7
77
|
const { url, urlMedium, filename, metadata = {} } = attributes
|
|
8
78
|
const width = metadata.width || 100
|
|
9
79
|
const height = metadata.height || 100
|
|
80
|
+
|
|
10
81
|
return (
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
82
|
+
<>
|
|
83
|
+
<Pressable style={styles.container} onPress={() => setVisible(true)}>
|
|
84
|
+
<Image
|
|
85
|
+
source={{ uri: urlMedium || url }}
|
|
86
|
+
style={[styles.image, { aspectRatio: width / height }]}
|
|
87
|
+
accessibilityLabel={filename}
|
|
88
|
+
/>
|
|
89
|
+
</Pressable>
|
|
90
|
+
<LightboxModal
|
|
91
|
+
visible={visible}
|
|
92
|
+
handleCloseModal={handleCloseModal}
|
|
93
|
+
uri={urlMedium || url}
|
|
94
|
+
metaProps={metaProps}
|
|
95
|
+
panGesture={panGesture}
|
|
96
|
+
animatedImageStyle={animatedImageStyle}
|
|
16
97
|
/>
|
|
17
|
-
|
|
98
|
+
</>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface LightboxModalProps {
|
|
103
|
+
visible: boolean
|
|
104
|
+
handleCloseModal: () => void
|
|
105
|
+
uri: string
|
|
106
|
+
metaProps: MetaProps
|
|
107
|
+
panGesture: PanGesture
|
|
108
|
+
animatedImageStyle: AnimatedStyle<ImageStyle>
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const LightboxModal = ({
|
|
112
|
+
uri,
|
|
113
|
+
visible,
|
|
114
|
+
handleCloseModal,
|
|
115
|
+
metaProps,
|
|
116
|
+
panGesture,
|
|
117
|
+
animatedImageStyle,
|
|
118
|
+
}: LightboxModalProps) => {
|
|
119
|
+
const styles = useStyles()
|
|
120
|
+
|
|
121
|
+
const { authorName, createdAt } = metaProps
|
|
122
|
+
|
|
123
|
+
const handleOpenInBrowser = () => {
|
|
124
|
+
Linking.openURL(uri)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Modal visible={visible} transparent animationType="fade" onRequestClose={handleCloseModal}>
|
|
129
|
+
<SafeAreaView style={styles.modal}>
|
|
130
|
+
<GestureHandlerRootView>
|
|
131
|
+
<GestureDetector gesture={panGesture}>
|
|
132
|
+
<Animated.Image
|
|
133
|
+
source={{ uri }}
|
|
134
|
+
style={[styles.lightboxImage, animatedImageStyle]}
|
|
135
|
+
resizeMode="contain"
|
|
136
|
+
/>
|
|
137
|
+
</GestureDetector>
|
|
138
|
+
<View style={styles.actionToolbar} accessibilityRole="toolbar">
|
|
139
|
+
<View style={styles.actionToolbarTextMeta}>
|
|
140
|
+
<Heading variant="h3" style={styles.actionToolbarTitle} numberOfLines={1}>
|
|
141
|
+
{authorName}
|
|
142
|
+
</Heading>
|
|
143
|
+
<Text variant="tertiary" style={styles.actionToolbarSubtitle}>
|
|
144
|
+
{formatDatePreview(createdAt)}
|
|
145
|
+
</Text>
|
|
146
|
+
</View>
|
|
147
|
+
<IconButton
|
|
148
|
+
onPress={handleOpenInBrowser}
|
|
149
|
+
name="general.newWindow"
|
|
150
|
+
accessibilityRole="link"
|
|
151
|
+
accessibilityLabel="Open image in browser"
|
|
152
|
+
accessibilityHint="Image can be downloaded and shared through the browser."
|
|
153
|
+
style={styles.actionButton}
|
|
154
|
+
iconStyle={styles.actionButtonIcon}
|
|
155
|
+
size="lg"
|
|
156
|
+
/>
|
|
157
|
+
<IconButton
|
|
158
|
+
onPress={handleCloseModal}
|
|
159
|
+
name="general.x"
|
|
160
|
+
accessibilityLabel="Close image"
|
|
161
|
+
style={styles.actionButton}
|
|
162
|
+
iconStyle={styles.actionButtonIcon}
|
|
163
|
+
/>
|
|
164
|
+
</View>
|
|
165
|
+
</GestureHandlerRootView>
|
|
166
|
+
</SafeAreaView>
|
|
167
|
+
</Modal>
|
|
18
168
|
)
|
|
19
169
|
}
|
|
20
170
|
|
|
21
171
|
const useStyles = () => {
|
|
172
|
+
const { width: windowWidth } = useWindowDimensions()
|
|
173
|
+
const backgroundColor = tokens.colorNeutral7
|
|
174
|
+
const transparentBackgroundColor = useMemo(
|
|
175
|
+
() => colorFunction(backgroundColor).alpha(0.8).toString(),
|
|
176
|
+
[backgroundColor]
|
|
177
|
+
)
|
|
178
|
+
|
|
22
179
|
return StyleSheet.create({
|
|
23
180
|
container: {
|
|
24
181
|
maxWidth: '100%',
|
|
@@ -27,5 +184,49 @@ const useStyles = () => {
|
|
|
27
184
|
borderRadius: 8,
|
|
28
185
|
minWidth: 200,
|
|
29
186
|
},
|
|
187
|
+
modal: {
|
|
188
|
+
flex: 1,
|
|
189
|
+
backgroundColor,
|
|
190
|
+
justifyContent: 'center',
|
|
191
|
+
alignItems: 'center',
|
|
192
|
+
},
|
|
193
|
+
lightboxImage: {
|
|
194
|
+
width: windowWidth,
|
|
195
|
+
height: '100%',
|
|
196
|
+
},
|
|
197
|
+
actionToolbar: {
|
|
198
|
+
width: '100%',
|
|
199
|
+
position: 'absolute',
|
|
200
|
+
top: 0,
|
|
201
|
+
flexDirection: 'row',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
gap: 20,
|
|
204
|
+
paddingHorizontal: 16,
|
|
205
|
+
paddingTop: 16,
|
|
206
|
+
paddingBottom: 8,
|
|
207
|
+
backgroundColor: transparentBackgroundColor,
|
|
208
|
+
},
|
|
209
|
+
actionToolbarTextMeta: {
|
|
210
|
+
flex: 1,
|
|
211
|
+
},
|
|
212
|
+
actionToolbarTitle: {
|
|
213
|
+
marginRight: 'auto',
|
|
214
|
+
flexShrink: 1,
|
|
215
|
+
color: tokens.colorNeutral88,
|
|
216
|
+
},
|
|
217
|
+
actionToolbarSubtitle: {
|
|
218
|
+
color: tokens.colorNeutral68,
|
|
219
|
+
},
|
|
220
|
+
actionButton: {
|
|
221
|
+
backgroundColor,
|
|
222
|
+
height: 40,
|
|
223
|
+
width: 40,
|
|
224
|
+
borderRadius: 50,
|
|
225
|
+
borderWidth: 1,
|
|
226
|
+
borderColor: tokens.colorNeutral24,
|
|
227
|
+
},
|
|
228
|
+
actionButtonIcon: {
|
|
229
|
+
color: tokens.colorNeutral88,
|
|
230
|
+
},
|
|
30
231
|
})
|
|
31
232
|
}
|
|
@@ -33,6 +33,11 @@ export function Message(props: MessageResource & { conversation_id: number }) {
|
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const metaProps = {
|
|
37
|
+
authorName: props.author.name,
|
|
38
|
+
createdAt: props.createdAt,
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
return (
|
|
37
42
|
<View style={styles.message}>
|
|
38
43
|
{!props.mine && (
|
|
@@ -44,7 +49,7 @@ export function Message(props: MessageResource & { conversation_id: number }) {
|
|
|
44
49
|
{!props.mine && <Text variant="tertiary">{props.author.name}</Text>}
|
|
45
50
|
<PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
|
|
46
51
|
<ErrorBoundary>
|
|
47
|
-
<MessageAttachments attachments={props.attachments} />
|
|
52
|
+
<MessageAttachments attachments={props.attachments} metaProps={metaProps} />
|
|
48
53
|
</ErrorBoundary>
|
|
49
54
|
{text && (
|
|
50
55
|
<View style={styles.messageText}>
|
|
@@ -6,18 +6,27 @@ import { VideoAttachment } from './attachments/video_attachment'
|
|
|
6
6
|
import { GiphyAttachment } from './attachments/giphy_attachment'
|
|
7
7
|
import { GenericFileAttachment } from './attachments/generic_file_attachment'
|
|
8
8
|
import { ExpandedLink } from './attachments/expanded_link'
|
|
9
|
-
import { ImageAttachment } from './attachments/image_attachment'
|
|
9
|
+
import { ImageAttachment, type MetaProps } from './attachments/image_attachment'
|
|
10
10
|
|
|
11
|
-
export function MessageAttachments(props: {
|
|
11
|
+
export function MessageAttachments(props: {
|
|
12
|
+
attachments: DenormalizedAttachmentResource[]
|
|
13
|
+
metaProps: MetaProps
|
|
14
|
+
}) {
|
|
12
15
|
const styles = useStyles()
|
|
13
|
-
const { attachments } = props
|
|
16
|
+
const { attachments, metaProps } = props
|
|
14
17
|
if (!attachments || attachments.length === 0) return null
|
|
15
18
|
return (
|
|
16
19
|
<View style={styles.attachmentsContainer}>
|
|
17
20
|
{attachments.map(attachment => {
|
|
18
21
|
switch (attachment.type) {
|
|
19
22
|
case 'MessageAttachment':
|
|
20
|
-
return
|
|
23
|
+
return (
|
|
24
|
+
<MessageAttachment
|
|
25
|
+
key={attachment.id}
|
|
26
|
+
attachment={attachment}
|
|
27
|
+
metaProps={metaProps}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
21
30
|
case 'giphy':
|
|
22
31
|
return (
|
|
23
32
|
<GiphyAttachment
|
|
@@ -35,13 +44,13 @@ export function MessageAttachments(props: { attachments: DenormalizedAttachmentR
|
|
|
35
44
|
)
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
function MessageAttachment({ attachment }: { attachment: any }) {
|
|
47
|
+
function MessageAttachment({ attachment, metaProps }: { attachment: any; metaProps: MetaProps }) {
|
|
39
48
|
const { attributes } = attachment
|
|
40
49
|
const contentType = attributes?.contentType
|
|
41
50
|
const basicType = contentType ? contentType.split('/')[0] : ''
|
|
42
51
|
switch (basicType) {
|
|
43
52
|
case 'image':
|
|
44
|
-
return <ImageAttachment attachment={attachment} />
|
|
53
|
+
return <ImageAttachment attachment={attachment} metaProps={metaProps} />
|
|
45
54
|
case 'video':
|
|
46
55
|
return <VideoAttachment attachment={attachment} />
|
|
47
56
|
case 'audio':
|