stream-chat-react-native 8.13.7 → 9.0.0-beta.10

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.
@@ -1,7 +1,5 @@
1
1
  #import "StreamChatReactNative.h"
2
2
  #import <React/RCTLog.h>
3
- #import <AssetsLibrary/AssetsLibrary.h>
4
- #import <MobileCoreServices/MobileCoreServices.h>
5
3
 
6
4
  #if __has_include(<React/RCTLog.h>)
7
5
  #import <React/RCTLog.h>
@@ -19,12 +17,12 @@ NSString *moduleName = @"StreamChatReactNative";
19
17
 
20
18
  RCT_EXPORT_MODULE()
21
19
 
22
- RCT_REMAP_METHOD(createResizedImage, uri:(NSString *)uri width:(double)width height:(double)height format:(NSString *)format quality:(double)quality mode:(NSString *)mode onlyScaleDown:(BOOL)onlyScaleDown rotation:(nonnull NSNumber *)rotation outputPath:(NSString *)outputPath keepMeta:(nonnull NSNumber *)keepMeta resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
20
+ RCT_REMAP_METHOD(createResizedImage, uri:(NSString *)uri width:(double)width height:(double)height format:(NSString *)format quality:(double)quality mode:(NSString *)mode onlyScaleDown:(BOOL)onlyScaleDown rotation:(nonnull NSNumber *)rotation outputPath:(NSString *)outputPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
23
21
  {
24
- [self createResizedImage:uri width:width height:height format:format quality:quality mode:mode onlyScaleDown:onlyScaleDown rotation:rotation outputPath:outputPath keepMeta:keepMeta resolve:resolve reject:reject];
22
+ [self createResizedImage:uri width:width height:height format:format quality:quality mode:mode onlyScaleDown:onlyScaleDown rotation:rotation outputPath:outputPath resolve:resolve reject:reject];
25
23
  }
26
24
 
27
- - (void)createResizedImage:(NSString *)uri width:(double)width height:(double)height format:(NSString *)format quality:(double)quality mode:(NSString *)mode onlyScaleDown:(BOOL)onlyScaleDown rotation:(nonnull NSNumber *)rotation outputPath:(NSString *)outputPath keepMeta:(nonnull NSNumber *)keepMeta resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
25
+ - (void)createResizedImage:(NSString *)uri width:(double)width height:(double)height format:(NSString *)format quality:(double)quality mode:(NSString *)mode onlyScaleDown:(BOOL)onlyScaleDown rotation:(nonnull NSNumber *)rotation outputPath:(NSString *)outputPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
28
26
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
29
27
  @try {
30
28
  CGSize newSize = CGSizeMake(width, height);
@@ -57,7 +55,7 @@ RCT_REMAP_METHOD(createResizedImage, uri:(NSString *)uri width:(double)width hei
57
55
  reject([NSString stringWithFormat: @"%ld", (long)error.code], error.description, nil);
58
56
  return;
59
57
  }
60
- NSDictionary * response = transformImage(image, uri, [rotation integerValue], newSize, fullPath, format, (int)quality, [keepMeta boolValue], @{@"mode": mode, @"onlyScaleDown": [NSNumber numberWithBool:onlyScaleDown]});
58
+ NSDictionary * response = transformImage(image, [rotation integerValue], newSize, fullPath, format, (int)quality, @{@"mode": mode, @"onlyScaleDown": [NSNumber numberWithBool:onlyScaleDown]});
61
59
  resolve(response);
62
60
  }];
63
61
  } @catch (NSException *exception) {
@@ -69,65 +67,21 @@ RCT_REMAP_METHOD(createResizedImage, uri:(NSString *)uri width:(double)width hei
69
67
 
70
68
 
71
69
 
72
- bool saveImage(NSString * fullPath, UIImage * image, NSString * format, float quality, NSMutableDictionary *metadata)
70
+ bool saveImage(NSString * fullPath, UIImage * image, NSString * format, float quality)
73
71
  {
74
- if(metadata == nil){
75
- NSData* data = nil;
76
- if ([format isEqualToString:@"JPEG"]) {
77
- data = UIImageJPEGRepresentation(image, quality / 100.0);
78
- } else if ([format isEqualToString:@"PNG"]) {
79
- data = UIImagePNGRepresentation(image);
80
- }
81
-
82
- if (data == nil) {
83
- return NO;
84
- }
85
-
86
- NSFileManager* fileManager = [NSFileManager defaultManager];
87
- return [fileManager createFileAtPath:fullPath contents:data attributes:nil];
72
+ NSData* data = nil;
73
+ if ([format isEqualToString:@"JPEG"]) {
74
+ data = UIImageJPEGRepresentation(image, quality / 100.0);
75
+ } else if ([format isEqualToString:@"PNG"]) {
76
+ data = UIImagePNGRepresentation(image);
88
77
  }
89
78
 
90
- // process / write metadata together with image data
91
- else{
92
-
93
- CFStringRef imgType = kUTTypeJPEG;
94
-
95
- if ([format isEqualToString:@"JPEG"]) {
96
- [metadata setObject:@(quality / 100.0) forKey:(__bridge NSString *)kCGImageDestinationLossyCompressionQuality];
97
- }
98
- else if([format isEqualToString:@"PNG"]){
99
- imgType = kUTTypePNG;
100
- }
101
- else{
102
- return NO;
103
- }
104
-
105
- NSMutableData * destData = [NSMutableData data];
106
-
107
- CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)destData, imgType, 1, NULL);
108
-
109
- @try{
110
- CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef) metadata);
111
-
112
- // write final image data with metadata to our destination
113
- if (CGImageDestinationFinalize(destination)){
114
-
115
- NSFileManager* fileManager = [NSFileManager defaultManager];
116
- return [fileManager createFileAtPath:fullPath contents:destData attributes:nil];
117
- }
118
- else{
119
- return NO;
120
- }
121
- }
122
- @finally{
123
- @try{
124
- CFRelease(destination);
125
- }
126
- @catch(NSException *exception){
127
- NSLog(@"Failed to release CGImageDestinationRef: %@", exception);
128
- }
129
- }
79
+ if (data == nil) {
80
+ return NO;
130
81
  }
82
+
83
+ NSFileManager* fileManager = [NSFileManager defaultManager];
84
+ return [fileManager createFileAtPath:fullPath contents:data attributes:nil];
131
85
  }
