Profile: Infinite scroll for members
This commit is contained in:
parent
1984318297
commit
e454abd4f3
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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' |
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user