stream-chat-react-native-core 9.3.0-beta.3 → 9.3.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.
Files changed (76) hide show
  1. package/lib/commonjs/components/ImageGallery/components/AnimatedGalleryImage.js +29 -0
  2. package/lib/commonjs/components/ImageGallery/components/AnimatedGalleryImage.js.map +1 -1
  3. package/lib/commonjs/components/ImageGallery/components/ImageGrid.js +25 -8
  4. package/lib/commonjs/components/ImageGallery/components/ImageGrid.js.map +1 -1
  5. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +2 -1
  6. package/lib/commonjs/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  7. package/lib/commonjs/components/MessageList/MessageFlashList.js +3 -2
  8. package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
  9. package/lib/commonjs/components/MessageList/MessageList.js +2 -1
  10. package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
  11. package/lib/commonjs/components/UIComponents/SvgAwareImage.js +54 -0
  12. package/lib/commonjs/components/UIComponents/SvgAwareImage.js.map +1 -0
  13. package/lib/commonjs/components/UIComponents/index.js +11 -0
  14. package/lib/commonjs/components/UIComponents/index.js.map +1 -1
  15. package/lib/commonjs/components/index.js +11 -0
  16. package/lib/commonjs/components/index.js.map +1 -1
  17. package/lib/commonjs/contexts/componentsContext/defaultComponents.js +2 -2
  18. package/lib/commonjs/contexts/componentsContext/defaultComponents.js.map +1 -1
  19. package/lib/commonjs/hooks/index.js +11 -0
  20. package/lib/commonjs/hooks/index.js.map +1 -1
  21. package/lib/commonjs/hooks/useIsSvg.js +22 -0
  22. package/lib/commonjs/hooks/useIsSvg.js.map +1 -0
  23. package/lib/commonjs/version.json +1 -1
  24. package/lib/module/components/ImageGallery/components/AnimatedGalleryImage.js +29 -0
  25. package/lib/module/components/ImageGallery/components/AnimatedGalleryImage.js.map +1 -1
  26. package/lib/module/components/ImageGallery/components/ImageGrid.js +25 -8
  27. package/lib/module/components/ImageGallery/components/ImageGrid.js.map +1 -1
  28. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js +2 -1
  29. package/lib/module/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.js.map +1 -1
  30. package/lib/module/components/MessageList/MessageFlashList.js +3 -2
  31. package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
  32. package/lib/module/components/MessageList/MessageList.js +2 -1
  33. package/lib/module/components/MessageList/MessageList.js.map +1 -1
  34. package/lib/module/components/UIComponents/SvgAwareImage.js +54 -0
  35. package/lib/module/components/UIComponents/SvgAwareImage.js.map +1 -0
  36. package/lib/module/components/UIComponents/index.js +11 -0
  37. package/lib/module/components/UIComponents/index.js.map +1 -1
  38. package/lib/module/components/index.js +11 -0
  39. package/lib/module/components/index.js.map +1 -1
  40. package/lib/module/contexts/componentsContext/defaultComponents.js +2 -2
  41. package/lib/module/contexts/componentsContext/defaultComponents.js.map +1 -1
  42. package/lib/module/hooks/index.js +11 -0
  43. package/lib/module/hooks/index.js.map +1 -1
  44. package/lib/module/hooks/useIsSvg.js +22 -0
  45. package/lib/module/hooks/useIsSvg.js.map +1 -0
  46. package/lib/module/version.json +1 -1
  47. package/lib/typescript/components/ImageGallery/components/AnimatedGalleryImage.d.ts.map +1 -1
  48. package/lib/typescript/components/ImageGallery/components/ImageGrid.d.ts.map +1 -1
  49. package/lib/typescript/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.d.ts.map +1 -1
  50. package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
  51. package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
  52. package/lib/typescript/components/UIComponents/SvgAwareImage.d.ts +11 -0
  53. package/lib/typescript/components/UIComponents/SvgAwareImage.d.ts.map +1 -0
  54. package/lib/typescript/components/UIComponents/index.d.ts +1 -0
  55. package/lib/typescript/components/UIComponents/index.d.ts.map +1 -1
  56. package/lib/typescript/components/index.d.ts +1 -0
  57. package/lib/typescript/components/index.d.ts.map +1 -1
  58. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts +2 -2
  59. package/lib/typescript/contexts/componentsContext/defaultComponents.d.ts.map +1 -1
  60. package/lib/typescript/hooks/index.d.ts +1 -0
  61. package/lib/typescript/hooks/index.d.ts.map +1 -1
  62. package/lib/typescript/hooks/useIsSvg.d.ts +15 -0
  63. package/lib/typescript/hooks/useIsSvg.d.ts.map +1 -0
  64. package/package.json +1 -1
  65. package/src/components/ImageGallery/components/AnimatedGalleryImage.tsx +35 -1
  66. package/src/components/ImageGallery/components/ImageGrid.tsx +15 -4
  67. package/src/components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview.tsx +3 -2
  68. package/src/components/MessageList/MessageFlashList.tsx +8 -3
  69. package/src/components/MessageList/MessageList.tsx +8 -2
  70. package/src/components/UIComponents/SvgAwareImage.tsx +49 -0
  71. package/src/components/UIComponents/index.ts +1 -0
  72. package/src/components/index.ts +1 -0
  73. package/src/contexts/componentsContext/defaultComponents.ts +3 -2
  74. package/src/hooks/index.ts +1 -0
  75. package/src/hooks/useIsSvg.ts +28 -0
  76. package/src/version.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { ImageProps, TextInputProps } from 'react-native';
