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: channel pinning and archiving #2866

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
5 changes: 5 additions & 0 deletions examples/SampleApp/src/ChatUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { UserResponse } from 'stream-chat';
import { StreamChatGenerics } from './types';

export const USER_TOKENS: Record<string, string> = {
luke_skywalker:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0',
e2etest1:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZTJldGVzdDEifQ.XlQOw8nl7fFzHoBkEiTcYGkNo5r7EBYA40LABGOk4hc',
e2etest2:
Expand All @@ -28,6 +30,9 @@ export const USER_TOKENS: Record<string, string> = {
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9kb2xwaGUifQ.tLl-I8ADBhTKB-x5FB9jK4-am0dELLXgydM6VN9rTL8',
};
export const USERS: Record<string, UserResponse<StreamChatGenerics>> = {
luke_skywalker: {
id: 'luke_skywalker',
},
neil: {
id: 'neil',
image: 'https://ca.slack-edge.com/T02RM6X6B-U01173D1D5J-0dead6eea6ea-512',
Expand Down
77 changes: 75 additions & 2 deletions examples/SampleApp/src/components/ChannelInfoOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import React, { useEffect } from 'react';
import { FlatList, Keyboard, SafeAreaView, StyleSheet, Text, View, ViewStyle } from 'react-native';
import {
FlatList,
Keyboard,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
ViewStyle,
} from 'react-native';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import {
Expand Down Expand Up @@ -33,6 +42,8 @@ import {
import { useAppOverlayContext } from '../context/AppOverlayContext';
import { useBottomSheetOverlayContext } from '../context/BottomSheetOverlayContext';
import { useChannelInfoOverlayContext } from '../context/ChannelInfoOverlayContext';
import { Archieve } from '../icons/Archieve';
import { Pin } from '../icons/Pin';

dayjs.extend(relativeTime);

Expand Down Expand Up @@ -111,7 +122,7 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => {
const halfScreenHeight = vh(50);
const width = vw(100) - 60;

const { channel, clientId, navigation } = data || {};
const { channel, clientId, membership, navigation } = data || {};

const {
theme: {
Expand Down Expand Up @@ -361,6 +372,68 @@ export const ChannelInfoOverlay = (props: ChannelInfoOverlayProps) => {
<Text style={[styles.rowText, { color: black }]}>View info</Text>
</View>
</TapGestureHandler>
<Pressable
onPress={async () => {
try {
if (membership?.pinned_at) {
await channel.unpin();
} else {
await channel.pin();
}
} catch (error) {
console.log('Error pinning/unpinning channel', error);
}

setOverlay('none');
}}
>
<View
style={[
styles.row,
{
borderTopColor: border,
},
]}
>
<View style={styles.rowInner}>
<Pin height={24} width={24} />
</View>
<Text style={[styles.rowText, { color: black }]}>
{membership?.pinned_at ? 'Unpin' : 'Pin'}
</Text>
</View>
</Pressable>
<Pressable
onPress={async () => {
try {
if (membership?.archived_at) {
await channel.unarchive();
} else {
await channel.archive();
}
} catch (error) {
console.log('Error archiving/unarchiving channel', error);
}

setOverlay('none');
}}
>
<View
style={[
styles.row,
{
borderTopColor: border,
},
]}
>
<View style={styles.rowInner}>
<Archieve height={24} width={24} />
</View>
<Text style={[styles.rowText, { color: black }]}>
{membership?.archived_at ? 'Unarchieve' : 'Archieve'}
</Text>
</View>
</Pressable>
{otherMembers.length > 1 && (
<TapGestureHandler
onHandlerStateChange={({ nativeEvent: { state } }) => {
Expand Down
34 changes: 31 additions & 3 deletions examples/SampleApp/src/components/ChannelPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { RectButton } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import {
ChannelPreviewMessenger,
ChannelPreviewMessengerProps,
ChannelPreviewStatus,
ChannelPreviewStatusProps,
Delete,
MenuPointHorizontal,
useChannelMembershipState,
useChatContext,
useTheme,
} from 'stream-chat-react-native';
Expand All @@ -19,6 +22,7 @@ import { useChannelInfoOverlayContext } from '../context/ChannelInfoOverlayConte
import type { StackNavigationProp } from '@react-navigation/stack';

import type { StackNavigatorParamList, StreamChatGenerics } from '../types';
import { Pin } from '../icons/Pin';

const styles = StyleSheet.create({
leftSwipeableButton: {
Expand All @@ -35,13 +39,35 @@ const styles = StyleSheet.create({
alignItems: 'center',
flexDirection: 'row',
},
statusContainer: {
display: 'flex',
flexDirection: 'row',
},
pinIconContainer: {
marginLeft: 8,
},
});

type ChannelListScreenNavigationProp = StackNavigationProp<
StackNavigatorParamList,
'ChannelListScreen'
>;

const CustomChannelPreviewStatus = (props: ChannelPreviewStatusProps) => {
const membership = useChannelMembershipState(props.channel);

return (
<View style={styles.statusContainer}>
<ChannelPreviewStatus {...props} />
{membership.pinned_at && (
<View style={styles.pinIconContainer}>
<Pin height={20} width={20} />
</View>
)}
</View>
);
};

export const ChannelPreview: React.FC<ChannelPreviewMessengerProps<StreamChatGenerics>> = (
props,
) => {
Expand All @@ -57,6 +83,8 @@ export const ChannelPreview: React.FC<ChannelPreviewMessengerProps<StreamChatGen

const navigation = useNavigation<ChannelListScreenNavigationProp>();

const membership = useChannelMembershipState(channel);

const {
theme: {
colors: { accent_red, white_smoke },
Expand All @@ -75,7 +103,7 @@ export const ChannelPreview: React.FC<ChannelPreviewMessengerProps<StreamChatGen
<View style={[styles.swipeableContainer, { backgroundColor: white_smoke }]}>
<RectButton
onPress={() => {
setData({ channel, clientId: client.userID, navigation });
setData({ channel, clientId: client.userID, membership, navigation });
setOverlay('channelInfo');
}}
style={[styles.leftSwipeableButton]}
Expand Down Expand Up @@ -104,7 +132,7 @@ export const ChannelPreview: React.FC<ChannelPreviewMessengerProps<StreamChatGen
</View>
)}
>
<ChannelPreviewMessenger {...props} />
<ChannelPreviewMessenger {...props} PreviewStatus={CustomChannelPreviewStatus} />
</Swipeable>
);
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import React from 'react';
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import dayjs from 'dayjs';
import calendar from 'dayjs/plugin/calendar';
import { Avatar, Spinner, useTheme, useViewport } from 'stream-chat-react-native';

import { MESSAGE_SEARCH_LIMIT } from '../../hooks/usePaginatedSearchedMessages';
import { DEFAULT_PAGINATION_LIMIT } from '../../utils/constants';

import type { MessageResponse } from 'stream-chat';

import type { StreamChatGenerics } from '../../types';
import type { StackNavigatorParamList, StreamChatGenerics } from '../../types';

dayjs.extend(calendar);

Expand Down Expand Up @@ -46,6 +45,7 @@ const styles = StyleSheet.create({
paddingLeft: 8,
},
title: { fontSize: 14, fontWeight: '700' },
titleContainer: {},
});

export type MessageSearchListProps = {
Expand Down Expand Up @@ -74,7 +74,8 @@ export const MessageSearchList: React.FC<MessageSearchListProps> = React.forward
},
} = useTheme();
const { vw } = useViewport();
const navigation = useNavigation();
const navigation =
useNavigation<NavigationProp<StackNavigatorParamList, 'ChannelListScreen'>>();

if (!messages && !refreshing) {
return null;
Expand All @@ -92,8 +93,10 @@ export const MessageSearchList: React.FC<MessageSearchListProps> = React.forward
>
<Text style={{ color: grey }}>
{`${
messages.length >= MESSAGE_SEARCH_LIMIT ? MESSAGE_SEARCH_LIMIT : messages.length
}${messages.length >= MESSAGE_SEARCH_LIMIT ? '+ ' : ' '} result${
messages.length >= DEFAULT_PAGINATION_LIMIT
? DEFAULT_PAGINATION_LIMIT
: messages.length
}${messages.length >= DEFAULT_PAGINATION_LIMIT ? '+ ' : ' '} result${
messages.length === 1 ? '' : 's'
}`}
</Text>
Expand Down Expand Up @@ -129,8 +132,6 @@ export const MessageSearchList: React.FC<MessageSearchListProps> = React.forward
testID='channel-preview-button'
>
<Avatar
channelId={item.channel?.id}
id={item.user?.id}
image={item.user?.image}
name={item.user?.name}
online={item?.user?.online}
Expand Down
2 changes: 2 additions & 0 deletions examples/SampleApp/src/context/ChannelInfoOverlayContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext, useState } from 'react';

import { ChannelState } from 'stream-chat';
import type { StackNavigationProp } from '@react-navigation/stack';
import type { ChannelContextValue } from 'stream-chat-react-native';

Expand All @@ -14,6 +15,7 @@ export type ChannelInfoOverlayData = Partial<
Pick<ChannelContextValue<StreamChatGenerics>, 'channel'>
> & {
clientId?: string;
membership?: ChannelState<StreamChatGenerics>['membership'];
navigation?: ChannelListScreenNavigationProp;
};

Expand Down
22 changes: 22 additions & 0 deletions examples/SampleApp/src/icons/Archieve.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
import { useTheme } from 'stream-chat-react-native';

import { IconProps } from '../utils/base';

export const Archieve: React.FC<IconProps> = ({ height = 512, width = 512 }) => {
const {
theme: {
colors: { grey },
},
} = useTheme();

return (
<Svg height={height} viewBox={'0 0 512 512'} width={width}>
<Path
d='M32 32l448 0c17.7 0 32 14.3 32 32l0 32c0 17.7-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96L0 64C0 46.3 14.3 32 32 32zm0 128l448 0 0 256c0 35.3-28.7 64-64 64L96 480c-35.3 0-64-28.7-64-64l0-256zm128 80c0 8.8 7.2 16 16 16l160 0c8.8 0 16-7.2 16-16s-7.2-16-16-16l-160 0c-8.8 0-16 7.2-16 16z'
fill={grey}
/>
</Svg>
);
};
11 changes: 8 additions & 3 deletions examples/SampleApp/src/screens/ChannelListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ const styles = StyleSheet.create({
const baseFilters = {
type: 'messaging',
};
const sort: ChannelSort<StreamChatGenerics> = { last_updated: -1 };
const sort: ChannelSort<StreamChatGenerics> = [
{ pinned_at: -1 },
{ last_message_at: -1 },
{ updated_at: -1 },
];

const options = {
presence: true,
state: true,
Expand All @@ -75,7 +80,7 @@ export const ChannelListScreen: React.FC = () => {
const { loading, loadMore, messages, refreshing, refreshList, reset } =
usePaginatedSearchedMessages(searchQuery);

const chatClientUserId = chatClient?.user?.id;
const chatClientUserId = chatClient?.user?.id || '';
const filters = useMemo(
() => ({
...baseFilters,
Expand All @@ -98,7 +103,7 @@ export const ChannelListScreen: React.FC = () => {
</View>
);

const setScrollRef = (ref: React.RefObject<FlatList<Channel<StreamChatGenerics>> | null>) => {
const setScrollRef = (ref: FlatList<Channel<StreamChatGenerics>> | null) => {
scrollRef.current = ref;
};

Expand Down
Loading
Loading