forked from Ivasoft/mattermost-mobile
Gekidou CRT - User avatar stack (#6139)
* User avatar stack * Fixed fetchPostThread & added observer * Reusing the user component * Refactor fix * fix lint Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
committed by
GitHub
parent
b4b5c80629
commit
deb222a01d
157
app/components/user_avatars_stack/index.tsx
Normal file
157
app/components/user_avatars_stack/index.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleProp, Text, TouchableOpacity, View, ViewStyle} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import UserAvatar from './user_avatar';
|
||||
import UsersList from './users_list';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const OVERFLOW_DISPLAY_LIMIT = 99;
|
||||
|
||||
type Props = {
|
||||
users: UserModel[];
|
||||
breakAt?: number;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const size = 24;
|
||||
const imgOverlap = -6;
|
||||
|
||||
return {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
firstAvatar: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderWidth: (size / 2) + 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: size / 2,
|
||||
},
|
||||
notFirstAvatars: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderWidth: (size / 2) + 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: size / 2,
|
||||
marginLeft: imgOverlap,
|
||||
},
|
||||
overflowContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
marginLeft: imgOverlap,
|
||||
},
|
||||
overflowItem: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.centerChannelBg,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
overflowText: {
|
||||
fontSize: 10,
|
||||
fontWeight: 'bold',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
textAlign: 'center',
|
||||
},
|
||||
listHeader: {
|
||||
marginBottom: 12,
|
||||
},
|
||||
listHeaderText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const UserAvatarsStack = ({breakAt = 3, style: baseContainerStyle, users}: Props) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const showParticipantsList = useCallback(preventDoubleTap(() => {
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{!isTablet && (
|
||||
<View style={style.listHeader}>
|
||||
<FormattedText
|
||||
id='mobile.participants.header'
|
||||
defaultMessage={'THREAD PARTICIPANTS'}
|
||||
style={style.listHeaderText}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<UsersList users={users}/>
|
||||
</>
|
||||
);
|
||||
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-set-user-status',
|
||||
renderContent,
|
||||
snapPoints: [(Math.min(14, users.length) + 3) * 40, 10],
|
||||
title: intl.formatMessage({id: 'mobile.participants.header', defaultMessage: 'THREAD PARTICIPANTS'}),
|
||||
theme,
|
||||
});
|
||||
}), [isTablet, theme, users]);
|
||||
|
||||
const displayUsers = users.slice(0, breakAt);
|
||||
const overflowUsersCount = Math.min(users.length - displayUsers.length, OVERFLOW_DISPLAY_LIMIT);
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={showParticipantsList}
|
||||
style={baseContainerStyle}
|
||||
>
|
||||
<View style={style.container}>
|
||||
{displayUsers.map((user, index) => (
|
||||
<UserAvatar
|
||||
key={user.id}
|
||||
style={index === 0 ? style.firstAvatar : style.notFirstAvatars}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
{Boolean(overflowUsersCount) && (
|
||||
<View style={style.overflowContainer}>
|
||||
<View style={style.overflowItem}>
|
||||
<Text style={style.overflowText} >
|
||||
{'+' + overflowUsersCount.toString()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAvatarsStack;
|
||||
17
app/components/user_avatars_stack/user_avatar/index.ts
Normal file
17
app/components/user_avatars_stack/user_avatar/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
|
||||
import UserAvatar from './user_avatar';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const enhanced = withObservables(['user'], ({user}: {user: UserModel}) => {
|
||||
return {
|
||||
user: user.observe(),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(UserAvatar));
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, View, ViewStyle} from 'react-native';
|
||||
|
||||
import ProfilePicture from '@components/profile_picture/image';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
export type Props = {
|
||||
style: StyleProp<ViewStyle>;
|
||||
user: UserModel;
|
||||
};
|
||||
|
||||
const UserAvatar = ({style, user}: Props) => {
|
||||
return (
|
||||
<View
|
||||
key={user.id}
|
||||
style={style}
|
||||
>
|
||||
<ProfilePicture
|
||||
author={user}
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAvatar;
|
||||
35
app/components/user_avatars_stack/users_list/index.tsx
Normal file
35
app/components/user_avatars_stack/users_list/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import UserItem from '@components/user_item';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
users: UserModel[];
|
||||
};
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
paddingLeft: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const UsersList = ({users}: Props) => {
|
||||
return (
|
||||
<>
|
||||
{users.map((user) => (
|
||||
<UserItem
|
||||
key={user.id}
|
||||
user={user}
|
||||
containerStyle={style.container}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersList;
|
||||
Reference in New Issue
Block a user