Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Fix notification badge for All Rooms space #7401

Merged
merged 2 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 48 additions & 46 deletions src/components/structures/MatrixChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ limitations under the License.
*/

import React, { ComponentType, createRef } from 'react';
import { createClient } from 'matrix-js-sdk/src/matrix';
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
import { ISyncStateData, SyncState } from 'matrix-js-sdk/src/sync';
import { MatrixError } from 'matrix-js-sdk/src/http-api';
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
Expand All @@ -32,7 +33,7 @@ import 'what-input';
import Analytics from "../../Analytics";
import CountlyAnalytics from "../../CountlyAnalytics";
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg";
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg";
import SdkConfig from "../../SdkConfig";
import dis from "../../dispatcher/dispatcher";
Expand All @@ -59,6 +60,7 @@ import { storeRoomAliasInCache } from '../../RoomAliasCache';
import ToastStore from "../../stores/ToastStore";
import * as StorageManager from "../../utils/StorageManager";
import type LoggedInViewType from "./LoggedInView";
import LoggedInView from './LoggedInView';
import { Action } from "../../dispatcher/actions";
import {
hideToast as hideAnalyticsToast,
Expand All @@ -68,7 +70,10 @@ import {
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import ErrorDialog from "../views/dialogs/ErrorDialog";
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
import {
RoomNotificationStateStore,
UPDATE_STATUS_INDICATOR,
} from "../../stores/notifications/RoomNotificationStateStore";
import { SettingLevel } from "../../settings/SettingLevel";
import { leaveRoomBehaviour } from "../../utils/membership";
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
Expand All @@ -92,7 +97,6 @@ import RoomDirectory from './RoomDirectory';
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
import CompleteSecurity from "./auth/CompleteSecurity";
import LoggedInView from './LoggedInView';
import Welcome from "../views/auth/Welcome";
import ForgotPassword from "./auth/ForgotPassword";
import E2eSetup from "./auth/E2eSetup";
Expand All @@ -114,6 +118,7 @@ import InfoDialog from "../views/dialogs/InfoDialog";
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
import AccessibleButton from "../views/elements/AccessibleButton";
import { ActionPayload } from "../../dispatcher/payloads";
import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";

/** constants for MatrixChat.state.view */
export enum Views {
Expand Down Expand Up @@ -257,8 +262,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onTokenLoginCompleted: () => {},
};

firstSyncComplete: boolean;
firstSyncPromise: IDeferred<void>;
private firstSyncComplete = false;
private firstSyncPromise: IDeferred<void>;

private screenAfterLogin?: IScreen;
private pageChanging: boolean;
Expand All @@ -270,12 +275,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private prevWindowWidth: number;

private readonly loggedInView: React.RefObject<LoggedInViewType>;
private readonly dispatcherRef: any;
private readonly dispatcherRef: string;
private readonly themeWatcher: ThemeWatcher;
private readonly fontWatcher: FontWatcher;

constructor(props, context) {
super(props, context);
constructor(props: IProps) {
super(props);

this.state = {
view: Views.LOADING,
Expand Down Expand Up @@ -321,6 +326,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// For PersistentElement
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);

RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator);

// Force users to go through the soft logout page if they're soft logged out
if (Lifecycle.isSoftLogout()) {
// When the session loads it'll be detected as soft logged out and a dispatch
Expand Down Expand Up @@ -494,15 +501,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
});
}

getFallbackHsUrl() {
if (this.props.serverConfig && this.props.serverConfig.isDefault) {
private getFallbackHsUrl(): string {
if (this.props.serverConfig?.isDefault) {
return this.props.config.fallback_hs_url;
} else {
return null;
}
}

getServerProperties() {
private getServerProperties() {
let props = this.state.serverConfig;
if (!props) props = this.props.serverConfig; // for unit tests
if (!props) props = SdkConfig.get()["validated_server_config"];
Expand Down Expand Up @@ -535,11 +542,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// to try logging out.
}

startPageChangeTimer() {
private startPageChangeTimer() {
PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
}

stopPageChangeTimer() {
private stopPageChangeTimer() {
const perfMonitor = PerformanceMonitor.instance;

perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
Expand All @@ -554,13 +561,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
: null;
}

shouldTrackPageChange(prevState: IState, state: IState) {
private shouldTrackPageChange(prevState: IState, state: IState): boolean {
return prevState.currentRoomId !== state.currentRoomId ||
prevState.view !== state.view ||
prevState.page_type !== state.page_type;
}

setStateForNewView(state: Partial<IState>) {
private setStateForNewView(state: Partial<IState>): void {
if (state.view === undefined) {
throw new Error("setStateForNewView with no view!");
}
Expand All @@ -572,7 +579,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.setState(newState);
}

private onAction = (payload: ActionPayload) => {
private onAction = (payload: ActionPayload): void => {
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);

// Start the onboarding process for certain actions
Expand Down Expand Up @@ -1486,29 +1493,28 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
return this.loggedInView.current.canResetTimelineInRoom(roomId);
});

cli.on('sync', (state, prevState, data) => {
cli.on('sync', (state: SyncState, prevState?: SyncState, data?: ISyncStateData) => {
// LifecycleStore and others cannot directly subscribe to matrix client for
// events because flux only allows store state changes during flux dispatches.
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
// would do this dispatch and expose the sync state itself (by listening to
// its own dispatch).
dis.dispatch({ action: 'sync_state', prevState, state });

if (state === "ERROR" || state === "RECONNECTING") {
if (state === SyncState.Error || state === SyncState.Reconnecting) {
if (data.error instanceof InvalidStoreError) {
Lifecycle.handleInvalidStoreError(data.error);
}
this.setState({ syncError: data.error || true });
this.setState({ syncError: data.error || {} as MatrixError });
} else if (this.state.syncError) {
this.setState({ syncError: null });
}

this.updateStatusIndicator(state, prevState);
if (state === "SYNCING" && prevState === "SYNCING") {
if (state === SyncState.Syncing && prevState === SyncState.Syncing) {
return;
}
logger.info("MatrixClient sync state => %s", state);
if (state !== "PREPARED") { return; }
if (state !== SyncState.Prepared) { return; }

this.firstSyncComplete = true;
this.firstSyncPromise.resolve();
Expand Down Expand Up @@ -1766,7 +1772,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
}

showScreen(screen: string, params?: {[key: string]: any}) {
public showScreen(screen: string, params?: {[key: string]: any}) {
const cli = MatrixClientPeg.get();
const isLoggedOutOrGuest = !cli || cli.isGuest();
if (!isLoggedOutOrGuest && AUTH_SCREENS.includes(screen)) {
Expand Down Expand Up @@ -1941,21 +1947,22 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
}

notifyNewScreen(screen: string, replaceLast = false) {
private notifyNewScreen(screen: string, replaceLast = false) {
if (this.props.onNewScreen) {
this.props.onNewScreen(screen, replaceLast);
}
this.setPageSubtitle();
}
onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {

private onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
dis.dispatch({
action: 'logout',
});
event.stopPropagation();
event.preventDefault();
}

handleResize = () => {
private handleResize = () => {
const LHS_THRESHOLD = 1000;
const width = UIStore.instance.windowWidth;

Expand All @@ -1975,28 +1982,28 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
dis.dispatch({ action: 'timeline_resize' });
}

onRegisterClick = () => {
private onRegisterClick = () => {
this.showScreen("register");
};

onLoginClick = () => {
private onLoginClick = () => {
this.showScreen("login");
};

onForgotPasswordClick = () => {
private onForgotPasswordClick = () => {
this.showScreen("forgot_password");
};

onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => {
private onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string): Promise<void> => {
return this.onUserCompletedLoginFlow(credentials, password);
};

// returns a promise which resolves to the new MatrixClient
onRegistered(credentials: IMatrixClientCreds) {
private onRegistered(credentials: IMatrixClientCreds): Promise<MatrixClient> {
return Lifecycle.setLoggedIn(credentials);
}

onSendEvent(roomId: string, event: MatrixEvent) {
private onSendEvent(roomId: string, event: MatrixEvent): void {
const cli = MatrixClientPeg.get();
if (!cli) return;

Expand All @@ -2023,31 +2030,26 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
}

updateStatusIndicator(state: string, prevState: string) {
const notificationState = RoomNotificationStateStore.instance.globalState;
private onUpdateStatusIndicator = (notificationState: SummarizedNotificationState, state: SyncState): void => {
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here

if (PlatformPeg.get()) {
PlatformPeg.get().setErrorStatus(state === 'ERROR');
PlatformPeg.get().setErrorStatus(state === SyncState.Error);
PlatformPeg.get().setNotificationCount(numUnreadRooms);
}

this.subTitleStatus = '';
if (state === "ERROR") {
if (state === SyncState.Error) {
this.subTitleStatus += `[${_t("Offline")}] `;
}
if (numUnreadRooms > 0) {
this.subTitleStatus += `[${numUnreadRooms}]`;
}

this.setPageSubtitle();
}

onCloseAllSettings() {
dis.dispatch({ action: 'close_settings' });
}
};

onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
private onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
this.setState({ serverConfig });
};

Expand All @@ -2065,7 +2067,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
* Note: SSO users (and any others using token login) currently do not pass through
* this, as they instead jump straight into the app after `attemptTokenLogin`.
*/
onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string) => {
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string): Promise<void> => {
this.accountPassword = password;
// self-destruct the password after 5mins
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
Expand All @@ -2083,11 +2085,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
};

// complete security / e2e setup has finished
onCompleteSecurityE2eSetupFinished = () => {
private onCompleteSecurityE2eSetupFinished = (): void => {
this.onLoggedIn();
};

getFragmentAfterLogin() {
private getFragmentAfterLogin(): string {
let fragmentAfterLogin = "";
const initialScreenAfterLogin = this.props.initialScreenAfterLogin;
if (initialScreenAfterLogin &&
Expand Down
25 changes: 20 additions & 5 deletions src/components/views/spaces/SpacePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import React, {
Dispatch,
ReactNode,
SetStateAction,
useCallback,
useEffect,
useLayoutEffect,
useRef,
Expand All @@ -33,7 +34,7 @@ import { useContextMenu } from "../../structures/ContextMenu";
import SpaceCreateMenu from "./SpaceCreateMenu";
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter";
import SpaceStore from "../../../stores/spaces/SpaceStore";
import {
getMetaSpaceName,
Expand All @@ -45,7 +46,10 @@ import {
UPDATE_TOP_LEVEL_SPACES,
} from "../../../stores/spaces";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import {
RoomNotificationStateStore,
UPDATE_STATUS_INDICATOR,
} from "../../../stores/notifications/RoomNotificationStateStore";
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import IconizedContextMenu, {
IconizedContextMenuCheckbox,
Expand All @@ -63,6 +67,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from "../../../dispatcher/actions";
import { NotificationState } from "../../../stores/notifications/NotificationState";

const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
Expand Down Expand Up @@ -136,20 +141,30 @@ const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceBut
</li>;
};

const getHomeNotificationState = (): NotificationState => {
return SpaceStore.instance.allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(MetaSpace.Home);
};

const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome;
});
const [notificationState, setNotificationState] = useState(getHomeNotificationState());
const updateNotificationState = useCallback(() => {
setNotificationState(getHomeNotificationState());
}, []);
useEffect(updateNotificationState, [updateNotificationState, allRoomsInHome]);
useEventEmitter(RoomNotificationStateStore.instance, UPDATE_STATUS_INDICATOR, updateNotificationState);

return <MetaSpaceButton
spaceKey={MetaSpace.Home}
className="mx_SpaceButton_home"
selected={selected}
isPanelCollapsed={isPanelCollapsed}
label={getMetaSpaceName(MetaSpace.Home, allRoomsInHome)}
notificationState={allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(MetaSpace.Home)}
notificationState={notificationState}
ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")}
/>;
Expand Down
Loading