2
+ import { TextInputProps } from 'react-native';
3
3
  import type { LocalMessage, UserResponse } from 'stream-chat';
4
4
  import { KeyboardCompatibleView } from '../../components/KeyboardCompatibleView/KeyboardCompatibleView';
5
5
  import type { MessageTextProps } from '../../components/Message/MessageItemView/MessageTextContainer';
@@ -293,7 +293,7 @@ declare const components: {
293
293
  };
294
294
  ImageGalleryVideoControls: React.MemoExoticComponent<(props: import("../..").ImageGalleryVideoControlProps) => React.JSX.Element>;
295
295
  MessageOverlayBackground: () => React.JSX.Element;
296
- ImageComponent: React.ComponentType<ImageProps>;
296
+ ImageComponent: (props: import("react-native").ImageProps) => React.JSX.Element;
297
297
  };
298
298
  /**
299
299
  * Optional component slots that have no default implementation.
@@ -1 +1 @@
1
- {"version":3,"file":"defaultComponents.d.ts","sourceRoot":"","sources":["../../../../src/contexts/componentsContext/defaultComponents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAS,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiD9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gEAAgE,CAAC;AAmBxG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+DAA+D,CAAC;AAiFtG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AAEjG;;;;;GAKG;AACH,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAC3B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAC1F,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA2JW,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC;CACzD,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,mCAAmC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC1D,oBAAoB,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;IAClD,iBAAiB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAC1B,wBAAwB,CAAC,EAAE,cAAc,CAAC;QAC1C,QAAQ,EAAE,MAAM,YAAY,EAAE,CAAC;KAChC,CAAC,CAAC;IACH,mBAAmB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC1C,cAAc,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;IAC1D,wBAAwB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC/C,yBAAyB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAChD,qBAAqB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5C,0BAA0B,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACjD,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACjE,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACpC,WAAW,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACpD,WAAW,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CACnC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,mBAAmB,CAAC,OAAO,UAAU,CAAc,CAAC"}
1
+ {"version":3,"file":"defaultComponents.d.ts","sourceRoot":"","sources":["../../../../src/contexts/componentsContext/defaultComponents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiD9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gEAAgE,CAAC;AAmBxG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+DAA+D,CAAC;AAkFtG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AAEjG;;;;;GAKG;AACH,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAC3B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAC1F,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4Jf,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,mCAAmC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC1D,oBAAoB,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;IAClD,iBAAiB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAC1B,wBAAwB,CAAC,EAAE,cAAc,CAAC;QAC1C,QAAQ,EAAE,MAAM,YAAY,EAAE,CAAC;KAChC,CAAC,CAAC;IACH,mBAAmB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC1C,cAAc,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;IAC1D,wBAAwB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC/C,yBAAyB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAChD,qBAAqB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5C,0BAA0B,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACjD,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACjE,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACpC,WAAW,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACpD,WAAW,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CACnC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,mBAAmB,CAAC,OAAO,UAAU,CAAc,CAAC"}
@@ -7,6 +7,7 @@ export * from './useStateStore';
7
7
  export * from './usePendingAttachmentUpload';
8
8
  export * from './useStableCallback';
9
9
  export * from './useLoadingImage';
10
+ export * from './useIsSvg';
10
11
  export * from './useMessageReminder';
11
12
  export * from './useQueryReminders';
12
13
  export * from './useAfterKeyboardOpenCallback';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mCAAmC,CAAC;AAClD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mCAAmC,CAAC;AAClD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC;AAC1D,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC;AACvD,cAAc,wCAAwC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Returns true if `uri` points to an SVG (either by `.svg` extension or by
3
+ * `image/svg+xml` data-URI prefix). Exported as a pure function for non-React
4
+ * callers; React components should prefer `useIsSvg` so the check is memoized
5
+ * per URI.
6
+ */
7
+ export declare const isSvgUri: (uri: string | null | undefined) => boolean;
8
+ /**
9
+ * Memoized variant of `isSvgUri`. Re-runs the string check only when `uri`
10
+ * actually changes — useful in hot render paths like the gallery where the
11
+ * surrounding component re-renders frequently (animated styles, swipe state)
12
+ * but the URI is stable.
13
+ */
14
+ export declare const useIsSvg: (uri: string | null | undefined) => boolean;
15
+ //# sourceMappingURL=useIsSvg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsSvg.d.ts","sourceRoot":"","sources":["../../../src/hooks/useIsSvg.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,OAUzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,OACrB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stream-chat-react-native-core",
3
3
  "description": "The official React Native and Expo components for Stream Chat, a service for building chat applications",
