Profile: Infinite scroll for members

This commit is contained in:
Alexander Zinchuk 2021-05-06 01:47:52 +03:00
parent 1984318297
commit e454abd4f3
10 changed files with 110 additions and 35 deletions

View File

@ -12,7 +12,7 @@ import {
ApiChatAdminRights,
} from '../../types';
import { DEBUG, ARCHIVED_FOLDER_ID, CHANNEL_MEMBERS_LIMIT } from '../../../config';
import { DEBUG, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE } from '../../../config';
import { invokeRequest, uploadFile } from './client';
import {
buildApiChatFromDialog,
@ -156,11 +156,12 @@ export async function fetchChats({
export function fetchFullChat(chat: ApiChat) {
const { id, accessHash, adminRights } = chat;
const input = buildInputEntity(id, accessHash);
return input instanceof GramJs.InputChannel
? getFullChannelInfo(input, adminRights)
: getFullChatInfo(input as number);
? getFullChannelInfo(id, accessHash!, adminRights)
: getFullChatInfo(id);
}
export async function searchChats({ query }: { query: string }) {
@ -298,7 +299,9 @@ async function getFullChatInfo(chatId: number): Promise<{
fullInfo: ApiChatFullInfo;
users?: ApiUser[];
} | undefined> {
const result = await invokeRequest(new GramJs.messages.GetFullChat({ chatId }));
const result = await invokeRequest(new GramJs.messages.GetFullChat({
chatId: buildInputEntity(chatId) as number,
}));
if (!result || !(result.fullChat instanceof GramJs.ChatFull)) {
return undefined;
@ -328,10 +331,13 @@ async function getFullChatInfo(chatId: number): Promise<{
}
async function getFullChannelInfo(
channel: GramJs.InputChannel,
id: number,
accessHash: string,
adminRights?: ApiChatAdminRights,
) {
const result = await invokeRequest(new GramJs.channels.GetFullChannel({ channel }));
const result = await invokeRequest(new GramJs.channels.GetFullChannel({
channel: buildInputEntity(id, accessHash) as GramJs.InputChannel,
}));
if (!result || !(result.fullChat instanceof GramJs.ChannelFull)) {
return undefined;
@ -354,12 +360,12 @@ async function getFullChannelInfo(
? exportedInvite.link
: undefined;
const { members, users } = (canViewParticipants && await getChannelMembers(channel)) || {};
const { members, users } = (canViewParticipants && await fetchMembers(id, accessHash)) || {};
const { members: kickedMembers, users: bannedUsers } = (
canViewParticipants && adminRights && await getChannelMembers(channel, 'kicked')
canViewParticipants && adminRights && await fetchMembers(id, accessHash, 'kicked')
) || {};
const { members: adminMembers, users: adminUsers } = (
canViewParticipants && adminRights && await getChannelMembers(channel, 'admin')
canViewParticipants && adminRights && await fetchMembers(id, accessHash, 'admin')
) || {};
return {
@ -785,9 +791,11 @@ export function toggleSignatures({
type ChannelMembersFilter = 'kicked' | 'admin' | 'recent';
async function getChannelMembers(
channel: GramJs.InputChannel,
export async function fetchMembers(
chatId: number,
accessHash: string,
memberFilter: ChannelMembersFilter = 'recent',
offset?: number,
) {
let filter: GramJs.TypeChannelParticipantsFilter;
@ -804,9 +812,10 @@ async function getChannelMembers(
}
const result = await invokeRequest(new GramJs.channels.GetParticipants({
channel,
channel: buildInputEntity(chatId, accessHash) as GramJs.InputChannel,
filter,
limit: CHANNEL_MEMBERS_LIMIT,
offset,
limit: MEMBERS_LOAD_SLICE,
}));
if (!result || result instanceof GramJs.channels.ChannelParticipantsNotModified) {

View File

@ -14,7 +14,7 @@ export {
fetchChatFolders, editChatFolder, deleteChatFolder, fetchRecommendedChatFolders,
getChatByUsername, togglePreHistoryHidden, updateChatDefaultBannedRights, updateChatMemberBannedRights,
updateChatTitle, updateChatAbout, toggleSignatures, updateChatAdmin, fetchGroupsForDiscussion, setDiscussionGroup,
migrateChat, openChatByInvite,
migrateChat, openChatByInvite, fetchMembers,
} from './chats';
export {

View File

@ -14,10 +14,10 @@ import {
MediaViewerOrigin, ProfileState, ProfileTabType, SharedMediaType,
} from '../../types';
import { SHARED_MEDIA_SLICE, SLIDE_TRANSITION_DURATION } from '../../config';
import { MEMBERS_SLICE, SHARED_MEDIA_SLICE, SLIDE_TRANSITION_DURATION } from '../../config';
import { IS_TOUCH_ENV } from '../../util/environment';
import {
isChatAdmin, isChatChannel, isChatGroup, isChatPrivate,
isChatAdmin, isChatBasicGroup, isChatChannel, isChatGroup, isChatPrivate,
} from '../../modules/helpers';
import {
selectChatMessages,
@ -57,6 +57,7 @@ type OwnProps = {
};
type StateProps = {
isBasicGroup?: boolean;
isChannel?: boolean;
resolvedUserId?: number;
chatMessages?: Record<number, ApiMessage>;
@ -72,7 +73,7 @@ type StateProps = {
};
type DispatchProps = Pick<GlobalActions, (
'setLocalMediaSearchType' | 'searchMediaMessagesLocal' | 'openMediaViewer' |
'setLocalMediaSearchType' | 'loadMoreMembers' | 'searchMediaMessagesLocal' | 'openMediaViewer' |
'openAudioPlayer' | 'openUserInfo' | 'focusMessage' | 'loadProfilePhotos'
)>;
@ -89,6 +90,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
chatId,
profileState,
onProfileStateChange,
isBasicGroup,
isChannel,
resolvedUserId,
chatMessages,
@ -102,6 +104,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
isRestricted,
lastSyncTime,
setLocalMediaSearchType,
loadMoreMembers,
searchMediaMessagesLocal,
openMediaViewer,
openAudioPlayer,
@ -125,7 +128,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
const tabType = tabs[activeTab].type as ProfileTabType;
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
isRightColumnShown, searchMediaMessagesLocal, tabType, mediaSearchType, members,
isRightColumnShown, loadMoreMembers, searchMediaMessagesLocal, tabType, mediaSearchType, members,
usersById, chatMessages, foundIds, chatId, lastSyncTime,
);
const activeKey = tabs.findIndex(({ type }) => type === resultType);
@ -306,8 +309,9 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
itemSelector={buildInfiniteScrollItemSelector(resultType)}
items={viewportIds}
cacheBuster={cacheBuster}
preloadBackwards={SHARED_MEDIA_SLICE}
isDisabled={tabType === 'members'}
sensitiveArea={500}
preloadBackwards={resultType === 'members' ? MEMBERS_SLICE : SHARED_MEDIA_SLICE}
isDisabled={resultType === 'members' && isBasicGroup}
noFastList
onLoadMore={getMore}
onScroll={handleScroll}
@ -366,6 +370,7 @@ export default memo(withGlobal<OwnProps>(
const { byId: usersById } = global.users;
const isGroup = chat && isChatGroup(chat);
const isBasicGroup = chat && isChatBasicGroup(chat);
const isChannel = chat && isChatChannel(chat);
const hasMembersTab = isGroup || (isChannel && isChatAdmin(chat!));
const members = chat && chat.fullInfo && chat.fullInfo.members;
@ -379,6 +384,7 @@ export default memo(withGlobal<OwnProps>(
}
return {
isBasicGroup,
isChannel,
resolvedUserId,
chatMessages,
@ -397,6 +403,7 @@ export default memo(withGlobal<OwnProps>(
},
(setGlobal, actions): DispatchProps => pick(actions, [
'setLocalMediaSearchType',
'loadMoreMembers',
'searchMediaMessagesLocal',
'openMediaViewer',
'openAudioPlayer',

View File

@ -17,7 +17,7 @@
// @optimization
&:not(:hover) {
.Picker .chat-item-clickable:nth-child(n + 18) {
.chat-item-clickable:nth-child(n + 18) {
display: none !important;
}
}

View File

@ -3,13 +3,14 @@ import { useMemo, useRef } from '../../../lib/teact/teact';
import { ApiChatMember, ApiMessage, ApiUser } from '../../../api/types';
import { ProfileTabType, SharedMediaType } from '../../../types';
import { MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
import { MEMBERS_SLICE, MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
import { getMessageContentIds, sortUserIds } from '../../../modules/helpers';
import useOnChange from '../../../hooks/useOnChange';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
export default function useProfileViewportIds(
isRightColumnShown: boolean,
loadMoreMembers: AnyToVoidFunction,
searchMessages: AnyToVoidFunction,
tabType: ProfileTabType,
mediaSearchType?: SharedMediaType,
@ -30,6 +31,10 @@ export default function useProfileViewportIds(
return sortUserIds(groupChatMembers.map(({ userId }) => userId), usersById);
}, [groupChatMembers, usersById]);
const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForMembers(
resultType, loadMoreMembers, lastSyncTime, memberIds,
);
const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia(
'media', resultType, searchMessages, lastSyncTime, chatMessages, foundIds,
);
@ -52,8 +57,9 @@ export default function useProfileViewportIds(
switch (resultType) {
case 'members':
viewportIds = memberIds;
getMore = undefined;
viewportIds = memberViewportIds;
getMore = getMoreMembers;
noProfileInfo = noProfileInfoForMembers;
break;
case 'media':
viewportIds = mediaViewportIds;
@ -80,6 +86,24 @@ export default function useProfileViewportIds(
return [resultType, viewportIds, getMore, noProfileInfo] as const;
}
function useInfiniteScrollForMembers(
currentResultType?: ProfileTabType,
handleLoadMore?: AnyToVoidFunction,
lastSyncTime?: number,
memberIds?: number[],
) {
const [viewportIds, getMore] = useInfiniteScroll(
lastSyncTime ? handleLoadMore : undefined,
memberIds,
undefined,
MEMBERS_SLICE,
);
const isOnTop = !viewportIds || !memberIds || viewportIds[0] === memberIds[0];
return [viewportIds, getMore, !isOnTop] as const;
}
function useInfiniteScrollForSharedMedia(
forSharedMediaType: SharedMediaType,
currentResultType?: ProfileTabType,

View File

@ -36,7 +36,7 @@ const InfiniteScroll: FC<OwnProps> = ({
itemSelector = DEFAULT_LIST_SELECTOR,
preloadBackwards = DEFAULT_PRELOAD_BACKWARDS,
sensitiveArea = DEFAULT_SENSITIVE_AREA,
// Used to turn off restoring scroll position (e.g. for frequently re-ordered chat or user lists)
// Used to turn off preloading and restoring scroll position (e.g. for frequently re-ordered chat or user lists)
isDisabled = false,
noFastList,
// Used to re-query `listItemElements` if rendering is delayed by transition
@ -70,7 +70,7 @@ const InfiniteScroll: FC<OwnProps> = ({
// Initial preload
useEffect(() => {
if (!loadMoreBackwards) {
if (isDisabled || !loadMoreBackwards) {
return;
}
@ -82,7 +82,7 @@ const InfiniteScroll: FC<OwnProps> = ({
loadMoreBackwards();
}
}
}, [items, loadMoreBackwards, preloadBackwards]);
}, [isDisabled, items, loadMoreBackwards, preloadBackwards]);
// Restore `scrollTop` after adding items
useLayoutEffect(() => {

View File

@ -53,13 +53,14 @@ export const CHAT_LIST_LOAD_SLICE = 100;
export const SHARED_MEDIA_SLICE = 42;
export const MESSAGE_SEARCH_SLICE = 42;
export const GLOBAL_SEARCH_SLICE = 20;
export const CHANNEL_MEMBERS_LIMIT = 30;
export const MEMBERS_SLICE = 30;
export const MEMBERS_LOAD_SLICE = 200;
export const PINNED_MESSAGES_LIMIT = 50;
export const BLOCKED_LIST_LIMIT = 100;
export const PROFILE_PHOTOS_LIMIT = 40;
export const TOP_CHAT_MESSAGES_PRELOAD_LIMIT = 25;
export const ALL_CHATS_PRELOAD_DISABLED = false;
export const TOP_CHAT_MESSAGES_PRELOAD_LIMIT = 0;
export const ALL_CHATS_PRELOAD_DISABLED = true;
export const ANIMATION_LEVEL_MIN = 0;
export const ANIMATION_LEVEL_MED = 1;

View File

@ -397,7 +397,7 @@ export type ActionTypes = (
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
'loadProfilePhotos' |
'loadProfilePhotos' | 'loadMoreMembers' |
// messages
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |

View File

@ -52,7 +52,7 @@ const TMP_CHAT_ID = -1;
const runThrottledForLoadChats = throttle((cb) => cb(), 1000, true);
const runThrottledForLoadTopChats = throttle((cb) => cb(), 3000, true);
const runDebouncedForFetchFullChat = debounce((cb) => cb(), 500, false, true);
const runDebouncedForLoadFullChat = debounce((cb) => cb(), 500, false, true);
addReducer('preloadTopChatMessages', (global, actions) => {
(async () => {
@ -173,7 +173,7 @@ addReducer('loadFullChat', (global, actions, payload) => {
if (force) {
loadFullChat(chat);
} else {
runDebouncedForFetchFullChat(() => loadFullChat(chat));
runDebouncedForLoadFullChat(() => loadFullChat(chat));
}
});
@ -669,6 +669,40 @@ addReducer('unlinkDiscussionGroup', (global, actions, payload) => {
})();
});
addReducer('loadMoreMembers', (global) => {
(async () => {
const { chatId } = selectCurrentMessageList(global) || {};
const chat = chatId ? selectChat(global, chatId) : undefined;
if (!chat) {
return;
}
const offset = (chat.fullInfo && chat.fullInfo.members && chat.fullInfo.members.length) || undefined;
const result = await callApi('fetchMembers', chat.id, chat.accessHash!, 'recent', offset);
if (!result) {
return;
}
const { members, users } = result;
if (!members || !members.length) {
return;
}
global = getGlobal();
global = addUsers(global, buildCollectionByKey(users, 'id'));
global = updateChat(global, chat.id, {
fullInfo: {
...chat.fullInfo,
members: [
...((chat.fullInfo || {}).members || []),
...(members || []),
],
},
});
setGlobal(global);
})();
});
async function loadChats(listType: 'active' | 'archived', offsetId?: number, offsetDate?: number) {
const result = await callApi('fetchChats', {
limit: CHAT_LIST_LOAD_SLICE,

View File

@ -5,7 +5,7 @@ import {
getPrivateChatUserId, isChatChannel, isChatPrivate, isHistoryClearMessage, isUserBot, isUserOnline,
} from '../helpers';
import { selectUser } from './users';
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, CHANNEL_MEMBERS_LIMIT } from '../../config';
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE } from '../../config';
export function selectChat(global: GlobalState, chatId: number): ApiChat | undefined {
return global.chats.byId[chatId];
@ -38,7 +38,7 @@ export function selectChatOnlineCount(global: GlobalState, chat: ApiChat) {
return undefined;
}
if (!chat.fullInfo.members || chat.fullInfo.members.length === CHANNEL_MEMBERS_LIMIT) {
if (!chat.fullInfo.members || chat.fullInfo.members.length === MEMBERS_LOAD_SLICE) {
return chat.fullInfo.onlineCount;
}