@planningcenter/chat-react-native 3.44.1-qa-876.0 → 3.44.1-qa-staging.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/giphy_attachment.js +2 -2
- package/build/components/conversation/attachments/giphy_attachment.js.map +1 -1
- package/build/components/conversation/attachments/image_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/image_attachment.js +14 -15
- package/build/components/conversation/attachments/image_attachment.js.map +1 -1
- package/build/components/conversation/attachments/video_attachment.d.ts.map +1 -1
- package/build/components/conversation/attachments/video_attachment.js +1 -2
- package/build/components/conversation/attachments/video_attachment.js.map +1 -1
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +4 -9
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_form.d.ts.map +1 -1
- package/build/components/conversation/message_form.js +6 -10
- package/build/components/conversation/message_form.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.d.ts.map +1 -1
- package/build/components/conversation/reply_shadow_message.js +6 -11
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/components/display/badge.d.ts.map +1 -1
- package/build/components/display/badge.js +3 -16
- package/build/components/display/badge.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +0 -1
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +2 -10
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +25 -24
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/utils/index.d.ts +0 -1
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +0 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/message_text.d.ts +3 -0
- package/build/utils/message_text.d.ts.map +1 -0
- package/build/utils/message_text.js +8 -0
- package/build/utils/message_text.js.map +1 -0
- package/package.json +3 -3
- package/src/components/conversation/attachments/giphy_attachment.tsx +2 -2
- package/src/components/conversation/attachments/image_attachment.tsx +26 -30
- package/src/components/conversation/attachments/video_attachment.tsx +1 -2
- package/src/components/conversation/message.tsx +4 -9
- package/src/components/conversation/message_form.tsx +8 -17
- package/src/components/conversation/reply_shadow_message.tsx +6 -11
- package/src/components/display/badge.tsx +2 -20
- package/src/hooks/use_conversation_messages.ts +2 -10
- package/src/screens/conversation_screen.tsx +30 -26
- package/src/utils/__tests__/message_text.test.ts +45 -0
- package/src/utils/index.ts +0 -1
- package/src/utils/message_text.ts +9 -0
- package/build/hooks/use_conversation_layout_diagnostics.d.ts +0 -21
- package/build/hooks/use_conversation_layout_diagnostics.d.ts.map +0 -1
- package/build/hooks/use_conversation_layout_diagnostics.js +0 -99
- package/build/hooks/use_conversation_layout_diagnostics.js.map +0 -1
- package/build/utils/aspect_ratio.d.ts +0 -2
- package/build/utils/aspect_ratio.d.ts.map +0 -1
- package/build/utils/aspect_ratio.js +0 -11
- package/build/utils/aspect_ratio.js.map +0 -1
- package/src/__tests__/hooks/use_conversation_layout_diagnostics.test.tsx +0 -142
- package/src/hooks/use_conversation_layout_diagnostics.ts +0 -123
- package/src/utils/__tests__/aspect_ratio.test.ts +0 -50
- package/src/utils/aspect_ratio.ts +0 -14
package/build/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA;AACzC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,iCAAiC,CAAA;AAC/C,cAAc,sBAAsB,CAAA;AACpC,cAAc,WAAW,CAAA;AACzB,cAAc,UAAU,CAAA;AACxB,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,cAAc,OAAO,CAAA","sourcesContent":["export * from './assert_keys_are_numbers'\nexport * from './attachment_kind'\nexport * from './chat_type_label'\nexport * from './cache'\nexport * from './client'\nexport * from './convert_attachments_for_create'\nexport * from './destructure_chat_group_graph_id'\nexport * from './host_bridge'\nexport * from './liquid_glass'\nexport * from './native_adapters'\nexport * from './pluralize'\nexport * from './conversation_safety_constants'\nexport * from './reaction_constants'\nexport * from './session'\nexport * from './styles'\nexport * from './system_messages'\nexport * from './theme'\nexport * from './uri'\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message_text.d.ts","sourceRoot":"","sources":["../../src/utils/message_text.ts"],"names":[],"mappings":"AAEA,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,UAEpD;AAED,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,WAElD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const EMPTY_TEXT_CHARACTERS = /\uFFFC/g;
|
|
2
|
+
export function stripEmptyTextCharacters(text) {
|
|
3
|
+
return text.replace(EMPTY_TEXT_CHARACTERS, '');
|
|
4
|
+
}
|
|
5
|
+
export function hasMessageText(text) {
|
|
6
|
+
return Boolean(text && stripEmptyTextCharacters(text).trim());
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=message_text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message_text.js","sourceRoot":"","sources":["../../src/utils/message_text.ts"],"names":[],"mappings":"AAAA,MAAM,qBAAqB,GAAG,SAAS,CAAA;AAEvC,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,OAAO,OAAO,CAAC,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;AAC/D,CAAC","sourcesContent":["const EMPTY_TEXT_CHARACTERS = /\\uFFFC/g\n\nexport function stripEmptyTextCharacters(text: string) {\n return text.replace(EMPTY_TEXT_CHARACTERS, '')\n}\n\nexport function hasMessageText(text?: string | null) {\n return Boolean(text && stripEmptyTextCharacters(text).trim())\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.44.1-qa-
|
|
3
|
+
"version": "3.44.1-qa-staging.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "./src/index.tsx",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
|
28
28
|
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
|
29
|
-
"@planningcenter/emoji-keyboard": "3.44.1-qa-
|
|
29
|
+
"@planningcenter/emoji-keyboard": "3.44.1-qa-staging.2",
|
|
30
30
|
"lodash-inflection": "^1.5.0",
|
|
31
31
|
"react-compiler-runtime": "^1.0.0"
|
|
32
32
|
},
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"sf-symbols-typescript": "^2.2.0",
|
|
74
74
|
"typescript": "~6.0.3"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "28a566e774a2e108f2583106d51596724c376e37"
|
|
77
77
|
}
|
|
@@ -3,7 +3,7 @@ import React from 'react'
|
|
|
3
3
|
import { Text, StyleSheet, Linking } from 'react-native'
|
|
4
4
|
import { useTheme } from '../../../hooks'
|
|
5
5
|
import { DenormalizedGiphyAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
6
|
-
import { assertKeysAreNumbers
|
|
6
|
+
import { assertKeysAreNumbers } from '../../../utils'
|
|
7
7
|
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
8
8
|
import { Image } from '../../display'
|
|
9
9
|
|
|
@@ -55,7 +55,7 @@ const useStyles = ({ imageWidth, imageHeight }: { imageWidth: number; imageHeigh
|
|
|
55
55
|
},
|
|
56
56
|
imageWrapper: {
|
|
57
57
|
width: '100%',
|
|
58
|
-
aspectRatio:
|
|
58
|
+
aspectRatio: imageWidth / imageHeight,
|
|
59
59
|
},
|
|
60
60
|
image: {
|
|
61
61
|
borderRadius: 8,
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
Modal,
|
|
8
8
|
View,
|
|
9
9
|
Linking,
|
|
10
|
+
Dimensions,
|
|
10
11
|
NativeSyntheticEvent,
|
|
11
12
|
NativeScrollEvent,
|
|
12
|
-
useWindowDimensions,
|
|
13
13
|
ViewToken,
|
|
14
14
|
} from 'react-native'
|
|
15
15
|
import {
|
|
@@ -30,12 +30,13 @@ import Animated, {
|
|
|
30
30
|
import { useSafeAreaInsets, SafeAreaProvider } from 'react-native-safe-area-context'
|
|
31
31
|
import { useTheme } from '../../../hooks'
|
|
32
32
|
import { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
33
|
-
import { Haptic, platformFontWeightMedium
|
|
33
|
+
import { Haptic, platformFontWeightMedium } from '../../../utils'
|
|
34
34
|
import { formatDatePreview } from '../../../utils/date'
|
|
35
35
|
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
36
36
|
import { IconButton, Image, Heading, Text } from '../../display'
|
|
37
37
|
import { AttachmentDeletingOverlay } from './attachment_deleting_overlay'
|
|
38
38
|
|
|
39
|
+
const { width: WINDOW_WIDTH, height: WINDOW_HEIGHT } = Dimensions.get('window')
|
|
39
40
|
const DISMISS_PAN_THRESHOLD = 175
|
|
40
41
|
const MIN_DISTANCE_FOR_PAN = 10 // Higher threshold gives pinching priority
|
|
41
42
|
const SINGLE_FINGER_POINTER = 1 // Single-finger panning / tapping helps to avoid conflicts with pinching
|
|
@@ -166,7 +167,6 @@ const LightboxGallery = ({
|
|
|
166
167
|
}: LightboxGalleryProps) => {
|
|
167
168
|
const styles = useStyles()
|
|
168
169
|
const insets = useSafeAreaInsets()
|
|
169
|
-
const { width: windowWidth, height: windowHeight } = useWindowDimensions()
|
|
170
170
|
const [currentImageIndex, setCurrentImageIndex] = useState(initialImageIndex)
|
|
171
171
|
const { soft: triggerMaxZoomHaptic, rigid: triggerDismissHaptic } = Haptic
|
|
172
172
|
|
|
@@ -177,8 +177,8 @@ const LightboxGallery = ({
|
|
|
177
177
|
const imageHeight = metadata.height
|
|
178
178
|
|
|
179
179
|
// Calculate available space for image display
|
|
180
|
-
const availableWindowWidth =
|
|
181
|
-
const availableWindowHeight =
|
|
180
|
+
const availableWindowWidth = WINDOW_WIDTH
|
|
181
|
+
const availableWindowHeight = WINDOW_HEIGHT - insets.top - insets.bottom
|
|
182
182
|
|
|
183
183
|
/* ============================
|
|
184
184
|
ANIMATION VALUES
|
|
@@ -299,8 +299,8 @@ const LightboxGallery = ({
|
|
|
299
299
|
'worklet'
|
|
300
300
|
|
|
301
301
|
// How far the focal point is from the center of the window
|
|
302
|
-
const windowCenterX =
|
|
303
|
-
const windowCenterY =
|
|
302
|
+
const windowCenterX = WINDOW_WIDTH / 2
|
|
303
|
+
const windowCenterY = WINDOW_HEIGHT / 2
|
|
304
304
|
|
|
305
305
|
// Position of focal point relative to current image position
|
|
306
306
|
const focalRelativeX = focalPointX - windowCenterX - currentSavedTranslateX
|
|
@@ -402,32 +402,29 @@ const LightboxGallery = ({
|
|
|
402
402
|
// Used in tandem with FlatList's initialScrollIndex to quickly calculate the position and size of each image before they load.
|
|
403
403
|
const getItemLayout = useCallback(
|
|
404
404
|
(_: unknown, index: number) => ({
|
|
405
|
-
length:
|
|
406
|
-
offset:
|
|
405
|
+
length: WINDOW_WIDTH,
|
|
406
|
+
offset: WINDOW_WIDTH * index,
|
|
407
407
|
index,
|
|
408
408
|
}),
|
|
409
|
-
[
|
|
409
|
+
[]
|
|
410
410
|
)
|
|
411
411
|
|
|
412
412
|
// Captures the current image's index after the FlatList finishes its scroll animation.
|
|
413
413
|
// Used in tandem with onViewableItemsChanged to ensure the final value for currentImageIndex is set.
|
|
414
|
-
const onMomentumScrollEnd = useCallback(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
},
|
|
429
|
-
[windowWidth]
|
|
430
|
-
)
|
|
414
|
+
const onMomentumScrollEnd = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
415
|
+
// Calculate the index of the image that is currently visible
|
|
416
|
+
const imageOffsetX = event.nativeEvent.contentOffset.x
|
|
417
|
+
const newImageIndex = Math.round(imageOffsetX / WINDOW_WIDTH)
|
|
418
|
+
|
|
419
|
+
// Check if the image index has changed and the FlatList didn't scroll past the first or last image
|
|
420
|
+
const didImageIndexChange = newImageIndex !== stableCurrentImageIndexRef.current
|
|
421
|
+
const isImageIndexWithinBounds =
|
|
422
|
+
newImageIndex >= 0 && newImageIndex < stableImageAttachmentsLengthRef.current
|
|
423
|
+
|
|
424
|
+
if (didImageIndexChange && isImageIndexWithinBounds) {
|
|
425
|
+
setCurrentImageIndex(newImageIndex)
|
|
426
|
+
}
|
|
427
|
+
}, [])
|
|
431
428
|
|
|
432
429
|
// Supplements onMomentumScrollEnd by capturing the current image's index while the FlatList is actively scrolling.
|
|
433
430
|
// Used in tandem with viewabilityConfig to trigger when the image is 50% visible in the window.
|
|
@@ -932,7 +929,6 @@ interface UseStylesProps {
|
|
|
932
929
|
|
|
933
930
|
const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {}) => {
|
|
934
931
|
const { top, bottom } = useSafeAreaInsets()
|
|
935
|
-
const { width: windowWidth } = useWindowDimensions()
|
|
936
932
|
const backgroundColor = tokens.colorNeutral7
|
|
937
933
|
const transparentBackgroundColor = useMemo(
|
|
938
934
|
() => colorFunction(backgroundColor).alpha(0.8).toString(),
|
|
@@ -950,7 +946,7 @@ const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {})
|
|
|
950
946
|
attachmentImageWrapper: {
|
|
951
947
|
width: '100%',
|
|
952
948
|
minWidth: 200,
|
|
953
|
-
aspectRatio:
|
|
949
|
+
aspectRatio: imageWidth / imageHeight,
|
|
954
950
|
},
|
|
955
951
|
image: {
|
|
956
952
|
borderRadius: 8,
|
|
@@ -964,7 +960,7 @@ const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {})
|
|
|
964
960
|
},
|
|
965
961
|
gestureImage: {
|
|
966
962
|
height: '100%',
|
|
967
|
-
width:
|
|
963
|
+
width: WINDOW_WIDTH,
|
|
968
964
|
backgroundColor: 'transparent',
|
|
969
965
|
},
|
|
970
966
|
gestureImageLoading: {
|
|
@@ -3,7 +3,6 @@ import React, { useRef, useState } from 'react'
|
|
|
3
3
|
import { View, StyleSheet } from 'react-native'
|
|
4
4
|
import { useTheme } from '../../../hooks'
|
|
5
5
|
import { DenormalizedMessageAttachmentResource } from '../../../types/resources/denormalized_attachment_resource'
|
|
6
|
-
import { safeAspectRatio } from '../../../utils'
|
|
7
6
|
import { Video, VideoPlayerHandle } from '../../../utils/native_adapters'
|
|
8
7
|
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
9
8
|
import { Icon } from '../../display'
|
|
@@ -59,7 +58,7 @@ export function VideoAttachment({
|
|
|
59
58
|
<Video.Player
|
|
60
59
|
ref={videoRef}
|
|
61
60
|
source={{ uri: url }}
|
|
62
|
-
aspectRatio={
|
|
61
|
+
aspectRatio={width / height}
|
|
63
62
|
style={styles.video}
|
|
64
63
|
onFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss}
|
|
65
64
|
/>
|
|
@@ -17,8 +17,6 @@ import {
|
|
|
17
17
|
useTheme,
|
|
18
18
|
} from '../../hooks'
|
|
19
19
|
import { useDeletingIds } from '../../hooks'
|
|
20
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
21
|
-
import { useLayoutWidthProbe } from '../../hooks/use_conversation_layout_diagnostics'
|
|
22
20
|
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
23
21
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
24
22
|
import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
@@ -27,6 +25,7 @@ import { DenormalizedMessageAttachmentResource } from '../../types/resources/den
|
|
|
27
25
|
import { ReactionCountResource } from '../../types/resources/reaction'
|
|
28
26
|
import { pluralize } from '../../utils'
|
|
29
27
|
import { isNewMessage } from '../../utils/cache/messages_cache'
|
|
28
|
+
import { stripEmptyTextCharacters } from '../../utils/message_text'
|
|
30
29
|
import { Haptic } from '../../utils/native_adapters'
|
|
31
30
|
import {
|
|
32
31
|
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
@@ -56,7 +55,8 @@ export function Message({
|
|
|
56
55
|
inReplyScreen,
|
|
57
56
|
...message
|
|
58
57
|
}: MessageProps) {
|
|
59
|
-
const {
|
|
58
|
+
const { reactionCounts, pending, error, attachments, author } = message
|
|
59
|
+
const text = stripEmptyTextCharacters(message.text ?? '')
|
|
60
60
|
const deletingMessageIds = useDeletingIds('deleteMessage')
|
|
61
61
|
const isDeleting = deletingMessageIds.has(message.id)
|
|
62
62
|
const styles = useMessageStyles(message.mine, isDeleting)
|
|
@@ -71,8 +71,6 @@ export function Message({
|
|
|
71
71
|
const isPersisted = !isNewMessage(message)
|
|
72
72
|
const [temporarilyHidePendingState, setTemporarilyHidePendingState] = React.useState(true)
|
|
73
73
|
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
74
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
75
|
-
const onMessageBubbleLayout = useLayoutWidthProbe('message.bubble', 0)
|
|
76
74
|
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
77
75
|
useAnimatedMessageBackgroundColor()
|
|
78
76
|
const { message: replyRootMessage } = useConversationMessage({
|
|
@@ -235,10 +233,7 @@ export function Message({
|
|
|
235
233
|
)}
|
|
236
234
|
<View
|
|
237
235
|
style={styles.messageBubble}
|
|
238
|
-
onLayout={e =>
|
|
239
|
-
setMessageBubbleHeight(e.nativeEvent.layout.height)
|
|
240
|
-
onMessageBubbleLayout?.(e)
|
|
241
|
-
}}
|
|
236
|
+
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
242
237
|
>
|
|
243
238
|
{isDeletedReplyRootMessage ? (
|
|
244
239
|
<View style={styles.messageText}>
|
|
@@ -24,8 +24,6 @@ import {
|
|
|
24
24
|
import { useAttachmentUploader } from '../../hooks/use_attachment_uploader'
|
|
25
25
|
import { useBroadcastTypingStatus } from '../../hooks/use_broadcast_typing_status'
|
|
26
26
|
import { useChatConfiguration } from '../../hooks/use_chat_configuration'
|
|
27
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
28
|
-
import { useLayoutWidthProbe } from '../../hooks/use_conversation_layout_diagnostics'
|
|
29
27
|
import { useMessageCreateOrUpdate } from '../../hooks/use_message_create_or_update'
|
|
30
28
|
import { useMessageDraft } from '../../hooks/use_message_draft'
|
|
31
29
|
import { ConversationScreenProps } from '../../screens/conversation_screen'
|
|
@@ -43,6 +41,7 @@ import {
|
|
|
43
41
|
import { pickAttachmentPreviewKind } from '../../utils/attachment_kind'
|
|
44
42
|
import { canSubmitMessage } from '../../utils/can_submit_message'
|
|
45
43
|
import { isInputEditable } from '../../utils/is_input_editable'
|
|
44
|
+
import { hasMessageText, stripEmptyTextCharacters } from '../../utils/message_text'
|
|
46
45
|
import {
|
|
47
46
|
DocumentPicker,
|
|
48
47
|
DocumentPickerResult,
|
|
@@ -195,7 +194,7 @@ function MessageFormRoot({
|
|
|
195
194
|
conversationDisabled: conversation.disabled ?? false,
|
|
196
195
|
pendingUploads: !!attachmentUploader?.pendingUploads,
|
|
197
196
|
hasFlaggedAttachments: !!attachmentUploader?.flaggedAttachmentCount,
|
|
198
|
-
hasText: text
|
|
197
|
+
hasText: hasMessageText(text),
|
|
199
198
|
hasSendableAttachments: !!attachmentUploader?.attachmentIds?.length,
|
|
200
199
|
})
|
|
201
200
|
const disabled = !canSubmit
|
|
@@ -243,11 +242,13 @@ function MessageFormRoot({
|
|
|
243
242
|
file: attachment.file,
|
|
244
243
|
})
|
|
245
244
|
)
|
|
245
|
+
const messageText = stripEmptyTextCharacters(text)
|
|
246
|
+
|
|
246
247
|
if (currentlyEditingMessage) {
|
|
247
|
-
mutateAsync({ text })
|
|
248
|
+
mutateAsync({ text: messageText })
|
|
248
249
|
reset()
|
|
249
250
|
} else {
|
|
250
|
-
mutateAsync({ text, attachments: attachmentsForSubmit })
|
|
251
|
+
mutateAsync({ text: messageText, attachments: attachmentsForSubmit })
|
|
251
252
|
reset()
|
|
252
253
|
}
|
|
253
254
|
}
|
|
@@ -432,11 +433,6 @@ function MessageFormInput() {
|
|
|
432
433
|
|
|
433
434
|
const broadcastTypingStatus = useBroadcastTypingStatus()
|
|
434
435
|
|
|
435
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
436
|
-
const onTextInputBoundaryLayout = useLayoutWidthProbe('form.textInputBoundary', 2)
|
|
437
|
-
const onInputErrorMessageLayout = useLayoutWidthProbe('form.errorText', 32)
|
|
438
|
-
const onTextInputLayout = useLayoutWidthProbe('form.textInput', 32)
|
|
439
|
-
|
|
440
436
|
const handleTextChange = (newText: string) => {
|
|
441
437
|
setText(newText)
|
|
442
438
|
|
|
@@ -455,13 +451,9 @@ function MessageFormInput() {
|
|
|
455
451
|
}
|
|
456
452
|
|
|
457
453
|
return (
|
|
458
|
-
<View style={styles.textInputBoundary}
|
|
454
|
+
<View style={styles.textInputBoundary}>
|
|
459
455
|
<MessageFormAttachments />
|
|
460
|
-
{attachmentError ?
|
|
461
|
-
<Text style={styles.inputErrorMessage} onLayout={onInputErrorMessageLayout}>
|
|
462
|
-
{attachmentError}
|
|
463
|
-
</Text>
|
|
464
|
-
) : null}
|
|
456
|
+
{attachmentError ? <Text style={styles.inputErrorMessage}>{attachmentError}</Text> : null}
|
|
465
457
|
<View style={styles.textInputRow}>
|
|
466
458
|
{usingGiphy ? (
|
|
467
459
|
<View style={styles.giphyBadge}>
|
|
@@ -483,7 +475,6 @@ function MessageFormInput() {
|
|
|
483
475
|
onSubmitEditing={onSubmit}
|
|
484
476
|
submitBehavior={usingGiphy ? 'submit' : 'newline'}
|
|
485
477
|
editable={inputEditable}
|
|
486
|
-
onLayout={onTextInputLayout}
|
|
487
478
|
/>
|
|
488
479
|
</View>
|
|
489
480
|
</View>
|
|
@@ -9,8 +9,6 @@ import {
|
|
|
9
9
|
useScalableNumberOfLines,
|
|
10
10
|
useTheme,
|
|
11
11
|
} from '../../hooks'
|
|
12
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
13
|
-
import { useLayoutWidthProbe } from '../../hooks/use_conversation_layout_diagnostics'
|
|
14
12
|
import { useConversationMessage } from '../../hooks/use_conversation_message'
|
|
15
13
|
import { useLiveRelativeTime } from '../../hooks/use_live_relative_time'
|
|
16
14
|
import { MessageResource } from '../../types'
|
|
@@ -20,7 +18,8 @@ import {
|
|
|
20
18
|
DenormalizedExpandedLinkAttachmentResource,
|
|
21
19
|
DenormalizedMessageAttachmentResource,
|
|
22
20
|
} from '../../types/resources/denormalized_attachment_resource'
|
|
23
|
-
import { assertKeysAreNumbers, pluralize
|
|
21
|
+
import { assertKeysAreNumbers, pluralize } from '../../utils'
|
|
22
|
+
import { stripEmptyTextCharacters } from '../../utils/message_text'
|
|
24
23
|
import {
|
|
25
24
|
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
26
25
|
MAX_FONT_SIZE_MULTIPLIER,
|
|
@@ -65,15 +64,14 @@ interface ShadowMessageContentProps extends MessageResource {
|
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageContentProps) {
|
|
68
|
-
const {
|
|
67
|
+
const { deletedAt, author, attachments } = message
|
|
68
|
+
const text = stripEmptyTextCharacters(message.text ?? '')
|
|
69
69
|
const styles = useStyles(message)
|
|
70
70
|
const { colors } = useTheme()
|
|
71
71
|
const navigation = useNavigation()
|
|
72
72
|
const timestamp = useLiveRelativeTime(message.createdAt)
|
|
73
73
|
|
|
74
74
|
const [messageBubbleHeight, setMessageBubbleHeight] = React.useState(0)
|
|
75
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
76
|
-
const onMessageBubbleLayout = useLayoutWidthProbe('replyShadow.bubble', 18)
|
|
77
75
|
const { animatedBackgroundColor, handleMessagePressIn, handleMessagePressOut } =
|
|
78
76
|
useAnimatedMessageBackgroundColor()
|
|
79
77
|
const scalableNumberOfLines = useScalableNumberOfLines(2)
|
|
@@ -123,10 +121,7 @@ function ShadowMessageContent({ conversation_id, ...message }: ShadowMessageCont
|
|
|
123
121
|
<View style={styles.messageContent}>
|
|
124
122
|
<View
|
|
125
123
|
style={styles.messageBubble}
|
|
126
|
-
onLayout={e =>
|
|
127
|
-
setMessageBubbleHeight(e.nativeEvent.layout.height)
|
|
128
|
-
onMessageBubbleLayout?.(e)
|
|
129
|
-
}}
|
|
124
|
+
onLayout={e => setMessageBubbleHeight(e.nativeEvent.layout.height)}
|
|
130
125
|
>
|
|
131
126
|
<MessageAttachmentImagery attachments={attachments} />
|
|
132
127
|
{text && (
|
|
@@ -331,7 +326,7 @@ const useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}
|
|
|
331
326
|
},
|
|
332
327
|
imageWrapper: {
|
|
333
328
|
width: 32 * fontScale,
|
|
334
|
-
aspectRatio:
|
|
329
|
+
aspectRatio: imageWidth / imageHeight,
|
|
335
330
|
opacity: 0.5,
|
|
336
331
|
},
|
|
337
332
|
image: {
|
|
@@ -3,8 +3,6 @@ import type { TextStyle, ViewProps, ViewStyle } from 'react-native'
|
|
|
3
3
|
import { Pressable, StyleSheet, View } from 'react-native'
|
|
4
4
|
import Svg, { Defs, LinearGradient, Path, Stop } from 'react-native-svg'
|
|
5
5
|
import { useFontScale, useTheme } from '../../hooks'
|
|
6
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
7
|
-
import { useLayoutWidthProbe } from '../../hooks/use_conversation_layout_diagnostics'
|
|
8
6
|
import { platformFontWeightMedium } from '../../utils'
|
|
9
7
|
import { tokens } from '../../vendor/tapestry/tokens'
|
|
10
8
|
import { Icon, IconString } from './icon'
|
|
@@ -109,14 +107,6 @@ export function Badge({
|
|
|
109
107
|
}: BadgeProps) {
|
|
110
108
|
const styles = useStyles({ appearance, maxFontSizeMultiplier, variant })
|
|
111
109
|
const { showBadgeProductLogo } = useTheme()
|
|
112
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
113
|
-
// Inset is metaLabel's remaining contentInset now that its left offset is
|
|
114
|
-
// a margin, not padding - see the CHAT-476 candidate fix on this style.
|
|
115
|
-
const onLabelLayout = useLayoutWidthProbe('badge.label', 0)
|
|
116
|
-
const onMetaLabelLayout = useLayoutWidthProbe(
|
|
117
|
-
'badge.metaLabel',
|
|
118
|
-
styles.metaLabel.paddingHorizontal * 2
|
|
119
|
-
)
|
|
120
110
|
|
|
121
111
|
const isMetaSubtle = variant === 'metaSubtle'
|
|
122
112
|
const hasMetaLabel = Boolean(metaLabel)
|
|
@@ -137,7 +127,6 @@ export function Badge({
|
|
|
137
127
|
style={styles.label}
|
|
138
128
|
numberOfLines={1}
|
|
139
129
|
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
140
|
-
onLayout={onLabelLayout}
|
|
141
130
|
>
|
|
142
131
|
{badgeLabel}
|
|
143
132
|
</Text>
|
|
@@ -149,7 +138,6 @@ export function Badge({
|
|
|
149
138
|
style={styles.metaLabel}
|
|
150
139
|
numberOfLines={numberOfLinesMetaLabel}
|
|
151
140
|
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
152
|
-
onLayout={onMetaLabelLayout}
|
|
153
141
|
>
|
|
154
142
|
{metaLabel}
|
|
155
143
|
</Text>
|
|
@@ -246,10 +234,7 @@ const useStyles = ({
|
|
|
246
234
|
paddingVertical: variantStylesMap[variant].paddingVertical,
|
|
247
235
|
paddingHorizontal: variantStylesMap[variant].paddingHorizontal,
|
|
248
236
|
backgroundColor: variantStylesMap[variant].backgroundColor,
|
|
249
|
-
|
|
250
|
-
// Neither platform clamps a negative radius; Android's BackgroundDrawable
|
|
251
|
-
// reads it as "no rounded borders" and skips the rounded path entirely.
|
|
252
|
-
borderRadius: Math.max(0, variantStylesMap[variant].borderRadius - 2),
|
|
237
|
+
borderRadius: variantStylesMap[variant].borderRadius - 2,
|
|
253
238
|
},
|
|
254
239
|
icon: {
|
|
255
240
|
color: statusColorMap[appearance].icon,
|
|
@@ -273,10 +258,7 @@ const useStyles = ({
|
|
|
273
258
|
fontSize: badgeFontSize,
|
|
274
259
|
alignSelf: 'center',
|
|
275
260
|
flexShrink: 1,
|
|
276
|
-
|
|
277
|
-
// outside contentInsets - a shrunk Text can never see it as part of
|
|
278
|
-
// its own measured content width, so it can't go negative from this.
|
|
279
|
-
marginLeft: variantStylesMap[variant].metaLabelPaddingLeft,
|
|
261
|
+
paddingLeft: variantStylesMap[variant].metaLabelPaddingLeft,
|
|
280
262
|
},
|
|
281
263
|
})
|
|
282
264
|
}
|
|
@@ -7,7 +7,7 @@ export const useConversationMessages = (
|
|
|
7
7
|
{ conversation_id, reply_root_id }: { conversation_id: number; reply_root_id?: string | null },
|
|
8
8
|
opts?: SuspensePaginatorOptions
|
|
9
9
|
) => {
|
|
10
|
-
const { data, refetch, isRefetching, fetchNextPage, hasNextPage
|
|
10
|
+
const { data, refetch, isRefetching, fetchNextPage, hasNextPage } =
|
|
11
11
|
useSuspensePaginator<MessageResource>(
|
|
12
12
|
getMessagesRequestArgs({ conversation_id, reply_root_id }),
|
|
13
13
|
opts
|
|
@@ -25,13 +25,5 @@ export const useConversationMessages = (
|
|
|
25
25
|
[data]
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
return {
|
|
29
|
-
messages,
|
|
30
|
-
refetch,
|
|
31
|
-
isRefetching,
|
|
32
|
-
fetchNextPage,
|
|
33
|
-
hasNextPage,
|
|
34
|
-
isFetchingNextPage,
|
|
35
|
-
queryKey,
|
|
36
|
-
}
|
|
28
|
+
return { messages, refetch, isRefetching, fetchNextPage, hasNextPage, queryKey }
|
|
37
29
|
}
|
|
@@ -32,11 +32,6 @@ import { useTheme } from '../hooks'
|
|
|
32
32
|
import { useMessageAvailableMembersAction } from '../hooks/direct_messages/use_message_available_members_action'
|
|
33
33
|
import { useConversation } from '../hooks/use_conversation'
|
|
34
34
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
35
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
36
|
-
import {
|
|
37
|
-
useConversationLayoutDiagnostics,
|
|
38
|
-
useLayoutWidthProbe,
|
|
39
|
-
} from '../hooks/use_conversation_layout_diagnostics'
|
|
40
35
|
import { useConversationMessages } from '../hooks/use_conversation_messages'
|
|
41
36
|
import { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events'
|
|
42
37
|
import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'
|
|
@@ -96,14 +91,12 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
96
91
|
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
97
92
|
route.params
|
|
98
93
|
const { data: conversation } = useConversation(route.params)
|
|
99
|
-
const { messages, fetchNextPage, hasNextPage
|
|
94
|
+
const { messages, fetchNextPage, hasNextPage } = useConversationMessages({
|
|
100
95
|
conversation_id,
|
|
101
96
|
reply_root_id,
|
|
102
97
|
})
|
|
103
98
|
useConversationJoltEvents({ conversationId: conversation_id })
|
|
104
99
|
useConversationMessagesJoltEvents({ conversationId: conversation_id })
|
|
105
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
106
|
-
useConversationLayoutDiagnostics({ conversationId: conversation_id, replyRootId: reply_root_id })
|
|
107
100
|
const { markHandledByMessageForm } = useVideoModerationJoltEvents()
|
|
108
101
|
useEnsureConversationsRouteExists()
|
|
109
102
|
useMarkLatestMessageRead({ conversation, messages })
|
|
@@ -133,9 +126,31 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
133
126
|
setShowJumpToBottomButton(offsetY > 200)
|
|
134
127
|
}
|
|
135
128
|
|
|
129
|
+
// iOS starts the sheet's dismiss gesture when the content scroll view sits at its
|
|
130
|
+
// top edge, which for an inverted list is wherever the newest message rests.
|
|
131
|
+
const restAwayFromDismissEdge = useCallback(() => {
|
|
132
|
+
listRef.current?.scrollToOffset({ animated: false, offset: 1 })
|
|
133
|
+
}, [])
|
|
134
|
+
|
|
135
|
+
const didSetInitialOffset = useRef(false)
|
|
136
|
+
const handleContentSizeChange = useCallback(() => {
|
|
137
|
+
if (didSetInitialOffset.current) return
|
|
138
|
+
didSetInitialOffset.current = true
|
|
139
|
+
restAwayFromDismissEdge()
|
|
140
|
+
}, [restAwayFromDismissEdge])
|
|
141
|
+
|
|
142
|
+
// A negative offset means the bounce is still animating; let it settle first.
|
|
143
|
+
const handleScrollSettled = useCallback(
|
|
144
|
+
(event: any) => {
|
|
145
|
+
const offsetY = event.nativeEvent.contentOffset.y
|
|
146
|
+
if (offsetY >= 0 && offsetY < 1) restAwayFromDismissEdge()
|
|
147
|
+
},
|
|
148
|
+
[restAwayFromDismissEdge]
|
|
149
|
+
)
|
|
150
|
+
|
|
136
151
|
const handleReturnToBottom = useCallback(() => {
|
|
137
152
|
listRef.current?.scrollToOffset({
|
|
138
|
-
offset:
|
|
153
|
+
offset: 1,
|
|
139
154
|
})
|
|
140
155
|
}, [])
|
|
141
156
|
|
|
@@ -188,6 +203,9 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
188
203
|
data={messagesWithSeparators}
|
|
189
204
|
keyExtractor={item => item.id}
|
|
190
205
|
onScroll={trackScroll}
|
|
206
|
+
onScrollEndDrag={handleScrollSettled}
|
|
207
|
+
onMomentumScrollEnd={handleScrollSettled}
|
|
208
|
+
onContentSizeChange={handleContentSizeChange}
|
|
191
209
|
scrollEventThrottle={10}
|
|
192
210
|
ListFooterComponent={
|
|
193
211
|
hasNextPage ? null : (
|
|
@@ -223,11 +241,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
223
241
|
/>
|
|
224
242
|
)
|
|
225
243
|
}}
|
|
226
|
-
onEndReached={() =>
|
|
227
|
-
// VirtualizedList dedupes onEndReached per content length, not
|
|
228
|
-
// per fetch, so it can refire while a page is already in flight.
|
|
229
|
-
if (hasNextPage && !isFetchingNextPage) fetchNextPage()
|
|
230
|
-
}}
|
|
244
|
+
onEndReached={() => fetchNextPage()}
|
|
231
245
|
ListHeaderComponent={<View style={styles.listHeader} />}
|
|
232
246
|
/>
|
|
233
247
|
)}
|
|
@@ -300,13 +314,11 @@ function InlineDateSeparator({ date }: DateSeparator) {
|
|
|
300
314
|
const { isThisYear } = getRelativeDateStatus(date)
|
|
301
315
|
const showYear = !isThisYear
|
|
302
316
|
const dateStamp = formatDate(date, { style: 'long', year: showYear })
|
|
303
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
304
|
-
const onDateTextLayout = useLayoutWidthProbe('dateSeparator.text', 16)
|
|
305
317
|
|
|
306
318
|
return (
|
|
307
319
|
<View style={styles.container}>
|
|
308
320
|
<View style={styles.separator} />
|
|
309
|
-
<Text variant="footnote" style={styles.dateText}
|
|
321
|
+
<Text variant="footnote" style={styles.dateText}>
|
|
310
322
|
{dateStamp}
|
|
311
323
|
</Text>
|
|
312
324
|
<View style={styles.separator} />
|
|
@@ -355,14 +367,11 @@ export const ConversationScreenTitle = ({
|
|
|
355
367
|
const resourceType = badge?.pcoResourceType || ''
|
|
356
368
|
const productName = badge?.appName
|
|
357
369
|
const name = badge?.text || undefined
|
|
358
|
-
// CHAT-476 diagnostics - remove with use_conversation_layout_diagnostics.ts
|
|
359
|
-
const onHeaderContainerLayout = useLayoutWidthProbe('header.container', 0)
|
|
360
370
|
|
|
361
371
|
return (
|
|
362
372
|
<PlatformPressable
|
|
363
373
|
accessibilityHint="Opens details about members and settings"
|
|
364
374
|
style={styles.container}
|
|
365
|
-
onLayout={onHeaderContainerLayout}
|
|
366
375
|
onPress={() => {
|
|
367
376
|
if (deleted) return
|
|
368
377
|
|
|
@@ -393,12 +402,7 @@ export const ConversationScreenTitle = ({
|
|
|
393
402
|
const usePressableHeaderStyle = () => {
|
|
394
403
|
const { width } = useWindowDimensions()
|
|
395
404
|
const slotReserve = useHeaderSlotReserve()
|
|
396
|
-
|
|
397
|
-
// Guards narrow windows (split-screen, freeform, DeX) where slotReserve * 2
|
|
398
|
-
// exceeds the available width. A negative maxWidth can hand a descendant
|
|
399
|
-
// <Text> a negative measure width, and android.text.Layout hard-throws on
|
|
400
|
-
// that (`Layout: -2 < 0`) rather than clamping the way iOS does.
|
|
401
|
-
const headerContainerStyle = { maxWidth: Math.max(0, width - slotReserve * 2) }
|
|
405
|
+
const headerContainerStyle = { maxWidth: width - slotReserve * 2 }
|
|
402
406
|
|
|
403
407
|
return StyleSheet.create({
|
|
404
408
|
container: {
|