4
- "version": "9.3.0-beta.3",
4
+ "version": "9.3.0",
5
5
  "author": {
6
6
  "company": "Stream.io Inc",
7
7
  "name": "Stream.io Inc"
@@ -1,16 +1,18 @@
1
1
  import React, { useMemo } from 'react';
2
- import { View } from 'react-native';
2
+ import { StyleSheet, View } from 'react-native';
3
3
  import type { ImageStyle, StyleProp } from 'react-native';
4
4
  import Animated, { SharedValue } from 'react-native-reanimated';
5
5
 
6
6
  import { useChatConfigContext } from '../../../contexts/chatConfigContext/ChatConfigContext';
7
7
  import { useImageGalleryContext } from '../../../contexts/imageGalleryContext/ImageGalleryContext';
8
8
  import { useStateStore } from '../../../hooks';
9
+ import { useIsSvg } from '../../../hooks/useIsSvg';
9
10
  import {
10
11
  ImageGalleryAsset,
11
12
  ImageGalleryState,
12
13
  } from '../../../state-store/image-gallery-state-store';
13
14
  import { getResizedImageUrl } from '../../../utils/getResizedImageUrl';
15
+ import { SvgAwareImage } from '../../UIComponents/SvgAwareImage';
14
16
  import { useAnimatedGalleryStyle } from '../hooks/useAnimatedGalleryStyle';
15
17
 
16
18
  const oneEighth = 1 / 8;
@@ -59,6 +61,7 @@ export const AnimatedGalleryImage = React.memo(
59
61
  });
60
62
  }, [photo.uri, resizableCDNHosts, screenHeight, screenWidth]);
61
63
 
64
+ const isSvg = useIsSvg(uri);
62
65
  const selected = currentIndex === index;
63
66
  const previous = currentIndex > index;
64
67
  const shouldRender = Math.abs(currentIndex - index) < 4;
@@ -83,6 +86,27 @@ export const AnimatedGalleryImage = React.memo(
83
86
  return <View style={[style, { transform: [{ scale: oneEighth }] }]} />;
84
87
  }
85
88
 
