diff --git a/app/soapbox/components/sidebar-navigation.tsx b/app/soapbox/components/sidebar-navigation.tsx index e7a65326d..df8c9697e 100644 --- a/app/soapbox/components/sidebar-navigation.tsx +++ b/app/soapbox/components/sidebar-navigation.tsx @@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { getSettings } from 'soapbox/actions/settings'; import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; +import { useStatContext } from 'soapbox/contexts/stat-context'; import ComposeButton from 'soapbox/features/ui/components/compose-button'; import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; import { getFeatures } from 'soapbox/utils/features'; @@ -24,12 +25,12 @@ const messages = defineMessages({ /** Desktop sidebar with links to different views in the app. */ const SidebarNavigation = () => { const intl = useIntl(); + const { unreadChatsCount } = useStatContext(); const instance = useAppSelector((state) => state.instance); const settings = useAppSelector((state) => getSettings(state)); const account = useOwnAccount(); const notificationCount = useAppSelector((state) => state.notifications.get('unread')); - const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0)); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); @@ -114,7 +115,7 @@ const SidebarNavigation = () => { } /> ); diff --git a/app/soapbox/components/thumb_navigation.tsx b/app/soapbox/components/thumb_navigation.tsx index 1935526ed..0c0b8d977 100644 --- a/app/soapbox/components/thumb_navigation.tsx +++ b/app/soapbox/components/thumb_navigation.tsx @@ -2,13 +2,15 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import ThumbNavigationLink from 'soapbox/components/thumb_navigation-link'; +import { useStatContext } from 'soapbox/contexts/stat-context'; import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; import { getFeatures } from 'soapbox/utils/features'; const ThumbNavigation: React.FC = (): JSX.Element => { const account = useOwnAccount(); + const { unreadChatsCount } = useStatContext(); + const notificationCount = useAppSelector((state) => state.notifications.unread); - const chatsCount = useAppSelector((state) => state.chats.items.reduce((acc, curr) => acc + Math.min(curr.unread || 0, 1), 0)); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); const features = getFeatures(useAppSelector((state) => state.instance)); @@ -21,7 +23,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => { text={} to='/chats' exact - count={chatsCount} + count={unreadChatsCount} /> ); } diff --git a/app/soapbox/contexts/stat-context.tsx b/app/soapbox/contexts/stat-context.tsx new file mode 100644 index 000000000..00d28436c --- /dev/null +++ b/app/soapbox/contexts/stat-context.tsx @@ -0,0 +1,29 @@ +import React, { createContext, useContext, useMemo, useState } from 'react'; + +type IStatContext = { + unreadChatsCount: number, + setUnreadChatsCount: React.Dispatch> +} + +const StatContext = createContext({ + unreadChatsCount: 0, +}); + +const StatProvider: React.FC = ({ children }) => { + const [unreadChatsCount, setUnreadChatsCount] = useState(0); + + const value = useMemo(() => ({ + unreadChatsCount, + setUnreadChatsCount, + }), [unreadChatsCount]); + + return ( + + {children} + + ); +}; + +const useStatContext = (): IStatContext => useContext(StatContext); + +export { StatProvider, useStatContext }; \ No newline at end of file diff --git a/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx b/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx index d89bee60a..fe29641ce 100644 --- a/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx +++ b/app/soapbox/features/chats/components/chat-pane/chat-pane.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { Stack } from 'soapbox/components/ui'; import { useChatContext } from 'soapbox/contexts/chat-context'; +import { useStatContext } from 'soapbox/contexts/stat-context'; import { useDebounce, useFeatures } from 'soapbox/hooks'; import { IChat, useChats } from 'soapbox/queries/chats'; @@ -19,6 +20,7 @@ import Blankslate from './blankslate'; const ChatPane = () => { const features = useFeatures(); const debounce = useDebounce; + const { unreadChatsCount } = useStatContext(); const [value, setValue] = useState(); const debouncedValue = debounce(value as string, 300); @@ -26,8 +28,6 @@ const ChatPane = () => { const { chat, setChat, isOpen, isSearching, setSearching, toggleChatPane } = useChatContext(); const { chatsQuery: { data: chats, isLoading } } = useChats(debouncedValue); - const unreadCount = sumBy(chats, (chat) => chat.unread); - const hasSearchValue = Number(debouncedValue?.length) > 0; const handleClickChat = (chat: IChat) => { @@ -89,7 +89,7 @@ const ChatPane = () => { { diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index d76c49756..2b3b07feb 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -118,6 +118,7 @@ import { WrappedRoute } from './util/react_router_helpers'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. import 'soapbox/components/status'; +import { StatProvider } from '../../contexts/stat-context'; const EmptyPage = HomePage; @@ -651,52 +652,54 @@ const UI: React.FC = ({ children }) => { }; return ( - -
- + + +
+ -
- +
+ - - - {!standalone && } - + + + {!standalone && } + - - {children} - - + + {children} + + - {me && floatingActionButton} + {me && floatingActionButton} - - {Component => } - + + {Component => } + - {me && ( - + {me && ( + + {Component => } + + )} + + {me && features.chats && !mobile && ( + + {Component => } + + )} + + + {Component => } - )} - {me && features.chats && !mobile && ( - + {Component => } - )} - - - - {Component => } - - - - {Component => } - +
-
-
+ +
); }; diff --git a/app/soapbox/queries/chats.ts b/app/soapbox/queries/chats.ts index 66a1c1f0d..0d750fb46 100644 --- a/app/soapbox/queries/chats.ts +++ b/app/soapbox/queries/chats.ts @@ -1,4 +1,5 @@ import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'; +import sumBy from 'lodash/sumBy'; import { useState } from 'react'; import { fetchRelationships } from 'soapbox/actions/accounts'; @@ -6,6 +7,7 @@ import snackbar from 'soapbox/actions/snackbar'; import { getNextLink } from 'soapbox/api'; import compareId from 'soapbox/compare_id'; import { useChatContext } from 'soapbox/contexts/chat-context'; +import { useStatContext } from 'soapbox/contexts/stat-context'; import { useApi, useAppDispatch, useFeatures } from 'soapbox/hooks'; import { normalizeChatMessage } from 'soapbox/normalizers'; import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries'; @@ -101,6 +103,7 @@ const useChats = (search?: string) => { const api = useApi(); const dispatch = useAppDispatch(); const features = useFeatures(); + const { setUnreadChatsCount } = useStatContext(); const getChats = async (pageParam?: any): Promise> => { const endpoint = features.chatsV2 ? '/api/v2/pleroma/chats' : '/api/v1/pleroma/chats'; @@ -116,6 +119,9 @@ const useChats = (search?: string) => { const link = getNextLink(response); const hasMore = !!link; + // TODO: change to response header + setUnreadChatsCount(sumBy(data, (chat) => chat.unread)); + // Set the relationships to these users in the redux store. dispatch(fetchRelationships(data.map((item) => item.account.id)));