@planningcenter/chat-react-native 3.44.1-qa-874.0 → 3.44.1-qa-875.0
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 +15 -14
- 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 +2 -1
- package/build/components/conversation/attachments/video_attachment.js.map +1 -1
- package/build/components/conversation/reply_shadow_message.js +2 -2
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/components/display/badge.js +3 -1
- package/build/components/display/badge.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +1 -0
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +10 -2
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +2 -3
- package/build/navigation/index.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +10 -3
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/utils/aspect_ratio.d.ts +2 -0
- package/build/utils/aspect_ratio.d.ts.map +1 -0
- package/build/utils/aspect_ratio.js +10 -0
- package/build/utils/aspect_ratio.js.map +1 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -0
- package/build/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/conversation/attachments/giphy_attachment.tsx +2 -2
- package/src/components/conversation/attachments/image_attachment.tsx +30 -26
- package/src/components/conversation/attachments/video_attachment.tsx +2 -1
- package/src/components/conversation/reply_shadow_message.tsx +2 -2
- package/src/components/display/badge.tsx +3 -1
- package/src/hooks/use_conversation_messages.ts +10 -2
- package/src/navigation/index.tsx +2 -3
- package/src/screens/conversation_screen.tsx +10 -3
- package/src/utils/__tests__/aspect_ratio.test.ts +50 -0
- package/src/utils/aspect_ratio.ts +13 -0
- package/src/utils/index.ts +1 -0
package/build/utils/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA;AACzC,cAAc,gBAAgB,CAAA;AAC9B,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"}
|
package/build/utils/index.js
CHANGED
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,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"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA;AACzC,cAAc,gBAAgB,CAAA;AAC9B,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 './aspect_ratio'\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"]}
|
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-875.0",
|
|
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-875.0",
|
|
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": "4a1e2d11d32c3d6b100d35c0aac5fe8d5fc60a59"
|
|
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 } from '../../../utils'
|
|
6
|
+
import { assertKeysAreNumbers, safeAspectRatio } 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: imageWidth
|
|
58
|
+
aspectRatio: safeAspectRatio(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,
|
|
11
10
|
NativeSyntheticEvent,
|
|
12
11
|
NativeScrollEvent,
|
|
12
|
+
useWindowDimensions,
|
|
13
13
|
ViewToken,
|
|
14
14
|
} from 'react-native'
|
|
15
15
|
import {
|
|
@@ -30,13 +30,12 @@ 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 } from '../../../utils'
|
|
33
|
+
import { Haptic, platformFontWeightMedium, safeAspectRatio } 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')
|
|
40
39
|
const DISMISS_PAN_THRESHOLD = 175
|
|
41
40
|
const MIN_DISTANCE_FOR_PAN = 10 // Higher threshold gives pinching priority
|
|
42
41
|
const SINGLE_FINGER_POINTER = 1 // Single-finger panning / tapping helps to avoid conflicts with pinching
|
|
@@ -167,6 +166,7 @@ const LightboxGallery = ({
|
|
|
167
166
|
}: LightboxGalleryProps) => {
|
|
168
167
|
const styles = useStyles()
|
|
169
168
|
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 = windowWidth
|
|
181
|
+
const availableWindowHeight = windowHeight - 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 = windowWidth / 2
|
|
303
|
+
const windowCenterY = windowHeight / 2
|
|
304
304
|
|
|
305
305
|
// Position of focal point relative to current image position
|
|
306
306
|
const focalRelativeX = focalPointX - windowCenterX - currentSavedTranslateX
|
|
@@ -402,29 +402,32 @@ 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: windowWidth,
|
|
406
|
+
offset: windowWidth * index,
|
|
407
407
|
index,
|
|
408
408
|
}),
|
|
409
|
-
[]
|
|
409
|
+
[windowWidth]
|
|
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
|
-
|
|
414
|
+
const onMomentumScrollEnd = useCallback(
|
|
415
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
416
|
+
// Calculate the index of the image that is currently visible
|
|
417
|
+
const imageOffsetX = event.nativeEvent.contentOffset.x
|
|
418
|
+
const newImageIndex = Math.round(imageOffsetX / windowWidth)
|
|
419
|
+
|
|
420
|
+
// Check if the image index has changed and the FlatList didn't scroll past the first or last image
|
|
421
|
+
const didImageIndexChange = newImageIndex !== stableCurrentImageIndexRef.current
|
|
422
|
+
const isImageIndexWithinBounds =
|
|
423
|
+
newImageIndex >= 0 && newImageIndex < stableImageAttachmentsLengthRef.current
|
|
424
|
+
|
|
425
|
+
if (didImageIndexChange && isImageIndexWithinBounds) {
|
|
426
|
+
setCurrentImageIndex(newImageIndex)
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
[windowWidth]
|
|
430
|
+
)
|
|
428
431
|
|
|
429
432
|
// Supplements onMomentumScrollEnd by capturing the current image's index while the FlatList is actively scrolling.
|
|
430
433
|
// Used in tandem with viewabilityConfig to trigger when the image is 50% visible in the window.
|
|
@@ -929,6 +932,7 @@ interface UseStylesProps {
|
|
|
929
932
|
|
|
930
933
|
const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {}) => {
|
|
931
934
|
const { top, bottom } = useSafeAreaInsets()
|
|
935
|
+
const { width: windowWidth } = useWindowDimensions()
|
|
932
936
|
const backgroundColor = tokens.colorNeutral7
|
|
933
937
|
const transparentBackgroundColor = useMemo(
|
|
934
938
|
() => colorFunction(backgroundColor).alpha(0.8).toString(),
|
|
@@ -946,7 +950,7 @@ const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {})
|
|
|
946
950
|
attachmentImageWrapper: {
|
|
947
951
|
width: '100%',
|
|
948
952
|
minWidth: 200,
|
|
949
|
-
aspectRatio: imageWidth
|
|
953
|
+
aspectRatio: safeAspectRatio(imageWidth, imageHeight),
|
|
950
954
|
},
|
|
951
955
|
image: {
|
|
952
956
|
borderRadius: 8,
|
|
@@ -960,7 +964,7 @@ const useStyles = ({ imageWidth = 100, imageHeight = 100 }: UseStylesProps = {})
|
|
|
960
964
|
},
|
|
961
965
|
gestureImage: {
|
|
962
966
|
height: '100%',
|
|
963
|
-
width:
|
|
967
|
+
width: windowWidth,
|
|
964
968
|
backgroundColor: 'transparent',
|
|
965
969
|
},
|
|
966
970
|
gestureImageLoading: {
|
|
@@ -3,6 +3,7 @@ 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'
|
|
6
7
|
import { Video, VideoPlayerHandle } from '../../../utils/native_adapters'
|
|
7
8
|
import { tokens } from '../../../vendor/tapestry/tokens'
|
|
8
9
|
import { Icon } from '../../display'
|
|
@@ -58,7 +59,7 @@ export function VideoAttachment({
|
|
|
58
59
|
<Video.Player
|
|
59
60
|
ref={videoRef}
|
|
60
61
|
source={{ uri: url }}
|
|
61
|
-
aspectRatio={width
|
|
62
|
+
aspectRatio={safeAspectRatio(width, height)}
|
|
62
63
|
style={styles.video}
|
|
63
64
|
onFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss}
|
|
64
65
|
/>
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
DenormalizedExpandedLinkAttachmentResource,
|
|
19
19
|
DenormalizedMessageAttachmentResource,
|
|
20
20
|
} from '../../types/resources/denormalized_attachment_resource'
|
|
21
|
-
import { assertKeysAreNumbers, pluralize } from '../../utils'
|
|
21
|
+
import { assertKeysAreNumbers, pluralize, safeAspectRatio } from '../../utils'
|
|
22
22
|
import {
|
|
23
23
|
CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,
|
|
24
24
|
MAX_FONT_SIZE_MULTIPLIER,
|
|
@@ -324,7 +324,7 @@ const useStyles = ({ mine, imageWidth = 32, imageHeight = 32 }: StylesProps = {}
|
|
|
324
324
|
},
|
|
325
325
|
imageWrapper: {
|
|
326
326
|
width: 32 * fontScale,
|
|
327
|
-
aspectRatio: imageWidth
|
|
327
|
+
aspectRatio: safeAspectRatio(imageWidth, imageHeight),
|
|
328
328
|
opacity: 0.5,
|
|
329
329
|
},
|
|
330
330
|
image: {
|
|
@@ -234,7 +234,9 @@ const useStyles = ({
|
|
|
234
234
|
paddingVertical: variantStylesMap[variant].paddingVertical,
|
|
235
235
|
paddingHorizontal: variantStylesMap[variant].paddingHorizontal,
|
|
236
236
|
backgroundColor: variantStylesMap[variant].backgroundColor,
|
|
237
|
-
|
|
237
|
+
// metaSubtle's borderRadius is 0, so this can't go below 0 - Android
|
|
238
|
+
// hard-throws on a negative borderRadius during layout.
|
|
239
|
+
borderRadius: Math.max(0, variantStylesMap[variant].borderRadius - 2),
|
|
238
240
|
},
|
|
239
241
|
icon: {
|
|
240
242
|
color: statusColorMap[appearance].icon,
|
|
@@ -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, isFetchingNextPage } =
|
|
11
11
|
useSuspensePaginator<MessageResource>(
|
|
12
12
|
getMessagesRequestArgs({ conversation_id, reply_root_id }),
|
|
13
13
|
opts
|
|
@@ -25,5 +25,13 @@ export const useConversationMessages = (
|
|
|
25
25
|
[data]
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
messages,
|
|
30
|
+
refetch,
|
|
31
|
+
isRefetching,
|
|
32
|
+
fetchNextPage,
|
|
33
|
+
hasNextPage,
|
|
34
|
+
isFetchingNextPage,
|
|
35
|
+
queryKey,
|
|
36
|
+
}
|
|
29
37
|
}
|
package/src/navigation/index.tsx
CHANGED
|
@@ -178,10 +178,9 @@ export const ChatStack = createNativeStackNavigator({
|
|
|
178
178
|
composeOptions(
|
|
179
179
|
{
|
|
180
180
|
headerTitle: (route.params as { title?: string })?.title ?? 'Chat',
|
|
181
|
+
headerBackVisible: false,
|
|
181
182
|
},
|
|
182
|
-
|
|
183
|
-
onPress: navigation.goBack,
|
|
184
|
-
})
|
|
183
|
+
withRightClose({ onPress: navigation.goBack })
|
|
185
184
|
),
|
|
186
185
|
},
|
|
187
186
|
ConversationFilters: {
|
|
@@ -91,7 +91,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
91
91
|
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
92
92
|
route.params
|
|
93
93
|
const { data: conversation } = useConversation(route.params)
|
|
94
|
-
const { messages, fetchNextPage, hasNextPage } = useConversationMessages({
|
|
94
|
+
const { messages, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationMessages({
|
|
95
95
|
conversation_id,
|
|
96
96
|
reply_root_id,
|
|
97
97
|
})
|
|
@@ -216,7 +216,11 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
216
216
|
/>
|
|
217
217
|
)
|
|
218
218
|
}}
|
|
219
|
-
onEndReached={() =>
|
|
219
|
+
onEndReached={() => {
|
|
220
|
+
// Android's inverted FlatList can refire onEndReached while a
|
|
221
|
+
// page fetch is already in flight, queueing unbounded requests.
|
|
222
|
+
if (hasNextPage && !isFetchingNextPage) fetchNextPage()
|
|
223
|
+
}}
|
|
220
224
|
ListHeaderComponent={<View style={styles.listHeader} />}
|
|
221
225
|
/>
|
|
222
226
|
)}
|
|
@@ -377,7 +381,10 @@ export const ConversationScreenTitle = ({
|
|
|
377
381
|
const usePressableHeaderStyle = () => {
|
|
378
382
|
const { width } = useWindowDimensions()
|
|
379
383
|
const slotReserve = useHeaderSlotReserve()
|
|
380
|
-
|
|
384
|
+
|
|
385
|
+
// Guards split-screen/DeX widths where slotReserve * 2 exceeds the
|
|
386
|
+
// available width, which would otherwise resolve to a negative maxWidth.
|
|
387
|
+
const headerContainerStyle = { maxWidth: Math.max(0, width - slotReserve * 2) }
|
|
381
388
|
|
|
382
389
|
return StyleSheet.create({
|
|
383
390
|
container: {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { safeAspectRatio } from '../aspect_ratio'
|
|
2
|
+
|
|
3
|
+
describe('safeAspectRatio', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
[200, 100, 2],
|
|
6
|
+
[100, 200, 0.5],
|
|
7
|
+
[100, 100, 1],
|
|
8
|
+
])('divides width %p by height %p to get %p', (width, height, expected) => {
|
|
9
|
+
expect(safeAspectRatio(width, height)).toBe(expected)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('with values that would produce a non-positive or non-finite ratio', () => {
|
|
13
|
+
it.each([
|
|
14
|
+
[0, 100],
|
|
15
|
+
[100, 0],
|
|
16
|
+
[0, 0],
|
|
17
|
+
[-100, 100],
|
|
18
|
+
[100, -100],
|
|
19
|
+
[undefined, 100],
|
|
20
|
+
[100, undefined],
|
|
21
|
+
[undefined, undefined],
|
|
22
|
+
[NaN, 100],
|
|
23
|
+
[100, NaN],
|
|
24
|
+
[Infinity, 100],
|
|
25
|
+
])('falls back to 1 for width %p, height %p', (width, height) => {
|
|
26
|
+
expect(safeAspectRatio(width, height)).toBe(1)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('falls back to a caller-supplied value instead of the default', () => {
|
|
31
|
+
expect(safeAspectRatio(0, 100, 0.75)).toBe(0.75)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('never returns 0, a negative number, or a non-finite number', () => {
|
|
35
|
+
const invertedFallbackCases: Array<[number | undefined, number | undefined]> = [
|
|
36
|
+
[0, 100],
|
|
37
|
+
[100, 0],
|
|
38
|
+
[-100, 100],
|
|
39
|
+
[NaN, 100],
|
|
40
|
+
[Infinity, 100],
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
for (const [width, height] of invertedFallbackCases) {
|
|
44
|
+
const ratio = safeAspectRatio(width, height)
|
|
45
|
+
expect(ratio).not.toBe(0)
|
|
46
|
+
expect(Number.isFinite(ratio)).toBe(true)
|
|
47
|
+
expect(ratio).toBeGreaterThan(0)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Android hard-throws when a <Text>/<Image> is measured with a non-positive
|
|
2
|
+
// resolved width (`Layout: -2 < 0`), so any aspectRatio derived from
|
|
3
|
+
// attachment metadata must never resolve to 0, negative, or non-finite.
|
|
4
|
+
export const safeAspectRatio = (
|
|
5
|
+
width: number | undefined,
|
|
6
|
+
height: number | undefined,
|
|
7
|
+
fallback = 1
|
|
8
|
+
): number => {
|
|
9
|
+
if (!width || !height) return fallback
|
|
10
|
+
|
|
11
|
+
const ratio = width / height
|
|
12
|
+
return Number.isFinite(ratio) && ratio > 0 ? ratio : fallback
|
|
13
|
+
}
|