Fix notification badge for All Rooms space (#7401)

This commit is contained in:
Michael Telatynski 2021-12-17 11:02:06 +00:00 committed by GitHub
parent bd226cd062
commit fb494a5098
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 64 deletions

View file

@ -15,7 +15,8 @@ limitations under the License.
*/ */
import React, { ComponentType, createRef } from 'react'; 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 { MatrixError } from 'matrix-js-sdk/src/http-api';
import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
@ -32,7 +33,7 @@ import 'what-input';
import Analytics from "../../Analytics"; import Analytics from "../../Analytics";
import CountlyAnalytics from "../../CountlyAnalytics"; import CountlyAnalytics from "../../CountlyAnalytics";
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker"; import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg"; import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg"; import PlatformPeg from "../../PlatformPeg";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
@ -59,6 +60,7 @@ import { storeRoomAliasInCache } from '../../RoomAliasCache';
import ToastStore from "../../stores/ToastStore"; import ToastStore from "../../stores/ToastStore";
import * as StorageManager from "../../utils/StorageManager"; import * as StorageManager from "../../utils/StorageManager";
import type LoggedInViewType from "./LoggedInView"; import type LoggedInViewType from "./LoggedInView";
import LoggedInView from './LoggedInView';
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { import {
hideToast as hideAnalyticsToast, hideToast as hideAnalyticsToast,
@ -68,7 +70,10 @@ import {
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast"; import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import ErrorDialog from "../views/dialogs/ErrorDialog"; 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 { SettingLevel } from "../../settings/SettingLevel";
import { leaveRoomBehaviour } from "../../utils/membership"; import { leaveRoomBehaviour } from "../../utils/membership";
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
@ -92,7 +97,6 @@ import RoomDirectory from './RoomDirectory';
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog"; import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog"; import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
import CompleteSecurity from "./auth/CompleteSecurity"; import CompleteSecurity from "./auth/CompleteSecurity";
import LoggedInView from './LoggedInView';
import Welcome from "../views/auth/Welcome"; import Welcome from "../views/auth/Welcome";
import ForgotPassword from "./auth/ForgotPassword"; import ForgotPassword from "./auth/ForgotPassword";
import E2eSetup from "./auth/E2eSetup"; import E2eSetup from "./auth/E2eSetup";
@ -114,6 +118,7 @@ import InfoDialog from "../views/dialogs/InfoDialog";
import FeedbackDialog from "../views/dialogs/FeedbackDialog"; import FeedbackDialog from "../views/dialogs/FeedbackDialog";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton from "../views/elements/AccessibleButton";
import { ActionPayload } from "../../dispatcher/payloads"; import { ActionPayload } from "../../dispatcher/payloads";
import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
export enum Views { export enum Views {
@ -257,8 +262,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onTokenLoginCompleted: () => {}, onTokenLoginCompleted: () => {},
}; };
firstSyncComplete: boolean; private firstSyncComplete = false;
firstSyncPromise: IDeferred<void>; private firstSyncPromise: IDeferred<void>;
private screenAfterLogin?: IScreen; private screenAfterLogin?: IScreen;
private pageChanging: boolean; private pageChanging: boolean;
@ -270,12 +275,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private prevWindowWidth: number; private prevWindowWidth: number;
private readonly loggedInView: React.RefObject<LoggedInViewType>; private readonly loggedInView: React.RefObject<LoggedInViewType>;
private readonly dispatcherRef: any; private readonly dispatcherRef: string;
private readonly themeWatcher: ThemeWatcher; private readonly themeWatcher: ThemeWatcher;
private readonly fontWatcher: FontWatcher; private readonly fontWatcher: FontWatcher;
constructor(props, context) { constructor(props: IProps) {
super(props, context); super(props);
this.state = { this.state = {
view: Views.LOADING, view: Views.LOADING,
@ -321,6 +326,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// For PersistentElement // For PersistentElement
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize); 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 // Force users to go through the soft logout page if they're soft logged out
if (Lifecycle.isSoftLogout()) { if (Lifecycle.isSoftLogout()) {
// When the session loads it'll be detected as soft logged out and a dispatch // When the session loads it'll be detected as soft logged out and a dispatch
@ -494,15 +501,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
} }
getFallbackHsUrl() { private getFallbackHsUrl(): string {
if (this.props.serverConfig && this.props.serverConfig.isDefault) { if (this.props.serverConfig?.isDefault) {
return this.props.config.fallback_hs_url; return this.props.config.fallback_hs_url;
} else { } else {
return null; return null;
} }
} }
getServerProperties() { private getServerProperties() {
let props = this.state.serverConfig; let props = this.state.serverConfig;
if (!props) props = this.props.serverConfig; // for unit tests if (!props) props = this.props.serverConfig; // for unit tests
if (!props) props = SdkConfig.get()["validated_server_config"]; if (!props) props = SdkConfig.get()["validated_server_config"];
@ -535,11 +542,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// to try logging out. // to try logging out.
} }
startPageChangeTimer() { private startPageChangeTimer() {
PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE); PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
} }
stopPageChangeTimer() { private stopPageChangeTimer() {
const perfMonitor = PerformanceMonitor.instance; const perfMonitor = PerformanceMonitor.instance;
perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
@ -554,13 +561,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
: null; : null;
} }
shouldTrackPageChange(prevState: IState, state: IState) { private shouldTrackPageChange(prevState: IState, state: IState): boolean {
return prevState.currentRoomId !== state.currentRoomId || return prevState.currentRoomId !== state.currentRoomId ||
prevState.view !== state.view || prevState.view !== state.view ||
prevState.page_type !== state.page_type; prevState.page_type !== state.page_type;
} }
setStateForNewView(state: Partial<IState>) { private setStateForNewView(state: Partial<IState>): void {
if (state.view === undefined) { if (state.view === undefined) {
throw new Error("setStateForNewView with no view!"); throw new Error("setStateForNewView with no view!");
} }
@ -572,7 +579,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.setState(newState); this.setState(newState);
} }
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload): void => {
// console.log(`MatrixClientPeg.onAction: ${payload.action}`); // console.log(`MatrixClientPeg.onAction: ${payload.action}`);
// Start the onboarding process for certain actions // Start the onboarding process for certain actions
@ -1486,7 +1493,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
return this.loggedInView.current.canResetTimelineInRoom(roomId); 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 // LifecycleStore and others cannot directly subscribe to matrix client for
// events because flux only allows store state changes during flux dispatches. // events because flux only allows store state changes during flux dispatches.
// So dispatch directly from here. Ideally we'd use a SyncStateStore that // So dispatch directly from here. Ideally we'd use a SyncStateStore that
@ -1494,21 +1501,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// its own dispatch). // its own dispatch).
dis.dispatch({ action: 'sync_state', prevState, state }); dis.dispatch({ action: 'sync_state', prevState, state });
if (state === "ERROR" || state === "RECONNECTING") { if (state === SyncState.Error || state === SyncState.Reconnecting) {
if (data.error instanceof InvalidStoreError) { if (data.error instanceof InvalidStoreError) {
Lifecycle.handleInvalidStoreError(data.error); Lifecycle.handleInvalidStoreError(data.error);
} }
this.setState({ syncError: data.error || true }); this.setState({ syncError: data.error || {} as MatrixError });
} else if (this.state.syncError) { } else if (this.state.syncError) {
this.setState({ syncError: null }); this.setState({ syncError: null });
} }
this.updateStatusIndicator(state, prevState); if (state === SyncState.Syncing && prevState === SyncState.Syncing) {
if (state === "SYNCING" && prevState === "SYNCING") {
return; return;
} }
logger.info("MatrixClient sync state => %s", state); logger.info("MatrixClient sync state => %s", state);
if (state !== "PREPARED") { return; } if (state !== SyncState.Prepared) { return; }
this.firstSyncComplete = true; this.firstSyncComplete = true;
this.firstSyncPromise.resolve(); this.firstSyncPromise.resolve();
@ -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 cli = MatrixClientPeg.get();
const isLoggedOutOrGuest = !cli || cli.isGuest(); const isLoggedOutOrGuest = !cli || cli.isGuest();
if (!isLoggedOutOrGuest && AUTH_SCREENS.includes(screen)) { if (!isLoggedOutOrGuest && AUTH_SCREENS.includes(screen)) {
@ -1941,13 +1947,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
} }
notifyNewScreen(screen: string, replaceLast = false) { private notifyNewScreen(screen: string, replaceLast = false) {
if (this.props.onNewScreen) { if (this.props.onNewScreen) {
this.props.onNewScreen(screen, replaceLast); this.props.onNewScreen(screen, replaceLast);
} }
this.setPageSubtitle(); this.setPageSubtitle();
} }
onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
private onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
dis.dispatch({ dis.dispatch({
action: 'logout', action: 'logout',
}); });
@ -1955,7 +1962,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
event.preventDefault(); event.preventDefault();
} }
handleResize = () => { private handleResize = () => {
const LHS_THRESHOLD = 1000; const LHS_THRESHOLD = 1000;
const width = UIStore.instance.windowWidth; const width = UIStore.instance.windowWidth;
@ -1975,28 +1982,28 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
dis.dispatch({ action: 'timeline_resize' }); dis.dispatch({ action: 'timeline_resize' });
} }
onRegisterClick = () => { private onRegisterClick = () => {
this.showScreen("register"); this.showScreen("register");
}; };
onLoginClick = () => { private onLoginClick = () => {
this.showScreen("login"); this.showScreen("login");
}; };
onForgotPasswordClick = () => { private onForgotPasswordClick = () => {
this.showScreen("forgot_password"); this.showScreen("forgot_password");
}; };
onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => { private onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string): Promise<void> => {
return this.onUserCompletedLoginFlow(credentials, password); return this.onUserCompletedLoginFlow(credentials, password);
}; };
// returns a promise which resolves to the new MatrixClient // returns a promise which resolves to the new MatrixClient
onRegistered(credentials: IMatrixClientCreds) { private onRegistered(credentials: IMatrixClientCreds): Promise<MatrixClient> {
return Lifecycle.setLoggedIn(credentials); return Lifecycle.setLoggedIn(credentials);
} }
onSendEvent(roomId: string, event: MatrixEvent) { private onSendEvent(roomId: string, event: MatrixEvent): void {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (!cli) return; if (!cli) return;
@ -2023,17 +2030,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
} }
updateStatusIndicator(state: string, prevState: string) { private onUpdateStatusIndicator = (notificationState: SummarizedNotificationState, state: SyncState): void => {
const notificationState = RoomNotificationStateStore.instance.globalState;
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
if (PlatformPeg.get()) { if (PlatformPeg.get()) {
PlatformPeg.get().setErrorStatus(state === 'ERROR'); PlatformPeg.get().setErrorStatus(state === SyncState.Error);
PlatformPeg.get().setNotificationCount(numUnreadRooms); PlatformPeg.get().setNotificationCount(numUnreadRooms);
} }
this.subTitleStatus = ''; this.subTitleStatus = '';
if (state === "ERROR") { if (state === SyncState.Error) {
this.subTitleStatus += `[${_t("Offline")}] `; this.subTitleStatus += `[${_t("Offline")}] `;
} }
if (numUnreadRooms > 0) { if (numUnreadRooms > 0) {
@ -2041,13 +2047,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
this.setPageSubtitle(); this.setPageSubtitle();
} };
onCloseAllSettings() { private onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
dis.dispatch({ action: 'close_settings' });
}
onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
this.setState({ serverConfig }); this.setState({ serverConfig });
}; };
@ -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 * 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`. * 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; this.accountPassword = password;
// self-destruct the password after 5mins // self-destruct the password after 5mins
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer); if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
@ -2083,11 +2085,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}; };
// complete security / e2e setup has finished // complete security / e2e setup has finished
onCompleteSecurityE2eSetupFinished = () => { private onCompleteSecurityE2eSetupFinished = (): void => {
this.onLoggedIn(); this.onLoggedIn();
}; };
getFragmentAfterLogin() { private getFragmentAfterLogin(): string {
let fragmentAfterLogin = ""; let fragmentAfterLogin = "";
const initialScreenAfterLogin = this.props.initialScreenAfterLogin; const initialScreenAfterLogin = this.props.initialScreenAfterLogin;
if (initialScreenAfterLogin && if (initialScreenAfterLogin &&

View file

@ -19,6 +19,7 @@ import React, {
Dispatch, Dispatch,
ReactNode, ReactNode,
SetStateAction, SetStateAction,
useCallback,
useEffect, useEffect,
useLayoutEffect, useLayoutEffect,
useRef, useRef,
@ -33,7 +34,7 @@ import { useContextMenu } from "../../structures/ContextMenu";
import SpaceCreateMenu from "./SpaceCreateMenu"; import SpaceCreateMenu from "./SpaceCreateMenu";
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel"; import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter";
import SpaceStore from "../../../stores/spaces/SpaceStore"; import SpaceStore from "../../../stores/spaces/SpaceStore";
import { import {
getMetaSpaceName, getMetaSpaceName,
@ -45,7 +46,10 @@ import {
UPDATE_TOP_LEVEL_SPACES, UPDATE_TOP_LEVEL_SPACES,
} from "../../../stores/spaces"; } from "../../../stores/spaces";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; 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 SpaceContextMenu from "../context_menus/SpaceContextMenu";
import IconizedContextMenu, { import IconizedContextMenu, {
IconizedContextMenuCheckbox, IconizedContextMenuCheckbox,
@ -63,6 +67,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher";
import defaultDispatcher from "../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../dispatcher/dispatcher";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { NotificationState } from "../../../stores/notifications/NotificationState";
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => { const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => { const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
@ -136,10 +141,22 @@ const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceBut
</li>; </li>;
}; };
const getHomeNotificationState = (): NotificationState => {
return SpaceStore.instance.allRoomsInHome
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(MetaSpace.Home);
};
const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
return SpaceStore.instance.allRoomsInHome; 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 return <MetaSpaceButton
spaceKey={MetaSpace.Home} spaceKey={MetaSpace.Home}
@ -147,9 +164,7 @@ const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
selected={selected} selected={selected}
isPanelCollapsed={isPanelCollapsed} isPanelCollapsed={isPanelCollapsed}
label={getMetaSpaceName(MetaSpace.Home, allRoomsInHome)} label={getMetaSpaceName(MetaSpace.Home, allRoomsInHome)}
notificationState={allRoomsInHome notificationState={notificationState}
? RoomNotificationStateStore.instance.globalState
: SpaceStore.instance.getNotificationState(MetaSpace.Home)}
ContextMenuComponent={HomeButtonContextMenu} ContextMenuComponent={HomeButtonContextMenu}
contextMenuTooltip={_t("Options")} contextMenuTooltip={_t("Options")}
/>; />;

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
import { ActionPayload } from "../../dispatcher/payloads"; import { ActionPayload } from "../../dispatcher/payloads";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
@ -23,17 +24,20 @@ import { DefaultTagID, TagID } from "../room-list/models";
import { FetchRoomFn, ListNotificationState } from "./ListNotificationState"; import { FetchRoomFn, ListNotificationState } from "./ListNotificationState";
import { RoomNotificationState } from "./RoomNotificationState"; import { RoomNotificationState } from "./RoomNotificationState";
import { SummarizedNotificationState } from "./SummarizedNotificationState"; import { SummarizedNotificationState } from "./SummarizedNotificationState";
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState"; import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
interface IState {} interface IState {}
export const UPDATE_STATUS_INDICATOR = Symbol("update-status-indicator");
export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> { export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
private static internalInstance = new RoomNotificationStateStore(); private static internalInstance = new RoomNotificationStateStore();
private roomMap = new Map<Room, RoomNotificationState>(); private roomMap = new Map<Room, RoomNotificationState>();
private roomThreadsMap = new Map<Room, ThreadsRoomNotificationState>(); private roomThreadsMap = new Map<Room, ThreadsRoomNotificationState>();
private listMap = new Map<TagID, ListNotificationState>(); private listMap = new Map<TagID, ListNotificationState>();
private _globalState = new SummarizedNotificationState();
private constructor() { private constructor() {
super(defaultDispatcher, {}); super(defaultDispatcher, {});
@ -44,18 +48,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
* on the SummarizedNotificationState is equivalent to rooms. * on the SummarizedNotificationState is equivalent to rooms.
*/ */
public get globalState(): SummarizedNotificationState { public get globalState(): SummarizedNotificationState {
// If we're not ready yet, just return an empty state return this._globalState;
if (!this.matrixClient) return new SummarizedNotificationState();
// Only count visible rooms to not torment the user with notification counts in rooms they can't see.
// This will include highlights from the previous version of the room internally
const globalState = new SummarizedNotificationState();
for (const room of this.matrixClient.getVisibleRooms()) {
if (VisibilityProvider.instance.isRoomVisible(room)) {
globalState.add(this.getRoomState(room));
}
}
return globalState;
} }
/** /**
@ -108,6 +101,30 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
return RoomNotificationStateStore.internalInstance; return RoomNotificationStateStore.internalInstance;
} }
private onSync = (state: SyncState, prevState?: SyncState, data?: ISyncStateData) => {
// Only count visible rooms to not torment the user with notification counts in rooms they can't see.
// This will include highlights from the previous version of the room internally
const globalState = new SummarizedNotificationState();
for (const room of this.matrixClient.getVisibleRooms()) {
if (VisibilityProvider.instance.isRoomVisible(room)) {
globalState.add(this.getRoomState(room));
}
}
if (this.globalState.symbol !== globalState.symbol ||
this.globalState.count !== globalState.count ||
this.globalState.color !== globalState.color ||
this.globalState.numUnreadStates !== globalState.numUnreadStates
) {
this._globalState = globalState;
this.emit(UPDATE_STATUS_INDICATOR, globalState, state, prevState, data);
}
};
protected async onReady() {
this.matrixClient.on("sync", this.onSync);
}
protected async onNotReady(): Promise<any> { protected async onNotReady(): Promise<any> {
for (const roomState of this.roomMap.values()) { for (const roomState of this.roomMap.values()) {
roomState.destroy(); roomState.destroy();