132
86
 
133
87
  NSString * generateFilePath(NSString * ext, NSString * outputPath)
@@ -268,81 +222,12 @@ UIImage* scaleImage (UIImage* image, CGSize toSize, NSString* mode, bool onlySca
268
222
  return newImage;
269
223
  }
270
224
 
271
- // Returns the image's metadata, or nil if failed to retrieve it.
272
- NSMutableDictionary * getImageMeta(NSString * path)
273
- {
274
- if([path hasPrefix:@"assets-library"]) {
275
-
276
- __block NSMutableDictionary* res = nil;
277
-
278
- ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
279
- {
280
-
281
- NSDictionary *exif = [[myasset defaultRepresentation] metadata];
282
- res = [exif mutableCopy];
283
-
284
- };
285
-
286
- ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
287
- NSURL *url = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
288
-
289
- [assetslibrary assetForURL:url resultBlock:resultblock failureBlock:^(NSError *error) { NSLog(@"error couldn't image from assets library"); }];
290
-
291
- return res;
292
-
293
- } else {
294
-
295
- NSData* imageData = nil;
296
-
297
- if ([path hasPrefix:@"data:"] || [path hasPrefix:@"file:"]) {
298
- NSURL *imageUrl = [[NSURL alloc] initWithString:path];
299
- imageData = [NSData dataWithContentsOfURL:imageUrl];
300
-
301
- } else {
302
- imageData = [NSData dataWithContentsOfFile:path];
303
- }
304
-
305
- if(imageData == nil){
306
- NSLog(@"Could not get image file data to extract metadata.");
307
- return nil;
308
- }
309
-
310
- CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
311
-
312
-
313
- if(source != nil){
314
-
315
- CFDictionaryRef metaRef = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
316
-
317
- // release CF image
318
- CFRelease(source);
319
-
320
- CFMutableDictionaryRef metaRefMutable = CFDictionaryCreateMutableCopy(NULL, 0, metaRef);
321
-
322
- // release the source meta ref now that we've copie it
323
- CFRelease(metaRef);
324
-
325
- // bridge CF object so it auto releases
326
- NSMutableDictionary* res = (NSMutableDictionary *)CFBridgingRelease(metaRefMutable);
327
-
328
- return res;
329
-
330
- }
331
- else{
332
- return nil;
333
- }
334
-
335
- }
336
- }
337
-
338
225
  NSDictionary * transformImage(UIImage *image,
339
- NSString * originalPath,
340
226
  int rotation,
341
227
  CGSize newSize,
342
228
  NSString* fullPath,
343
229
  NSString* format,
344
230
  int quality,
345
- BOOL keepMeta,
346
231
  NSDictionary* options)
