Skip to content

Commit

Permalink
feat(uikit): added mini profile card
Browse files Browse the repository at this point in the history
  • Loading branch information
bang9 committed Oct 24, 2022
1 parent 29f66a0 commit 0877463
Show file tree
Hide file tree
Showing 17 changed files with 248 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ type OutlinedButtonProps = {
onPress?: () => void;
};

const OutlinedButton = ({ children, containerStyle }: OutlinedButtonProps) => {
const OutlinedButton = ({ children, onPress, containerStyle }: OutlinedButtonProps) => {
const { colors } = useUIKitTheme();
return (
<Pressable style={[styles.outlinedButton, { borderColor: colors.onBackground01 }, containerStyle]}>
<Pressable
onPress={onPress}
style={[styles.outlinedButton, { borderColor: colors.onBackground01 }, containerStyle]}
>
<Text button color={colors.onBackground01} numberOfLines={1} style={styles.outlinedButtonText}>
{children}
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native';
import { StyleProp, View, ViewStyle } from 'react-native';

import Divider from '../../components/Divider';
import Text from '../../components/Text';
Expand All @@ -15,17 +15,19 @@ type Props = {

bodyLabel: string;
body: string;

containerStyle?: StyleProp<ViewStyle>;
};

const ProfileCard = ({ uri, username, bodyLabel, body, button }: Props) => {
const ProfileCard = ({ uri, username, bodyLabel, body, button, containerStyle }: Props) => {
const { colors } = useUIKitTheme();
const color = colors.ui.profileCard.default.none;

return (
<View style={[styles.container, { backgroundColor: color.background }]}>
<View style={[styles.container, { backgroundColor: color.background }, containerStyle]}>
<View style={styles.profileContainer}>
<Avatar uri={uri} size={80} containerStyle={styles.profileAvatar} />
<Text h1 color={color.textUsername}>
<Text h1 color={color.textUsername} numberOfLines={1}>
{username}
</Text>
</View>
Expand All @@ -35,7 +37,7 @@ const ProfileCard = ({ uri, username, bodyLabel, body, button }: Props) => {
<Text body2 color={color.textBodyLabel} style={styles.profileInfoBodyLabel}>
{bodyLabel}
</Text>
<Text body3 color={color.textBody}>
<Text body3 numberOfLines={1} color={color.textBody}>
{body}
</Text>
</View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import React from 'react';
import { View } from 'react-native';
import { Pressable, View } from 'react-native';

import { Avatar, createStyleSheet } from '@sendbird/uikit-react-native-foundation';
import type { SendbirdMessage } from '@sendbird/uikit-utils';

import { useProfileCard } from '../../hooks/useContext';

type Props = {
message: SendbirdMessage;
grouping: boolean;
};
const MessageIncomingAvatar = ({ message, grouping }: Props) => {
const { show } = useProfileCard();
if (grouping) return <View style={styles.avatar} />;
return (
<View style={styles.avatar}>
{(message.isFileMessage() || message.isUserMessage()) && <Avatar size={26} uri={message.sender?.profileUrl} />}
{(message.isFileMessage() || message.isUserMessage()) && (
<Pressable onPress={() => show(message.sender)}>
<Avatar size={26} uri={message.sender?.profileUrl} />
</Pressable>
)}
</View>
);
};
Expand Down
14 changes: 10 additions & 4 deletions packages/uikit-react-native/src/components/UserActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { TouchableOpacity, View } from 'react-native';
import { Pressable, TouchableOpacity, View } from 'react-native';
import type { GestureResponderEvent } from 'react-native';

import { Avatar, Icon, Text, createStyleSheet, useUIKitTheme } from '@sendbird/uikit-react-native-foundation';
import { conditionChaining } from '@sendbird/uikit-utils';
Expand All @@ -8,18 +9,23 @@ type Props = {
uri: string;
name: string;
label?: string;

muted: boolean;
disabled: boolean;
onPressActionMenu?: () => void;

onPressActionMenu?: (ev: GestureResponderEvent) => void;
onPressAvatar?: (ev: GestureResponderEvent) => void;
};
const UserActionBar = ({ muted, uri, name, disabled, onPressActionMenu, label }: Props) => {
const UserActionBar = ({ muted, uri, name, disabled, label, onPressActionMenu, onPressAvatar }: Props) => {
const { colors } = useUIKitTheme();

const iconColor = conditionChaining([disabled], [colors.onBackground04, colors.onBackground01]);

return (
<View style={styles.container}>
<Avatar muted={muted} size={36} uri={uri} containerStyle={styles.avatar} />
<Pressable onPress={onPressAvatar} style={styles.avatar}>
<Avatar muted={muted} size={36} uri={uri} />
</Pressable>
<View style={[styles.infoContainer, { borderBottomColor: colors.onBackground04 }]}>
<Text subtitle2 numberOfLines={1} style={styles.name} color={colors.onBackground01}>
{name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ import {
ToastProvider,
UIKitThemeProvider,
} from '@sendbird/uikit-react-native-foundation';
import type { SendbirdChatSDK } from '@sendbird/uikit-utils';

import { LocalizationProvider } from '../contexts/Localization';
import { PlatformServiceProvider } from '../contexts/PlatformService';
import { SendbirdChatProvider } from '../contexts/SendbirdChat';
import type {
SendbirdChatSDK,
SendbirdGroupChannel,
SendbirdGroupChannelCreateParams,
SendbirdMember,
SendbirdUser,
} from '@sendbird/uikit-utils';

import { LocalizationProvider } from '../contexts/LocalizationCtx';
import { PlatformServiceProvider } from '../contexts/PlatformServiceCtx';
import { ProfileCardProvider } from '../contexts/ProfileCardCtx';
import { SendbirdChatProvider } from '../contexts/SendbirdChatCtx';
import { useLocalization } from '../hooks/useContext';
import InternalLocalCacheStorage from '../libs/InternalLocalCacheStorage';
import StringSetEn from '../localization/StringSet.en';
Expand Down Expand Up @@ -69,6 +76,13 @@ export type SendbirdUIKitContainerProps = React.PropsWithChildren<{
toast?: {
dismissTimeout?: number;
};
profileCard?: {
onCreateChannel: (channel: SendbirdGroupChannel) => void;
onBeforeCreateChannel?: (
channelParams: SendbirdGroupChannelCreateParams,
users: SendbirdUser[] | SendbirdMember[],
) => SendbirdGroupChannelCreateParams | Promise<SendbirdGroupChannelCreateParams>;
};
errorBoundary?: {
onError?: (props: ErrorBoundaryProps) => void;
ErrorInfoComponent?: (props: ErrorBoundaryProps) => JSX.Element;
Expand All @@ -83,6 +97,7 @@ const SendbirdUIKitContainer = ({
localization,
styles,
toast,
profileCard,
errorBoundary,
}: SendbirdUIKitContainerProps) => {
const unsubscribes = useRef<(() => void)[]>([]).current;
Expand Down Expand Up @@ -163,7 +178,12 @@ const SendbirdUIKitContainer = ({
>
<LocalizedDialogProvider>
<ToastProvider dismissTimeout={toast?.dismissTimeout}>
<InternalErrorBoundaryContainer {...errorBoundary}>{children}</InternalErrorBoundaryContainer>
<ProfileCardProvider
onCreateChannel={profileCard?.onCreateChannel}
onBeforeCreateChannel={profileCard?.onBeforeCreateChannel}
>
<InternalErrorBoundaryContainer {...errorBoundary}>{children}</InternalErrorBoundaryContainer>
</ProfileCardProvider>
</ToastProvider>
</LocalizedDialogProvider>
</HeaderStyleProvider>
Expand Down
122 changes: 122 additions & 0 deletions packages/uikit-react-native/src/contexts/ProfileCardCtx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useContext, useState } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { Modal, OutlinedButton, ProfileCard, createStyleSheet } from '@sendbird/uikit-react-native-foundation';
import type {
SendbirdGroupChannel,
SendbirdGroupChannelCreateParams,
SendbirdMember,
SendbirdUser,
} from '@sendbird/uikit-utils';
import { Logger, PASS, useIIFE } from '@sendbird/uikit-utils';

import { LocalizationContext } from '../contexts/LocalizationCtx';
import { SendbirdChatContext } from '../contexts/SendbirdChatCtx';

type OnCreateChannel = (channel: SendbirdGroupChannel) => void;
type OnBeforeCreateChannel = (
channelParams: SendbirdGroupChannelCreateParams,
users: SendbirdUser[] | SendbirdMember[],
) => SendbirdGroupChannelCreateParams | Promise<SendbirdGroupChannelCreateParams>;

export type ProfileCardContextType = {
show(user: SendbirdUser | SendbirdMember): void;
hide(): void;
};

type Props = React.PropsWithChildren<{
onCreateChannel?: OnCreateChannel;
onBeforeCreateChannel?: OnBeforeCreateChannel;
}>;

export const ProfileCardContext = React.createContext<ProfileCardContextType | null>(null);
export const ProfileCardProvider = ({ children, onCreateChannel, onBeforeCreateChannel = PASS }: Props) => {
const chatContext = useContext(SendbirdChatContext);
const localizationContext = useContext(LocalizationContext);
const { bottom, left, right } = useSafeAreaInsets();

const [user, setUser] = useState<SendbirdUser | SendbirdMember>();
const [visible, setVisible] = useState(false);

const show: ProfileCardContextType['show'] = (user) => {
setUser(user);
setVisible(true);
};

const hide: ProfileCardContextType['hide'] = () => {
setVisible(false);
};

if (!chatContext) throw new Error('SendbirdChatContext is not provided');
if (!localizationContext) throw new Error('LocalizationContext is not provided');

const isMe = chatContext.currentUser && user?.userId === chatContext.currentUser.userId;
const messageToUser = async () => {
if (user) {
const params: SendbirdGroupChannelCreateParams = {
invitedUserIds: [user.userId],
name: '',
coverUrl: '',
isDistinct: false,
};

if (chatContext.currentUser) params.operatorUserIds = [chatContext.currentUser.userId];
const processedParams = await onBeforeCreateChannel(params, [user]);

hide();
const channel = await chatContext.sdk.groupChannel.createChannel(processedParams);

if (onCreateChannel) {
onCreateChannel(channel);
} else {
Logger.warn(
'Please set `onCreateChannel` before message to user from profile card, see `profileCard` prop in the `SendbirdUIKitContainer` props',
);
}
}
};

return (
<ProfileCardContext.Provider value={{ show, hide }}>
{children}
<Modal
type={'slide'}
onClose={hide}
onDismiss={() => setUser(undefined)}
visible={visible && Boolean(user)}
backgroundStyle={styles.modal}
>
{user && (
<ProfileCard
containerStyle={[
styles.profileCardContainer,
{ paddingLeft: left, paddingRight: right, paddingBottom: bottom },
]}
uri={user.profileUrl}
username={user.nickname || localizationContext.STRINGS.LABELS.USER_NO_NAME}
bodyLabel={localizationContext.STRINGS.PROFILE_CARD.BODY_LABEL}
body={localizationContext.STRINGS.PROFILE_CARD.BODY(user)}
button={useIIFE(() => {
if (isMe) return undefined;
return (
<OutlinedButton onPress={messageToUser}>
{localizationContext.STRINGS.PROFILE_CARD.BUTTON_MESSAGE}
</OutlinedButton>
);
})}
/>
)}
</Modal>
</ProfileCardContext.Provider>
);
};

const styles = createStyleSheet({
modal: {
justifyContent: 'flex-end',
},
profileCardContainer: {
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
},
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { useCallback } from 'react';
import React from 'react';

import { useActiveGroupChannel, useChannelHandler } from '@sendbird/uikit-chat-hooks';
import { Icon } from '@sendbird/uikit-react-native-foundation';
import type { SendbirdMember } from '@sendbird/uikit-utils';
import { useForceUpdate, useUniqId } from '@sendbird/uikit-utils';
import { useForceUpdate, useFreshCallback, useUniqId } from '@sendbird/uikit-utils';

import UserActionBar from '../components/UserActionBar';
import type { GroupChannelMembersFragment } from '../domain/groupChannelUserList/types';
import createUserListModule from '../domain/userList/module/createUserListModule';
import type { UserListModule } from '../domain/userList/types';
import { useLocalization, useSendbirdChat } from '../hooks/useContext';
import { useLocalization, useProfileCard, useSendbirdChat } from '../hooks/useContext';

const noop = () => '';
const name = 'createGroupChannelMembersFragment';
Expand All @@ -21,10 +21,12 @@ const createGroupChannelMembersFragment = (
return ({ channel, onPressHeaderLeft, onPressHeaderRight, renderUser }) => {
const uniqId = useUniqId(name);
const forceUpdate = useForceUpdate();
const { sdk, currentUser } = useSendbirdChat();
const { activeChannel } = useActiveGroupChannel(sdk, channel);

const { STRINGS } = useLocalization();
const { sdk, currentUser } = useSendbirdChat();
const { show } = useProfileCard();

const { activeChannel } = useActiveGroupChannel(sdk, channel);

useChannelHandler(sdk, `${name}_${uniqId}`, {
// Note: Removed from v4
Expand Down Expand Up @@ -63,27 +65,25 @@ const createGroupChannelMembersFragment = (
},
});

const _renderUser: NonNullable<typeof renderUser> = useCallback(
(user, selectedUsers, setSelectedUsers) => {
if (renderUser) return renderUser(user, selectedUsers, setSelectedUsers);
const _renderUser: NonNullable<typeof renderUser> = useFreshCallback((user, selectedUsers, setSelectedUsers) => {
if (renderUser) return renderUser(user, selectedUsers, setSelectedUsers);

return (
<UserActionBar
muted={user.isMuted}
uri={user.profileUrl}
label={user.role === 'operator' ? STRINGS.GROUP_CHANNEL_MEMBERS.USER_BAR_OPERATOR : ''}
name={
(user.nickname || STRINGS.LABELS.USER_NO_NAME) +
(user.userId === currentUser?.userId ? STRINGS.GROUP_CHANNEL_MEMBERS.USER_BAR_ME_POSTFIX : '')
}
disabled={user.userId === currentUser?.userId}
// TODO: implement ban/mute actions, use channel.members with handlers instead member query
onPressActionMenu={undefined}
/>
);
},
[renderUser],
);
return (
<UserActionBar
muted={user.isMuted}
uri={user.profileUrl}
label={user.role === 'operator' ? STRINGS.GROUP_CHANNEL_MEMBERS.USER_BAR_OPERATOR : ''}
name={
(user.nickname || STRINGS.LABELS.USER_NO_NAME) +
(user.userId === currentUser?.userId ? STRINGS.GROUP_CHANNEL_MEMBERS.USER_BAR_ME_POSTFIX : '')
}
disabled={user.userId === currentUser?.userId}
// TODO: implement ban/mute actions, use channel.members with handlers instead member query
onPressActionMenu={undefined}
onPressAvatar={() => show(user)}
/>
);
});

return (
<UserListModule.Provider headerRight={noop} headerTitle={STRINGS.GROUP_CHANNEL_MEMBERS.HEADER_TITLE}>
Expand Down
Loading

0 comments on commit 0877463

Please sign in to comment.