Skip to content

Commit 70d28b4

Browse files
committed
Merge branch 'develop' into fix/expo-app-ui-bugs
2 parents 9493a7b + 5f83ae5 commit 70d28b4

File tree

54 files changed

+1704
-545
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1704
-545
lines changed

examples/SampleApp/src/screens/ThreadScreen.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
useTheme,
1212
useTranslationContext,
1313
useTypingString,
14+
PortalWhileClosingView,
1415
} from 'stream-chat-react-native';
1516
import { useStateStore } from 'stream-chat-react-native';
1617

@@ -161,7 +162,12 @@ export const ThreadScreen: React.FC<ThreadScreenProps> = ({
161162
onAlsoSentToChannelHeaderPress={onAlsoSentToChannelHeaderPress}
162163
messageId={targetedMessageIdFromParams}
163164
>
164-
<ThreadHeader thread={thread} />
165+
<PortalWhileClosingView
166+
portalHostName='overlay-header'
167+
portalName='channel-header'
168+
>
169+
<ThreadHeader thread={thread} />
170+
</PortalWhileClosingView>
165171
<Thread
166172
onThreadDismount={onThreadDismount}
167173
shouldUseFlashList={messageListImplementation === 'flashlist'}

package/expo-package/src/native/StreamShimmerViewNativeComponent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { ColorValue, HostComponent, ViewProps } from 'react-native';
22

3-
import type { WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
3+
import type { Int32, WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
44
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
55

66
export interface NativeProps extends ViewProps {
77
baseColor?: ColorValue;
8+
duration?: WithDefault<Int32, 1200>;
89
enabled?: WithDefault<boolean, true>;
910
gradientColor?: ColorValue;
1011
}

package/native-package/src/native/StreamShimmerViewNativeComponent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { ColorValue, HostComponent, ViewProps } from 'react-native';
22

3-
import type { WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
3+
import type { Int32, WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
44
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
55

66
export interface NativeProps extends ViewProps {
77
baseColor?: ColorValue;
8+
duration?: WithDefault<Int32, 1200>;
89
enabled?: WithDefault<boolean, true>;
910
gradientColor?: ColorValue;
1011
}

package/shared-native/android/StreamShimmerFrameLayout.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
2828
attrs: AttributeSet? = null,
2929
) : FrameLayout(context, attrs) {
3030
private var baseColor: Int = DEFAULT_BASE_COLOR
31+
private var durationMs: Long = DEFAULT_DURATION_MS
3132
private var gradientColor: Int = DEFAULT_GRADIENT_COLOR
3233
private var enabled: Boolean = true
3334

@@ -40,6 +41,7 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
4041

4142
private var shimmerShader: LinearGradient? = null
4243
private var shimmerTranslateX: Float = 0f
44+
private var animatedDurationMs: Long = 0L
4345
private var animatedViewWidth: Float = 0f
4446
private var animator: ValueAnimator? = null
4547

@@ -61,6 +63,14 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
6163
invalidate()
6264
}
6365

66+
fun setDuration(duration: Int) {
67+
val normalizedDurationMs =
68+
if (duration > 0) duration.toLong() else DEFAULT_DURATION_MS
69+
if (durationMs == normalizedDurationMs) return
70+
durationMs = normalizedDurationMs
71+
updateAnimatorState()
72+
}
73+
6474
fun setShimmerEnabled(enabled: Boolean) {
6575
if (this.enabled == enabled) return
6676
this.enabled = enabled
@@ -190,16 +200,17 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
190200
private fun startShimmer() {
191201
val viewWidth = width.toFloat()
192202
if (viewWidth <= 0f) return
193-
// Keep the existing animator if the same-sized shimmer is already active.
194-
if (animator != null && animatedViewWidth == viewWidth) return
203+
// Keep the existing animator only when size and duration still match the current request.
204+
if (animator != null && animatedViewWidth == viewWidth && animatedDurationMs == durationMs) return
195205

196206
stopShimmer()
197207

198208
// Animate from fully offscreen left to fully offscreen right so the strip enters/exits cleanly.
199209
val shimmerWidth = (viewWidth * SHIMMER_STRIP_WIDTH_RATIO).coerceAtLeast(1f)
200210
animatedViewWidth = viewWidth
211+
animatedDurationMs = durationMs
201212
animator = ValueAnimator.ofFloat(-shimmerWidth, viewWidth).apply {
202-
duration = SHIMMER_DURATION_MS
213+
duration = durationMs
203214
repeatCount = ValueAnimator.INFINITE
204215
interpolator = LinearInterpolator()
205216
addUpdateListener {
@@ -213,6 +224,7 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
213224
private fun stopShimmer() {
214225
animator?.cancel()
215226
animator = null
227+
animatedDurationMs = 0L
216228
animatedViewWidth = 0f
217229
}
218230

@@ -237,8 +249,8 @@ class StreamShimmerFrameLayout @JvmOverloads constructor(
237249

238250
companion object {
239251
private const val DEFAULT_BASE_COLOR = 0x00FFFFFF
252+
private const val DEFAULT_DURATION_MS = 1200L
240253
private const val DEFAULT_GRADIENT_COLOR = 0x59FFFFFF
241-
private const val SHIMMER_DURATION_MS = 1200L
242254
private const val SHIMMER_STRIP_WIDTH_RATIO = 1.25f
243255
private const val EDGE_HIGHLIGHT_ALPHA_FACTOR = 0.1f
244256
private const val SOFT_HIGHLIGHT_ALPHA_FACTOR = 0.24f

package/shared-native/android/StreamShimmerViewManager.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ class StreamShimmerViewManager : ViewGroupManager<StreamShimmerFrameLayout>(),
6363
view.setBaseColor(color ?: DEFAULT_BASE_COLOR)
6464
}
6565

66+
override fun setDuration(view: StreamShimmerFrameLayout, duration: Int) {
67+
view.setDuration(duration)
68+
}
69+
6670
override fun setGradientColor(view: StreamShimmerFrameLayout, color: Int?) {
6771
view.setGradientColor(color ?: DEFAULT_GRADIENT_COLOR)
6872
}

package/shared-native/ios/StreamShimmerView.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ public final class StreamShimmerView: UIView {
1313
private static let midHighlightAlpha: CGFloat = 0.48
1414
private static let innerHighlightAlpha: CGFloat = 0.72
1515
private static let defaultHighlightAlpha: CGFloat = 0.35
16+
private static let defaultShimmerDuration: CFTimeInterval = 1.2
1617
private static let shimmerStripWidthRatio: CGFloat = 1.25
17-
private static let shimmerDuration: CFTimeInterval = 1.2
1818
private static let shimmerAnimationKey = "stream_shimmer_translate_x"
1919

2020
private let baseLayer = CALayer()
@@ -23,6 +23,8 @@ public final class StreamShimmerView: UIView {
2323
private var baseColor: UIColor = UIColor(white: 1, alpha: 0)
2424
private var gradientColor: UIColor = UIColor(white: 1, alpha: defaultHighlightAlpha)
2525
private var enabled = false
26+
private var shimmerDuration: CFTimeInterval = defaultShimmerDuration
27+
private var lastAnimatedDuration: CFTimeInterval = 0
2628
private var lastAnimatedSize: CGSize = .zero
2729
private var isAppActive = true
2830

@@ -74,16 +76,19 @@ public final class StreamShimmerView: UIView {
7476
public func apply(
7577
baseColor: UIColor,
7678
gradientColor: UIColor,
79+
durationMilliseconds: Double,
7780
enabled: Bool
7881
) {
7982
self.baseColor = baseColor
8083
self.gradientColor = gradientColor
84+
shimmerDuration = Self.normalizedDuration(milliseconds: durationMilliseconds)
8185
self.enabled = enabled
8286
updateLayersForCurrentState()
8387
}
8488

8589
public func stopAnimation() {
8690
shimmerLayer.removeAnimation(forKey: Self.shimmerAnimationKey)
91+
lastAnimatedDuration = 0
8792
lastAnimatedSize = .zero
8893
}
8994

@@ -172,7 +177,10 @@ public final class StreamShimmerView: UIView {
172177
}
173178

174179
// If an animation already exists for the same size, keep it running instead of restarting.
175-
if shimmerLayer.animation(forKey: Self.shimmerAnimationKey) != nil, lastAnimatedSize == bounds.size {
180+
if shimmerLayer.animation(forKey: Self.shimmerAnimationKey) != nil,
181+
lastAnimatedSize == bounds.size,
182+
lastAnimatedDuration == shimmerDuration
183+
{
176184
return
177185
}
178186

@@ -183,14 +191,20 @@ public final class StreamShimmerView: UIView {
183191
let animation = CABasicAnimation(keyPath: "transform.translation.x")
184192
animation.fromValue = 0
185193
animation.toValue = bounds.width + shimmerWidth
186-
animation.duration = Self.shimmerDuration
194+
animation.duration = shimmerDuration
187195
animation.repeatCount = .infinity
188196
animation.timingFunction = CAMediaTimingFunction(name: .linear)
189197
animation.isRemovedOnCompletion = true
190198
shimmerLayer.add(animation, forKey: Self.shimmerAnimationKey)
199+
lastAnimatedDuration = shimmerDuration
191200
lastAnimatedSize = bounds.size
192201
}
193202

203+
private static func normalizedDuration(milliseconds: Double) -> CFTimeInterval {
204+
guard milliseconds > 0 else { return defaultShimmerDuration }
205+
return milliseconds / 1000
206+
}
207+
194208
private func color(_ color: UIColor, alphaFactor: CGFloat) -> UIColor {
195209
// Preserve the resolved color channels and shape only alpha for smooth highlight falloff.
196210
let resolvedColor = color.resolvedColor(with: traitCollection)

package/shared-native/ios/StreamShimmerViewComponentView.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
8585

8686
[_shimmerView applyWithBaseColor:baseColor
8787
gradientColor:gradientColor
88+
durationMilliseconds:newProps.duration
8889
enabled:newProps.enabled];
8990

9091
[super updateProps:props oldProps:oldProps];

package/src/components/Attachment/Attachment.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import {
1313

1414
import { AudioAttachment as AudioAttachmentDefault } from './Audio';
1515

16+
import { UnsupportedAttachment as UnsupportedAttachmentDefault } from './UnsupportedAttachment';
1617
import { URLPreview as URLPreviewDefault } from './UrlPreview';
18+
import { URLPreviewCompact as URLPreviewCompactDefault } from './UrlPreview/URLPreviewCompact';
1719

1820
import { FileAttachment as FileAttachmentDefault } from '../../components/Attachment/FileAttachment';
1921
import { Gallery as GalleryDefault } from '../../components/Attachment/Gallery';
@@ -43,7 +45,10 @@ export type AttachmentPropsWithContext = Pick<
4345
| 'Giphy'
4446
| 'isAttachmentEqual'
4547
| 'UrlPreview'
48+
| 'URLPreviewCompact'
4649
| 'myMessageTheme'
50+
| 'urlPreviewType'
51+
| 'UnsupportedAttachment'
4752
> &
4853
Pick<MessageContextValue, 'message'> & {
4954
/**
@@ -64,8 +69,11 @@ const AttachmentWithContext = (props: AttachmentPropsWithContext) => {
6469
Gallery,
6570
Giphy,
6671
UrlPreview,
72+
URLPreviewCompact,
6773
index,
6874
message,
75+
urlPreviewType,
76+
UnsupportedAttachment,
6977
} = props;
7078
const audioAttachmentStyles = useAudioAttachmentStyles();
7179

@@ -74,6 +82,9 @@ const AttachmentWithContext = (props: AttachmentPropsWithContext) => {
7482
}
7583

7684
if (attachment.og_scrape_url || attachment.title_link) {
85+
if (urlPreviewType === 'compact') {
86+
return <URLPreviewCompact attachment={attachment} />;
87+
}
7788
return <UrlPreview attachment={attachment} />;
7889
}
7990

@@ -109,8 +120,7 @@ const AttachmentWithContext = (props: AttachmentPropsWithContext) => {
109120
return <FileAttachment attachment={attachment} />;
110121
}
111122

112-
// TODO: Handle custom attachments
113-
return <UrlPreview attachment={attachment} />;
123+
return <UnsupportedAttachment attachment={attachment} />;
114124
};
115125

116126
const areEqual = (prevProps: AttachmentPropsWithContext, nextProps: AttachmentPropsWithContext) => {
@@ -162,6 +172,9 @@ export const Attachment = (props: AttachmentProps) => {
162172
Giphy: PropGiphy,
163173
myMessageTheme: PropMyMessageTheme,
164174
UrlPreview: PropUrlPreview,
175+
URLPreviewCompact: PropURLPreviewCompact,
176+
urlPreviewType: PropUrlPreviewType,
177+
UnsupportedAttachment: PropUnsupportedAttachment,
165178
} = props;
166179

167180
const {
@@ -172,6 +185,9 @@ export const Attachment = (props: AttachmentProps) => {
172185
isAttachmentEqual,
173186
myMessageTheme: ContextMyMessageTheme,
174187
UrlPreview: ContextUrlPreview,
188+
URLPreviewCompact: ContextURLPreviewCompact,
189+
urlPreviewType: ContextUrlPreviewType,
190+
UnsupportedAttachment: ContextUnsupportedAttachment,
175191
} = useMessagesContext();
176192

177193
const { message } = useMessageContext();
@@ -186,6 +202,11 @@ export const Attachment = (props: AttachmentProps) => {
186202
const Giphy = PropGiphy || ContextGiphy || GiphyDefault;
187203
const UrlPreview = PropUrlPreview || ContextUrlPreview || URLPreviewDefault;
188204
const myMessageTheme = PropMyMessageTheme || ContextMyMessageTheme;
205+
const URLPreviewCompact =
206+
PropURLPreviewCompact || ContextURLPreviewCompact || URLPreviewCompactDefault;
207+
const urlPreviewType = PropUrlPreviewType || ContextUrlPreviewType;
208+
const UnsupportedAttachment =
209+
PropUnsupportedAttachment || ContextUnsupportedAttachment || UnsupportedAttachmentDefault;
189210

190211
return (
191212
<MemoizedAttachment
@@ -199,6 +220,9 @@ export const Attachment = (props: AttachmentProps) => {
199220
isAttachmentEqual,
200221
myMessageTheme,
201222
UrlPreview,
223+
URLPreviewCompact,
224+
urlPreviewType,
225+
UnsupportedAttachment,
202226
}}
203227
/>
204228
);

package/src/components/Attachment/FileAttachment.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { Pressable, StyleProp, StyleSheet, TextStyle, ViewStyle } from 'react-na
33

44
import type { Attachment } from 'stream-chat';
55

6-
import { FilePreview } from './FilePreview';
76
import { openUrlSafely } from './utils/openUrlSafely';
87

9-
import { FileIcon as FileIconDefault, FileIconProps } from '../../components/Attachment/FileIcon';
8+
import { FileIconProps } from '../../components/Attachment/FileIcon';
9+
1010
import {
1111
MessageContextValue,
1212
useMessageContext,
@@ -21,7 +21,7 @@ export type FileAttachmentPropsWithContext = Pick<
2121
MessageContextValue,
2222
'onLongPress' | 'onPress' | 'onPressIn' | 'preventPress'
2323
> &
24-
Pick<MessagesContextValue, 'additionalPressableProps'> & {
24+
Pick<MessagesContextValue, 'additionalPressableProps' | 'FilePreview'> & {
2525
/** The attachment to render */
2626
attachment: Attachment;
2727
attachmentIconSize?: FileIconProps['size'];
@@ -41,6 +41,7 @@ const FileAttachmentWithContext = (props: FileAttachmentPropsWithContext) => {
4141
additionalPressableProps,
4242
attachment,
4343
attachmentIconSize,
44+
FilePreview,
4445
onLongPress,
4546
onPress,
4647
onPressIn,
@@ -98,14 +99,17 @@ export type FileAttachmentProps = Partial<Omit<FileAttachmentPropsWithContext, '
9899
Pick<FileAttachmentPropsWithContext, 'attachment'>;
99100

100101
export const FileAttachment = (props: FileAttachmentProps) => {
102+
const { FilePreview: PropFilePreview } = props;
101103
const { onLongPress, onPress, onPressIn, preventPress } = useMessageContext();
102-
const { additionalPressableProps, FileAttachmentIcon = FileIconDefault } = useMessagesContext();
104+
const { additionalPressableProps, FilePreview: ContextFilePreview } = useMessagesContext();
105+
106+
const FilePreview = PropFilePreview || ContextFilePreview;
103107

104108
return (
105109
<FileAttachmentWithContext
106110
{...{
107111
additionalPressableProps,
108-
FileAttachmentIcon,
112+
FilePreview,
109113
onLongPress,
110114
onPress,
111115
onPressIn,

package/src/components/Attachment/FilePreview.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ export type FilePreviewProps = Partial<Pick<MessagesContextValue, 'FileAttachmen
3030
export const FilePreview = (props: FilePreviewProps) => {
3131
const {
3232
attachment,
33+
FileAttachmentIcon: PropFileAttachmentIcon,
3334
attachmentIconSize,
3435
styles: stylesProp = {},
3536
titleNumberOfLines = 2,
3637
indicator,
3738
} = props;
38-
const { FileAttachmentIcon = FileIconDefault } = useMessagesContext();
39+
const { FileAttachmentIcon: ContextFileAttachmentIcon } = useMessagesContext();
40+
const FileAttachmentIcon = PropFileAttachmentIcon || ContextFileAttachmentIcon || FileIconDefault;
3941

4042
const styles = useStyles();
4143

0 commit comments

Comments
 (0)