, isFirstRun: boolean): void => {
let peekCount = 0;
let inactiveCallLinksToPeek = 0;
for (const item of callItems) {
const { mode } = item;
if (isGroupOrAdhocCallMode(mode)) {
const isActive = getIsCallActive({ callHistoryGroup: item });
if (isActive) {
// Don't peek if you're already in the call.
const activeCallConversationId = activeCall?.conversationId;
if (activeCallConversationId) {
const conversation = getConversationForItem(item);
const isInCall = getIsInCall({
activeCallConversationId,
callHistoryGroup: item,
conversation,
isActive,
});
if (isInCall) {
continue;
}
}
maybeEnqueueCallPeek(item);
peekCount += 1;
continue;
}
if (
mode === CallMode.Adhoc &&
isFirstRun &&
inactiveCallLinksToPeek < INACTIVE_CALL_LINKS_TO_PEEK &&
isMoreRecentThan(item.timestamp, INACTIVE_CALL_LINK_AGE_THRESHOLD)
) {
const peekedAt = inactiveCallLinksPeekedAtRef.current.get(
item.peerId
);
if (
peekedAt &&
isMoreRecentThan(peekedAt, INACTIVE_CALL_LINK_PEEK_INTERVAL)
) {
continue;
}
maybeEnqueueCallPeek(item);
inactiveCallLinksToPeek += 1;
peekCount += 1;
}
}
}
if (peekCount === 0) {
return;
}
log.info(`Found ${peekCount} calls to peek.`);
if (peekQueueTimerRef.current != null) {
return;
}
log.info('Starting background call peek.');
peekQueueTimerRef.current = setInterval(() => {
if (searchStateItemsRef.current) {
enqueueCallPeeks(searchStateItemsRef.current, false);
}
if (peekQueueRef.current.size > 0) {
doCallPeeks();
}
}, PEEK_QUEUE_INTERVAL);
doCallPeeks();
},
[
activeCall?.conversationId,
doCallPeeks,
getConversationForItem,
getIsCallActive,
getIsInCall,
maybeEnqueueCallPeek,
]
);
useEffect(() => {
const controller = new AbortController();
async function search() {
const options: CallHistoryFilterOptions = {
query: queryInput.toLowerCase().normalize().trim(),
status: statusInput,
};
let timer = setTimeout(() => {
setSearchState(prevSearchState => {
if (prevSearchState.state === 'init') {
return defaultPendingState;
}
return prevSearchState;
});
timer = setTimeout(() => {
// Show loading indicator after a delay
setSearchState(defaultPendingState);
}, 300);
}, 50);
let results: SearchResults | null = null;
try {
const [count, items] = await Promise.all([
getCallHistoryGroupsCountRef.current(options),
getCallHistoryGroupsRef.current(options, {
offset: 0,
limit: 100, // preloaded rows
}),
]);
results = { count, items };
} catch (error) {
log.error('CallsList#fetchTotal error fetching', error);
}
// Clear the loading indicator timeout
clearTimeout(timer);
// Ignore old requests
if (controller.signal.aborted) {
return;
}
if (results) {
enqueueCallPeeks(results.items, true);
searchStateItemsRef.current = results.items;
}
// Only commit the new search state once the results are ready
setSearchState({
state: results == null ? 'rejected' : 'fulfilled',
options,
results,
});
const isUpdatingSameSearch =
prevOptionsRef.current != null &&
isSameOptions(options, prevOptionsRef.current);
// Commit only at the end in case the search was aborted.
prevOptionsRef.current = options;
// Only reset the scroll position to the top when the user has changed the
// search parameters
if (!isUpdatingSameSearch) {
infiniteLoaderRef.current?.resetLoadMoreRowsCache(true);
listRef.current?.scrollToPosition(0);
}
}
drop(search());
return () => {
controller.abort();
};
}, [queryInput, statusInput, callHistoryEdition, enqueueCallPeeks]);
const loadMoreRows = useCallback(
async (props: IndexRange) => {
const { state, options } = searchState;
if (state !== 'fulfilled') {
return;
}
strictAssert(
options != null,
'options should never be null when status is fulfilled'
);
let { startIndex, stopIndex } = props;
if (startIndex > stopIndex) {
// flip
[startIndex, stopIndex] = [stopIndex, startIndex];
}
const offset = startIndex;
const limit = stopIndex - startIndex + 1;
try {
const groups = await getCallHistoryGroupsRef.current(options, {
offset,
limit,
});
if (searchState.options !== options) {
return;
}
enqueueCallPeeks(groups, false);
setSearchState(prevSearchState => {
strictAssert(
prevSearchState.results != null,
'results should never be null here'
);
const newItems = prevSearchState.results.items.slice();
newItems.splice(startIndex, stopIndex, ...groups);
searchStateItemsRef.current = newItems;
return {
...prevSearchState,
results: {
...prevSearchState.results,
items: newItems,
},
};
});
} catch (error) {
log.error('CallsList#loadMoreRows error fetching', error);
}
},
[enqueueCallPeeks, searchState]
);
const isRowLoaded = useCallback(
(props: Index) => {
return searchState.results?.items[props.index] != null;
},
[searchState]
);
const rowHeight = useCallback(
({ index }: Index) => {
const item = rows.at(index) ?? null;
if (item === 'EmptyState') {
// arbitary large number so the empty state can be as big as it wants,
// scrolling should always be locked when the list is empty
return 9999;
}
return CALL_LIST_ITEM_ROW_HEIGHT;
},
[rows]
);
const rowRenderer = useCallback(
({ key, index, style }: ListRowProps) => {
const item = rows.at(index) ?? null;
if (item === 'CreateCallLink') {
return (
{i18n('icu:CallsList__CreateCallLink')}
}
leading={
}
onClick={onCreateCallLink}
/>
);
}
if (item === 'EmptyState') {
return (
{searchStateQuery === '' ? (
i18n('icu:CallsList__EmptyState--noQuery')
) : (
,
}}
/>
)}
);
}
const conversation = getConversationForItem(item);
const activeCallConversationId = activeCall?.conversationId;
const isActive = getIsCallActive({
callHistoryGroup: item,
});
const isInCall = getIsInCall({
activeCallConversationId,
callHistoryGroup: item,
conversation,
isActive,
});
const isAdhoc = item?.type === CallType.Adhoc;
const isCallButtonVisible = Boolean(
!isAdhoc || (isAdhoc && getCallLink(item.peerId))
);
const isActiveVisible = Boolean(isCallButtonVisible && item && isActive);
if (searchPending || item == null || conversation == null) {
return (
}
title={
}
subtitleMaxLines={1}
subtitle={
}
/>
);
}
const isSelected =
selectedCallHistoryGroup != null &&
isSameCallHistoryGroup(item, selectedCallHistoryGroup);
const wasMissed =
item.direction === CallDirection.Incoming &&
(item.status === DirectCallStatus.Missed ||
item.status === GroupCallStatus.Missed);
let statusText;
if (wasMissed) {
statusText = i18n('icu:CallsList__ItemCallInfo--Missed');
} else if (isAdhoc) {
statusText = i18n('icu:CallsList__ItemCallInfo--CallLink');
} else if (item.type === CallType.Group) {
statusText = i18n('icu:CallsList__ItemCallInfo--GroupCall');
} else if (item.direction === CallDirection.Outgoing) {
statusText = i18n('icu:CallsList__ItemCallInfo--Outgoing');
} else if (item.direction === CallDirection.Incoming) {
statusText = i18n('icu:CallsList__ItemCallInfo--Incoming');
} else {
strictAssert(false, 'Cannot format call');
}
return (
}
trailing={
isCallButtonVisible ? (
{
if (isInCall) {
togglePip();
} else if (activeCall) {
if (isActiveVisible) {
setIsLeaveCallDialogVisible(true);
}
} else if (isAdhoc) {
startCallLinkLobbyByRoomId(item.peerId);
} else if (conversation) {
if (item.type === CallType.Audio) {
onOutgoingAudioCallInConversation(conversation.id);
} else {
onOutgoingVideoCallInConversation(conversation.id);
}
}
}}
i18n={i18n}
/>
) : undefined
}
title={
}
subtitleMaxLines={1}
subtitle={
{item.children.length > 1 ? `(${item.children.length}) ` : ''}
{statusText} ·{' '}
{isActiveVisible ? (
i18n('icu:CallsList__ItemCallInfo--Active')
) : (
)}
}
onClick={() => {
if (isAdhoc) {
onChangeCallsTabSelectedView({
type: 'callLink',
roomId: item.peerId,
callHistoryGroup: item,
});
return;
}
if (conversation == null) {
return;
}
onChangeCallsTabSelectedView({
type: 'conversation',
conversationId: conversation.id,
callHistoryGroup: item,
});
}}
/>
);
},
[
activeCall,
rows,
searchStateQuery,
searchPending,
getCallLink,
getConversationForItem,
getIsCallActive,
getIsInCall,
selectedCallHistoryGroup,
onChangeCallsTabSelectedView,
onCreateCallLink,
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
startCallLinkLobbyByRoomId,
togglePip,
i18n,
]
);
const handleSearchInputChange = useCallback(
(event: ChangeEvent) => {
setQueryInput(event.target.value);
},
[]
);
const handleSearchInputClear = useCallback(() => {
setQueryInput('');
}, []);
const handleStatusToggle = useCallback(() => {
setStatusInput(prevStatus => {
return prevStatus === CallHistoryFilterStatus.All
? CallHistoryFilterStatus.Missed
: CallHistoryFilterStatus.All;
});
}, []);
return (
<>
{isLeaveCallDialogVisible && (
{
setIsLeaveCallDialogVisible(false);
}}
title={i18n('icu:CallsList__LeaveCallDialogTitle')}
actions={[
{
text: i18n('icu:CallsList__LeaveCallDialogButton--leave'),
style: 'affirmative',
action: () => {
hangUpActiveCall(
'Calls Tab leave active call to join different call'
);
},
},
]}
>
{i18n('icu:CallsList__LeaveCallDialogBody')}
)}
{(ref, size) => {
return (
{size != null && (
{({ onRowsRendered, registerChild }) => {
return (
);
}}
)}
);
}}
>
);
}