@planningcenter/chat-react-native 3.44.1-qa-875.0 → 3.44.1-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/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/reply_shadow_message.js +2 -2
- package/build/components/conversation/reply_shadow_message.js.map +1 -1
- package/build/components/display/badge.js +1 -3
- 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 +3 -10
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/message_report_screen.d.ts.map +1 -1
- package/build/screens/message_report_screen.js +2 -4
- package/build/screens/message_report_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/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/reply_shadow_message.tsx +2 -2
- package/src/components/display/badge.tsx +1 -3
- package/src/hooks/use_conversation_messages.ts +2 -10
- package/src/screens/conversation_screen.tsx +3 -10
- package/src/screens/message_report_screen.tsx +6 -4
- package/src/utils/index.ts +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 -10
- package/build/utils/aspect_ratio.js.map +0 -1
- package/src/utils/__tests__/aspect_ratio.test.ts +0 -50
- package/src/utils/aspect_ratio.ts +0 -13
|
@@ -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
|
/>
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
DenormalizedExpandedLinkAttachmentResource,
|
|
19
19
|
DenormalizedMessageAttachmentResource,
|
|
20
20
|
} from '../../types/resources/denormalized_attachment_resource'
|
|
21
|
-
import { assertKeysAreNumbers, pluralize
|
|
21
|
+
import { assertKeysAreNumbers, pluralize } 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:
|
|
327
|
+
aspectRatio: imageWidth / imageHeight,
|
|
328
328
|
opacity: 0.5,
|
|
329
329
|
},
|
|
330
330
|
image: {
|
|
@@ -234,9 +234,7 @@ const useStyles = ({
|
|
|
234
234
|
paddingVertical: variantStylesMap[variant].paddingVertical,
|
|
235
235
|
paddingHorizontal: variantStylesMap[variant].paddingHorizontal,
|
|
236
236
|
backgroundColor: variantStylesMap[variant].backgroundColor,
|
|
237
|
-
|
|
238
|
-
// hard-throws on a negative borderRadius during layout.
|
|
239
|
-
borderRadius: Math.max(0, variantStylesMap[variant].borderRadius - 2),
|
|
237
|
+
borderRadius: variantStylesMap[variant].borderRadius - 2,
|
|
240
238
|
},
|
|
241
239
|
icon: {
|
|
242
240
|
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 } =
|
|
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
|
}
|
|
@@ -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
|
|
94
|
+
const { messages, fetchNextPage, hasNextPage } = useConversationMessages({
|
|
95
95
|
conversation_id,
|
|
96
96
|
reply_root_id,
|
|
97
97
|
})
|
|
@@ -216,11 +216,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
216
216
|
/>
|
|
217
217
|
)
|
|
218
218
|
}}
|
|
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
|
-
}}
|
|
219
|
+
onEndReached={() => fetchNextPage()}
|
|
224
220
|
ListHeaderComponent={<View style={styles.listHeader} />}
|
|
225
221
|
/>
|
|
226
222
|
)}
|
|
@@ -381,10 +377,7 @@ export const ConversationScreenTitle = ({
|
|
|
381
377
|
const usePressableHeaderStyle = () => {
|
|
382
378
|
const { width } = useWindowDimensions()
|
|
383
379
|
const slotReserve = useHeaderSlotReserve()
|
|
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) }
|
|
380
|
+
const headerContainerStyle = { maxWidth: width - slotReserve * 2 }
|
|
388
381
|
|
|
389
382
|
return StyleSheet.create({
|
|
390
383
|
container: {
|
|
@@ -30,9 +30,11 @@ export const MessageReportScreenOptions = ({
|
|
|
30
30
|
}: NativeStackScreenProps<any>): NativeStackNavigationOptions => {
|
|
31
31
|
const handleClose = () =>
|
|
32
32
|
navigation.dispatch(
|
|
33
|
-
StackActions.popTo(
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
StackActions.popTo(
|
|
34
|
+
'Conversation',
|
|
35
|
+
{ conversation_id: route.params?.conversation_id },
|
|
36
|
+
{ merge: true }
|
|
37
|
+
)
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
return composeOptions(
|
|
@@ -100,7 +102,7 @@ function MessageReportScreenContent({
|
|
|
100
102
|
}, [isFormValid, selectedReason, reasonDetail, submitReport])
|
|
101
103
|
|
|
102
104
|
const handleClose = useCallback(() => {
|
|
103
|
-
navigation.dispatch(StackActions.popTo('Conversation', { conversation_id }))
|
|
105
|
+
navigation.dispatch(StackActions.popTo('Conversation', { conversation_id }, { merge: true }))
|
|
104
106
|
}, [navigation, conversation_id])
|
|
105
107
|
|
|
106
108
|
const formVisible = status !== 'pending' && status !== 'success'
|
package/src/utils/index.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aspect_ratio.d.ts","sourceRoot":"","sources":["../../src/utils/aspect_ratio.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,GAAG,SAAS,EACzB,QAAQ,MAAM,GAAG,SAAS,EAC1B,iBAAY,KACX,MAKF,CAAA"}
|
|
@@ -1,10 +0,0 @@
|
|
|
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 = (width, height, fallback = 1) => {
|
|
5
|
-
if (!width || !height)
|
|
6
|
-
return fallback;
|
|
7
|
-
const ratio = width / height;
|
|
8
|
-
return Number.isFinite(ratio) && ratio > 0 ? ratio : fallback;
|
|
9
|
-
};
|
|
10
|
-
//# sourceMappingURL=aspect_ratio.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aspect_ratio.js","sourceRoot":"","sources":["../../src/utils/aspect_ratio.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,qEAAqE;AACrE,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,KAAyB,EACzB,MAA0B,EAC1B,QAAQ,GAAG,CAAC,EACJ,EAAE;IACV,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAA;IAEtC,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC/D,CAAC,CAAA","sourcesContent":["// Android hard-throws when a <Text>/<Image> is measured with a non-positive\n// resolved width (`Layout: -2 < 0`), so any aspectRatio derived from\n// attachment metadata must never resolve to 0, negative, or non-finite.\nexport const safeAspectRatio = (\n width: number | undefined,\n height: number | undefined,\n fallback = 1\n): number => {\n if (!width || !height) return fallback\n\n const ratio = width / height\n return Number.isFinite(ratio) && ratio > 0 ? ratio : fallback\n}\n"]}
|
|
@@ -1,50 +0,0 @@
|
|
|
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
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
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
|
-
}
|