Skip to content

Commit 298a371

Browse files
feat: add static location and live location support (#2587)
Co-authored-by: martincupela <[email protected]>
1 parent 1d1d8ae commit 298a371

Some content is hidden

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

50 files changed

+1878
-120
lines changed

examples/vite/src/App.tsx

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ChannelSort,
66
LocalMessage,
77
TextComposerMiddleware,
8+
LiveLocationManagerConstructorParameters,
89
} from 'stream-chat';
910
import {
1011
AIStateIndicator,
@@ -18,9 +19,10 @@ import {
1819
Thread,
1920
ThreadList,
2021
useCreateChatClient,
21-
useMessageComposer,
2222
VirtualizedMessageList as MessageList,
2323
Window,
24+
useChatContext,
25+
useLiveLocationSharingManager,
2426
} from 'stream-chat-react';
2527
import { createTextComposerEmojiMiddleware, EmojiPicker } from 'stream-chat-react/emojis';
2628
import { init, SearchIndex } from 'emoji-mart';
@@ -55,13 +57,73 @@ const sort: ChannelSort = { pinned_at: 1, last_message_at: -1, updated_at: -1 };
5557
// @ts-ignore
5658
const isMessageAIGenerated = (message: LocalMessage) => !!message?.ai_generated;
5759

60+
const ShareLiveLocation = () => {
61+
const { channel } = useChatContext();
62+
63+
return (
64+
<button
65+
onClick={() => {
66+
console.log('trying to fetch location');
67+
navigator.geolocation.getCurrentPosition(
68+
(position) => {
69+
const { latitude, longitude } = position.coords;
70+
console.log('got location ', position);
71+
channel?.startLiveLocationSharing({
72+
latitude,
73+
longitude,
74+
end_time: new Date(Date.now() + 1 * 1000 * 3600 * 24).toISOString(),
75+
});
76+
},
77+
console.warn,
78+
{ timeout: 2000 },
79+
);
80+
}}
81+
>
82+
location
83+
</button>
84+
);
85+
};
86+
87+
const watchLocationNormal: LiveLocationManagerConstructorParameters['watchLocation'] = (
88+
watcher,
89+
) => {
90+
const watch = navigator.geolocation.watchPosition((position) => {
91+
watcher({ latitude: position.coords.latitude, longitude: position.coords.longitude });
92+
});
93+
94+
return () => navigator.geolocation.clearWatch(watch);
95+
};
96+
97+
const watchLocationTimed: LiveLocationManagerConstructorParameters['watchLocation'] = (
98+
watcher,
99+
) => {
100+
const timer = setInterval(() => {
101+
navigator.geolocation.getCurrentPosition((position) => {
102+
watcher({
103+
latitude: position.coords.latitude,
104+
longitude: position.coords.longitude,
105+
});
106+
});
107+
}, 5000);
108+
109+
return () => {
110+
clearInterval(timer);
111+
console.log('cleanup');
112+
};
113+
};
114+
58115
const App = () => {
59116
const chatClient = useCreateChatClient({
60117
apiKey,
61118
tokenOrProvider: userToken,
62119
userData: { id: userId },
63120
});
64121

122+
useLiveLocationSharingManager({
123+
client: chatClient,
124+
watchLocation: watchLocationNormal,
125+
});
126+
65127
useEffect(() => {
66128
if (!chatClient) return;
67129

@@ -97,6 +159,7 @@ const App = () => {
97159
<MessageList returnAllReadData />
98160
<AIStateIndicator />
99161
<MessageInput focus audioRecordingEnabled />
162+
<ShareLiveLocation />
100163
</Window>
101164
<Thread virtualized />
102165
</Channel>

i18next-parser.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ module.exports = {
99
namespaceSeparator: false,
1010
output: 'src/i18n/$LOCALE.json',
1111
sort(a, b) {
12-
return a < b ? -1 : 1; // alfabetical order
12+
return a < b ? -1 : 1; // alphabetical order
1313
},
14-
useKeysAsDefaultValue: true,
1514
verbose: true,
1615
};

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
"emoji-mart": "^5.4.0",
143143
"react": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0",
144144
"react-dom": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0",
145-
"stream-chat": "^9.10.1"
145+
"stream-chat": "^9.12.0"
146146
},
147147
"peerDependenciesMeta": {
148148
"@breezystack/lamejs": {
@@ -183,7 +183,7 @@
183183
"@playwright/test": "^1.42.1",
184184
"@semantic-release/changelog": "^6.0.3",
185185
"@semantic-release/git": "^10.0.1",
186-
"@stream-io/stream-chat-css": "^5.11.1",
186+
"@stream-io/stream-chat-css": "^5.11.2",
187187
"@testing-library/dom": "^10.4.0",
188188
"@testing-library/jest-dom": "^6.6.3",
189189
"@testing-library/react": "^16.2.0",
@@ -236,7 +236,7 @@
236236
"react": "^19.0.0",
237237
"react-dom": "^19.0.0",
238238
"semantic-release": "^24.2.3",
239-
"stream-chat": "^9.10.1",
239+
"stream-chat": "^9.12.0",
240240
"ts-jest": "^29.2.5",
241241
"typescript": "^5.4.5",
242242
"typescript-eslint": "^8.17.0"

src/components/Attachment/Attachment.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
isFileAttachment,
55
isImageAttachment,
66
isScrapedContent,
7+
isSharedLocationResponse,
78
isVideoAttachment,
89
isVoiceRecordingAttachment,
910
} from 'stream-chat';
@@ -13,6 +14,7 @@ import {
1314
CardContainer,
1415
FileContainer,
1516
GalleryContainer,
17+
GeolocationContainer,
1618
ImageContainer,
1719
MediaContainer,
1820
UnsupportedAttachmentContainer,
@@ -21,7 +23,7 @@ import {
2123
import { SUPPORTED_VIDEO_FORMATS } from './utils';
2224

2325
import type { ReactPlayerProps } from 'react-player';
24-
import type { Attachment as StreamAttachment } from 'stream-chat';
26+
import type { SharedLocationResponse, Attachment as StreamAttachment } from 'stream-chat';
2527
import type { AttachmentActionsProps } from './AttachmentActions';
2628
import type { AudioProps } from './Audio';
2729
import type { VoiceRecordingProps } from './VoiceRecording';
@@ -31,6 +33,7 @@ import type { GalleryProps, ImageProps } from '../Gallery';
3133
import type { UnsupportedAttachmentProps } from './UnsupportedAttachment';
3234
import type { ActionHandlerReturnType } from '../Message/hooks/useActionHandler';
3335
import type { GroupedRenderedAttachment } from './utils';
36+
import type { GeolocationProps } from './Geolocation';
3437

3538
const CONTAINER_MAP = {
3639
audio: AudioContainer,
@@ -49,12 +52,13 @@ export const ATTACHMENT_GROUPS_ORDER = [
4952
'audio',
5053
'voiceRecording',
5154
'file',
55+
'geolocation',
5256
'unsupported',
5357
] as const;
5458

5559
export type AttachmentProps = {
5660
/** The message attachments to render, see [attachment structure](https://getstream.io/chat/docs/javascript/message_format/?language=javascript) **/
57-
attachments: StreamAttachment[];
61+
attachments: (StreamAttachment | SharedLocationResponse)[];
5862
/** The handler function to call when an action is performed on an attachment, examples include canceling a \/giphy command or shuffling the results. */
5963
actionHandler?: ActionHandlerReturnType;
6064
/** Custom UI component for displaying attachment actions, defaults to and accepts same props as: [AttachmentActions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/AttachmentActions.tsx) */
@@ -67,6 +71,7 @@ export type AttachmentProps = {
6771
File?: React.ComponentType<FileAttachmentProps>;
6872
/** Custom UI component for displaying a gallery of image type attachments, defaults to and accepts same props as: [Gallery](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/Gallery.tsx) */
6973
Gallery?: React.ComponentType<GalleryProps>;
74+
Geolocation?: React.ComponentType<GeolocationProps>;
7075
/** Custom UI component for displaying an image type attachment, defaults to and accepts same props as: [Image](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/Image.tsx) */
7176
Image?: React.ComponentType<ImageProps>;
7277
/** Optional flag to signal that an attachment is a displayed as a part of a quoted message */
@@ -113,16 +118,26 @@ const renderGroupedAttachments = ({
113118
.filter((attachment) => !isImageAttachment(attachment))
114119
.reduce<GroupedRenderedAttachment>(
115120
(typeMap, attachment) => {
116-
const attachmentType = getAttachmentType(attachment);
117-
118-
const Container = CONTAINER_MAP[attachmentType];
119-
typeMap[attachmentType].push(
120-
<Container
121-
key={`${attachmentType}-${typeMap[attachmentType].length}`}
122-
{...rest}
123-
attachment={attachment}
124-
/>,
125-
);
121+
if (isSharedLocationResponse(attachment)) {
122+
typeMap.geolocation.push(
123+
<GeolocationContainer
124+
{...rest}
125+
key='geolocation-container'
126+
location={attachment}
127+
/>,
128+
);
129+
} else {
130+
const attachmentType = getAttachmentType(attachment);
131+
132+
const Container = CONTAINER_MAP[attachmentType];
133+
typeMap[attachmentType].push(
134+
<Container
135+
key={`${attachmentType}-${typeMap[attachmentType].length}`}
136+
{...rest}
137+
attachment={attachment}
138+
/>,
139+
);
140+
}
126141

127142
return typeMap;
128143
},
@@ -137,6 +152,7 @@ const renderGroupedAttachments = ({
137152
image: [],
138153
// eslint-disable-next-line sort-keys
139154
gallery: [],
155+
geolocation: [],
140156
voiceRecording: [],
141157
},
142158
);

src/components/Attachment/AttachmentContainer.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ import React, { useLayoutEffect, useRef, useState } from 'react';
33
import ReactPlayer from 'react-player';
44
import clsx from 'clsx';
55
import * as linkify from 'linkifyjs';
6-
import type { Attachment, LocalAttachment } from 'stream-chat';
6+
import type { Attachment, LocalAttachment, SharedLocationResponse } from 'stream-chat';
7+
import { isSharedLocationResponse } from 'stream-chat';
78

89
import { AttachmentActions as DefaultAttachmentActions } from './AttachmentActions';
910
import { Audio as DefaultAudio } from './Audio';
1011
import { VoiceRecording as DefaultVoiceRecording } from './VoiceRecording';
1112
import { Gallery as DefaultGallery, ImageComponent as DefaultImage } from '../Gallery';
1213
import { Card as DefaultCard } from './Card';
1314
import { FileAttachment as DefaultFile } from './FileAttachment';
15+
import { Geolocation as DefaultGeolocation } from './Geolocation';
1416
import { UnsupportedAttachment as DefaultUnsupportedAttachment } from './UnsupportedAttachment';
1517
import type {
1618
AttachmentComponentType,
1719
GalleryAttachment,
20+
GeolocationContainerProps,
1821
RenderAttachmentProps,
1922
RenderGalleryProps,
2023
} from './utils';
@@ -26,7 +29,7 @@ import type {
2629
} from '../../types/types';
2730

2831
export type AttachmentContainerProps = {
29-
attachment: Attachment | GalleryAttachment;
32+
attachment: Attachment | GalleryAttachment | SharedLocationResponse;
3033
componentType: AttachmentComponentType;
3134
};
3235
export const AttachmentWithinContainer = ({
@@ -37,7 +40,7 @@ export const AttachmentWithinContainer = ({
3740
const isGAT = isGalleryAttachmentType(attachment);
3841
let extra = '';
3942

40-
if (!isGAT) {
43+
if (!isGAT && !isSharedLocationResponse(attachment)) {
4144
extra =
4245
componentType === 'card' && !attachment?.image_url && !attachment?.thumb_url
4346
? 'no-image'
@@ -50,7 +53,9 @@ export const AttachmentWithinContainer = ({
5053
'str-chat__message-attachment str-chat__message-attachment-dynamic-size',
5154
{
5255
[`str-chat__message-attachment--${componentType}`]: componentType,
53-
[`str-chat__message-attachment--${attachment?.type}`]: attachment?.type,
56+
[`str-chat__message-attachment--${(attachment as Attachment)?.type}`]: (
57+
attachment as Attachment
58+
)?.type,
5459
[`str-chat__message-attachment--${componentType}--${extra}`]:
5560
componentType && extra,
5661
'str-chat__message-attachment--svg-image': isSvgAttachment(attachment),
@@ -288,6 +293,15 @@ export const MediaContainer = (props: RenderAttachmentProps) => {
288293
);
289294
};
290295

296+
export const GeolocationContainer = ({
297+
Geolocation = DefaultGeolocation,
298+
location,
299+
}: GeolocationContainerProps) => (
300+
<AttachmentWithinContainer attachment={location} componentType='geolocation'>
301+
<Geolocation location={location} />
302+
</AttachmentWithinContainer>
303+
);
304+
291305
export const UnsupportedAttachmentContainer = ({
292306
attachment,
293307
UnsupportedAttachment = DefaultUnsupportedAttachment,

0 commit comments

Comments
 (0)