@planningcenter/chat-react-native 3.17.0-rc.0 → 3.17.0-rc.1
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/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +16 -6
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts +1 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/reply_connectors.d.ts +1 -0
- package/build/components/conversation/reply_connectors.d.ts.map +1 -1
- package/build/components/conversation/reply_connectors.js +33 -14
- package/build/components/conversation/reply_connectors.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts +12 -0
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -0
- package/build/components/conversation/{shadow_message.js → reply_shadow_message.js} +35 -5
- package/build/components/conversation/reply_shadow_message.js.map +1 -0
- package/build/hooks/use_conversation_message.d.ts +11 -0
- package/build/hooks/use_conversation_message.d.ts.map +1 -0
- package/build/hooks/use_conversation_message.js +8 -0
- package/build/hooks/use_conversation_message.js.map +1 -0
- package/build/hooks/use_conversation_messages.d.ts +1 -20
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +2 -33
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js +1 -1
- package/build/hooks/use_conversation_messages_jolt_events.js.map +1 -1
- package/build/hooks/use_message_create_or_update.d.ts +1 -1
- package/build/hooks/use_message_create_or_update.d.ts.map +1 -1
- package/build/hooks/use_message_create_or_update.js +1 -1
- package/build/hooks/use_message_create_or_update.js.map +1 -1
- package/build/hooks/use_message_reaction_toggle.js +1 -1
- package/build/hooks/use_message_reaction_toggle.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +10 -2
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +27 -0
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/types/resources/message.d.ts +3 -0
- package/build/types/resources/message.d.ts.map +1 -1
- package/build/types/resources/message.js.map +1 -1
- package/build/utils/cache/optimistically_create_message.js +1 -1
- package/build/utils/cache/optimistically_create_message.js.map +1 -1
- package/build/utils/cache/optimistically_update_message.js +1 -1
- package/build/utils/cache/optimistically_update_message.js.map +1 -1
- package/build/utils/pluralize.js +1 -1
- package/build/utils/pluralize.js.map +1 -1
- package/build/utils/request/get_message.d.ts +20 -0
- package/build/utils/request/get_message.d.ts.map +1 -0
- package/build/utils/request/get_message.js +18 -0
- package/build/utils/request/get_message.js.map +1 -0
- package/build/utils/request/get_messages.d.ts +20 -0
- package/build/utils/request/get_messages.d.ts.map +1 -0
- package/build/utils/request/get_messages.js +20 -0
- package/build/utils/request/get_messages.js.map +1 -0
- package/build/utils/request/messages_data_options.d.ts +7 -0
- package/build/utils/request/messages_data_options.d.ts.map +1 -0
- package/build/utils/request/messages_data_options.js +18 -0
- package/build/utils/request/messages_data_options.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/utils/pluralize.tsx +3 -0
- package/src/components/conversation/message.tsx +19 -6
- package/src/components/conversation/message_form.tsx +2 -2
- package/src/components/conversation/reply_connectors.tsx +42 -20
- package/src/components/conversation/{shadow_message.tsx → reply_shadow_message.tsx} +54 -5
- package/src/hooks/use_conversation_message.ts +20 -0
- package/src/hooks/use_conversation_messages.ts +3 -53
- package/src/hooks/use_conversation_messages_jolt_events.ts +1 -1
- package/src/hooks/use_message_create_or_update.ts +2 -2
- package/src/hooks/use_message_reaction_toggle.ts +1 -1
- package/src/screens/conversation_screen.tsx +48 -2
- package/src/types/resources/message.ts +3 -0
- package/src/utils/cache/optimistically_create_message.ts +1 -1
- package/src/utils/cache/optimistically_update_message.ts +1 -1
- package/src/utils/pluralize.ts +1 -1
- package/src/utils/request/get_message.ts +32 -0
- package/src/utils/request/get_messages.ts +34 -0
- package/src/utils/request/messages_data_options.ts +18 -0
- package/build/components/conversation/shadow_message.d.ts +0 -8
- package/build/components/conversation/shadow_message.d.ts.map +0 -1
- package/build/components/conversation/shadow_message.js.map +0 -1
- package/build/utils/request/messages.d.ts +0 -15
- package/build/utils/request/messages.d.ts.map +0 -1
- package/build/utils/request/messages.js +0 -22
- package/build/utils/request/messages.js.map +0 -1
- package/src/utils/request/messages.ts +0 -21
|
@@ -10,6 +10,8 @@ import { REPLIES_FEATURE_ENABLED } from '../../utils'
|
|
|
10
10
|
|
|
11
11
|
const MY_REPLY_CONNECTOR_WIDTH = 38
|
|
12
12
|
const CONNECTOR_BORDER_WIDTH = 4
|
|
13
|
+
const OFFSET_CONNECTOR_CONTAINER = CONNECTOR_BORDER_WIDTH / 2 // Accounts for the border width ensuring the connectors are centered under the avatar
|
|
14
|
+
export const AVATAR_CONNECTOR_SPACING = 8
|
|
13
15
|
|
|
14
16
|
interface ReplyConnectorProps {
|
|
15
17
|
message: MessageResource
|
|
@@ -18,8 +20,10 @@ interface ReplyConnectorProps {
|
|
|
18
20
|
|
|
19
21
|
export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) {
|
|
20
22
|
const styles = useStyles()
|
|
23
|
+
const { nextRendersAuthor, threadPosition, renderAuthor, isReplyShadowMessage } = message
|
|
21
24
|
|
|
22
25
|
if (!REPLIES_FEATURE_ENABLED) return null
|
|
26
|
+
if (messageBubbleHeight === 0) return null // Prevents UI shifting
|
|
23
27
|
|
|
24
28
|
const connectorMap: Record<string, React.ReactNode | null> = {
|
|
25
29
|
shortTailCurve: <ShortTailCurveConnector height={messageBubbleHeight / 2} />,
|
|
@@ -29,11 +33,13 @@ export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConne
|
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
const getConnectorKey = () => {
|
|
32
|
-
const { threadPosition, renderAuthor } = message
|
|
33
|
-
|
|
34
36
|
if (threadPosition === 'first' && !renderAuthor) return 'shortHeadCurve'
|
|
35
37
|
if (threadPosition === 'last' && !renderAuthor) return 'shortTailCurve'
|
|
36
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
(threadPosition === 'first' && renderAuthor) ||
|
|
40
|
+
threadPosition === 'center' ||
|
|
41
|
+
isReplyShadowMessage
|
|
42
|
+
)
|
|
37
43
|
return 'verticalLine'
|
|
38
44
|
return 'default'
|
|
39
45
|
}
|
|
@@ -41,13 +47,20 @@ export function TheirReplyConnector({ message, messageBubbleHeight }: ReplyConne
|
|
|
41
47
|
const ConnectorComponent = connectorMap[getConnectorKey()]
|
|
42
48
|
if (!ConnectorComponent) return null
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
const spacerStyle = nextRendersAuthor ? styles.theirReplyConnectorSpacer : null
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View style={[styles.theirReplyConnectorContainer, spacerStyle]}>{ConnectorComponent}</View>
|
|
54
|
+
)
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnectorProps) {
|
|
48
58
|
const styles = useStyles()
|
|
59
|
+
const { nextRendersAuthor, threadPosition, prevIsMyReply, nextIsMyReply, isReplyShadowMessage } =
|
|
60
|
+
message
|
|
49
61
|
|
|
50
62
|
if (!REPLIES_FEATURE_ENABLED) return null
|
|
63
|
+
if (messageBubbleHeight === 0) return null // Prevents UI shifting
|
|
51
64
|
|
|
52
65
|
const connectorMap: Record<string, React.ReactNode | null> = {
|
|
53
66
|
longTailCurve: (
|
|
@@ -63,14 +76,12 @@ export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnecto
|
|
|
63
76
|
/>
|
|
64
77
|
),
|
|
65
78
|
verticalLine: <VerticalLineConnector style={styles.myReplyConnectorPosition} />,
|
|
66
|
-
swirl: <SwirlConnector style={styles.myReplyConnectorPosition} />,
|
|
79
|
+
swirl: <SwirlConnector style={styles.myReplyConnectorPosition} height={messageBubbleHeight} />,
|
|
67
80
|
default: null,
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
const getConnectorKey = () => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (threadPosition === 'first') return 'longHeadCurve'
|
|
84
|
+
if (threadPosition === 'first' || isReplyShadowMessage) return 'longHeadCurve'
|
|
74
85
|
if (threadPosition === 'center' && prevIsMyReply && nextIsMyReply) return 'verticalLine'
|
|
75
86
|
if (threadPosition === 'center' && (!prevIsMyReply || !nextIsMyReply)) return 'swirl'
|
|
76
87
|
if (threadPosition === 'last') return 'longTailCurve'
|
|
@@ -81,7 +92,9 @@ export function MyReplyConnector({ message, messageBubbleHeight }: ReplyConnecto
|
|
|
81
92
|
const ConnectorComponent = connectorMap[getConnectorKey()]
|
|
82
93
|
if (!ConnectorComponent) return null
|
|
83
94
|
|
|
84
|
-
|
|
95
|
+
const spacerStyle = nextRendersAuthor ? styles.myReplyConnectorSpacer : null
|
|
96
|
+
|
|
97
|
+
return <View style={[styles.myReplyConnectorContainer, spacerStyle]}>{ConnectorComponent}</View>
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
function VerticalLineConnector({ style }: { style?: ViewStyle }) {
|
|
@@ -109,21 +122,23 @@ function ShortTailCurveConnector({ height }: { height: number }) {
|
|
|
109
122
|
return <View style={[styles.shortTailCurveConnector, { height }]} />
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
function SwirlConnector({ style }: { style?: ViewStyle }) {
|
|
125
|
+
function SwirlConnector({ style, height }: { style?: ViewStyle; height: number }) {
|
|
113
126
|
const styles = useStyles()
|
|
114
127
|
const { colors } = useTheme()
|
|
115
|
-
const borderColor = colors.borderColorDefaultBase
|
|
116
128
|
|
|
117
129
|
return (
|
|
118
130
|
<View style={[styles.swirlConnectorContainer, style]}>
|
|
119
|
-
<View style={
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
<View style={{ height }}>
|
|
132
|
+
<View style={styles.swirlConnectorVerticalLineHead} />
|
|
133
|
+
<Svg width={27} height={34} fill="none">
|
|
134
|
+
<Path
|
|
135
|
+
stroke={colors.borderColorDefaultBase}
|
|
136
|
+
strokeWidth={CONNECTOR_BORDER_WIDTH}
|
|
137
|
+
d="M2.07 34c0-6.142 1.201-12.283 3.343-17m0 0c2.305-5.075 5.699-8.5 9.857-8.5 4.86 0 8.8 3.806 8.8 8.5s-3.94 8.5-8.8 8.5c-4.158 0-7.552-3.425-9.857-8.5zm0 0C3.271 12.283 2.07 6.142 2.07 0"
|
|
138
|
+
/>
|
|
139
|
+
</Svg>
|
|
140
|
+
<View style={styles.swirlConnectorVerticalLineTail} />
|
|
141
|
+
</View>
|
|
127
142
|
<View style={styles.swirlConnectorVerticalLineTail} />
|
|
128
143
|
</View>
|
|
129
144
|
)
|
|
@@ -136,19 +151,26 @@ const useStyles = () => {
|
|
|
136
151
|
return StyleSheet.create({
|
|
137
152
|
theirReplyConnectorContainer: {
|
|
138
153
|
width: '50%',
|
|
154
|
+
marginRight: OFFSET_CONNECTOR_CONTAINER,
|
|
139
155
|
alignSelf: 'flex-end',
|
|
140
156
|
flex: 1,
|
|
141
157
|
},
|
|
158
|
+
theirReplyConnectorSpacer: {
|
|
159
|
+
paddingBottom: AVATAR_CONNECTOR_SPACING,
|
|
160
|
+
},
|
|
142
161
|
myReplyConnectorContainer: {
|
|
143
162
|
width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,
|
|
144
163
|
position: 'absolute',
|
|
145
|
-
left: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
164
|
+
left: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL - OFFSET_CONNECTOR_CONTAINER,
|
|
146
165
|
top: 0,
|
|
147
166
|
bottom: 0,
|
|
148
167
|
},
|
|
149
168
|
myReplyConnectorPosition: {
|
|
150
169
|
left: '50%',
|
|
151
170
|
},
|
|
171
|
+
myReplyConnectorSpacer: {
|
|
172
|
+
bottom: AVATAR_CONNECTOR_SPACING,
|
|
173
|
+
},
|
|
152
174
|
verticalLineConnector: {
|
|
153
175
|
flex: 1,
|
|
154
176
|
borderLeftWidth: CONNECTOR_BORDER_WIDTH,
|
|
@@ -22,14 +22,45 @@ import {
|
|
|
22
22
|
} from '../../utils/styles'
|
|
23
23
|
import Animated from 'react-native-reanimated'
|
|
24
24
|
import { TheirReplyConnector, MyReplyConnector } from './reply_connectors'
|
|
25
|
-
import { assertKeysAreNumbers } from '../../utils'
|
|
25
|
+
import { assertKeysAreNumbers, pluralize } from '../../utils'
|
|
26
26
|
import { useNavigation } from '@react-navigation/native'
|
|
27
|
+
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
27
28
|
|
|
28
|
-
interface
|
|
29
|
+
interface ReplyShadowMessageProps extends MessageResource {
|
|
30
|
+
messageId: string
|
|
29
31
|
conversation_id: number
|
|
32
|
+
inReplyScreen?: boolean
|
|
33
|
+
isReplyShadowMessage: boolean
|
|
34
|
+
nextRendersAuthor: boolean
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
export function
|
|
37
|
+
export function ReplyShadowMessage({
|
|
38
|
+
conversation_id,
|
|
39
|
+
inReplyScreen,
|
|
40
|
+
messageId,
|
|
41
|
+
isReplyShadowMessage,
|
|
42
|
+
nextRendersAuthor,
|
|
43
|
+
}: ReplyShadowMessageProps) {
|
|
44
|
+
const { message, isError, isLoading } = useConversationMessage({ conversation_id, messageId })
|
|
45
|
+
|
|
46
|
+
if (inReplyScreen) return null
|
|
47
|
+
if (isLoading) return <ShadowMessageFallback text="Loading..." />
|
|
48
|
+
if (isError || !message) return <ShadowMessageFallback text="Message not found" />
|
|
49
|
+
|
|
50
|
+
const enrichedMessage = {
|
|
51
|
+
...message,
|
|
52
|
+
isReplyShadowMessage,
|
|
53
|
+
nextRendersAuthor,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <ShadowMessageContent conversation_id={conversation_id} {...enrichedMessage} />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ShadowMessageContentProps extends MessageResource {
|
|
60
|
+
conversation_id: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageContentProps) {
|
|
33
64
|
const { text } = message
|
|
34
65
|
const styles = useStyles(message)
|
|
35
66
|
const { colors } = useTheme()
|
|
@@ -39,11 +70,12 @@ export function ShadowMessage({ conversation_id, ...message }: ShadowMessageProp
|
|
|
39
70
|
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
40
71
|
useAnimatedMessageBackgroundColor()
|
|
41
72
|
const scalableNumberOfLines = useScalableNumberOfLines(2)
|
|
73
|
+
const replyCountText = pluralize(message.replyCount, 'reply')
|
|
42
74
|
|
|
43
75
|
const handleNavigateToReplies = () => {
|
|
44
76
|
navigation.navigate('ConversationReply', {
|
|
45
77
|
conversation_id,
|
|
46
|
-
reply_root_id: message.
|
|
78
|
+
reply_root_id: message.replyRootId,
|
|
47
79
|
// TODO: Add a way to pass the reply root author's name
|
|
48
80
|
})
|
|
49
81
|
}
|
|
@@ -89,7 +121,7 @@ export function ShadowMessage({ conversation_id, ...message }: ShadowMessageProp
|
|
|
89
121
|
</View>
|
|
90
122
|
<View style={styles.messageMeta}>
|
|
91
123
|
<Text variant="footnote" style={styles.replyCountText}>
|
|
92
|
-
{
|
|
124
|
+
{replyCountText}
|
|
93
125
|
</Text>
|
|
94
126
|
</View>
|
|
95
127
|
</View>
|
|
@@ -203,6 +235,23 @@ function MessageAttachmentIcon({ iconName }: { iconName: IconProps['name'] }) {
|
|
|
203
235
|
)
|
|
204
236
|
}
|
|
205
237
|
|
|
238
|
+
// TODO: The mine prop is not used yet, but in the future we will be adding a way to find the `mine` value for this fallback.
|
|
239
|
+
function ShadowMessageFallback({ text, mine }: { text: string; mine?: boolean }) {
|
|
240
|
+
const styles = useStyles({ mine })
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<View style={styles.message}>
|
|
244
|
+
<View style={styles.messageContent}>
|
|
245
|
+
<View style={styles.messageBubble}>
|
|
246
|
+
<Text variant="footnote" style={styles.messageText}>
|
|
247
|
+
{text}
|
|
248
|
+
</Text>
|
|
249
|
+
</View>
|
|
250
|
+
</View>
|
|
251
|
+
</View>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
206
255
|
interface StylesProps {
|
|
207
256
|
imageWidth?: number
|
|
208
257
|
imageHeight?: number
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MessageResource } from '../types'
|
|
2
|
+
import { useApiGet } from './use_api'
|
|
3
|
+
import { getMessageRequestArgs, getMessageQueryKey } from '../utils/request/get_message'
|
|
4
|
+
|
|
5
|
+
export const useConversationMessage = ({
|
|
6
|
+
conversation_id,
|
|
7
|
+
messageId,
|
|
8
|
+
}: {
|
|
9
|
+
conversation_id: number
|
|
10
|
+
messageId: string
|
|
11
|
+
}) => {
|
|
12
|
+
const {
|
|
13
|
+
data: message,
|
|
14
|
+
isError,
|
|
15
|
+
isLoading,
|
|
16
|
+
} = useApiGet<MessageResource>(getMessageRequestArgs({ conversation_id, messageId }))
|
|
17
|
+
const queryKey = getMessageQueryKey({ conversation_id, messageId })
|
|
18
|
+
|
|
19
|
+
return { message, isError, isLoading, queryKey }
|
|
20
|
+
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { MessageResource } from '../types'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
SuspensePaginatorOptions,
|
|
6
|
-
useSuspensePaginator,
|
|
7
|
-
} from './use_suspense_api'
|
|
3
|
+
import { SuspensePaginatorOptions, useSuspensePaginator } from './use_suspense_api'
|
|
4
|
+
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
8
5
|
|
|
9
6
|
export const useConversationMessages = (
|
|
10
|
-
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string },
|
|
7
|
+
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },
|
|
11
8
|
opts?: SuspensePaginatorOptions
|
|
12
9
|
) => {
|
|
13
10
|
const { data, refetch, isRefetching, fetchNextPage } = useSuspensePaginator<MessageResource>(
|
|
@@ -27,50 +24,3 @@ export const useConversationMessages = (
|
|
|
27
24
|
|
|
28
25
|
return { messages, refetch, isRefetching, fetchNextPage, queryKey }
|
|
29
26
|
}
|
|
30
|
-
|
|
31
|
-
export const getMessagesRequestArgs = ({
|
|
32
|
-
conversation_id,
|
|
33
|
-
reply_root_id,
|
|
34
|
-
}: {
|
|
35
|
-
conversation_id: number
|
|
36
|
-
reply_root_id?: string
|
|
37
|
-
}) => {
|
|
38
|
-
const url = reply_root_id
|
|
39
|
-
? `/me/conversations/${conversation_id}/messages/${reply_root_id}/replies`
|
|
40
|
-
: `/me/conversations/${conversation_id}/messages`
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
url,
|
|
44
|
-
data: {
|
|
45
|
-
perPage: 25,
|
|
46
|
-
fields: {
|
|
47
|
-
Message: [
|
|
48
|
-
'text',
|
|
49
|
-
'text_edited_at',
|
|
50
|
-
'mine',
|
|
51
|
-
'attachments',
|
|
52
|
-
'created_at',
|
|
53
|
-
'deleted_at',
|
|
54
|
-
'author',
|
|
55
|
-
'reaction_counts',
|
|
56
|
-
'reply_count',
|
|
57
|
-
'reply_root',
|
|
58
|
-
],
|
|
59
|
-
Person: ['name', 'avatar'],
|
|
60
|
-
ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],
|
|
61
|
-
},
|
|
62
|
-
include: ['author', 'reaction_counts'],
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const getMessagesQueryKey = ({
|
|
68
|
-
conversation_id,
|
|
69
|
-
reply_root_id,
|
|
70
|
-
}: {
|
|
71
|
-
conversation_id: number
|
|
72
|
-
reply_root_id?: string
|
|
73
|
-
}) => {
|
|
74
|
-
const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
75
|
-
return getRequestQueryKey(requestArgs)
|
|
76
|
-
}
|
|
@@ -12,7 +12,7 @@ import { transformMessageEventDataToMessageResource } from '../utils/jolt/transf
|
|
|
12
12
|
import { getRequestQueryKey } from './use_suspense_api'
|
|
13
13
|
import { JoltReactionEvent, JoltTypingEvent } from '../types/jolt_events'
|
|
14
14
|
import { transformReactionEventDataToReactionCountResource } from '../utils/jolt/transform_reaction_event_data_to_reaction_count_resource'
|
|
15
|
-
import { getMessagesRequestArgs } from '../utils/request/
|
|
15
|
+
import { getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
16
16
|
import { TYPING_TIMEOUT_INTERVAL, useTypingStatusCache } from './use_typing_status_cache'
|
|
17
17
|
import { isTemporaryMessageId } from './use_message_create_or_update'
|
|
18
18
|
import { completeMessageCreationTracking } from '../utils/performance_tracking'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InfiniteData, useMutation } from '@tanstack/react-query'
|
|
2
|
-
import { getMessagesQueryKey, getMessagesRequestArgs } from '
|
|
2
|
+
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
3
3
|
import { useApiClient } from './use_api_client'
|
|
4
4
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
5
5
|
import { chatQueryClient } from '../contexts/api_provider'
|
|
@@ -18,7 +18,7 @@ import { startMessageCreationTracking } from '../utils/performance_tracking'
|
|
|
18
18
|
interface Props {
|
|
19
19
|
conversationId: number
|
|
20
20
|
message?: MessageResource
|
|
21
|
-
replyRootId?: string
|
|
21
|
+
replyRootId?: string | null
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function useMessageCreateOrUpdate({ conversationId, message, replyRootId }: Props) {
|
|
@@ -2,7 +2,7 @@ import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query
|
|
|
2
2
|
import { useCallback } from 'react'
|
|
3
3
|
import { Alert } from 'react-native'
|
|
4
4
|
import { useApiClient } from './use_api_client'
|
|
5
|
-
import { getMessagesQueryKey, getMessagesRequestArgs } from '
|
|
5
|
+
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
6
6
|
import { ApiCollection, ApiResource, MessageResource } from '../types'
|
|
7
7
|
import { ReactionCountResource } from '../types/resources/reaction'
|
|
8
8
|
import { updateRecordInPagesData } from '../utils'
|
|
@@ -34,10 +34,12 @@ import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'
|
|
|
34
34
|
import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'
|
|
35
35
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
36
36
|
import { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'
|
|
37
|
+
import { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'
|
|
38
|
+
import { REPLIES_FEATURE_ENABLED } from '../utils'
|
|
37
39
|
|
|
38
40
|
export type ConversationRouteProps = {
|
|
39
41
|
conversation_id: number
|
|
40
|
-
reply_root_id?: string
|
|
42
|
+
reply_root_id?: string | null
|
|
41
43
|
replyRootAuthor?: string
|
|
42
44
|
chat_group_graph_id?: string
|
|
43
45
|
clear_input?: boolean
|
|
@@ -136,6 +138,16 @@ export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
|
136
138
|
return <InlineDateSeparator {...item} />
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
if (item.type === 'ReplyShadowMessage') {
|
|
142
|
+
return (
|
|
143
|
+
<ReplyShadowMessage
|
|
144
|
+
{...item}
|
|
145
|
+
conversation_id={conversation_id}
|
|
146
|
+
inReplyScreen={!!reply_root_id}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
return (
|
|
140
152
|
<Message
|
|
141
153
|
{...item}
|
|
@@ -219,13 +231,21 @@ const useDateSeparatorStyles = () => {
|
|
|
219
231
|
})
|
|
220
232
|
}
|
|
221
233
|
|
|
234
|
+
type ReplyShadowMessage = {
|
|
235
|
+
type: 'ReplyShadowMessage'
|
|
236
|
+
id: string
|
|
237
|
+
messageId: string
|
|
238
|
+
isReplyShadowMessage: boolean
|
|
239
|
+
nextRendersAuthor: boolean
|
|
240
|
+
}
|
|
241
|
+
|
|
222
242
|
interface GroupMessagesProps {
|
|
223
243
|
ms: MessageResource[]
|
|
224
244
|
inReplyScreen?: boolean
|
|
225
245
|
}
|
|
226
246
|
|
|
227
247
|
export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
228
|
-
let enrichedMessages: (MessageResource | DateSeparator)[] = []
|
|
248
|
+
let enrichedMessages: (MessageResource | DateSeparator | ReplyShadowMessage)[] = []
|
|
229
249
|
let encounteredOneOfMyMessages = false
|
|
230
250
|
|
|
231
251
|
ms.forEach((message, i) => {
|
|
@@ -233,7 +253,9 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
233
253
|
const nextMessage = ms[i - 1]
|
|
234
254
|
const date = moment(message.createdAt).format('YYYY-MM-DD')
|
|
235
255
|
const inThread = message.replyRootId !== null
|
|
256
|
+
const nextMessageInThread = nextMessage?.replyRootId !== null
|
|
236
257
|
const threadRoot = message.replyRootId === message.id
|
|
258
|
+
const nextMessageThreadRoot = nextMessage?.replyRootId === nextMessage?.id
|
|
237
259
|
const prevMessageDifferentThread = message.replyRootId !== prevMessage?.replyRootId
|
|
238
260
|
const nextMessageDifferentThread = message.replyRootId !== nextMessage?.replyRootId
|
|
239
261
|
const prevMessageDifferentAuthor = message.author?.id !== prevMessage?.author?.id
|
|
@@ -244,8 +266,14 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
244
266
|
const nextMessageMoreThan5Minutes =
|
|
245
267
|
nextMessage &&
|
|
246
268
|
new Date(nextMessage.createdAt).getTime() - new Date(message.createdAt).getTime() > 60000 * 5
|
|
269
|
+
const prevMessageIsDateSeparator =
|
|
270
|
+
prevMessage && date !== moment(prevMessage.createdAt).format('YYYY-MM-DD')
|
|
247
271
|
const nextMessageIsDateSeparator =
|
|
248
272
|
nextMessage && date !== moment(nextMessage.createdAt).format('YYYY-MM-DD')
|
|
273
|
+
const insertReplyShadowMessage =
|
|
274
|
+
message.replyRootId &&
|
|
275
|
+
!threadRoot &&
|
|
276
|
+
(prevMessageDifferentThread || prevMessageIsDateSeparator)
|
|
249
277
|
|
|
250
278
|
if (message.mine && !encounteredOneOfMyMessages) {
|
|
251
279
|
encounteredOneOfMyMessages = true
|
|
@@ -257,6 +285,13 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
257
285
|
message.renderAuthor =
|
|
258
286
|
!message.mine && (!prevMessage || prevMessageDifferentAuthor || prevMessageMoreThan5Minutes)
|
|
259
287
|
message.threadPosition = null
|
|
288
|
+
message.nextRendersAuthor = nextMessage?.renderAuthor
|
|
289
|
+
message.isReplyShadowMessage = false
|
|
290
|
+
message.nextIsReplyShadowMessage =
|
|
291
|
+
REPLIES_FEATURE_ENABLED &&
|
|
292
|
+
nextMessageInThread &&
|
|
293
|
+
!nextMessageThreadRoot &&
|
|
294
|
+
(nextMessageDifferentThread || nextMessageIsDateSeparator)
|
|
260
295
|
|
|
261
296
|
if (!inReplyScreen && inThread) {
|
|
262
297
|
message.prevIsMyReply = prevMessage?.mine
|
|
@@ -274,6 +309,17 @@ export const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {
|
|
|
274
309
|
}
|
|
275
310
|
|
|
276
311
|
enrichedMessages.push(message)
|
|
312
|
+
|
|
313
|
+
if (insertReplyShadowMessage && REPLIES_FEATURE_ENABLED) {
|
|
314
|
+
enrichedMessages.push({
|
|
315
|
+
type: 'ReplyShadowMessage',
|
|
316
|
+
id: `${message.id}-${message.replyRootId}`,
|
|
317
|
+
messageId: message.replyRootId!,
|
|
318
|
+
isReplyShadowMessage: true,
|
|
319
|
+
nextRendersAuthor: message?.renderAuthor,
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
|
|
277
323
|
if (!prevMessage || date !== moment(prevMessage.createdAt).format('YYYY-MM-DD')) {
|
|
278
324
|
enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })
|
|
279
325
|
}
|
|
@@ -26,6 +26,9 @@ export interface MessageResource {
|
|
|
26
26
|
threadPosition?: 'first' | 'center' | 'last' | null
|
|
27
27
|
prevIsMyReply?: boolean
|
|
28
28
|
nextIsMyReply?: boolean
|
|
29
|
+
nextRendersAuthor?: boolean
|
|
30
|
+
isReplyShadowMessage?: boolean
|
|
31
|
+
nextIsReplyShadowMessage?: boolean
|
|
29
32
|
|
|
30
33
|
// Properties for mutation state
|
|
31
34
|
pending?: boolean // Indicates if the message is optimistically created and pending server response
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InfiniteData } from '@tanstack/react-query'
|
|
2
2
|
import { ApiCollection, CurrentPersonResource, MessageResource } from '../../types'
|
|
3
|
-
import { getMessagesQueryKey } from '../../
|
|
3
|
+
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
4
4
|
import { chatQueryClient } from '../../contexts/api_provider'
|
|
5
5
|
import { updateOrCreateRecordInPagesData } from './page_mutations'
|
|
6
6
|
import { convertAttachmentsForCreate } from '../convert_attachments_for_create'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InfiniteData } from '@tanstack/react-query'
|
|
2
2
|
import { ApiCollection, MessageResource } from '../../types'
|
|
3
|
-
import { getMessagesQueryKey } from '../../
|
|
3
|
+
import { getMessagesQueryKey } from '../../utils/request/get_messages'
|
|
4
4
|
import { chatQueryClient } from '../../contexts/api_provider'
|
|
5
5
|
import { updateOrCreateRecordInPagesData } from './page_mutations'
|
|
6
6
|
|
package/src/utils/pluralize.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const irregularInflections: Record<string, string> = { person: 'people' }
|
|
1
|
+
const irregularInflections: Record<string, string> = { person: 'people', reply: 'replies' }
|
|
2
2
|
|
|
3
3
|
export function pluralize(count: number, singularWord: string, includeCount = true) {
|
|
4
4
|
const plural = count !== 1
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api'
|
|
2
|
+
import { getMessageFields, getMessagesInclude } from './messages_data_options'
|
|
3
|
+
|
|
4
|
+
export const getMessageRequestArgs = ({
|
|
5
|
+
conversation_id,
|
|
6
|
+
messageId,
|
|
7
|
+
}: {
|
|
8
|
+
conversation_id: number
|
|
9
|
+
messageId: string
|
|
10
|
+
}) => {
|
|
11
|
+
const url = `/me/conversations/${conversation_id}/messages/${messageId}`
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
url,
|
|
15
|
+
data: {
|
|
16
|
+
perPage: 25,
|
|
17
|
+
fields: getMessageFields,
|
|
18
|
+
include: getMessagesInclude,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const getMessageQueryKey = ({
|
|
24
|
+
conversation_id,
|
|
25
|
+
messageId,
|
|
26
|
+
}: {
|
|
27
|
+
conversation_id: number
|
|
28
|
+
messageId: string
|
|
29
|
+
}) => {
|
|
30
|
+
const requestArgs = getMessageRequestArgs({ conversation_id, messageId })
|
|
31
|
+
return getRequestQueryKey(requestArgs)
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getRequestQueryKey } from '../../hooks/use_suspense_api'
|
|
2
|
+
import { getMessageFields, getMessagesInclude } from './messages_data_options'
|
|
3
|
+
|
|
4
|
+
export const getMessagesRequestArgs = ({
|
|
5
|
+
conversation_id,
|
|
6
|
+
reply_root_id,
|
|
7
|
+
}: {
|
|
8
|
+
conversation_id: number
|
|
9
|
+
reply_root_id?: string | null
|
|
10
|
+
}) => {
|
|
11
|
+
const url = reply_root_id
|
|
12
|
+
? `/me/conversations/${conversation_id}/messages/${reply_root_id}/replies`
|
|
13
|
+
: `/me/conversations/${conversation_id}/messages`
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
url,
|
|
17
|
+
data: {
|
|
18
|
+
perPage: 25,
|
|
19
|
+
fields: getMessageFields,
|
|
20
|
+
include: getMessagesInclude,
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const getMessagesQueryKey = ({
|
|
26
|
+
conversation_id,
|
|
27
|
+
reply_root_id,
|
|
28
|
+
}: {
|
|
29
|
+
conversation_id: number
|
|
30
|
+
reply_root_id?: string | null
|
|
31
|
+
}) => {
|
|
32
|
+
const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
33
|
+
return getRequestQueryKey(requestArgs)
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const getMessageFields = {
|
|
2
|
+
Message: [
|
|
3
|
+
'text',
|
|
4
|
+
'text_edited_at',
|
|
5
|
+
'mine',
|
|
6
|
+
'attachments',
|
|
7
|
+
'created_at',
|
|
8
|
+
'deleted_at',
|
|
9
|
+
'author',
|
|
10
|
+
'reaction_counts',
|
|
11
|
+
'reply_count',
|
|
12
|
+
'reply_root',
|
|
13
|
+
],
|
|
14
|
+
Person: ['name', 'avatar'],
|
|
15
|
+
ReactionCount: ['value', 'count', 'mine', 'message_id', 'author_ids'],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const getMessagesInclude = ['author', 'reaction_counts']
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { MessageResource } from '../../types';
|
|
3
|
-
interface ShadowMessageProps extends MessageResource {
|
|
4
|
-
conversation_id: number;
|
|
5
|
-
}
|
|
6
|
-
export declare function ShadowMessage({ conversation_id, ...message }: ShadowMessageProps): React.JSX.Element;
|
|
7
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=shadow_message.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shadow_message.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/shadow_message.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAkB7C,UAAU,kBAAmB,SAAQ,eAAe;IAClD,eAAe,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,aAAa,CAAC,EAAE,eAAe,EAAE,GAAG,OAAO,EAAE,EAAE,kBAAkB,qBAsEhF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shadow_message.js","sourceRoot":"","sources":["../../../src/components/conversation/shadow_message.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react'\nimport { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'\nimport { Avatar, Icon, IconProps, Image, Text } from '../display'\nimport {\n useAnimatedMessageBackgroundColor,\n useFontScale,\n useScalableNumberOfLines,\n useTheme,\n} from '../../hooks'\nimport { MessageResource } from '../../types'\nimport {\n DenormalizedAttachmentResource,\n DenormalizedGiphyAttachmentResource,\n DenormalizedExpandedLinkAttachmentResource,\n DenormalizedMessageAttachmentResource,\n} from '../../types/resources/denormalized_attachment_resource'\nimport {\n CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n MAX_FONT_SIZE_MULTIPLIER,\n MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,\n platformFontWeightMedium,\n} from '../../utils/styles'\nimport Animated from 'react-native-reanimated'\nimport { TheirReplyConnector, MyReplyConnector } from './reply_connectors'\nimport { assertKeysAreNumbers } from '../../utils'\nimport { useNavigation } from '@react-navigation/native'\n\ninterface ShadowMessageProps extends MessageResource {\n conversation_id: number\n}\n\nexport function ShadowMessage({ conversation_id, ...message }: ShadowMessageProps) {\n const { text } = message\n const styles = useStyles(message)\n const { colors } = useTheme()\n const navigation = useNavigation()\n\n const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)\n const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =\n useAnimatedMessageBackgroundColor()\n const scalableNumberOfLines = useScalableNumberOfLines(2)\n\n const handleNavigateToReplies = () => {\n navigation.navigate('ConversationReply', {\n conversation_id,\n reply_root_id: message.id,\n // TODO: Add a way to pass the reply root author's name\n })\n }\n\n return (\n <Pressable\n android_ripple={{ color: colors.androidRippleNeutral }}\n onPress={handleNavigateToReplies}\n onPressIn={handleMessagePressIn}\n onPressOut={handleMessagePressOut}\n accessibilityHint=\"Navigate to reply screen for this message\"\n accessibilityRole=\"link\"\n >\n <Animated.View style={[styles.message, animatedBackgroundColor]}>\n {!message.mine && (\n <View>\n <View style={styles.avatarWrapper}>\n <Avatar\n size=\"xs\"\n sourceUri={message.author.avatar}\n style={styles.avatar}\n maxFontSizeMultiplier={1}\n />\n </View>\n <TheirReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />\n </View>\n )}\n <View style={styles.messageContent}>\n <View\n style={styles.messageBubble}\n onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}\n >\n <MessageAttachmentImagery attachments={message.attachments} />\n {text && (\n <Text\n variant=\"footnote\"\n style={styles.messageText}\n numberOfLines={scalableNumberOfLines}\n >\n {text}\n </Text>\n )}\n </View>\n <View style={styles.messageMeta}>\n <Text variant=\"footnote\" style={styles.replyCountText}>\n {message.replyCount} replies\n </Text>\n </View>\n </View>\n {message.mine && (\n <MyReplyConnector message={message} messageBubbleHeight={messageBubbleHeight} />\n )}\n </Animated.View>\n </Pressable>\n )\n}\n\nfunction MessageAttachmentImagery({\n attachments,\n}: {\n attachments: DenormalizedAttachmentResource[]\n}) {\n if (!attachments || attachments.length === 0) return null\n\n const attachment = attachments[0]\n\n if (attachment.type === 'giphy') {\n return <GiphyImage attachment={attachment} />\n }\n if (attachment.type === 'ExpandedLink') {\n return <ExpandedLinkImage attachment={attachment} />\n }\n if (attachment.type === 'MessageAttachment') {\n const contentType = attachment.attributes?.contentType\n const basicType = contentType?.split('/')[0]\n\n switch (basicType) {\n case 'image':\n return <MessageAttachmentImage attachment={attachment} />\n case 'video':\n return <MessageAttachmentIcon iconName=\"general.outlinedVideoFile\" />\n case 'audio':\n return <MessageAttachmentIcon iconName=\"general.outlinedMusicFile\" />\n case 'application':\n return <MessageAttachmentIcon iconName=\"general.outlinedGenericFile\" />\n default:\n return null\n }\n }\n\n return null\n}\n\nfunction GiphyImage({ attachment }: { attachment: DenormalizedGiphyAttachmentResource }) {\n const { title, giphy } = attachment\n const { url } = giphy.fixedWidth\n const { width, height } = assertKeysAreNumbers(giphy.fixedWidth)\n const styles = useStyles({ imageWidth: width, imageHeight: height })\n\n return (\n <Image\n source={{ uri: url }}\n wrapperStyle={styles.imageWrapper}\n style={styles.image}\n alt={title}\n loaderSize={16}\n />\n )\n}\n\nfunction ExpandedLinkImage({\n attachment,\n}: {\n attachment: DenormalizedExpandedLinkAttachmentResource\n}) {\n const { title = '', imageUrl, imageHeight, imageWidth } = attachment.attributes\n const styles = useStyles({ imageWidth, imageHeight })\n\n return (\n <Image\n source={{ uri: imageUrl }}\n wrapperStyle={styles.imageWrapper}\n style={styles.image}\n alt={title}\n loaderSize={16}\n />\n )\n}\n\nfunction MessageAttachmentImage({\n attachment,\n}: {\n attachment: DenormalizedMessageAttachmentResource\n}) {\n const { url, urlMedium, filename, metadata = {} } = attachment.attributes\n const styles = useStyles({ imageWidth: metadata.width, imageHeight: metadata.height })\n\n return (\n <Image\n source={{ uri: urlMedium || url }}\n style={styles.image}\n wrapperStyle={styles.imageWrapper}\n alt={filename}\n loaderSize={16}\n />\n )\n}\n\nfunction MessageAttachmentIcon({ iconName }: { iconName: IconProps['name'] }) {\n const styles = useStyles()\n return (\n <Icon\n name={iconName}\n style={styles.attachmentIcon}\n maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}\n />\n )\n}\n\ninterface StylesProps {\n imageWidth?: number\n imageHeight?: number\n mine?: boolean\n}\n\nconst useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}) => {\n const { colors } = useTheme()\n const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER })\n const { width } = useWindowDimensions()\n const tabletWidth = width >= 744 // Smallest iPad Mini's width\n\n return StyleSheet.create({\n message: {\n gap: 8,\n flexDirection: mine ? 'row-reverse' : 'row',\n paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n },\n messageContent: {\n flex: 1,\n gap: 4,\n marginBottom: 12,\n },\n avatarWrapper: {\n width: MESSAGE_AUTHOR_AVATAR_COLUMN_WIDTH,\n alignItems: 'center',\n },\n avatar: {\n marginBottom: 8,\n opacity: 0.5,\n },\n messageBubble: {\n flexDirection: 'row',\n alignSelf: mine ? 'flex-end' : 'flex-start',\n alignItems: 'center',\n gap: 8,\n borderColor: colors.borderColorDefaultBase,\n borderWidth: 1,\n borderRadius: 8,\n maxWidth: tabletWidth ? 360 : '80%',\n paddingVertical: 6,\n paddingHorizontal: 8,\n },\n messageText: {\n color: colors.textColorDefaultPlaceholder,\n flexShrink: 1,\n },\n messageMeta: {\n flexDirection: 'row',\n justifyContent: mine ? 'flex-end' : 'flex-start',\n },\n replyCountText: {\n color: colors.interaction,\n fontWeight: platformFontWeightMedium,\n },\n imageWrapper: {\n width: 32 * fontScale,\n aspectRatio: imageWidth / imageHeight,\n opacity: 0.5,\n },\n image: {\n borderRadius: 4,\n },\n attachmentIcon: {\n color: colors.iconColorDefaultDim,\n fontSize: 16,\n },\n })\n}\n"]}
|