Update nav tab badges, fix several call tabs issues

This commit is contained in:
Jamie Kyle 2023-08-14 16:28:47 -07:00 committed by Jamie Kyle
parent ed6ffb695a
commit 9c7dc22a23
43 changed files with 1095 additions and 936 deletions

View file

@ -17,11 +17,13 @@ import * as Errors from '../../types/errors';
export type CallHistoryState = ReadonlyDeep<{
// This informs the app that underlying call history data has changed.
edition: number;
unreadCount: number;
callHistoryByCallId: Record<string, CallHistoryDetails>;
}>;
const CALL_HISTORY_CACHE = 'callHistory/CACHE';
const CALL_HISTORY_RESET = 'callHistory/RESET';
const CALL_HISTORY_UPDATE_UNREAD = 'callHistory/UPDATE_UNREAD';
export type CallHistoryCache = ReadonlyDeep<{
type: typeof CALL_HISTORY_CACHE;
@ -32,17 +34,58 @@ export type CallHistoryReset = ReadonlyDeep<{
type: typeof CALL_HISTORY_RESET;
}>;
export type CallHistoryUpdateUnread = ReadonlyDeep<{
type: typeof CALL_HISTORY_UPDATE_UNREAD;
payload: number;
}>;
export type CallHistoryAction = ReadonlyDeep<
CallHistoryCache | CallHistoryReset
CallHistoryCache | CallHistoryReset | CallHistoryUpdateUnread
>;
export function getEmptyState(): CallHistoryState {
return {
edition: 0,
unreadCount: 0,
callHistoryByCallId: {},
};
}
function updateCallHistoryUnreadCount(): ThunkAction<
void,
RootStateType,
unknown,
CallHistoryUpdateUnread
> {
return async dispatch => {
try {
const unreadCount = await window.Signal.Data.getCallHistoryUnreadCount();
dispatch({ type: CALL_HISTORY_UPDATE_UNREAD, payload: unreadCount });
} catch (error) {
log.error(
'Error updating call history unread count',
Errors.toLogFormat(error)
);
}
};
}
function markCallHistoryRead(
conversationId: string,
callId: string
): ThunkAction<void, RootStateType, unknown, CallHistoryUpdateUnread> {
return async dispatch => {
try {
await window.Signal.Data.markCallHistoryRead(callId);
await window.ConversationController.get(conversationId)?.updateUnread();
} catch (error) {
log.error('Error marking call history read', Errors.toLogFormat(error));
} finally {
dispatch(updateCallHistoryUnreadCount());
}
};
}
function cacheCallHistory(callHistory: CallHistoryDetails): CallHistoryCache {
return {
type: CALL_HISTORY_CACHE,
@ -65,6 +108,7 @@ function clearAllCallHistory(): ThunkAction<
} finally {
// Just force a reset, even if the clear failed.
dispatch({ type: CALL_HISTORY_RESET });
dispatch(updateCallHistoryUnreadCount());
}
};
}
@ -72,6 +116,8 @@ function clearAllCallHistory(): ThunkAction<
export const actions = {
cacheCallHistory,
clearAllCallHistory,
updateCallHistoryUnreadCount,
markCallHistoryRead,
};
export const useCallHistoryActions = (): BoundActionCreatorsMapObject<
@ -93,6 +139,11 @@ export function reducer(
[action.payload.callId]: action.payload,
},
};
case CALL_HISTORY_UPDATE_UNREAD:
return {
...state,
unreadCount: action.payload,
};
default:
return state;
}

View file

@ -29,3 +29,10 @@ export const getCallHistorySelector = createSelector(
};
}
);
export const getCallHistoryUnreadCount = createSelector(
getCallHistory,
callHistory => {
return callHistory.unreadCount;
}
);

View file

