fix: show loading spinner while sogs is fetching initial messages

pull/2481/head
Audric Ackermann 3 years ago
parent e464d6c573
commit 1d45aa6f45

@ -38,7 +38,6 @@ const StyledYourSessionIDSelectable = styled.p`
user-select: none; user-select: none;
text-align: center; text-align: center;
word-break: break-all; word-break: break-all;
padding: 0px var(--margins-lg);
font-weight: 300; font-weight: 300;
color: var(--color-text); color: var(--color-text);
font-size: var(--font-size-sm); font-size: var(--font-size-sm);

@ -54,6 +54,8 @@ import { ConversationMessageRequestButtons } from './ConversationRequestButtons'
import { ConversationRequestinfo } from './ConversationRequestInfo'; import { ConversationRequestinfo } from './ConversationRequestInfo';
import { getCurrentRecoveryPhrase } from '../../util/storage'; import { getCurrentRecoveryPhrase } from '../../util/storage';
import loadImage from 'blueimp-load-image'; import loadImage from 'blueimp-load-image';
import { SessionSpinner } from '../basic/SessionSpinner';
import styled from 'styled-components';
// tslint:disable: jsx-curly-spacing // tslint:disable: jsx-curly-spacing
interface State { interface State {
@ -78,8 +80,25 @@ interface Props {
lightBoxOptions?: LightBoxOptions; lightBoxOptions?: LightBoxOptions;
stagedAttachments: Array<StagedAttachmentType>; stagedAttachments: Array<StagedAttachmentType>;
isSelectedConvoInitialLoadingInProgress: boolean;
} }
const StyledSpinnerContainer = styled.div`
display: flex;
justify-content: center;
width: 100%;
height: 100%;
align-items: center;
`;
const ConvoLoadingSpinner = () => {
return (
<StyledSpinnerContainer>
<SessionSpinner loading={true} />
</StyledSpinnerContainer>
);
};
export class SessionConversation extends React.Component<Props, State> { export class SessionConversation extends React.Component<Props, State> {
private readonly messageContainerRef: React.RefObject<HTMLDivElement>; private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
private dragCounter: number; private dragCounter: number;
@ -219,6 +238,7 @@ export class SessionConversation extends React.Component<Props, State> {
selectedMessages, selectedMessages,
isRightPanelShowing, isRightPanelShowing,
lightBoxOptions, lightBoxOptions,
isSelectedConvoInitialLoadingInProgress,
} = this.props; } = this.props;
if (!selectedConversation || !messagesProps) { if (!selectedConversation || !messagesProps) {
@ -233,6 +253,10 @@ export class SessionConversation extends React.Component<Props, State> {
<div className="conversation-header"> <div className="conversation-header">
<ConversationHeaderWithDetails /> <ConversationHeaderWithDetails />
</div> </div>
{isSelectedConvoInitialLoadingInProgress ? (
<ConvoLoadingSpinner />
) : (
<>
<div <div
// if you change the classname, also update it on onKeyDown // if you change the classname, also update it on onKeyDown
className={classNames('conversation-content', selectionMode && 'selection-mode')} className={classNames('conversation-content', selectionMode && 'selection-mode')}
@ -269,10 +293,15 @@ export class SessionConversation extends React.Component<Props, State> {
/> />
</div> </div>
<div <div
className={classNames('conversation-item__options-pane', isRightPanelShowing && 'show')} className={classNames(
'conversation-item__options-pane',
isRightPanelShowing && 'show'
)}
> >
<SessionRightPanelWithDetails /> <SessionRightPanelWithDetails />
</div> </div>
</>
)}
</SessionTheme> </SessionTheme>
); );
} }

@ -17,7 +17,10 @@ import { openGroupV2CompleteURLRegex } from '../../../session/apis/open_group_ap
import { ToastUtils } from '../../../session/utils'; import { ToastUtils } from '../../../session/utils';
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import { getOverlayMode } from '../../../state/selectors/section'; import { getOverlayMode } from '../../../state/selectors/section';
import { openConversationWithMessages } from '../../../state/ducks/conversations'; import {
markConversationInitialLoadingInProgress,
openConversationWithMessages,
} from '../../../state/ducks/conversations';
async function joinOpenGroup( async function joinOpenGroup(
serverUrl: string, serverUrl: string,
@ -60,7 +63,14 @@ export const OverlayCommunity = () => {
function joinSogsUICallback(args: JoinSogsRoomUICallbackArgs) { function joinSogsUICallback(args: JoinSogsRoomUICallbackArgs) {
setLoading(args.loadingState === 'started'); setLoading(args.loadingState === 'started');
if (args.conversationKey) {
dispatch(
markConversationInitialLoadingInProgress({
conversationKey: args.conversationKey,
isInitialFetchingInProgress: true,
})
);
}
if (args.loadingState === 'finished' && overlayModeIsCommunity && args.conversationKey) { if (args.loadingState === 'finished' && overlayModeIsCommunity && args.conversationKey) {
closeOverlay(); closeOverlay();
void openConversationWithMessages({ conversationKey: args.conversationKey, messageId: null }); // open to last unread for a session run sogs void openConversationWithMessages({ conversationKey: args.conversationKey, messageId: null }); // open to last unread for a session run sogs

@ -2,8 +2,6 @@ import _ from 'lodash';
import { OpenGroupV2Room } from '../../../../data/opengroups'; import { OpenGroupV2Room } from '../../../../data/opengroups';
import { getConversationController } from '../../../conversations'; import { getConversationController } from '../../../conversations';
import { PromiseUtils, ToastUtils } from '../../../utils'; import { PromiseUtils, ToastUtils } from '../../../utils';
import { getEventSogsFirstPoll } from '../../../utils/GlobalEvents';
import { sleepFor, waitForTask } from '../../../utils/Promise';
import { forceSyncConfigurationNowIfNeeded } from '../../../utils/syncUtils'; import { forceSyncConfigurationNowIfNeeded } from '../../../utils/syncUtils';
import { import {
@ -154,20 +152,6 @@ export async function joinOpenGroupV2WithUIEvents(
await joinOpenGroupV2(parsedRoom, fromConfigMessage); await joinOpenGroupV2(parsedRoom, fromConfigMessage);
if (!fromConfigMessage && showToasts) {
// this is very hacky but is made so we wait for the poller to receive the first messages related to that room.
// once the poller added all the messages to the queue of jobs to be run, we still wait a bit for them to be processed.
// This won't age well, but I am not too sure how we can design better.
await waitForTask(done => {
const eventToWait = getEventSogsFirstPoll(parsedRoom.serverPublicKey, parsedRoom.roomId);
window.Whisper.events.on(eventToWait, async () => {
window.Whisper.events.off(eventToWait);
await sleepFor(5000);
done(0);
});
}, 25000);
}
const isConvoCreated = getConversationController().get(conversationID); const isConvoCreated = getConversationController().get(conversationID);
if (isConvoCreated) { if (isConvoCreated) {
if (showToasts) { if (showToasts) {

@ -20,7 +20,10 @@ import {
roomHasBlindEnabled, roomHasBlindEnabled,
} from '../sogsv3/sogsV3Capabilities'; } from '../sogsv3/sogsV3Capabilities';
import { OpenGroupReaction } from '../../../../types/Reaction'; import { OpenGroupReaction } from '../../../../types/Reaction';
import { getEventSogsFirstPoll } from '../../../utils/GlobalEvents'; import {
markConversationInitialLoadingInProgress,
openConversationWithMessages,
} from '../../../../state/ducks/conversations';
export type OpenGroupMessageV4 = { export type OpenGroupMessageV4 = {
/** AFAIK: indicates the number of the message in the group. e.g. 2nd message will be 1 or 2 */ /** AFAIK: indicates the number of the message in the group. e.g. 2nd message will be 1 or 2 */
@ -319,14 +322,33 @@ export class OpenGroupServerPoller {
// ==> At this point all those results need to trigger conversation updates, so update what we have to update // ==> At this point all those results need to trigger conversation updates, so update what we have to update
await handleBatchPollResults(this.serverUrl, batchPollResults, subrequestOptions); await handleBatchPollResults(this.serverUrl, batchPollResults, subrequestOptions);
const roomsInDb = OpenGroupData.getV2OpenGroupRoomsByServerUrl(this.serverUrl);
if (roomsInDb?.[0]?.serverPublicKey) {
for (const room of subrequestOptions) { for (const room of subrequestOptions) {
if (room.type === 'messages' && !room.messages?.sinceSeqNo && room.messages?.roomId) { if (room.type === 'messages' && !room.messages?.sinceSeqNo && room.messages?.roomId) {
window.Whisper.events.trigger( const conversationKey = getOpenGroupV2ConversationId(
getEventSogsFirstPoll(roomsInDb[0].serverPublicKey, room.messages.roomId) this.serverUrl,
room.messages.roomId
);
global.setTimeout(() => {
const stateConversations = window.inboxStore?.getState().conversations;
if (
stateConversations.conversationLookup?.[conversationKey]?.isInitialFetchingInProgress
) {
if (
stateConversations.selectedConversation &&
conversationKey === stateConversations.selectedConversation
) {
void openConversationWithMessages({ conversationKey, messageId: null }).then(() => {
window.inboxStore?.dispatch(
markConversationInitialLoadingInProgress({
conversationKey,
isInitialFetchingInProgress: false,
})
); );
});
}
} }
}, 5000);
} }
} }
} catch (e) { } catch (e) {

@ -1,3 +0,0 @@
export function getEventSogsFirstPoll(serverpubkey: string, roomId: string) {
return `first-poll-sogs:${roomId}-${serverpubkey}`;
}

@ -264,6 +264,7 @@ export interface ReduxConversationType {
currentNotificationSetting?: ConversationNotificationSettingType; currentNotificationSetting?: ConversationNotificationSettingType;
isPinned?: boolean; isPinned?: boolean;
isInitialFetchingInProgress?: boolean;
isApproved?: boolean; isApproved?: boolean;
didApproveMe?: boolean; didApproveMe?: boolean;
@ -717,10 +718,6 @@ const conversationsSlice = createSlice({
initialMessages: Array<MessageModelPropsWithoutConvoProps>; initialMessages: Array<MessageModelPropsWithoutConvoProps>;
}> }>
) { ) {
if (state.selectedConversation === action.payload.conversationKey) {
return state;
}
// this is quite hacky, but we don't want to show the showScrollButton if we have only a small amount of messages, // this is quite hacky, but we don't want to show the showScrollButton if we have only a small amount of messages,
// or if the first unread message is not far from the most recent one. // or if the first unread message is not far from the most recent one.
// this is because when a new message get added, we do not add it to redux depending on the showScrollButton state. // this is because when a new message get added, we do not add it to redux depending on the showScrollButton state.
@ -835,6 +832,20 @@ const conversationsSlice = createSlice({
state.mentionMembers = action.payload; state.mentionMembers = action.payload;
return state; return state;
}, },
markConversationInitialLoadingInProgress(
state: ConversationsStateType,
action: PayloadAction<{ conversationKey: string; isInitialFetchingInProgress: boolean }>
) {
window?.log?.info(
`mark conversation initialLoading ${action.payload.conversationKey}: ${action.payload.isInitialFetchingInProgress}`
);
if (state.conversationLookup[action.payload.conversationKey]) {
state.conversationLookup[action.payload.conversationKey].isInitialFetchingInProgress =
action.payload.isInitialFetchingInProgress;
}
return state;
},
}, },
extraReducers: (builder: any) => { extraReducers: (builder: any) => {
// Add reducers for additional action types here, and handle loading state as needed // Add reducers for additional action types here, and handle loading state as needed
@ -945,7 +956,7 @@ function applyConversationChanged(
selectedConversation, selectedConversation,
conversationLookup: { conversationLookup: {
...conversationLookup, ...conversationLookup,
[id]: data, [id]: { ...data, isInitialFetchingInProgress: existing.isInitialFetchingInProgress },
}, },
}; };
} }
@ -981,6 +992,7 @@ export const {
setNextMessageToPlayId, setNextMessageToPlayId,
updateMentionsMembers, updateMentionsMembers,
resetConversationExternal, resetConversationExternal,
markConversationInitialLoadingInProgress,
} = actions; } = actions;
export async function openConversationWithMessages(args: { export async function openConversationWithMessages(args: {

@ -1172,3 +1172,8 @@ export const getOldBottomMessageId = createSelector(
getConversations, getConversations,
(state: ConversationsStateType): string | null => state.oldBottomMessageId || null (state: ConversationsStateType): string | null => state.oldBottomMessageId || null
); );
export const getIsSelectedConvoInitialLoadingInProgress = createSelector(
getSelectedConversation,
(convo: ReduxConversationType | undefined): boolean => Boolean(convo?.isInitialFetchingInProgress)
);

@ -3,6 +3,7 @@ import { mapDispatchToProps } from '../actions';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getTheme } from '../selectors/theme'; import { getTheme } from '../selectors/theme';
import { import {
getIsSelectedConvoInitialLoadingInProgress,
getLightBoxOptions, getLightBoxOptions,
getSelectedConversation, getSelectedConversation,
getSelectedConversationKey, getSelectedConversationKey,
@ -29,6 +30,7 @@ const mapStateToProps = (state: StateType) => {
lightBoxOptions: getLightBoxOptions(state), lightBoxOptions: getLightBoxOptions(state),
stagedAttachments: getStagedAttachmentsForCurrentConversation(state), stagedAttachments: getStagedAttachmentsForCurrentConversation(state),
hasOngoingCallWithFocusedConvo: getHasOngoingCallWithFocusedConvo(state), hasOngoingCallWithFocusedConvo: getHasOngoingCallWithFocusedConvo(state),
isSelectedConvoInitialLoadingInProgress: getIsSelectedConvoInitialLoadingInProgress(state),
}; };
}; };

1
ts/window.d.ts vendored

@ -6,6 +6,7 @@ import { Store } from 'redux';
import { ConversationCollection, ConversationModel } from './models/conversation'; import { ConversationCollection, ConversationModel } from './models/conversation';
import { ConversationType } from './state/ducks/conversations'; import { ConversationType } from './state/ducks/conversations';
import { StateType } from './state/reducer';
export interface LibTextsecure { export interface LibTextsecure {
messaging: boolean; messaging: boolean;

Loading…
Cancel
Save