89
+ if (isSvg) {
90
+ // The outer Animated.View is sized at 8× screen so raster images stay
91
+ // crisp under pinch zoom (see useAnimatedGalleryStyle). rn-svg on
92
+ // Android rasterizes the SVG to a bitmap at its layout size and an
93
+ // 8x screen bitmap exceeds RecordingCanvas's per draw byte limit. The
94
+ // inner SvgAwareImage is sized at 1x screen with a counter scale of 8 so
95
+ // the bitmap stays small while the composed visible scale (1/8 × 8 === 1)
96
+ // is unchanged.
97
+ return (
98
+ <Animated.View
99
+ accessibilityLabel={accessibilityLabel}
100
+ style={[...animatedStyles, style, styles.svgOuter]}
101
+ >
102
+ <SvgAwareImage
103
+ source={{ uri }}
104
+ style={[{ height: screenHeight, width: screenWidth }, styles.svgInner]}
105
+ />
106
+ </Animated.View>
107
+ );
108
+ }
109
+
86
110
  return (
87
111
  <Animated.Image
88
112
  accessibilityLabel={accessibilityLabel}
@@ -106,3 +130,13 @@ export const AnimatedGalleryImage = React.memo(
106
130
  );
107
131
 
108
132
  AnimatedGalleryImage.displayName = 'AnimatedGalleryImage';
133
+
134
+ const styles = StyleSheet.create({
135
+ svgInner: {
136
+ transform: [{ scale: 8 }],
137
+ },
138
+ svgOuter: {
139
+ alignItems: 'center',
140
+ justifyContent: 'center',
141
+ },
142
+ });
@@ -3,7 +3,6 @@ import { Image, Pressable, StyleSheet, View } from 'react-native';
3
3
 
4
4
  import type { ImageGalleryGridProps } from './types';
5
5
 
6
- import { VideoThumbnail } from '../../../components/Attachment/VideoThumbnail';
7
6
  import { useImageGalleryContext } from '../../../contexts/imageGalleryContext/ImageGalleryContextBase';
8
7
  import { useTheme } from '../../../contexts/themeContext/ThemeContext';
9
8
  import { useStateStore } from '../../../hooks/useStateStore';
@@ -14,7 +13,9 @@ import type {
14
13
  } from '../../../state-store/image-gallery-state-store';
15
14
  import { primitives } from '../../../theme';
16
15
  import { FileTypes } from '../../../types/types';
16
+ import { VideoPlayIndicator } from '../../ui/VideoPlayIndicator';
17
17
  import { StreamBottomSheetModalFlatList } from '../../UIComponents/StreamBottomSheetModalFlatList';
18
+ import { SvgAwareImage } from '../../UIComponents/SvgAwareImage';
18
19
 
19
20
  export type ImageGalleryGridImageComponent = ({
20
21
  item,
@@ -42,11 +43,14 @@ const GridImage = ({ item }: { item: GridImageItem }) => {
42
43
  return (
43
44
  <Pressable accessibilityLabel='Grid Image' onPress={selectAndClose}>
44
45
  {type === FileTypes.Video ? (
45
- <View style={[styles.image, { height: size, width: size }]}>
46
- <VideoThumbnail thumb_url={thumb_url} />
46
+ <View style={[styles.image, { height: size, width: size }, styles.videoCell]}>
47
+ {thumb_url ? <Image source={{ uri: thumb_url }} style={StyleSheet.absoluteFill} /> : null}
48
+ <View pointerEvents='none' style={[StyleSheet.absoluteFill, styles.playIndicator]}>
49
+ <VideoPlayIndicator size='md' />
50
+ </View>
47
51
  </View>
48
52
  ) : (
49
- <Image source={{ uri }} style={[styles.image, { height: size, width: size }]} />
53
+ <SvgAwareImage source={{ uri }} style={[styles.image, { height: size, width: size }]} />
50
54
  )}
51
55
  </Pressable>
52
56
  );
@@ -114,6 +118,13 @@ const useStyles = () => {
114
118
  ...contentContainer,
115
119
  },
116
120
  image: { margin: 1, ...gridImage },
121
+ playIndicator: {
122
+ alignItems: 'center',
123
+ justifyContent: 'center',
124
+ },
125
+ videoCell: {
126
+ overflow: 'hidden',
127
+ },
117
128
  });
118
129
  }, [contentContainer, gridImage, semantics]);
119
130
  };
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback, useMemo, useState } from 'react';
2
2
 
3
- import { Image, StyleSheet, View } from 'react-native';
3
+ import { StyleSheet, View } from 'react-native';
4
4
 
5
5
  import { LocalImageAttachment } from 'stream-chat';
6
6
 
