diff --git a/app/soapbox/actions/streaming.ts b/app/soapbox/actions/streaming.ts index 6ec38b876..d0ceb6595 100644 --- a/app/soapbox/actions/streaming.ts +++ b/app/soapbox/actions/streaming.ts @@ -2,7 +2,7 @@ import { getSettings } from 'soapbox/actions/settings'; import messages from 'soapbox/locales/messages'; import { ChatKeys, IChat, isLastMessage } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; -import { updateChatListItem } from 'soapbox/utils/chats'; +import { getUnreadChatsCount, updateChatListItem } from 'soapbox/utils/chats'; import { removePageItem } from 'soapbox/utils/queries'; import { play, soundCache } from 'soapbox/utils/sounds'; @@ -27,6 +27,7 @@ import { processTimelineUpdate, } from './timelines'; +import type { IStatContext } from 'soapbox/contexts/stat-context'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity, Chat } from 'soapbox/types/entities'; @@ -79,11 +80,16 @@ const updateChatQuery = (chat: IChat) => { queryClient.setQueryData(ChatKeys.chat(chat.id), newChat as any); }; +interface StreamOpts { + statContext?: IStatContext, +} + const connectTimelineStream = ( timelineId: string, path: string, pollingRefresh: ((dispatch: AppDispatch, done?: () => void) => void) | null = null, accept: ((status: APIEntity) => boolean) | null = null, + opts?: StreamOpts, ) => connectStream(path, pollingRefresh, (dispatch: AppDispatch, getState: () => RootState) => { const locale = getLocale(getState()); @@ -145,6 +151,9 @@ const connectTimelineStream = ( if (settings.getIn(['chats', 'sound'])) { play(soundCache.chat); } + + // Increment unread counter + opts?.statContext?.setUnreadChatsCount(getUnreadChatsCount()); } }); break; @@ -186,8 +195,8 @@ const refreshHomeTimelineAndNotification = (dispatch: AppDispatch, done?: () => dispatch(expandNotifications({}, () => dispatch(fetchAnnouncements(done)))))); -const connectUserStream = () => - connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); +const connectUserStream = (opts?: StreamOpts) => + connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification, null, opts); const connectCommunityStream = ({ onlyMedia }: Record = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`); diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index 67022cf8b..a47eaa6d1 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -17,6 +17,7 @@ import * as BuildConfig from 'soapbox/build-config'; import GdprBanner from 'soapbox/components/gdpr-banner'; import Helmet from 'soapbox/components/helmet'; import LoadingScreen from 'soapbox/components/loading-screen'; +import { StatProvider } from 'soapbox/contexts/stat-context'; import AuthLayout from 'soapbox/features/auth-layout'; import EmbeddedStatus from 'soapbox/features/embedded-status'; import PublicLayout from 'soapbox/features/public-layout'; @@ -296,11 +297,13 @@ const Soapbox: React.FC = () => { return ( - - - - - + + + + + + + ); diff --git a/app/soapbox/contexts/stat-context.tsx b/app/soapbox/contexts/stat-context.tsx index 00d28436c..a66f72128 100644 --- a/app/soapbox/contexts/stat-context.tsx +++ b/app/soapbox/contexts/stat-context.tsx @@ -26,4 +26,4 @@ const StatProvider: React.FC = ({ children }) => { const useStatContext = (): IStatContext => useContext(StatContext); -export { StatProvider, useStatContext }; \ No newline at end of file +export { StatProvider, useStatContext, IStatContext }; \ No newline at end of file diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index e57edfb86..865024965 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -24,7 +24,7 @@ import Icon from 'soapbox/components/icon'; import SidebarNavigation from 'soapbox/components/sidebar-navigation'; import ThumbNavigation from 'soapbox/components/thumb-navigation'; import { Layout } from 'soapbox/components/ui'; -import { StatProvider } from 'soapbox/contexts/stat-context'; +import { useStatContext } from 'soapbox/contexts/stat-context'; import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useInstance } from 'soapbox/hooks'; import AdminPage from 'soapbox/pages/admin-page'; import ChatsPage from 'soapbox/pages/chats-page'; @@ -323,6 +323,7 @@ const UI: React.FC = ({ children }) => { const dispatch = useAppDispatch(); const { data: pendingPolicy } = usePendingPolicy(); const instance = useInstance(); + const statContext = useStatContext(); const [draggingOver, setDraggingOver] = useState(false); @@ -420,7 +421,7 @@ const UI: React.FC = ({ children }) => { const connectStreaming = () => { if (!disconnect.current && accessToken && streamingUrl) { - disconnect.current = dispatch(connectUserStream()); + disconnect.current = dispatch(connectUserStream({ statContext })); } }; @@ -642,58 +643,56 @@ const UI: React.FC = ({ children }) => { }; return ( - - -
- + +
+ -
- +
+ - - - {!standalone && } - + + + {!standalone && } + - - {children} - - + + {children} + + - {me && floatingActionButton} + {me && floatingActionButton} - - {Component => } - + + {Component => } + - {me && ( - - {Component => } - - )} - - {me && features.chats && ( - - {Component => ( -
- -
- )} -
- )} - - - + {me && ( + {Component => } + )} - - {Component => } + {me && features.chats && ( + + {Component => ( +
+ +
+ )}
-
+ )} + + + + {Component => } + + + + {Component => } +
- - +
+
); }; diff --git a/app/soapbox/utils/chats.ts b/app/soapbox/utils/chats.ts index a19bf789a..53676c898 100644 --- a/app/soapbox/utils/chats.ts +++ b/app/soapbox/utils/chats.ts @@ -1,4 +1,5 @@ import { InfiniteData } from '@tanstack/react-query'; +import sumBy from 'lodash/sumBy'; import { normalizeChatMessage } from 'soapbox/normalizers'; import { ChatKeys } from 'soapbox/queries/chats'; @@ -71,4 +72,13 @@ const updateChatListItem = (newChat: ChatPayload) => { } }; -export { updateChatListItem }; \ No newline at end of file +/** Get unread chats count. */ +const getUnreadChatsCount = (): number => { + const chats = flattenPages( + queryClient.getQueryData>>(ChatKeys.chatSearch()), + ); + + return sumBy(chats, chat => chat.unread); +}; + +export { updateChatListItem, getUnreadChatsCount }; \ No newline at end of file