@ -66,6 +66,10 @@ import type { HasStories } from '../../types/Stories';
import { getHasStoriesSelector } from './stories2';
import { canEditMessage } from '../../util/canEditMessage';
import { isOutgoing } from '../../messages/helpers';
import {
countAllConversationsUnreadStats,
type UnreadStats,
} from '../../util/countUnreadStats';
export type ConversationWithStoriesType = ConversationType & {
hasStories?: HasStories;
@ -532,37 +536,12 @@ export const getAllGroupsWithInviteAccess = createSelector(
})
);
export type UnreadStats = Readonly<{
unreadCount: number;
unreadMentionsCount: number;
markedUnread: boolean;
}>;
export const getAllConversationsUnreadStats = createSelector(
getLeftPaneLists,
(leftPaneLists: LeftPaneLists): UnreadStats => {
let unreadCount = 0;
let unreadMentionsCount = 0;
let markedUnread = false;
function count(conversations: ReadonlyArray<ConversationType>) {
conversations.forEach(conversation => {
if (conversation.unreadCount != null) {
unreadCount += conversation.unreadCount;
}
if (conversation.unreadMentionsCount != null) {
unreadMentionsCount += conversation.unreadMentionsCount;
}
if (conversation.markedUnread) {
markedUnread = true;
}
});
}
count(leftPaneLists.pinnedConversations);
count(leftPaneLists.conversations);
return { unreadCount, unreadMentionsCount, markedUnread };
getAllConversations,
(conversations): UnreadStats => {
return countAllConversationsUnreadStats(conversations, {
includeMuted: false,
});
}
);

View file

@ -4,6 +4,9 @@
import { createSelector } from 'reselect';
import type { StateType } from '../reducer';
import type { NavStateType } from '../ducks/nav';
import { getAllConversationsUnreadStats } from './conversations';
import { getStoriesNotificationCount } from './stories';
import type { UnreadStats } from '../../util/countUnreadStats';
function getNav(state: StateType): NavStateType {
return state.nav;
@ -12,3 +15,17 @@ function getNav(state: StateType): NavStateType {
export const getSelectedNavTab = createSelector(getNav, nav => {
return nav.selectedNavTab;
});
export const getAppUnreadStats = createSelector(
getAllConversationsUnreadStats,
getStoriesNotificationCount,
(conversationsUnreadStats, storiesNotificationCount): UnreadStats => {
return {
// Note: Conversation unread stats includes the call history unread count.
unreadCount:
conversationsUnreadStats.unreadCount + storiesNotificationCount,
unreadMentionsCount: conversationsUnreadStats.unreadMentionsCount,
markedUnread: conversationsUnreadStats.markedUnread,
};
}
);

View file

@ -42,3 +42,8 @@ export const isOSUnsupported = createSelector(
getUpdatesState,
({ dialogType }) => dialogType === DialogType.UnsupportedOS
);
export const getHasPendingUpdate = createSelector(
getUpdatesState,
({ didSnooze }) => didSnooze === true
);

View file

@ -27,6 +27,9 @@ import { useCallingActions } from '../ducks/calling';
import { getActiveCallState } from '../selectors/calling';
import { useCallHistoryActions } from '../ducks/callHistory';
import { getCallHistoryEdition } from '../selectors/callHistory';
import { getHasPendingUpdate } from '../selectors/updates';
import { getHasAnyFailedStorySends } from '../selectors/stories';
import { getAppUnreadStats } from '../selectors/nav';
function getCallHistoryFilter(
allConversations: Array<ConversationType>,
@ -91,11 +94,16 @@ export function SmartCallsTab(): JSX.Element {
const activeCall = useSelector(getActiveCallState);
const callHistoryEdition = useSelector(getCallHistoryEdition);
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const appUnreadStats = useSelector(getAppUnreadStats);
const {
onOutgoingAudioCallInConversation,
onOutgoingVideoCallInConversation,
} = useCallingActions();
const { clearAllCallHistory: clearCallHistory } = useCallHistoryActions();
const { clearAllCallHistory: clearCallHistory, markCallHistoryRead } =
useCallHistoryActions();
const getCallHistoryGroupsCount = useCallback(
async (options: CallHistoryFilterOptions) => {
@ -145,12 +153,16 @@ export function SmartCallsTab(): JSX.Element {
<CallsTab
activeCall={activeCall}
allConversations={allConversations}
appUnreadStats={appUnreadStats}
getConversation={getConversation}
getCallHistoryGroupsCount={getCallHistoryGroupsCount}
getCallHistoryGroups={getCallHistoryGroups}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
i18n={i18n}
navTabsCollapsed={navTabsCollapsed}
onClearCallHistory={clearCallHistory}
onMarkCallHistoryRead={markCallHistoryRead}
onToggleNavTabsCollapse={toggleNavTabsCollapse}
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}

View file

@ -20,6 +20,9 @@ import { showToast } from '../../util/showToast';
import { ToastStickerPackInstallFailed } from '../../components/ToastStickerPackInstallFailed';
import { getNavTabsCollapsed } from '../selectors/items';
import { useItemsActions } from '../ducks/items';
import { getHasAnyFailedStorySends } from '../selectors/stories';
import { getHasPendingUpdate } from '../selectors/updates';
import { getAppUnreadStats } from '../selectors/nav';
function renderConversationView() {
return <SmartConversationView />;
@ -36,6 +39,10 @@ function renderMiniPlayer(options: { shouldFlow: boolean }) {
export function SmartChatsTab(): JSX.Element {
const i18n = useSelector(getIntl);
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const appUnreadStats = useSelector(getAppUnreadStats);
const { selectedConversationId, targetedMessage, targetedMessageSource } =
useSelector<StateType, ConversationsStateType>(
state => state.conversations
@ -137,7 +144,10 @@ export function SmartChatsTab(): JSX.Element {
return (
<ChatsTab
appUnreadStats={appUnreadStats}
i18n={i18n}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
navTabsCollapsed={navTabsCollapsed}
onToggleNavTabsCollapse={toggleNavTabsCollapse}
prevConversationId={prevConversationId}

View file

@ -11,7 +11,6 @@ import {
getMe,
} from '../selectors/conversations';
import { getPreferredBadgeSelector } from '../selectors/badges';
import type { StateType } from '../reducer';
import {
getHasAnyFailedStorySends,
getStoriesNotificationCount,
@ -23,6 +22,8 @@ import { getStoriesEnabled } from '../selectors/items';
import { getSelectedNavTab } from '../selectors/nav';
import type { NavTab } from '../ducks/nav';
import { useNavActions } from '../ducks/nav';
import { getHasPendingUpdate } from '../selectors/updates';
import { getCallHistoryUnreadCount } from '../selectors/callHistory';
export type SmartNavTabsProps = Readonly<{
navTabsCollapsed: boolean;
@ -48,11 +49,9 @@ export function SmartNavTabs({
const storiesEnabled = useSelector(getStoriesEnabled);
const unreadConversationsStats = useSelector(getAllConversationsUnreadStats);
const unreadStoriesCount = useSelector(getStoriesNotificationCount);
const unreadCallsCount = useSelector(getCallHistoryUnreadCount);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const hasPendingUpdate = useSelector((state: StateType) => {
return state.updates.didSnooze;
});
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const { toggleProfileEditor } = useGlobalModalActions();
const { startUpdate } = useUpdatesActions();
@ -87,6 +86,7 @@ export function SmartNavTabs({
selectedNavTab={selectedNavTab}
storiesEnabled={storiesEnabled}
theme={theme}
unreadCallsCount={unreadCallsCount}
unreadConversationsStats={unreadConversationsStats}
unreadStoriesCount={unreadStoriesCount}
/>

View file

@ -45,6 +45,7 @@ function mapStateToProps(
firstName,
familyName,
id: conversationId,
phoneNumber,
username,
} = getMe(state);
const recentEmojis = selectRecentEmojis(state);
@ -74,6 +75,7 @@ function mapStateToProps(
isUsernameFlagEnabled,
recentEmojis,
skinTone,
phoneNumber,
userAvatarData,
username,
usernameEditState,

View file

@ -21,6 +21,7 @@ import {
} from '../selectors/items';
import {
getAddStoryData,
getHasAnyFailedStorySends,
getSelectedStoryData,
getStories,
} from '../selectors/stories';
@ -30,6 +31,8 @@ import { useStoriesActions } from '../ducks/stories';
import { useToastActions } from '../ducks/toast';
import { useAudioPlayerActions } from '../ducks/audioPlayer';
import { useItemsActions } from '../ducks/items';
import { getHasPendingUpdate } from '../selectors/updates';
import { getAppUnreadStats } from '../selectors/nav';
function renderStoryCreator(): JSX.Element {
return <SmartStoryCreator />;
@ -66,6 +69,9 @@ export function SmartStoriesTab(): JSX.Element | null {
);
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const appUnreadStats = useSelector(getAppUnreadStats);
const remoteConfig = useSelector(getRemoteConfig);
const maxAttachmentSizeInKb = getMaximumAttachmentSizeInKb(
@ -92,8 +98,11 @@ export function SmartStoriesTab(): JSX.Element | null {
return (
<StoriesTab
appUnreadStats={appUnreadStats}
addStoryData={addStoryData}
getPreferredBadge={getPreferredBadge}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
hiddenStories={hiddenStories}
i18n={i18n}
maxAttachmentSizeInKb={maxAttachmentSizeInKb}