@@ -26,6 +26,7 @@ export const ImageAttachmentUploadPreview = ({
26
26
  const [loading, setLoading] = useState(true);
27
27
  const { allowSendBeforeAttachmentsUpload } = useMessageInputContext();
28
28
  const {
29
+ ImageComponent,
29
30
  ImageLoadingIndicator,
30
31
  ImageUploadInProgressIndicator,
31
32
  ImageUploadRetryIndicator,
@@ -67,7 +68,7 @@ export const ImageAttachmentUploadPreview = ({
67
68
  return (
68
69
  <View style={[styles.wrapper, wrapper]} testID={'image-attachment-upload-preview'}>
69
70
  <View style={[styles.image, upload]}>
70
- <Image
71
+ <ImageComponent
71
72
  onError={onErrorHandler}
72
73
  onLoadEnd={onLoadEndHandler}
73
74
  source={{ uri: previewUri }}
@@ -55,7 +55,7 @@ import { mergeThemes, useTheme } from '../../contexts/themeContext/ThemeContext'
55
55
  import { ThreadContextValue, useThreadContext } from '../../contexts/threadContext/ThreadContext';
56
56
 
57
57
  import { useStableCallback, useStateStore } from '../../hooks';
58
- import { bumpOverlayLayoutRevision } from '../../state-store';
58
+ import { bumpOverlayLayoutRevision, useHasActiveId } from '../../state-store';
59
59
  import { MessageInputHeightState } from '../../state-store/message-input-height-store';
60
60
  import { primitives } from '../../theme';
61
61
  import { transitions } from '../../utils/animations/transitions';
@@ -400,13 +400,18 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
400
400
  }
401
401
  }, [autoscrollToRecent, hasPendingInitialTargetLoad]);
402
402
 
403
+ // While the message overlay is open we suppress autoscroll-to-recent so that
404
+ // incoming messages do not shift visible content and invalidate the overlay's
405
+ // anchored geometry. Content anchoring (startRenderingFromBottom) stays on.
406
+ const isOverlayOpen = useHasActiveId();
407
+
403
408
  const maintainVisibleContentPosition = useMemo(() => {
404
409
  return {
405
410
  animateAutoscrollToBottom: true,
406
- autoscrollToBottomThreshold: autoscrollToRecent ? 1 : undefined,
411
+ autoscrollToBottomThreshold: autoscrollToRecent && !isOverlayOpen ? 1 : undefined,
407
412
  startRenderingFromBottom: true,
408
413
  };
409
- }, [autoscrollToRecent]);
414
+ }, [isOverlayOpen, autoscrollToRecent]);
410
415
 
411
416
  useEffect(() => {
412
417
  if (disabled) {
@@ -68,7 +68,7 @@ import { ThreadContextValue, useThreadContext } from '../../contexts/threadConte
68
68
 
69
69
  import { useStableCallback } from '../../hooks';
70
70
  import { useStateStore } from '../../hooks/useStateStore';
71
- import { bumpOverlayLayoutRevision } from '../../state-store';
71
+ import { bumpOverlayLayoutRevision, useHasActiveId } from '../../state-store';
72
72
  import { MessageInputHeightState } from '../../state-store/message-input-height-store';
73
73
  import { primitives } from '../../theme';
74
74
  import { transitions } from '../../utils/animations/transitions';
@@ -440,7 +440,13 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
440
440
 
441
441
  const minIndexForVisible = Math.min(1, processedMessageList.length);
442
442
 
443
- const autoscrollToTopThreshold = autoscrollToRecent ? (isLiveStreaming ? 300 : 10) : undefined;
443
+ // While the message overlay is open we suppress autoscroll-to-recent so that
444
+ // incoming messages do not shift visible content and invalidate the overlay's
445
+ // anchored geometry. Content anchoring (minIndexForVisible) stays on.
446
+ const isOverlayOpen = useHasActiveId();
447
+
448
+ const autoscrollToTopThreshold =
449
+ autoscrollToRecent && !isOverlayOpen ? (isLiveStreaming ? 300 : 10) : undefined;
444
450
 
445
451
  const maintainVisibleContentPosition = useMemo(
446
452
  () => ({
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Image, ImageProps, View } from 'react-native';
3
+
4
+ import { SvgUri } from 'react-native-svg';
5
+
6
+ import { useIsSvg } from '../../hooks/useIsSvg';
7
+
8
+ const getSourceUri = (source: ImageProps['source']): string | undefined => {
9
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
10
+ return undefined;
11
+ }
12
+ return source.uri;
13
+ };
14
+
15
+ /**
16
+ * Default `ImageComponent` for the SDK. Behaves exactly like RN's `Image` for
17
+ * raster sources, but transparently renders SVG URIs (`.svg`, `image/svg+xml`
18
+ * data URIs) via `SvgUri` from `react-native-svg`. Integrators who override
19
+ * `ImageComponent` with a custom image library (e.g. FastImage) are
20
+ * responsible for SVG handling in their override.
21
+ */
22
+ export const SvgAwareImage = (props: ImageProps) => {
23
+ const uri = getSourceUri(props.source);
24
+ const isSvg = useIsSvg(uri);
25
+
26
+ if (!isSvg || !uri) {
27
+ return <Image {...props} />;
28
+ }
29
+
30
+ const { accessibilityLabel, onError, onLoad, onLoadEnd, style, testID } = props;
31
+
32
+ return (
33
+ <View accessibilityLabel={accessibilityLabel} style={style} testID={testID}>
34
+ <SvgUri
35
+ height='100%'
36
+ onError={(error) => {
37
+ onError?.({ nativeEvent: { error } } as never);
38
+ onLoadEnd?.();
39
+ }}
40
+ onLoad={() => {
41
+ onLoad?.({} as never);
42
+ onLoadEnd?.();
43
+ }}
44
+ uri={uri}
45
+ width='100%'
46
+ />
47
+ </View>
48
+ );
49
+ };
@@ -1,6 +1,7 @@
1
1
  export * from './BottomSheetModal';
2
2
  export * from './StreamBottomSheetModalFlatList';
3
3
  export * from './ImageBackground';
4
+ export * from './SvgAwareImage';
4
5
  export * from './Spinner';
5
6
  export * from './SwipableWrapper';
6
7
  export * from './PortalWhileClosingView';
@@ -188,6 +188,7 @@ export * from './ui';
188
188
  export * from './UIComponents/BottomSheetModal';
189
189
  export * from './UIComponents/StreamBottomSheetModalFlatList';
190
190
  export * from './UIComponents/ImageBackground';
191
+ export * from './UIComponents/SvgAwareImage';
191
192
  export * from './UIComponents/Spinner';
192
193
  export * from './UIComponents/SwipableWrapper';
193
194
  export * from './UIComponents/PortalWhileClosingView';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Image, ImageProps, TextInputProps } from 'react-native';
2
+ import { TextInputProps } from 'react-native';
3
3
 
4
4
  import type { LocalMessage, UserResponse } from 'stream-chat';
5
5
 
@@ -149,6 +149,7 @@ import { ThreadListItemMessagePreview } from '../../components/ThreadList/Thread
149
149
  import { ThreadListUnreadBanner } from '../../components/ThreadList/ThreadListUnreadBanner';
150
150
  import { ThreadMessagePreviewDeliveryStatus } from '../../components/ThreadList/ThreadMessagePreviewDeliveryStatus';
151
151
  import { ChannelAvatar } from '../../components/ui/Avatar/ChannelAvatar';
152
+ import { SvgAwareImage } from '../../components/UIComponents/SvgAwareImage';
152
153
  import { DefaultMessageOverlayBackground } from '../../contexts/overlayContext/MessageOverlayHostLayer';
153
154
  import type { MessageActionsProps } from '../../contexts/overlayContext/MessageOverlayHostLayer';
154
155
 
@@ -317,7 +318,7 @@ const components = {
317
318
  MessageOverlayBackground: DefaultMessageOverlayBackground,
318
319
 
319
320
  // Image
320
- ImageComponent: Image as React.ComponentType<ImageProps>,
321
+ ImageComponent: SvgAwareImage,
321
322
  };
322
323
 
323
324
  /**
@@ -7,6 +7,7 @@ export * from './useStateStore';
7
7
  export * from './usePendingAttachmentUpload';
8
8
  export * from './useStableCallback';
9
9
  export * from './useLoadingImage';
10
+ export * from './useIsSvg';
10
11
  export * from './useMessageReminder';
11
12
  export * from './useQueryReminders';
12
13
  export * from './useAfterKeyboardOpenCallback';
@@ -0,0 +1,28 @@
1
+ import { useMemo } from 'react';
2
+
3
+ /**
4
+ * Returns true if `uri` points to an SVG (either by `.svg` extension or by
5
+ * `image/svg+xml` data-URI prefix). Exported as a pure function for non-React
6
+ * callers; React components should prefer `useIsSvg` so the check is memoized
7
+ * per URI.
8
+ */
9
+ export const isSvgUri = (uri: string | null | undefined): boolean => {
10
+ if (typeof uri !== 'string' || uri.length === 0) {
11
+ return false;
12
+ }
13
+ const lower = uri.toLowerCase();
14
+ if (lower.startsWith('data:image/svg+xml')) {
15
+ return true;
16
+ }
17
+ const pathOnly = lower.split('#')[0].split('?')[0];
18
+ return pathOnly.endsWith('.svg');
19
+ };
20
+
21
+ /**
22
+ * Memoized variant of `isSvgUri`. Re-runs the string check only when `uri`
23
+ * actually changes — useful in hot render paths like the gallery where the
24
+ * surrounding component re-renders frequently (animated styles, swipe state)
25
+ * but the URI is stable.
26
+ */
27
+ export const useIsSvg = (uri: string | null | undefined): boolean =>
28
+ useMemo(() => isSvgUri(uri), [uri]);
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "9.3.0-beta.3"
2
+ "version": "9.3.0"
3
3
  }