Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add static location and live location support #2587

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
import {
ChannelFilters,
ChannelOptions,
ChannelSort,
LiveLocationManagerConstructorParameters,
} from 'stream-chat';
import {
AIStateIndicator,
Channel,
Expand All @@ -13,6 +18,8 @@ import {
useCreateChatClient,
ThreadList,
ChatView,
useChatContext,
useLiveLocationSharingManager,
} from 'stream-chat-react';
import 'stream-chat-react/css/v2/index.css';

Expand Down Expand Up @@ -64,13 +71,68 @@ type StreamChatGenerics = {
userType: LocalUserType;
};

const ShareLiveLocation = () => {
const { channel } = useChatContext();

return (
<button
onClick={() => {
console.log('trying to fetch location');
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
console.log('got location ', position);
channel?.startLiveLocationSharing({
latitude,
longitude,
end_time: new Date(Date.now() + 1 * 1000 * 3600 * 24).toISOString(),
});
},
console.warn,
{ timeout: 2000 },
);
}}
>
location
</button>
);
};

const watchLocationNormal: LiveLocationManagerConstructorParameters['watchLocation'] = (
watcher,
) => {
const watch = navigator.geolocation.watchPosition((position) => {
watcher({ latitude: position.coords.latitude, longitude: position.coords.longitude });
});

return () => navigator.geolocation.clearWatch(watch);
};

const watchLocationTimed: LiveLocationManagerConstructorParameters['watchLocation'] = (watcher) => {
const timer = setInterval(() => {
navigator.geolocation.getCurrentPosition((position) => {
watcher({ latitude: position.coords.latitude, longitude: position.coords.longitude });
});
}, 5000);

return () => {
clearInterval(timer);
console.log('cleanup');
};
};

const App = () => {
const chatClient = useCreateChatClient<StreamChatGenerics>({
apiKey,
tokenOrProvider: userToken,
userData: { id: userId },
});

useLiveLocationSharingManager({
client: chatClient,
watchLocation: watchLocationNormal,
});

if (!chatClient) return <>Loading...</>;

return (
Expand All @@ -92,6 +154,7 @@ const App = () => {
<MessageList returnAllReadData />
<AIStateIndicator />
<MessageInput focus />
<ShareLiveLocation />
</Window>
<Thread virtualized />
</Channel>
Expand Down
9 changes: 9 additions & 0 deletions src/components/Attachment/Attachment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CardContainer,
FileContainer,
GalleryContainer,
GeolocationContainer,
ImageContainer,
MediaContainer,
UnsupportedAttachmentContainer,
Expand All @@ -38,6 +39,7 @@ const CONTAINER_MAP = {
audio: AudioContainer,
card: CardContainer,
file: FileContainer,
geolocation: GeolocationContainer,
media: MediaContainer,
unsupported: UnsupportedAttachmentContainer,
voiceRecording: VoiceRecordingContainer,
Expand All @@ -51,6 +53,7 @@ export const ATTACHMENT_GROUPS_ORDER = [
'audio',
'voiceRecording',
'file',
'geolocation',
'unsupported',
] as const;

Expand All @@ -71,6 +74,9 @@ export type AttachmentProps<
File?: React.ComponentType<FileAttachmentProps<StreamChatGenerics>>;
/** 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) */
Gallery?: React.ComponentType<GalleryProps<StreamChatGenerics>>;
Geolocation?: React.ComponentType<{
attachment: StreamAttachment<StreamChatGenerics>;
}>;
/** 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) */
Image?: React.ComponentType<ImageProps>;
/** Optional flag to signal that an attachment is a displayed as a part of a quoted message */
Expand Down Expand Up @@ -144,6 +150,7 @@ const renderGroupedAttachments = <
image: [],
// eslint-disable-next-line sort-keys
gallery: [],
geolocation: [],
voiceRecording: [],
},
);
Expand Down Expand Up @@ -183,6 +190,8 @@ const getAttachmentType = <
return 'voiceRecording';
} else if (isFileAttachment(attachment)) {
return 'file';
} else if (attachment.type === 'live_location' || attachment.type === 'static_location') {
return 'geolocation';
}

return 'unsupported';
Expand Down
12 changes: 12 additions & 0 deletions src/components/Attachment/AttachmentContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Gallery as DefaultGallery, ImageComponent as DefaultImage } from '../Ga
import { Card as DefaultCard } from './Card';
import { FileAttachment as DefaultFile } from './FileAttachment';
import { UnsupportedAttachment as DefaultUnsupportedAttachment } from './UnsupportedAttachment';
import { Geolocation as DefaultGeolocation } from './Geolocation';
import {
AttachmentComponentType,
GalleryAttachment,
Expand Down Expand Up @@ -318,6 +319,17 @@ export const MediaContainer = <
);
};

export const GeolocationContainer = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>({
attachment,
Geolocation = DefaultGeolocation,
}: RenderAttachmentProps<StreamChatGenerics>) => (
<>
<Geolocation attachment={attachment} />
</>
);

export const UnsupportedAttachmentContainer = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>({
Expand Down
48 changes: 48 additions & 0 deletions src/components/Attachment/Geolocation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import type { Attachment, DefaultGenerics, ExtendableGenerics } from 'stream-chat';
import { useChatContext, useMessageContext } from '../../context';

export const Geolocation = <SCG extends ExtendableGenerics = DefaultGenerics>({
attachment,
}: {
attachment: Attachment<SCG>;
}) => {
const { channel } = useChatContext();
const { isMyMessage, message } = useMessageContext();

const stoppedSharing = !!attachment.stopped_sharing;
const expired: boolean =
typeof attachment.end_time === 'string' && Date.now() > new Date(attachment.end_time).getTime();

return (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 10,
paddingBlock: 15,
paddingInline: 10,
width: 'auto',
}}
>
{attachment.type === 'live_location' && !stoppedSharing && !expired && isMyMessage() && (
<button
onClick={() =>
channel?.stopLiveLocationSharing({
attachments: message.attachments,
id: message.id,
type: message.type,
})
}
>
Stop sharing
</button>
)}
{/* TODO: {MAP} */}
<span>
lat: {attachment.latitude}, lng: {attachment.longitude}
</span>
{(stoppedSharing || expired) && <span style={{ fontSize: 12 }}>Location sharing ended</span>}
</div>
);
};
37 changes: 37 additions & 0 deletions src/components/Attachment/hooks/useLiveLocationSharingManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { LiveLocationManager } from 'stream-chat';
import { useEffect, useMemo } from 'react';
import type {
ExtendableGenerics,
LiveLocationManagerConstructorParameters,
StreamChat,
} from 'stream-chat';

export const useLiveLocationSharingManager = <SCG extends ExtendableGenerics>({
client,
retrieveAndDeserialize,
serializeAndStore,
watchLocation,
}: Omit<LiveLocationManagerConstructorParameters<SCG>, 'client'> & {
client?: StreamChat<SCG> | null;
}) => {
const manager = useMemo(() => {
if (!client) return null;

return new LiveLocationManager<SCG>({
client,
retrieveAndDeserialize,
serializeAndStore,
watchLocation,
});
}, [client, retrieveAndDeserialize, serializeAndStore, watchLocation]);

useEffect(() => {
if (!manager) return;

manager.registerSubscriptions();

return () => manager.unregisterSubscriptions();
}, [manager]);

return manager;
};
1 change: 1 addition & 0 deletions src/components/Attachment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './components';
export * from './UnsupportedAttachment';
export * from './FileAttachment';
export * from './utils';
export * from './hooks/useLiveLocationSharingManager';
Loading