Skip to content

Commit 6cb2001

Browse files
committed
feat: implement video attachment upload previews with thumbnail
1 parent 215b442 commit 6cb2001

File tree

6 files changed

+113
-13
lines changed

6 files changed

+113
-13
lines changed

examples/SampleApp/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3584,7 +3584,7 @@ SPEC CHECKSUMS:
35843584
op-sqlite: a7e46cfdaebeef219fd0e939332967af9fe6d406
35853585
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
35863586
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
3587-
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
3587+
RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f
35883588
RCTDeprecation: 300c5eb91114d4339b0bb39505d0f4824d7299b7
35893589
RCTRequired: e0446b01093475b7082fbeee5d1ef4ad1fe20ac4
35903590
RCTTypeSafety: cb974efcdc6695deedf7bf1eb942f2a0603a063f

package/src/components/Channel/Channel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ import { AttachmentUploadProgressIndicator as AttachmentUploadProgressIndicatorD
179179
import { AudioAttachmentUploadPreview as AudioAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview';
180180
import { FileAttachmentUploadPreview as FileAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview';
181181
import { ImageAttachmentUploadPreview as ImageAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview';
182+
import { VideoAttachmentUploadPreview as VideoAttachmentUploadPreviewDefault } from '../MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview';
182183
import { AudioRecorder as AudioRecorderDefault } from '../MessageInput/components/AudioRecorder/AudioRecorder';
183184
import { AudioRecordingButton as AudioRecordingButtonDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingButton';
184185
import { AudioRecordingInProgress as AudioRecordingInProgressDefault } from '../MessageInput/components/AudioRecorder/AudioRecordingInProgress';
@@ -758,7 +759,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
758759
UnreadMessagesNotification = UnreadMessagesNotificationDefault,
759760
AttachmentUploadProgressIndicator = AttachmentUploadProgressIndicatorDefault,
760761
UrlPreview = CardDefault,
761-
VideoAttachmentUploadPreview = FileAttachmentUploadPreviewDefault,
762+
VideoAttachmentUploadPreview = VideoAttachmentUploadPreviewDefault,
762763
VideoThumbnail = VideoThumbnailDefault,
763764
isOnline,
764765
maximumMessageLimit,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React, { useMemo } from 'react';
2+
3+
import { StyleSheet, Text, View } from 'react-native';
4+
5+
import { LocalImageAttachment, LocalVideoAttachment } from 'stream-chat';
6+
7+
import { FileAttachmentUploadPreview } from './FileAttachmentUploadPreview';
8+
import { ImageAttachmentUploadPreview } from './ImageAttachmentUploadPreview';
9+
10+
import { useTheme } from '../../../../contexts/themeContext/ThemeContext';
11+
import { Recorder } from '../../../../icons';
12+
import { primitives } from '../../../../theme';
13+
import { UploadAttachmentPreviewProps } from '../../../../types/types';
14+
import { formatMsToMinSec } from '../../../../utils/utils';
15+
16+
export type VideoAttachmentUploadPreviewProps<CustomLocalMetadata = Record<string, unknown>> =
17+
UploadAttachmentPreviewProps<LocalVideoAttachment<CustomLocalMetadata>>;
18+
19+
export const VideoAttachmentUploadPreview = ({
20+
attachment,
21+
handleRetry,
22+
removeAttachments,
23+
}: VideoAttachmentUploadPreviewProps) => {
24+
const styles = useStyles();
25+
26+
const durationLabel = useMemo(
27+
() => (attachment.duration ? formatMsToMinSec(attachment.duration) : undefined),
28+
[attachment.duration],
29+
);
30+
31+
return attachment.localMetadata.previewUri ? (
32+
<>
33+
<ImageAttachmentUploadPreview
34+
attachment={attachment as unknown as LocalImageAttachment}
35+
handleRetry={handleRetry}
36+
removeAttachments={removeAttachments}
37+
/>
38+
{durationLabel ? (
39+
<View style={styles.durationContainer}>
40+
<Recorder height={12} width={12} pathFill={styles.durationText.color} />
41+
<Text style={styles.durationText}>{durationLabel}</Text>
42+
</View>
43+
) : null}
44+
</>
45+
) : (
46+
<FileAttachmentUploadPreview
47+
attachment={attachment}
48+
handleRetry={handleRetry}
49+
removeAttachments={removeAttachments}
50+
/>
51+
);
52+
};
53+
54+
const useStyles = () => {
55+
const {
56+
theme: {
57+
semantics,
58+
messageInput: {
59+
videoAttachmentUploadPreview: { durationContainer, durationText },
60+
},
61+
},
62+
} = useTheme();
63+
64+
const { badgeBgInverse, badgeText } = semantics;
65+
66+
return useMemo(
67+
() =>
68+
StyleSheet.create({
69+
durationContainer: {
70+
position: 'absolute',
71+
left: 8,
72+
bottom: 8,
73+
borderRadius: primitives.radiusMax,
74+
backgroundColor: badgeBgInverse,
75+
paddingVertical: primitives.spacingXxs,
76+
paddingHorizontal: primitives.spacingXs,
77+
flexDirection: 'row',
78+
alignItems: 'center',
79+
...durationContainer,
80+
},
81+
durationText: {
82+
fontSize: primitives.typographyFontSizeXxs,
83+
fontWeight: primitives.typographyFontWeightBold,
84+
color: badgeText,
85+
marginLeft: primitives.spacingXxs,
86+
...durationText,
87+
},
88+
}),
89+
[badgeBgInverse, badgeText, durationContainer, durationText],
90+
);
91+
};

package/src/contexts/messageInputContext/MessageInputContext.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type { AttachmentUploadProgressIndicatorProps } from '../../components/Me
3939
import { AudioAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview';
4040
import { FileAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/FileAttachmentUploadPreview';
4141
import { ImageAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/ImageAttachmentUploadPreview';
42+
import { VideoAttachmentUploadPreviewProps } from '../../components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview';
4243
import type { AudioRecorderProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecorder';
4344
import type { AudioRecordingButtonProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingButton';
4445
import type { AudioRecordingInProgressProps } from '../../components/MessageInput/components/AudioRecorder/AudioRecordingInProgress';
@@ -251,7 +252,7 @@ export type InputMessageInputContextValue = {
251252
AudioAttachmentUploadPreview: React.ComponentType<AudioAttachmentUploadPreviewProps>;
252253
ImageAttachmentUploadPreview: React.ComponentType<ImageAttachmentUploadPreviewProps>;
253254
FileAttachmentUploadPreview: React.ComponentType<FileAttachmentUploadPreviewProps>;
254-
VideoAttachmentUploadPreview: React.ComponentType<FileAttachmentUploadPreviewProps>;
255+
VideoAttachmentUploadPreview: React.ComponentType<VideoAttachmentUploadPreviewProps>;
255256

256257
/**
257258
* Custom UI component to display the remaining cooldown a user will have to wait before

package/src/contexts/themeContext/utils/theme.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -411,10 +411,8 @@ export type Theme = {
411411
overlay: ViewStyle;
412412
};
413413
videoAttachmentUploadPreview: {
414-
recorderIconContainer: ViewStyle;
415-
recorderIcon: IconProps;
416-
itemContainer: ViewStyle;
417-
upload: ImageStyle;
414+
durationContainer: ViewStyle;
415+
durationText: TextStyle;
418416
};
419417
wrapper: ViewStyle;
420418
linkPreviewList: {
@@ -1160,6 +1158,10 @@ export const defaultTheme: Theme = {
11601158
upload: {},
11611159
wrapper: {},
11621160
},
1161+
videoAttachmentUploadPreview: {
1162+
durationContainer: {},
1163+
durationText: {},
1164+
},
11631165
inputBox: {},
11641166
inputBoxContainer: {},
11651167
inputBoxWrapper: {},
@@ -1222,12 +1224,6 @@ export const defaultTheme: Theme = {
12221224
indicatorColor: '',
12231225
overlay: {},
12241226
},
1225-
videoAttachmentUploadPreview: {
1226-
itemContainer: {},
1227-
recorderIcon: {},
1228-
recorderIconContainer: {},
1229-
upload: {},
1230-
},
12311227
wrapper: {},
12321228
linkPreviewList: {
12331229
linkContainer: {},

package/src/utils/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@ export const getDurationLabelFromDuration = (duration: number) => {
234234
return durationLabel;
235235
};
236236

237+
export const formatMsToMinSec = (ms: number) => {
238+
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
239+
const minutes = Math.floor(totalSeconds / 60);
240+
const seconds = totalSeconds % 60;
241+
242+
const mm = minutes; // no padding for minutes
243+
const ss = String(seconds).padStart(2, '0');
244+
245+
return `${mm}m ${ss}s`.replace(/^0m\s/, '');
246+
};
247+
237248
/**
238249
* Utility to escape special characters in a string.
239250
* @param text

0 commit comments

Comments
 (0)