347
232
  {
348
233
  if (image == nil) {
@@ -368,25 +253,8 @@ NSDictionary * transformImage(UIImage *image,
368
253
  if (scaledImage == nil) {
369
254
  [NSException raise:moduleName format:@"Can't resize the image."];
370
255
  }
371
-
372
-
373
- NSMutableDictionary *metadata = nil;
374
-
375
- // to be consistent with Android, we will only allow JPEG
376
- // to do this.
377
- if(keepMeta && [format isEqualToString:@"JPEG"]){
378
-
379
- metadata = getImageMeta(originalPath);
380
-
381
- // remove orientation (since we fix it)
382
- // width/height meta is adjusted automatically
383
- // NOTE: This might still leave some stale values due to resize
384
- metadata[(NSString*)kCGImagePropertyOrientation] = @(1);
385
-
386
- }
387
-
388
256
  // Compress and save the image
389
- if (!saveImage(fullPath, scaledImage, format, quality, metadata)) {
257
+ if (!saveImage(fullPath, scaledImage, format, quality)) {
390
258
  [NSException raise:moduleName format:@"Can't save the image. Check your compression format and your output path"];
391
259
  }
392
260
 
@@ -0,0 +1,249 @@
1
+ import QuartzCore
2
+ import UIKit
3
+
4
+ /// Native shimmer view used by the Fabric component view.
5
+ ///
6
+ /// It renders a base layer and a moving gradient highlight entirely in native code, so shimmer
7
+ /// animation stays off the JS thread. The view updates its gradient when size or colors change and
8
+ /// stops animation when it is not drawable (backgrounded, detached, hidden, or zero sized).
9
+ @objcMembers
10
+ public final class StreamShimmerView: UIView {
11
+ private static let edgeHighlightAlpha: CGFloat = 0.1
12
+ private static let softHighlightAlpha: CGFloat = 0.24
13
+ private static let midHighlightAlpha: CGFloat = 0.48
14
+ private static let innerHighlightAlpha: CGFloat = 0.72
15
+ private static let defaultHighlightAlpha: CGFloat = 0.35
16
+ private static let defaultShimmerDuration: CFTimeInterval = 1.2
17
+ private static let shimmerStripWidthRatio: CGFloat = 1.25
18
+ private static let shimmerAnimationKey = "stream_shimmer_translate_x"
19
+
20
+ private let baseLayer = CALayer()
21
+ private let shimmerLayer = CAGradientLayer()
22
+
23
+ private var baseColor: UIColor = UIColor(white: 1, alpha: 0)
24
+ private var gradientColor: UIColor = UIColor(white: 1, alpha: defaultHighlightAlpha)
25
+ private var enabled = false
26
+ private var shimmerDuration: CFTimeInterval = defaultShimmerDuration
27
+ private var lastAnimatedDuration: CFTimeInterval = 0
28
+ private var lastAnimatedSize: CGSize = .zero
29
+ private var isAppActive = true
30
+
31
+ public override init(frame: CGRect) {
32
+ super.init(frame: frame)
33
+ setupLayers()
34
+ setupLifecycleObservers()
35
+ }
36
+
37
+ public required init?(coder: NSCoder) {
38
+ super.init(coder: coder)
39
+ setupLayers()
40
+ setupLifecycleObservers()
41
+ }
42
+
43
+ deinit {
44
+ NotificationCenter.default.removeObserver(self)
45
+ }
46
+
47
+ public override func layoutSubviews() {
48
+ super.layoutSubviews()
49
+ updateLayersForCurrentState()
50
+ }
51
+
52
+ public override func didMoveToWindow() {
53
+ super.didMoveToWindow()
54
+ if window == nil {
55
+ // Detaching from window means this view is no longer drawable. Stop and clear animation so
56
+ // a later reattach starts from a clean state.
57
+ stopAnimation()
58
+ } else {
59
+ // Reattaching (including reparenting across windows) re-evaluates state and restarts only
60
+ // when needed by current bounds/visibility/enablement.
61
+ updateLayersForCurrentState()
62
+ }
63
+ }
64
+
65
+ public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
66
+ super.traitCollectionDidChange(previousTraitCollection)
67
+ if let previousTraitCollection,
68
+ traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection)
69
+ {
70
+ // In current usage, colors are typically driven by JS props. We still refresh on trait
71
+ // changes so dynamically resolved native colors remain correct if that path is used later.
72
+ updateLayersForCurrentState()
73
+ }
74
+ }
75
+
76
+ public func apply(
77
+ baseColor: UIColor,
78
+ gradientColor: UIColor,
79
+ durationMilliseconds: Double,
80
+ enabled: Bool
81
+ ) {
82
+ self.baseColor = baseColor
83
+ self.gradientColor = gradientColor
84
+ shimmerDuration = Self.normalizedDuration(milliseconds: durationMilliseconds)
85
+ self.enabled = enabled
86
+ updateLayersForCurrentState()
87
+ }
88
+
89
+ public func stopAnimation() {
90
+ shimmerLayer.removeAnimation(forKey: Self.shimmerAnimationKey)
91
+ lastAnimatedDuration = 0
92
+ lastAnimatedSize = .zero
93
+ }
94
+
95
+ private func setupLayers() {
96
+ isUserInteractionEnabled = false
97
+
98
+ shimmerLayer.contentsScale = UIScreen.main.scale
99
+ shimmerLayer.allowsEdgeAntialiasing = true
100
+ shimmerLayer.startPoint = CGPoint(x: 0, y: 0.5)
101
+ shimmerLayer.endPoint = CGPoint(x: 1, y: 0.5)
102
+ shimmerLayer.locations = [0.0, 0.08, 0.2, 0.32, 0.4, 0.5, 0.6, 0.68, 0.8, 0.92, 1.0]
103
+
104
+ layer.addSublayer(baseLayer)
105
+ layer.addSublayer(shimmerLayer)
106
+ }
107
+
108
+ private func setupLifecycleObservers() {
109
+ NotificationCenter.default.addObserver(
110
+ self,
111
+ selector: #selector(handleWillEnterForeground),
112
+ name: UIApplication.willEnterForegroundNotification,
113
+ object: nil
114
+ )
115
+ NotificationCenter.default.addObserver(
116
+ self,
117
+ selector: #selector(handleDidEnterBackground),
118
+ name: UIApplication.didEnterBackgroundNotification,
119
+ object: nil
120
+ )
121
+ }
122
+
123
+ @objc
124
+ private func handleWillEnterForeground() {
125
+ // iOS can drop active layer animations while the app is backgrounded. We explicitly rerun
126
+ // a state update on foreground so shimmer reliably restarts when returning to the app.
127
+ isAppActive = true
128
+ updateLayersForCurrentState()
129
+ }
130
+
131
+ @objc
132
+ private func handleDidEnterBackground() {
133
+ isAppActive = false
134
+ stopAnimation()
135
+ }
136
+
137
+ private func updateLayersForCurrentState() {
138
+ let bounds = self.bounds
139
+ guard !bounds.isEmpty else {
140
+ stopAnimation()
141
+ return
142
+ }
143
+
144
+ baseLayer.frame = bounds
145
+ baseLayer.backgroundColor = baseColor.cgColor
146
+
147
+ updateShimmerLayer(for: bounds)
148
+ updateShimmerAnimation(for: bounds)
149
+ }
150
+
151
+ private func updateShimmerLayer(for bounds: CGRect) {
152
+ // Rebuild the shimmer gradient for current width/colors. Keep this tied to real state changes
153
+ // such as layout/prop updates, not continuous per frame calls.
154
+ let shimmerWidth = max(bounds.width * Self.shimmerStripWidthRatio, 1)
155
+ let transparentHighlight = color(gradientColor, alphaFactor: 0)
156
+ shimmerLayer.frame = CGRect(x: -shimmerWidth, y: 0, width: shimmerWidth, height: bounds.height)
157
+ shimmerLayer.colors = [
158
+ transparentHighlight.cgColor,
159
+ color(gradientColor, alphaFactor: Self.edgeHighlightAlpha).cgColor,
160
+ color(gradientColor, alphaFactor: Self.softHighlightAlpha).cgColor,
161
+ color(gradientColor, alphaFactor: Self.midHighlightAlpha).cgColor,
162
+ color(gradientColor, alphaFactor: Self.innerHighlightAlpha).cgColor,
163
+ gradientColor.cgColor,
164
+ color(gradientColor, alphaFactor: Self.innerHighlightAlpha).cgColor,
165
+ color(gradientColor, alphaFactor: Self.midHighlightAlpha).cgColor,
166
+ color(gradientColor, alphaFactor: Self.softHighlightAlpha).cgColor,
167
+ color(gradientColor, alphaFactor: Self.edgeHighlightAlpha).cgColor,
168
+ transparentHighlight.cgColor,
169
+ ]
170
+ shimmerLayer.isHidden = !enabled
171
+ }
172
+
173
+ private func updateShimmerAnimation(for bounds: CGRect) {
174
+ guard enabled, isAppActive, window != nil, bounds.width > 0, bounds.height > 0 else {
175
+ stopAnimation()
176
+ return
177
+ }
178
+
179
+ // If an animation already exists for the same size, keep it running instead of restarting.
180
+ if shimmerLayer.animation(forKey: Self.shimmerAnimationKey) != nil,
181
+ lastAnimatedSize == bounds.size,
182
+ lastAnimatedDuration == shimmerDuration
183
+ {
184
+ return
185
+ }
186
+
187
+ stopAnimation()
188
+
189
+ // Start just outside the left edge and sweep fully past the right edge for a clean pass.
190
+ let shimmerWidth = max(bounds.width * Self.shimmerStripWidthRatio, 1)
191
+ let animation = CABasicAnimation(keyPath: "transform.translation.x")
192
+ animation.fromValue = 0
193
+ animation.toValue = bounds.width + shimmerWidth
194
+ animation.duration = shimmerDuration
195
+ animation.repeatCount = .infinity
196
+ animation.timingFunction = CAMediaTimingFunction(name: .linear)
197
+ animation.isRemovedOnCompletion = true
198
+ shimmerLayer.add(animation, forKey: Self.shimmerAnimationKey)
199
+ lastAnimatedDuration = shimmerDuration
200
+ lastAnimatedSize = bounds.size
201
+ }
202
+
203
+ private static func normalizedDuration(milliseconds: Double) -> CFTimeInterval {
204
+ guard milliseconds > 0 else { return defaultShimmerDuration }
205
+ return milliseconds / 1000
206
+ }
207
+
208
+ private func color(_ color: UIColor, alphaFactor: CGFloat) -> UIColor {
209
+ // Preserve the resolved color channels and shape only alpha for smooth highlight falloff.
210
+ let resolvedColor = color.resolvedColor(with: traitCollection)
211
+
212
+ var red: CGFloat = 0
213
+ var green: CGFloat = 0
214
+ var blue: CGFloat = 0
215
+ var alpha: CGFloat = 0
216
+
217
+ if resolvedColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
218
+ return UIColor(red: red, green: green, blue: blue, alpha: alpha * alphaFactor)
219
+ }
220
+
221
+ guard
222
+ let converted = resolvedColor.cgColor.converted(
223
+ to: CGColorSpace(name: CGColorSpace.extendedSRGB)!,
224
+ intent: .defaultIntent,
225
+ options: nil
226
+ ),
227
+ let components = converted.components
228
+ else {
229
+ return resolvedColor.withAlphaComponent(resolvedColor.cgColor.alpha * alphaFactor)
230
+ }
231
+
232
+ switch components.count {
233
+ case 2:
234
+ return UIColor(
235
+ white: components[0],
236
+ alpha: components[1] * alphaFactor
237
+ )
238
+ case 4:
239
+ return UIColor(
240
+ red: components[0],
241
+ green: components[1],
242
+ blue: components[2],
243
+ alpha: components[3] * alphaFactor
244
+ )
245
+ default:
246
+ return resolvedColor.withAlphaComponent(resolvedColor.cgColor.alpha * alphaFactor)
247
+ }
248
+ }
249
+ }
@@ -0,0 +1,22 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+ #import <React/RCTViewComponentView.h>
5
+ #endif
6
+
7
+ #ifndef StreamShimmerViewComponentView_h
8
+ #define StreamShimmerViewComponentView_h
9
+
10
+ NS_ASSUME_NONNULL_BEGIN
11
+
12
+ @interface StreamShimmerViewComponentView :
13
+ #ifdef RCT_NEW_ARCH_ENABLED
14
+ RCTViewComponentView
15
+ #else
16
+ UIView
17
+ #endif
18
+ @end
19
+
20
+ NS_ASSUME_NONNULL_END
21
+
22
+ #endif /* StreamShimmerViewComponentView_h */
@@ -0,0 +1,108 @@
1
+ #import "StreamShimmerViewComponentView.h"
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+
5
+ #if __has_include(<react/renderer/components/StreamChatReactNativeSpec/ComponentDescriptors.h>)
6
+ #import <react/renderer/components/StreamChatReactNativeSpec/ComponentDescriptors.h>
7
+ #import <react/renderer/components/StreamChatReactNativeSpec/Props.h>
8
+ #import <react/renderer/components/StreamChatReactNativeSpec/RCTComponentViewHelpers.h>
9
+ #elif __has_include(<react/renderer/components/StreamChatExpoSpec/ComponentDescriptors.h>)
10
+ #import <react/renderer/components/StreamChatExpoSpec/ComponentDescriptors.h>
11
+ #import <react/renderer/components/StreamChatExpoSpec/Props.h>
12
+ #import <react/renderer/components/StreamChatExpoSpec/RCTComponentViewHelpers.h>
13
+ #else
14
+ #error "Unable to find generated codegen headers for StreamShimmerView."
15
+ #endif
16
+
17
+ #if __has_include(<stream_chat_react_native/stream_chat_react_native-Swift.h>)
18
+ #import <stream_chat_react_native/stream_chat_react_native-Swift.h>
19
+ #elif __has_include(<stream_chat_expo/stream_chat_expo-Swift.h>)
20
+ #import <stream_chat_expo/stream_chat_expo-Swift.h>
21
+ #elif __has_include("stream_chat_react_native-Swift.h")
22
+ #import "stream_chat_react_native-Swift.h"
23
+ #elif __has_include("stream_chat_expo-Swift.h")
24
+ #import "stream_chat_expo-Swift.h"
25
+ #else
26
+ #error "Unable to import generated Swift header for StreamShimmerView."
27
+ #endif
28
+
29
+ #import <React/RCTConversions.h>
30
+
31
+ using namespace facebook::react;
32
+
33
+ @interface StreamShimmerViewComponentView () <RCTStreamShimmerViewViewProtocol>
34
+ @end
35
+
36
+ // Fabric bridge for StreamShimmerView. This component view owns the native shimmer instance,
37
+ // applies codegen props, and keeps shimmer rendered as a background layer while Fabric manages
38
+ // React children. Keeping shimmer as a layer avoids child-order conflicts during mount/unmount.
39
+ @implementation StreamShimmerViewComponentView {
40
+ StreamShimmerView *_shimmerView;
41
+ }
42
+
43
+ + (ComponentDescriptorProvider)componentDescriptorProvider
44
+ {
45
+ return concreteComponentDescriptorProvider<StreamShimmerViewComponentDescriptor>();
46
+ }
47
+
48
+ - (instancetype)initWithFrame:(CGRect)frame
49
+ {
50
+ if (self = [super initWithFrame:frame]) {
51
+ static const auto defaultProps = std::make_shared<const StreamShimmerViewProps>();
52
+ _props = defaultProps;
53
+
54
+ _shimmerView = [[StreamShimmerView alloc] initWithFrame:self.bounds];
55
+ _shimmerView.userInteractionEnabled = NO;
56
+ [self.layer insertSublayer:_shimmerView.layer atIndex:0];
57
+ }
58
+
59
+ return self;
60
+ }
61
+
62
+ - (void)layoutSubviews
63
+ {
64
+ [super layoutSubviews];
65
+ _shimmerView.frame = self.bounds;
66
+
67
+ // Keep shimmer pinned as the layer furthest back. Some layer operations can reorder sublayers, and
68
+ // this guard restores expected layering without touching Fabric managed child views.
69
+ BOOL needsReinsert = _shimmerView.layer.superlayer != self.layer;
70
+ if (!needsReinsert) {
71
+ CALayer *firstLayer = self.layer.sublayers.firstObject;
72
+ needsReinsert = firstLayer != _shimmerView.layer;
73
+ }
74
+ if (needsReinsert) {
75
+ [self.layer insertSublayer:_shimmerView.layer atIndex:0];
76
+ }
77
+ }
78
+
79
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
80
+ {
81
+ const auto &newProps = *std::static_pointer_cast<const StreamShimmerViewProps>(props);
82
+
83
+ UIColor *baseColor = RCTUIColorFromSharedColor(newProps.baseColor) ?: [UIColor colorWithWhite:1 alpha:0];
84
+ UIColor *gradientColor = RCTUIColorFromSharedColor(newProps.gradientColor) ?: [UIColor whiteColor];
85
+
86
+ [_shimmerView applyWithBaseColor:baseColor
87
+ gradientColor:gradientColor
88
+ durationMilliseconds:newProps.duration
89
+ enabled:newProps.enabled];
90
+
91
+ [super updateProps:props oldProps:oldProps];
92
+ }
93
+
94
+ - (void)prepareForRecycle
95
+ {
96
+ [super prepareForRecycle];
97
+ // Defensive cleanup for recycled cells/views so offscreen instances do not keep animating.
98
+ [_shimmerView stopAnimation];
99
+ }
100
+
101
+ - (void)dealloc
102
+ {
103
+ [_shimmerView stopAnimation];
104
+ }
105
+
106
+ @end
107
+
108
+ #endif
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stream-chat-react-native",
3
3
  "description": "The official React Native SDK for Stream Chat, a service for building chat applications",
4
- "version": "8.13.7",
4
+ "version": "9.0.0-beta.10",
5
5
  "homepage": "https://www.npmjs.com/package/stream-chat-react-native",
6
6
  "author": {
7
7
  "company": "Stream.io Inc",
@@ -15,7 +15,10 @@
15
15
  "files": [
16
16
  "src",
17
17
  "types",
18
- "android",
18
+ "android/src",
19
+ "android/build.gradle",
20
+ "android/gradle.properties",
21
+ "android/gradle",
19
22
  "ios",
20
23
  "*.podspec",
21
24
  "package.json"
@@ -26,7 +29,7 @@
26
29
  "dependencies": {
27
30
  "es6-symbol": "^3.1.3",
28
31
  "mime": "^4.0.7",
29
- "stream-chat-react-native-core": "8.13.7"
32
+ "stream-chat-react-native-core": "9.0.0-beta.10"
30
33
  },
31
34
  "peerDependencies": {
32
35
  "@react-native-camera-roll/camera-roll": ">=7.8.0",
@@ -78,16 +81,22 @@
78
81
  }
79
82
  },
80
83
  "scripts": {
81
- "prepack": " cp ../../README.md .",
82
- "postpack": "rm README.md"
84
+ "postinstall": "if [ -f ../scripts/sync-shared-native.sh ] && [ -d ../shared-native/ios ]; then bash ../scripts/sync-shared-native.sh native-package; fi",
85
+ "prepack": "bash ../scripts/sync-shared-native.sh native-package && cp ../../README.md .",
86
+ "postpack": "rm README.md && bash ../scripts/clean-shared-native-copies.sh native-package"
83
87
  },
84
88
  "devDependencies": {
85
89
  "react-native": "^0.79.3"
86
90
  },
87
91
  "codegenConfig": {
88
92
  "name": "StreamChatReactNativeSpec",
89
- "type": "modules",
90
- "jsSrcsDir": "src/native"
93
+ "type": "all",
94
+ "jsSrcsDir": "src/native",
95
+ "ios": {
96
+ "componentProvider": {
97
+ "StreamShimmerView": "StreamShimmerViewComponentView"
98
+ }
99
+ }
91
100
  },
92
101
  "resolutions": {
93
102
  "@types/react": "^19.0.0"
@@ -22,7 +22,6 @@ export const compressImage = async ({
22
22
  Math.min(Math.max(0, compressImageQuality), 1) * 100,
23
23
  0,
24
24
  undefined,
25
- false,
26
25
  { mode: 'cover' },
27
26
  );
28
27
  return compressedUri;
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  getLocalAssetUri,
12
12
  getPhotos,
13
13
  iOS14RefreshGallerySelection,
14
+ NativeShimmerView,
14
15
  oniOS14GalleryLibrarySelectionChange,
15
16
  overrideAudioRecordingConfiguration,
16
17
  pickDocument,
@@ -32,6 +33,7 @@ registerNativeHandlers({
32
33
  getLocalAssetUri,
33
34
  getPhotos,
34
35
  iOS14RefreshGallerySelection,
36
+ NativeShimmerView,
35
37
  oniOS14GalleryLibrarySelectionChange,
36
38
  overrideAudioRecordingConfiguration,
37
39
  pickDocument,
@@ -13,7 +13,6 @@ export interface Spec extends TurboModule {
13
13
  onlyScaleDown: boolean,
14
14
  rotation?: number,
15
15
  outputPath?: string | null,
16
- keepMeta?: boolean,
17
16
  ): Promise<{
18
17
  base64: string;
19
18
  height: number;