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
@@ -520,7 +520,8 @@ export async function fetchPostThread(serverUrl: string, postId: string, fetchOn
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await client.getPostThread(postId);
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
const data = await client.getPostThread(postId, isCRTEnabled, isCRTEnabled);
|
||||
const result = processPostsFetched(data);
|
||||
if (!fetchOnly) {
|
||||
const models = await operator.handlePosts({
|
||||
@@ -528,7 +529,6 @@ export async function fetchPostThread(serverUrl: string, postId: string, fetchOn
|
||||
actionType: ActionType.POSTS.RECEIVED_IN_THREAD,
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
const isCRTEnabled = await getIsCRTEnabled(operator.database);
|
||||
if (isCRTEnabled) {
|
||||
const threadModels = await prepareThreadsFromReceivedPosts(operator, result.posts);
|
||||
if (threadModels?.length) {
|
||||
|
||||
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;
|
||||
@@ -15,8 +15,9 @@ import {getConfig, observeConfig} from './system';
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type Model from '@nozbe/watermelondb/Model';
|
||||
import type ThreadModel from '@typings/database/models/servers/thread';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const {SERVER: {THREADS_IN_TEAM, THREAD, POST, CHANNEL}} = MM_TABLES;
|
||||
const {SERVER: {CHANNEL, POST, THREAD, THREADS_IN_TEAM, THREAD_PARTICIPANT, USER}} = MM_TABLES;
|
||||
|
||||
export const getIsCRTEnabled = async (database: Database): Promise<boolean> => {
|
||||
const config = await getConfig(database);
|
||||
@@ -190,3 +191,9 @@ export const queryThreads = (database: Database, teamId?: string, onlyUnreads =
|
||||
|
||||
return database.get<ThreadModel>(THREAD).query(...query);
|
||||
};
|
||||
|
||||
export const queryThreadParticipants = (database: Database, threadId: string) => {
|
||||
return database.get<UserModel>(USER).query(
|
||||
Q.on(THREAD_PARTICIPANT, Q.where('thread_id', threadId)),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -332,6 +332,7 @@
|
||||
"mobile.oauth.switch_to_browser.title": "Redirecting...",
|
||||
"mobile.oauth.try_again": "Try again",
|
||||
"mobile.open_gm.error": "We couldn't open a group message with those users. Please check your connection and try again.",
|
||||
"mobile.participants.header": "THREAD PARTICIPANTS",
|
||||
"mobile.permission_denied_dismiss": "Don't Allow",
|
||||
"mobile.permission_denied_retry": "Settings",
|
||||
"mobile.post_info.add_reaction": "Add Reaction",
|
||||
|
||||
Reference in New Issue
Block a user