Support new unread badge count type

This commit is contained in:
trevor-signal
2026-08-28 20:14:58 +00:00
committed by GitHub
parent 91e76d3437
commit 616e0260a2
36 changed files with 455 additions and 251 deletions
+16
View File
@@ -8248,6 +8248,22 @@
"messageformat": "Reset",
"description": "Preferences > Notifications > Reset Section > Confirmation Modal > Label of the button that confirms restoring the default notification settings"
},
"icu:Preferences__Notifications__AppBadgeSection__BadgeCount__Label": {
"messageformat": "Badge count",
"description": "Preferences > Notifications > App Badge Section > Label for the badge count setting select box"
},
"icu:Preferences__Notifications__AppBadgeSection__BadgeCount__Description": {
"messageformat": "Select if the badge count displays the number of unread messages or the number of unread chats",
"description": "Preferences > Notifications > App Badge Section > Description for the badge count setting select box"
},
"icu:Preferences__Notifications__AppBadgeSection__BadgeCount__UnreadMessages": {
"messageformat": "Unread messages",
"description": "Preferences > Notifications > App Badge Section > Option for showing the number of unread messages in the badge count"
},
"icu:Preferences__Notifications__AppBadgeSection__BadgeCount__UnreadChats": {
"messageformat": "Unread chats",
"description": "Preferences > Notifications > App Badge Section > Option for showing the number of unread chats in the badge count"
},
"icu:PlaintextExport--PreferencesRow--Header": {
"messageformat": "Export chat history",
"description": "Shown in the Preferences/Chats page, header for a row"
+3 -16
View File
@@ -2688,22 +2688,9 @@ if (!app.isDefaultProtocolClient('signalcaptcha')) {
);
}
ipc.on(
'set-badge',
(_event: Electron.Event, badge: number | 'marked-unread') => {
if (badge === 'marked-unread') {
if (process.platform === 'darwin') {
// Will show a ● on macOS when undefined
app.setBadgeCount(undefined);
} else {
// All other OS's need a number
app.setBadgeCount(1);
}
} else {
app.setBadgeCount(badge);
}
}
);
ipc.on('set-badge-count', (_event: Electron.Event, badgeCount: number) => {
app.setBadgeCount(badgeCount);
});
ipc.on('remove-setup-menu-items', () => {
setupMenu();
+7
View File
@@ -105,6 +105,12 @@ message AccountData {
}
message AccountSettings {
enum UnreadBadgeType {
UNKNOWN_BADGE_TYPE = 0; // Interpret as "Unread messages"
UNREAD_MESSAGES = 1;
UNREAD_CHATS = 2;
}
bool readReceipts = 1;
bool sealedSenderIndicators = 2;
bool typingIndicators = 3;
@@ -138,6 +144,7 @@ message AccountData {
bool allowSealedSenderFromAnyone = 30;
bool allowAutomaticKeyVerification = 31;
bool hasSeenAdminDeleteEducationDialog = 32;
UnreadBadgeType unreadBadgeType = 33;
optional bool includeMutedChatsInBadge = 34; // If unset, consider this disabled
optional bool reactionNotifications = 35; // If unset, consider this enabled
optional bool notifyForCallsIfMuted = 36; // If unset, consider this disabled
+7
View File
@@ -189,6 +189,12 @@ message Payments {
}
message AccountRecord {
enum UnreadBadgeType {
UNKNOWN_BADGE_TYPE = 0; // Interpret as "Unread messages"
UNREAD_MESSAGES = 1;
UNREAD_CHATS = 2;
}
enum PhoneNumberSharingMode {
UNKNOWN = 0;
EVERYBODY = 1;
@@ -312,6 +318,7 @@ message AccountRecord {
optional bool releaseNotesChatBlocked = 50;
optional bool releaseNotesChatMarkedUnread = 51;
optional uint64 releaseNotesChatBlockedAt = 52; // only set if known (>0)
UnreadBadgeType unreadBadgeType = 53;
OptionalBool includeMutedChatsInBadge = 54; // If unset, consider this off
OptionalBool reactionNotifications = 55; // If unset, consider this on
OptionalBool notifyForCallsIfMuted = 56; // If unset, consider this off
+14 -17
View File
@@ -41,7 +41,11 @@ import { getTitleNoDefault } from './util/getTitle.preload.ts';
import * as StorageService from './services/storage.preload.ts';
import { cdsLookup } from './textsecure/WebAPI.preload.ts';
import type { ConversationPropsForUnreadStats } from './util/countUnreadStats.std.ts';
import { countAllConversationsUnreadStats } from './util/countUnreadStats.std.ts';
import {
countAllConversationsUnreadStats,
getUnreadCountForBadge,
} from './util/countUnreadStats.std.ts';
import { STORAGE_KEY_DEFAULTS } from './types/StorageKeys.std.ts';
import { isTestOrMockEnvironment } from './environment.std.ts';
import { isConversationAccepted } from './util/isConversationAccepted.preload.ts';
import { areWePending } from './util/groupMembershipUtils.preload.ts';
@@ -390,6 +394,9 @@ export class ConversationController {
const badgeCountMutedConversationsSetting =
itemStorage.get('badge-count-muted-conversations') || false;
const unreadCountBadgeType =
itemStorage.get('unreadCountBadgeType') ??
STORAGE_KEY_DEFAULTS.unreadCountBadgeType;
const { activeProfile } = window.reduxStore.getState().notificationProfiles;
const unreadStats = countAllConversationsUnreadStats(
@@ -420,22 +427,12 @@ export class ConversationController {
drop(itemStorage.put('unreadCount', unreadStats.unreadCount));
if (unreadStats.unreadCount > 0) {
const total =
unreadStats.unreadCount + unreadStats.readChatsMarkedUnreadCount;
window.IPC.setBadge(total);
window.IPC.updateTrayIcon(total);
window.document.title = `${window.getTitle()} (${total})`;
} else if (unreadStats.readChatsMarkedUnreadCount > 0) {
const total = unreadStats.readChatsMarkedUnreadCount;
window.IPC.setBadge(total);
window.IPC.updateTrayIcon(total);
window.document.title = `${window.getTitle()} (${total})`;
} else {
window.IPC.setBadge(0);
window.IPC.updateTrayIcon(0);
window.document.title = window.getTitle();
}
const total = getUnreadCountForBadge(unreadStats, unreadCountBadgeType);
window.IPC.setBadgeCount(total);
window.IPC.updateTrayIcon(total);
window.document.title =
total > 0 ? `${window.getTitle()} (${total})` : window.getTitle();
}
onEmpty(): void {
+3 -4
View File
@@ -17,7 +17,6 @@ import type {
ActiveCallStateType,
PeekNotConnectedGroupCallType,
} from '../state/ducks/calling.preload.ts';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import type { getCallIdFromEra } from '../util/callDisposition.preload.ts';
import type { CallLinkType } from '../types/CallLink.std.ts';
import type { CallStateType } from '../state/selectors/calling.std.ts';
@@ -35,7 +34,7 @@ enum CallsTabSidebarView {
type CallsTabProps = Readonly<{
activeCall: ActiveCallStateType | undefined;
allConversations: ReadonlyArray<ConversationType>;
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
getCallHistoryGroupsCount: (
options: CallHistoryFilterOptions
) => Promise<number>;
@@ -95,7 +94,7 @@ export type CallsTabSelectedView =
export function CallsTab({
activeCall,
allConversations,
otherTabsUnreadStats,
otherTabsUnreadCount,
getCallHistoryGroupsCount,
getCallHistoryGroups,
getCallIdFromEra,
@@ -205,7 +204,7 @@ export function CallsTab({
? i18n('icu:CallsTab__HeaderTitle--CallsList')
: i18n('icu:CallsTab__HeaderTitle--NewCall')
}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
navTabsCollapsed={navTabsCollapsed}
+1 -5
View File
@@ -18,11 +18,7 @@ export default {
},
args: {
i18n,
otherTabsUnreadStats: {
unreadCount: 0,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
},
otherTabsUnreadCount: 0,
isStaging: false,
hasPendingUpdate: false,
hasFailedStorySends: false,
+3 -4
View File
@@ -6,12 +6,11 @@ import type { JSX } from 'react';
import type { LocalizerType } from '../types/I18N.std.ts';
import type { NavTabPanelProps } from './NavTabs.dom.tsx';
import { WhatsNewLink } from './WhatsNewLink.dom.tsx';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import type { SmartConversationViewProps } from '../state/smart/ConversationView.preload.tsx';
import { tw } from '../axo/tw.dom.tsx';
export type ChatsTabProps = Readonly<{
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
i18n: LocalizerType;
isStaging: boolean;
hasPendingUpdate: boolean;
@@ -26,7 +25,7 @@ export type ChatsTabProps = Readonly<{
}>;
export function ChatsTab({
otherTabsUnreadStats,
otherTabsUnreadCount,
i18n,
isStaging,
hasPendingUpdate,
@@ -43,7 +42,7 @@ export function ChatsTab({
<>
<div id="LeftPane">
{renderLeftPane({
otherTabsUnreadStats,
otherTabsUnreadCount,
collapsed: navTabsCollapsed,
hasPendingUpdate,
hasFailedStorySends,
+2 -5
View File
@@ -146,11 +146,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
const isUpdateDownloaded = false;
return {
otherTabsUnreadStats: {
unreadCount: 0,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
},
otherTabsUnreadCount: 0,
backupMediaDownloadProgress: {
isBackupMediaEnabled: true,
downloadBannerDismissed: false,
@@ -341,6 +337,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
currentChatFolders={CurrentChatFolders.createEmpty()}
allChatFoldersUnreadStats={new Map()}
allChatFoldersMutedStats={new Map()}
unreadCountBadgeType="unread-messages"
selectedChatFolder={null}
onSelectedChatFolderIdChange={action('onSelectedChatFolderIdChange')}
onChatFolderMarkRead={action('onChatFolderMarkRead')}
+3 -4
View File
@@ -63,7 +63,6 @@ import {
NavSidebarActionButton,
NavSidebarSearchHeader,
} from './NavSidebar.dom.tsx';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import { BackupMediaDownloadProgress } from './BackupMediaDownloadProgress.dom.tsx';
import type {
ServerAlertsType,
@@ -89,7 +88,7 @@ export type PropsType = {
isPaused: boolean;
downloadBannerDismissed: boolean;
};
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
hasAnyCurrentCustomChatFolders: boolean;
hasClockSkewDialog: boolean;
hasExpiredDialog: boolean;
@@ -225,7 +224,7 @@ export type PropsType = {
export function LeftPane({
backupMediaDownloadProgress,
otherTabsUnreadStats,
otherTabsUnreadCount,
blockConversation,
cancelBackupMediaDownload,
challengeStatus,
@@ -811,7 +810,7 @@ export function LeftPane({
title={i18n('icu:LeftPane--chats')}
hideHeader={hideHeader}
i18n={i18n}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
navTabsCollapsed={navTabsCollapsed}
+3 -4
View File
@@ -20,13 +20,12 @@ import { resolveStorySendStatus } from '../util/resolveStorySendStatus.std.ts';
import { useRetryStorySend } from '../hooks/useRetryStorySend.dom.tsx';
import { NavSidebar } from './NavSidebar.dom.tsx';
import type { WidthBreakpoint } from './_util.std.ts';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import { AxoConfirmDialog } from '../axo/AxoConfirmDialog.dom.tsx';
import { strictAssert } from '../util/assert.std.ts';
export type PropsType = {
i18n: LocalizerType;
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
hasFailedStorySends: boolean;
hasPendingUpdate: boolean;
navTabsCollapsed: boolean;
@@ -51,7 +50,7 @@ export type PropsType = {
export function MyStories({
i18n,
otherTabsUnreadStats,
otherTabsUnreadCount,
hasFailedStorySends,
hasPendingUpdate,
navTabsCollapsed,
@@ -101,7 +100,7 @@ export function MyStories({
<NavSidebar
i18n={i18n}
title={i18n('icu:MyStories__title')}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
navTabsCollapsed={navTabsCollapsed}
+3 -4
View File
@@ -20,7 +20,6 @@ import {
getWidthFromPreferredWidth,
} from '../util/leftPaneWidth.std.ts';
import { WidthBreakpoint, getNavSidebarWidthBreakpoint } from './_util.std.ts';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import type { SmartPropsType as SmartToastManagerPropsType } from '../state/smart/ToastManager.preload.tsx';
import { AxoDragRegion } from '../axo/AxoDragRegion.dom.tsx';
@@ -67,7 +66,7 @@ export type NavSidebarProps = Readonly<{
requiresFullWidth: boolean;
savePreferredLeftPaneWidth: (width: number) => void;
title: string;
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
renderToastManager: (_: SmartToastManagerPropsType) => JSX.Element;
}>;
@@ -91,7 +90,7 @@ export function NavSidebar({
requiresFullWidth,
savePreferredLeftPaneWidth,
title,
otherTabsUnreadStats,
otherTabsUnreadCount,
renderToastManager,
}: NavSidebarProps): JSX.Element {
const isRTL = i18n.getLocaleDirection() === 'rtl';
@@ -190,7 +189,7 @@ export function NavSidebar({
onToggleNavTabsCollapse={onToggleNavTabsCollapse}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
/>
)}
<div
+2
View File
@@ -28,9 +28,11 @@ const createProps = (
unreadCallsCount: overrideProps.unreadCallsCount ?? 0,
unreadConversationsStats: overrideProps.unreadConversationsStats ?? {
unreadCount: 0,
unreadChatsCount: 0,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
},
unreadCountBadgeType: overrideProps.unreadCountBadgeType ?? 'unread-messages',
unreadStoriesCount: overrideProps.unreadStoriesCount ?? 0,
});
+31 -46
View File
@@ -10,19 +10,21 @@ import type { Location } from '../types/Nav.std.ts';
import { Tooltip, TooltipPlacement } from './Tooltip.dom.tsx';
import { Theme } from '../util/theme.std.ts';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import { getUnreadCountForBadge } from '../util/countUnreadStats.std.ts';
import type { UnreadCountBadgeType } from '../types/StorageKeys.std.ts';
type NavTabsItemBadgesProps = Readonly<{
i18n: LocalizerType;
hasError?: boolean;
hasPendingUpdate?: boolean;
unreadStats: UnreadStats | null;
unreadCount: number;
}>;
function NavTabsItemBadges({
i18n,
hasError,
hasPendingUpdate,
unreadStats,
unreadCount,
}: NavTabsItemBadgesProps) {
if (hasError) {
return (
@@ -39,31 +41,17 @@ function NavTabsItemBadges({
return <div className="NavTabs__ItemUpdateBadge" />;
}
if (unreadStats != null) {
if (unreadStats.unreadCount > 0) {
const total =
unreadStats.unreadCount + unreadStats.readChatsMarkedUnreadCount;
return (
<span className="NavTabs__ItemUnreadBadge">
<span className="NavTabs__ItemIconLabel">
{i18n('icu:NavTabs__ItemIconLabel--UnreadCount', {
count: total,
})}
</span>
<span aria-hidden>{total}</span>
if (unreadCount > 0) {
return (
<span className="NavTabs__ItemUnreadBadge">
<span className="NavTabs__ItemIconLabel">
{i18n('icu:NavTabs__ItemIconLabel--UnreadCount', {
count: unreadCount,
})}
</span>
);
}
if (unreadStats.readChatsMarkedUnreadCount > 0) {
return (
<span className="NavTabs__ItemUnreadBadge">
<span className="NavTabs__ItemIconLabel">
{i18n('icu:NavTabs__ItemIconLabel--MarkedUnread')}
</span>
</span>
);
}
<span aria-hidden>{unreadCount}</span>
</span>
);
}
return null;
@@ -76,7 +64,7 @@ type NavTabProps = Readonly<{
id: NavTab;
label: string;
navTabClassName: string;
unreadStats: UnreadStats | null;
unreadCount: number;
hasPendingUpdate?: boolean;
}>;
@@ -87,7 +75,7 @@ function NavTabsItem({
id,
label,
navTabClassName,
unreadStats,
unreadCount,
hasPendingUpdate,
}: NavTabProps) {
const isRTL = i18n.getLocaleDirection() === 'rtl';
@@ -112,7 +100,7 @@ function NavTabsItem({
/>
<NavTabsItemBadges
i18n={i18n}
unreadStats={unreadStats}
unreadCount={unreadCount}
hasError={hasError}
hasPendingUpdate={hasPendingUpdate}
/>
@@ -124,7 +112,7 @@ function NavTabsItem({
}
export type NavTabPanelProps = Readonly<{
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
collapsed: boolean;
hasFailedStorySends: boolean;
hasPendingUpdate: boolean;
@@ -132,7 +120,7 @@ export type NavTabPanelProps = Readonly<{
}>;
export type NavTabsToggleProps = Readonly<{
otherTabsUnreadStats: UnreadStats | null;
otherTabsUnreadCount: number;
i18n: LocalizerType;
hasFailedStorySends: boolean;
hasPendingUpdate: boolean;
@@ -145,7 +133,7 @@ export function NavTabsToggle({
hasFailedStorySends,
hasPendingUpdate,
navTabsCollapsed,
otherTabsUnreadStats,
otherTabsUnreadCount,
onToggleNavTabsCollapse,
}: NavTabsToggleProps): JSX.Element {
function handleToggle() {
@@ -178,7 +166,7 @@ export function NavTabsToggle({
<span className="NavTabs__ItemLabel">{label}</span>
<NavTabsItemBadges
i18n={i18n}
unreadStats={otherTabsUnreadStats}
unreadCount={otherTabsUnreadCount}
hasError={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
/>
@@ -204,6 +192,7 @@ export type NavTabsProps = Readonly<{
storiesEnabled: boolean;
unreadCallsCount: number;
unreadConversationsStats: UnreadStats;
unreadCountBadgeType: UnreadCountBadgeType;
unreadStoriesCount: number;
}>;
@@ -222,6 +211,7 @@ export function NavTabs({
storiesEnabled,
unreadCallsCount,
unreadConversationsStats,
unreadCountBadgeType,
unreadStoriesCount,
}: NavTabsProps): JSX.Element {
function handleSelectionChange(key: Key) {
@@ -266,7 +256,7 @@ export function NavTabs({
// These are all shown elsewhere when nav tabs are shown
hasFailedStorySends={false}
hasPendingUpdate={false}
otherTabsUnreadStats={null}
otherTabsUnreadCount={0}
/>
<TabList className="NavTabs__TabList">
<NavTabsItem
@@ -275,7 +265,10 @@ export function NavTabs({
label={i18n('icu:NavTabs__ItemLabel--Chats')}
iconClassName="NavTabs__ItemIcon--Chats"
navTabClassName="NavTabs__Item--Chats"
unreadStats={unreadConversationsStats}
unreadCount={getUnreadCountForBadge(
unreadConversationsStats,
unreadCountBadgeType
)}
/>
<NavTabsItem
i18n={i18n}
@@ -283,11 +276,7 @@ export function NavTabs({
label={i18n('icu:NavTabs__ItemLabel--Calls')}
iconClassName="NavTabs__ItemIcon--Calls"
navTabClassName="NavTabs__Item--Calls"
unreadStats={{
unreadCount: unreadCallsCount,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
}}
unreadCount={unreadCallsCount}
/>
{storiesEnabled && (
<NavTabsItem
@@ -297,11 +286,7 @@ export function NavTabs({
iconClassName="NavTabs__ItemIcon--Stories"
hasError={hasFailedStorySends}
navTabClassName="NavTabs__Item--Stories"
unreadStats={{
unreadCount: unreadStoriesCount,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
}}
unreadCount={unreadStoriesCount}
/>
)}
<NavTabsItem
@@ -310,7 +295,7 @@ export function NavTabs({
label={i18n('icu:NavTabs__ItemLabel--Settings')}
iconClassName="NavTabs__ItemIcon--Settings"
navTabClassName="NavTabs__Item--Settings"
unreadStats={null}
unreadCount={0}
hasPendingUpdate={hasPendingUpdate}
/>
</TabList>
+5 -15
View File
@@ -517,11 +517,7 @@ export default {
notificationContent: 'name',
notifyWhileMuted: { calls: false, mentions: true, replies: true },
osName: 'windows',
otherTabsUnreadStats: {
unreadCount: 0,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
},
otherTabsUnreadCount: 0,
settingsLocation: {
page: SettingsPage.Profile,
state: ProfileEditorPage.None,
@@ -539,6 +535,7 @@ export default {
themeSetting: 'system',
theme: ThemeType.light,
universalExpireTimer: DurationInSeconds.HOUR,
unreadCountBadgeType: 'unread-messages',
weArePrimaryDevice: false,
whoCanFindMe: PhoneNumberDiscoverability.Discoverable,
whoCanSeeMe: PhoneNumberSharingMode.Everybody,
@@ -645,6 +642,7 @@ export default {
onToggleNavTabsCollapse: action('onToggleNavTabsCollapse'),
onTypingIndicatorsChange: action('onTypingIndicatorsChange'),
onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'),
onUnreadCountBadgeTypeChange: action('onUnreadCountBadgeTypeChange'),
onWhoCanFindMeChange: action('onWhoCanFindMeChange'),
onWhoCanSeeMeChange: action('onWhoCanSeeMeChange'),
onZoomFactorChange: action('onZoomFactorChange'),
@@ -1407,20 +1405,12 @@ export const NavTabsCollapsedWithBadges = Template.bind({});
NavTabsCollapsedWithBadges.args = {
navTabsCollapsed: true,
hasFailedStorySends: false,
otherTabsUnreadStats: {
unreadCount: 1,
unreadMentionsCount: 2,
readChatsMarkedUnreadCount: 0,
},
otherTabsUnreadCount: 1,
};
export const NavTabsCollapsedWithExclamation = Template.bind({});
NavTabsCollapsedWithExclamation.args = {
navTabsCollapsed: true,
hasFailedStorySends: true,
otherTabsUnreadStats: {
unreadCount: 1,
unreadMentionsCount: 2,
readChatsMarkedUnreadCount: 0,
},
otherTabsUnreadCount: 1,
};
+35 -4
View File
@@ -49,6 +49,7 @@ import type { MediaDeviceSettings } from '../types/Calling.std.ts';
import type { ValidationResultType as BackupValidationResultType } from '../services/backups/index.preload.ts';
import type {
AutoDownloadAttachmentType,
UnreadCountBadgeType,
NotificationSettingType,
SentMediaQualitySettingType,
ZoomFactorType,
@@ -78,7 +79,6 @@ import type {
BackupsSubscriptionType,
BackupStatusType,
} from '../types/backups.node.ts';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import type { BadgeType } from '../badges/types.std.ts';
import type { MessageCountBySchemaVersionType } from '../sql/Interface.std.ts';
import type { MessageAttributesType } from '../model-types.d.ts';
@@ -187,6 +187,7 @@ export type PropsDataType = {
sentMediaQualitySetting: SentMediaQualitySettingType;
themeSetting: ThemeSettingType | undefined;
universalExpireTimer: DurationInSeconds;
unreadCountBadgeType: UnreadCountBadgeType;
whoCanFindMe: PhoneNumberDiscoverability;
whoCanSeeMe: PhoneNumberSharingMode;
zoomFactor: ZoomFactorType | undefined;
@@ -203,7 +204,7 @@ export type PropsDataType = {
initialSpellCheckSetting: boolean;
me: ConversationType;
navTabsCollapsed: boolean;
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
preferredWidthFromStorage: number;
shouldShowUpdateDialog: boolean;
theme: ThemeType;
@@ -378,6 +379,7 @@ type PropsFunctionType = {
onToggleNavTabsCollapse: (navTabsCollapsed: boolean) => void;
onTypingIndicatorsChange: CheckboxChangeHandlerType;
onUniversalExpireTimerChange: SelectChangeHandlerType<number>;
onUnreadCountBadgeTypeChange: SelectChangeHandlerType<UnreadCountBadgeType>;
onWhoCanFindMeChange: SelectChangeHandlerType<PhoneNumberDiscoverability>;
onWhoCanSeeMeChange: SelectChangeHandlerType<PhoneNumberSharingMode>;
onZoomFactorChange: SelectChangeHandlerType<ZoomFactorType>;
@@ -576,10 +578,11 @@ export function Preferences({
onToggleNavTabsCollapse,
onTypingIndicatorsChange,
onUniversalExpireTimerChange,
onUnreadCountBadgeTypeChange,
onWhoCanFindMeChange,
onWhoCanSeeMeChange,
onZoomFactorChange,
otherTabsUnreadStats,
otherTabsUnreadCount,
settingsLocation,
phoneNumber = '',
pickLocalBackupFolder,
@@ -619,6 +622,7 @@ export function Preferences({
theme,
themeSetting,
universalExpireTimer,
unreadCountBadgeType,
validateBackup,
whoCanFindMe,
whoCanSeeMe,
@@ -1594,6 +1598,33 @@ export function Preferences({
<List
label={i18n('icu:Preferences__Notifications__AppBadgeSection__Title')}
>
<AxoSelectItem.Root
label={i18n(
'icu:Preferences__Notifications__AppBadgeSection__BadgeCount__Label'
)}
description={i18n(
'icu:Preferences__Notifications__AppBadgeSection__BadgeCount__Description'
)}
value={unreadCountBadgeType}
onValueChange={value => {
onUnreadCountBadgeTypeChange(value as UnreadCountBadgeType);
}}
placeholder=""
options={[
{
label: i18n(
'icu:Preferences__Notifications__AppBadgeSection__BadgeCount__UnreadMessages'
),
value: 'unread-messages',
},
{
label: i18n(
'icu:Preferences__Notifications__AppBadgeSection__BadgeCount__UnreadChats'
),
value: 'unread-chats',
},
]}
/>
<AxoSwitchItem.Root
label={i18n('icu:countMutedConversationsDescription')}
checked={hasCountMutedConversations}
@@ -2432,7 +2463,7 @@ export function Preferences({
<NavSidebar
title={i18n('icu:Preferences--header')}
i18n={i18n}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={false}
navTabsCollapsed={navTabsCollapsed}
+4 -5
View File
@@ -25,12 +25,11 @@ import { NavSidebar, NavSidebarActionButton } from './NavSidebar.dom.tsx';
import { StoriesAddStoryButton } from './StoriesAddStoryButton.dom.tsx';
import { I18n } from './I18n.dom.tsx';
import type { WidthBreakpoint } from './_util.std.ts';
import type { UnreadStats } from '../util/countUnreadStats.std.ts';
import { AxoDropdownMenu } from '../axo/AxoDropdownMenu.dom.tsx';
export type PropsType = {
addStoryData: AddStoryData;
otherTabsUnreadStats: UnreadStats;
otherTabsUnreadCount: number;
deleteStoryForEveryone: (story: StoryViewType) => unknown;
getPreferredBadge: PreferredBadgeSelectorType;
hasFailedStorySends: boolean;
@@ -70,7 +69,7 @@ export type PropsType = {
export function StoriesTab({
addStoryData,
otherTabsUnreadStats,
otherTabsUnreadCount,
deleteStoryForEveryone,
getPreferredBadge,
hasFailedStorySends,
@@ -117,7 +116,7 @@ export function StoriesTab({
{addStoryData && renderStoryCreator()}
{isMyStories && myStories.length ? (
<MyStories
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
hasViewReceiptSetting={hasViewReceiptSetting}
@@ -149,7 +148,7 @@ export function StoriesTab({
preferredLeftPaneWidth={preferredLeftPaneWidth}
requiresFullWidth
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
renderToastManager={renderToastManager}
actions={
<>
@@ -21,6 +21,7 @@ import type {
AllChatFoldersUnreadStats,
UnreadStats,
} from '../../util/countUnreadStats.std.ts';
import { getUnreadCountForBadge } from '../../util/countUnreadStats.std.ts';
import { WidthBreakpoint } from '../_util.std.ts';
import { AxoSelect } from '../../axo/AxoSelect.dom.tsx';
import { AxoContextMenu } from '../../axo/AxoContextMenu.dom.tsx';
@@ -33,12 +34,14 @@ import type {
import type { AxoSymbol } from '../../axo/AxoSymbol.dom.tsx';
import { UserText } from '../UserText.dom.tsx';
import { CurrentChatFolders } from '../../types/CurrentChatFolders.std.ts';
import type { UnreadCountBadgeType } from '../../types/StorageKeys.std.ts';
export type LeftPaneChatFoldersProps = Readonly<{
i18n: LocalizerType;
navSidebarWidthBreakpoint: WidthBreakpoint | null;
currentChatFolders: CurrentChatFolders;
allChatFoldersUnreadStats: AllChatFoldersUnreadStats;
unreadCountBadgeType: UnreadCountBadgeType;
allChatFoldersMutedStats: AllChatFoldersMutedStats;
selectedChatFolder: ChatFolder | null;
onSelectedChatFolderIdChange: (newValue: ChatFolderId) => void;
@@ -50,17 +53,15 @@ export type LeftPaneChatFoldersProps = Readonly<{
onChatFolderOpenSettings: (chatFolderId: ChatFolderId) => void;
}>;
function getBadgeValue(unreadStats: UnreadStats | null): number | null {
function getBadgeValue(
unreadStats: UnreadStats | null,
unreadCountBadgeType: UnreadCountBadgeType
): number | null {
if (unreadStats == null) {
return null;
}
if (unreadStats.unreadCount > 0) {
return unreadStats.unreadCount + unreadStats.readChatsMarkedUnreadCount;
}
if (unreadStats.readChatsMarkedUnreadCount > 0) {
return unreadStats.readChatsMarkedUnreadCount;
}
return null;
const total = getUnreadCountForBadge(unreadStats, unreadCountBadgeType);
return total > 0 ? total : null;
}
function getChatFolderLabel(
@@ -143,6 +144,7 @@ export function LeftPaneChatFolders(
i18n={i18n}
chatFolder={chatFolder}
unreadStats={unreadStats}
unreadCountBadgeType={props.unreadCountBadgeType}
/>
);
})}
@@ -177,6 +179,7 @@ export function LeftPaneChatFolders(
i18n={i18n}
chatFolder={chatFolder}
unreadStats={unreadStats}
unreadCountBadgeType={props.unreadCountBadgeType}
mutedStats={mutedStats}
onChatFolderMarkRead={props.onChatFolderMarkRead}
onChatFolderUpdateMute={props.onChatFolderUpdateMute}
@@ -195,12 +198,13 @@ function ChatFolderSelectItem(props: {
i18n: LocalizerType;
chatFolder: ChatFolder;
unreadStats: UnreadStats | null;
unreadCountBadgeType: UnreadCountBadgeType;
}): JSX.Element {
const { i18n, unreadStats } = props;
const { i18n, unreadStats, unreadCountBadgeType } = props;
const badgeValue = useMemo(() => {
return getBadgeValue(unreadStats);
}, [unreadStats]);
return getBadgeValue(unreadStats, unreadCountBadgeType);
}, [unreadStats, unreadCountBadgeType]);
return (
<AxoSelect.Item
@@ -230,6 +234,7 @@ function ChatFolderSegmentedControlItem(props: {
i18n: LocalizerType;
chatFolder: ChatFolder;
unreadStats: UnreadStats | null;
unreadCountBadgeType: UnreadCountBadgeType;
mutedStats: MutedStats | null;
onChatFolderMarkRead: (chatFolderId: ChatFolderId) => void;
onChatFolderUpdateMute: (
@@ -238,11 +243,11 @@ function ChatFolderSegmentedControlItem(props: {
) => void;
onChatFolderOpenSettings: (chatFolderId: ChatFolderId) => void;
}): JSX.Element {
const { i18n, unreadStats } = props;
const { i18n, unreadStats, unreadCountBadgeType } = props;
const badgeValue = useMemo(() => {
return getBadgeValue(unreadStats);
}, [unreadStats]);
return getBadgeValue(unreadStats, unreadCountBadgeType);
}, [unreadStats, unreadCountBadgeType]);
return (
<ChatFolderSegmentedControlItemContextMenu
+19
View File
@@ -72,6 +72,7 @@ import {
parsePhoneNumberSharingMode,
} from '../../types/PhoneNumberSharingMode.std.ts';
import { missingCaseError } from '../../util/missingCaseError.std.ts';
import { STORAGE_KEY_DEFAULTS } from '../../types/StorageKeys.std.ts';
import {
isCallHistory,
isChatSessionRefreshed,
@@ -1015,6 +1016,23 @@ export class BackupExportStream extends Readable {
throw missingCaseError(rawPhoneNumberSharingMode);
}
const UNREAD_BADGE_TYPE_ENUM =
Backups.AccountData.AccountSettings.UnreadBadgeType;
const unreadCountBadgeType =
itemStorage.get('unreadCountBadgeType') ??
STORAGE_KEY_DEFAULTS.unreadCountBadgeType;
let unreadBadgeType: Backups.AccountData.AccountSettings.UnreadBadgeType;
switch (unreadCountBadgeType) {
case 'unread-messages':
unreadBadgeType = UNREAD_BADGE_TYPE_ENUM.UNREAD_MESSAGES;
break;
case 'unread-chats':
unreadBadgeType = UNREAD_BADGE_TYPE_ENUM.UNREAD_CHATS;
break;
default:
throw missingCaseError(unreadCountBadgeType);
}
const usernameLink = itemStorage.get('usernameLink');
const subscriberId = itemStorage.get('subscriberId');
@@ -1085,6 +1103,7 @@ export class BackupExportStream extends Readable {
itemStorage.get('notifyForMentionsIfMuted') ?? null,
notifyForRepliesIfMuted:
itemStorage.get('notifyForRepliesIfMuted') ?? null,
unreadBadgeType,
hasSetMyStoriesPrivacy:
itemStorage.get('hasSetMyStoriesPrivacy') ?? null,
hasViewedOnboardingStory:
+20
View File
@@ -170,6 +170,10 @@ import type { ThemeType } from '../../util/preload.preload.ts';
import { toNumber } from '../../util/toNumber.std.ts';
import { isKnownProtoEnumMember } from '../../util/isKnownProtoEnumMember.std.ts';
import { Emoji } from '../../axo/emoji.std.ts';
import {
STORAGE_KEY_DEFAULTS,
type UnreadCountBadgeType,
} from '../../types/StorageKeys.std.ts';
const { isNumber } = lodash;
@@ -903,6 +907,22 @@ export class BackupImportStream extends Writable {
'notifyForRepliesIfMuted',
accountSettings?.notifyForRepliesIfMuted ?? undefined
);
let unreadCountBadgeType: UnreadCountBadgeType;
switch (accountSettings?.unreadBadgeType) {
case Backups.AccountData.AccountSettings.UnreadBadgeType.UNREAD_CHATS:
unreadCountBadgeType = 'unread-chats';
break;
case Backups.AccountData.AccountSettings.UnreadBadgeType.UNREAD_MESSAGES:
unreadCountBadgeType = 'unread-messages';
break;
case Backups.AccountData.AccountSettings.UnreadBadgeType
.UNKNOWN_BADGE_TYPE:
default:
unreadCountBadgeType = STORAGE_KEY_DEFAULTS.unreadCountBadgeType;
}
await itemStorage.put('unreadCountBadgeType', unreadCountBadgeType);
await itemStorage.put(
'hasSetMyStoriesPrivacy',
accountSettings?.hasSetMyStoriesPrivacy === true
+46 -3
View File
@@ -24,6 +24,8 @@ import {
PhoneNumberSharingMode,
parsePhoneNumberSharingMode,
} from '../types/PhoneNumberSharingMode.std.ts';
import type { UnreadCountBadgeType } from '../types/StorageKeys.std.ts';
import { STORAGE_KEY_DEFAULTS } from '../types/StorageKeys.std.ts';
import {
PhoneNumberDiscoverability,
parsePhoneNumberDiscoverability,
@@ -438,6 +440,22 @@ export function toAccountRecord({
throw missingCaseError(localPhoneNumberSharingMode);
}
const localUnreadCountBadgeType = itemStorage.get(
'unreadCountBadgeType',
STORAGE_KEY_DEFAULTS.unreadCountBadgeType
);
let unreadBadgeType: Proto.AccountRecord.UnreadBadgeType;
switch (localUnreadCountBadgeType) {
case 'unread-messages':
unreadBadgeType = Proto.AccountRecord.UnreadBadgeType.UNREAD_MESSAGES;
break;
case 'unread-chats':
unreadBadgeType = Proto.AccountRecord.UnreadBadgeType.UNREAD_CHATS;
break;
default:
throw missingCaseError(localUnreadCountBadgeType);
}
const phoneNumberDiscoverability = parsePhoneNumberDiscoverability(
itemStorage.get('phoneNumberDiscoverability')
);
@@ -637,6 +655,7 @@ export function toAccountRecord({
displayBadgesOnProfile: itemStorage.get('displayBadgesOnProfile') ?? null,
keepMutedChatsArchived: itemStorage.get('keepMutedChatsArchived') ?? null,
unreadBadgeType,
notifyForCallsIfMuted: toOptionalBool(
itemStorage.get('notifyForCallsIfMuted')
@@ -1686,6 +1705,7 @@ export async function mergeAccountRecord(
notifyForCallsIfMuted,
notifyForMentionsIfMuted,
notifyForRepliesIfMuted,
unreadBadgeType,
hasCompletedUsernameOnboarding,
hasSeenGroupStoryEducationSheet,
hasSeenAdminDeleteEducationDialog,
@@ -1978,6 +1998,28 @@ export async function mergeAccountRecord(
'notifyForRepliesIfMuted',
fromOptionalBool(notifyForRepliesIfMuted) ?? undefined
);
{
let unreadCountBadgeType: UnreadCountBadgeType;
switch (unreadBadgeType) {
case Proto.AccountRecord.UnreadBadgeType.UNREAD_CHATS:
unreadCountBadgeType = 'unread-chats';
break;
case Proto.AccountRecord.UnreadBadgeType.UNREAD_MESSAGES:
unreadCountBadgeType = 'unread-messages';
break;
case Proto.AccountRecord.UnreadBadgeType.UNKNOWN_BADGE_TYPE:
default:
unreadCountBadgeType = STORAGE_KEY_DEFAULTS.unreadCountBadgeType;
}
const previousUnreadCountBadgeType = itemStorage.get(
'unreadCountBadgeType'
);
await itemStorage.put('unreadCountBadgeType', unreadCountBadgeType);
if (previousUnreadCountBadgeType !== unreadCountBadgeType) {
window.Whisper.events.emit('updateUnreadCount');
}
}
await itemStorage.put('hasSetMyStoriesPrivacy', hasSetMyStoriesPrivacy);
{
await itemStorage.put('hasViewedOnboardingStory', hasViewedOnboardingStory);
@@ -2000,9 +2042,9 @@ export async function mergeAccountRecord(
hasSeenAdminDeleteEducationDialog ?? false
);
{
// default to false (exclude muted chats)
const countMutedConversations =
fromOptionalBool(includeMutedChatsInBadge) ?? false;
fromOptionalBool(includeMutedChatsInBadge) ??
STORAGE_KEY_DEFAULTS['badge-count-muted-conversations'];
const previous = itemStorage.get('badge-count-muted-conversations', false);
await itemStorage.put(
'badge-count-muted-conversations',
@@ -2014,7 +2056,8 @@ export async function mergeAccountRecord(
}
await itemStorage.put(
'reaction-notification',
fromOptionalBool(reactionNotifications) ?? true
fromOptionalBool(reactionNotifications) ??
STORAGE_KEY_DEFAULTS['reaction-notification']
);
{
await itemStorage.put(
+16 -26
View File
@@ -60,6 +60,7 @@ import {
getBadgeCountMutedConversations,
getPinnedConversationIds,
getStoriesEnabled,
getUnreadCountBadgeType,
} from './items.dom.ts';
import { createLogger } from '../../logging/log.std.ts';
import { TimelineMessageLoadingState } from '../../util/timelineUtil.std.ts';
@@ -85,6 +86,7 @@ import {
import {
countAllChatFoldersUnreadStats,
countAllConversationsUnreadStats,
getUnreadCountForBadge,
} from '../../util/countUnreadStats.std.ts';
import type { AllChatFoldersMutedStats } from '../../util/countMutedStats.std.ts';
import { countAllChatFoldersMutedStats } from '../../util/countMutedStats.std.ts';
@@ -769,22 +771,14 @@ export const getAllChatFoldersUnreadStats: StateSelector<AllChatFoldersUnreadSta
createSelector(
getCurrentChatFolders,
getAllConversations,
getBadgeCountMutedConversations,
getActiveProfile,
(
currentChatFolders,
allConversations,
badgeCountMutedConversations,
activeProfile
) => {
(currentChatFolders, allConversations, activeProfile) => {
return countAllChatFoldersUnreadStats(
currentChatFolders,
allConversations,
{
activeProfile,
includeMuted: badgeCountMutedConversations
? 'setting-on'
: 'setting-off',
includeMuted: 'force-include',
}
);
}
@@ -1510,42 +1504,38 @@ const getStoriesNotificationCount = createSelector(
}
);
export const getOtherTabsUnreadStats = createSelector(
export const getOtherTabsUnreadCount = createSelector(
getSelectedNavTab,
getAllConversationsUnreadStats,
getUnreadCountBadgeType,
getCallHistoryUnreadCount,
getStoriesNotificationCount,
(
selectedNavTab,
conversationsUnreadStats,
unreadCountBadgeType,
callHistoryUnreadCount,
storiesNotificationCount
): UnreadStats => {
let unreadCount = 0;
let unreadMentionsCount = 0;
let readChatsMarkedUnreadCount = 0;
): number => {
let count = 0;
if (selectedNavTab !== NavTab.Chats) {
unreadCount += conversationsUnreadStats.unreadCount;
unreadMentionsCount += conversationsUnreadStats.unreadMentionsCount;
readChatsMarkedUnreadCount +=
conversationsUnreadStats.readChatsMarkedUnreadCount;
count += getUnreadCountForBadge(
conversationsUnreadStats,
unreadCountBadgeType
);
}
// Note: Conversation unread stats includes the call history unread count.
if (selectedNavTab !== NavTab.Calls) {
unreadCount += callHistoryUnreadCount;
count += callHistoryUnreadCount;
}
if (selectedNavTab !== NavTab.Stories) {
unreadCount += storiesNotificationCount;
count += storiesNotificationCount;
}
return {
unreadCount,
unreadMentionsCount,
readChatsMarkedUnreadCount,
};
return count;
}
);
+11
View File
@@ -12,6 +12,8 @@ import type {
CustomColorType,
} from '../../types/Colors.std.ts';
import type { AciString } from '../../types/ServiceId.std.ts';
import type { UnreadCountBadgeType } from '../../types/StorageKeys.std.ts';
import { STORAGE_KEY_DEFAULTS } from '../../types/StorageKeys.std.ts';
import { DEFAULT_CONVERSATION_COLOR } from '../../types/Colors.std.ts';
import { getPreferredReactionEmoji as getPreferredReactionEmojiFromStoredValue } from '../../reactions/preferredReactionEmoji.std.ts';
import type { NotifyWhileMuted } from '../../util/notifyWhileMuted.std.ts';
@@ -229,6 +231,15 @@ export const getGlobalNotifyWhileMuted = createSelector(
})
);
export const getUnreadCountBadgeType = createSelector(
getItems,
(state: ItemsStateType): UnreadCountBadgeType => {
return (
state.unreadCountBadgeType ?? STORAGE_KEY_DEFAULTS.unreadCountBadgeType
);
}
);
export const getTextFormattingEnabled = createSelector(
getItems,
(state: ItemsStateType): boolean => state.textFormatting ?? true
+3 -3
View File
@@ -13,7 +13,7 @@ import { CallsTab } from '../../components/CallsTab.dom.tsx';
import {
getAllConversations,
getConversationSelector,
getOtherTabsUnreadStats,
getOtherTabsUnreadCount,
} from '../selectors/conversations.dom.ts';
import { filterAndSortConversations } from '../../util/filterAndSortConversations.std.ts';
import type {
@@ -152,7 +152,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
const otherTabsUnreadCount = useSelector(getOtherTabsUnreadCount);
const {
createCallLink,
@@ -223,7 +223,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() {
<CallsTab
activeCall={activeCall}
allConversations={allConversations}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
getConversation={getConversation}
getCallIdFromEra={getCallIdFromEra}
getCallHistoryGroupsCount={getCallHistoryGroupsCount}
+3 -3
View File
@@ -22,7 +22,7 @@ import { getHasAnyFailedStorySends } from '../selectors/stories.preload.ts';
import { getHasPendingUpdate } from '../selectors/updates.std.ts';
import { getSelectedConversationId } from '../selectors/nav.std.ts';
import {
getOtherTabsUnreadStats,
getOtherTabsUnreadCount,
getTargetedMessage,
getTargetedMessageSource,
} from '../selectors/conversations.dom.ts';
@@ -46,7 +46,7 @@ export const SmartChatsTab = memo(function SmartChatsTab() {
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
const otherTabsUnreadCount = useSelector(getOtherTabsUnreadCount);
const selectedConversationId = useSelector(getSelectedConversationId);
const targetedMessageId = useSelector(getTargetedMessage)?.id;
const targetedMessageSource = useSelector(getTargetedMessageSource);
@@ -132,7 +132,7 @@ export const SmartChatsTab = memo(function SmartChatsTab() {
return (
<ChatsTab
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
i18n={i18n}
isStaging={isStagingServer()}
hasFailedStorySends={hasFailedStorySends}
+2 -2
View File
@@ -316,7 +316,7 @@ async function saveAlerts(alerts: ServerAlertsType): Promise<void> {
export const SmartLeftPane = memo(function SmartLeftPane({
hasFailedStorySends,
hasPendingUpdate,
otherTabsUnreadStats,
otherTabsUnreadCount,
}: NavTabPanelProps) {
const challengeStatus = useSelector(getChallengeStatus);
const composerStep = useSelector(getComposerStep);
@@ -488,7 +488,7 @@ export const SmartLeftPane = memo(function SmartLeftPane({
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
openUsernameReservationModal={openUsernameReservationModal}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
pauseBackupMediaDownload={pauseBackupMediaDownload}
preferredWidthFromStorage={preferredWidthFromStorage}
preloadConversation={maybePreloadConversation}
@@ -12,6 +12,7 @@ import {
getAllChatFoldersMutedStats,
getAllChatFoldersUnreadStats,
} from '../selectors/conversations.dom.ts';
import { getUnreadCountBadgeType } from '../selectors/items.dom.ts';
import { useChatFolderActions } from '../ducks/chatFolders.preload.ts';
import { NavSidebarWidthBreakpointContext } from '../../components/NavSidebar.dom.tsx';
import { useNavActions } from '../ducks/nav.std.ts';
@@ -26,6 +27,7 @@ export const SmartLeftPaneChatFolders = memo(
const currentChatFolders = useSelector(getCurrentChatFolders);
const allChatFoldersUnreadStats = useSelector(getAllChatFoldersUnreadStats);
const allChatFoldersMutedStats = useSelector(getAllChatFoldersMutedStats);
const unreadCountBadgeType = useSelector(getUnreadCountBadgeType);
const selectedChatFolder = useSelector(getSelectedChatFolder);
const navSidebarWidthBreakpoint = useContext(
NavSidebarWidthBreakpointContext
@@ -59,6 +61,7 @@ export const SmartLeftPaneChatFolders = memo(
currentChatFolders={currentChatFolders}
allChatFoldersUnreadStats={allChatFoldersUnreadStats}
allChatFoldersMutedStats={allChatFoldersMutedStats}
unreadCountBadgeType={unreadCountBadgeType}
selectedChatFolder={selectedChatFolder}
onSelectedChatFolderIdChange={updateSelectedChatFolderId}
onChatFolderMarkRead={markChatFolderRead}
+6 -1
View File
@@ -11,7 +11,10 @@ import {
getHasAnyFailedStorySends,
getStoriesNotificationCount,
} from '../selectors/stories.preload.ts';
import { getStoriesEnabled } from '../selectors/items.dom.ts';
import {
getStoriesEnabled,
getUnreadCountBadgeType,
} from '../selectors/items.dom.ts';
import { getSelectedNavTab } from '../selectors/nav.std.ts';
import { useNavActions } from '../ducks/nav.std.ts';
import { getHasPendingUpdate } from '../selectors/updates.std.ts';
@@ -38,6 +41,7 @@ export const SmartNavTabs = memo(function SmartNavTabs({
}: SmartNavTabsProps): JSX.Element {
const i18n = useSelector(getIntl);
const selectedNavTab = useSelector(getSelectedNavTab);
const unreadCountBadgeType = useSelector(getUnreadCountBadgeType);
const storiesEnabled = useSelector(getStoriesEnabled);
const unreadConversationsStats = useSelector(getAllConversationsUnreadStats);
const unreadStoriesCount = useSelector(getStoriesNotificationCount);
@@ -60,6 +64,7 @@ export const SmartNavTabs = memo(function SmartNavTabs({
return (
<NavTabs
unreadCountBadgeType={unreadCountBadgeType}
hasFailedStorySends={hasFailedStorySends}
hasPendingUpdate={hasPendingUpdate}
i18n={i18n}
+38 -43
View File
@@ -13,7 +13,7 @@ import {
getConversationSelector,
getConversationsWithCustomColorSelector,
getMe,
getOtherTabsUnreadStats,
getOtherTabsUnreadCount,
} from '../selectors/conversations.dom.ts';
import {
getBackupKey,
@@ -123,26 +123,13 @@ import { DonationsErrorBoundary } from '../../components/DonationsErrorBoundary.
import type { SmartPreferencesChatFoldersPageProps } from './PreferencesChatFoldersPage.preload.tsx';
import type { SmartPreferencesEditChatFolderPageProps } from './PreferencesEditChatFolderPage.preload.tsx';
import type { ExternalProps as SmartNotificationProfilesProps } from './PreferencesNotificationProfiles.preload.tsx';
import type { ZoomFactorType } from '../../types/StorageKeys.std.ts';
import {
STORAGE_KEY_DEFAULTS,
type ZoomFactorType,
} from '../../types/StorageKeys.std.ts';
import type { BlockedConversation } from '../../components/Preferences.dom.tsx';
import { pinReminderService } from '../../services/pinReminder.preload.ts';
const DEFAULT_NOTIFICATION_SETTING = 'message';
// Defaults for the settings that "Reset notification settings" restores
const NOTIFICATION_SETTING_DEFAULTS = {
'notification-setting': DEFAULT_NOTIFICATION_SETTING,
'call-system-notification': true,
'reaction-notification': true,
'notification-draw-attention': false,
'audio-notification': false,
audioMessage: false,
'badge-count-muted-conversations': false,
notifyForCallsIfMuted: undefined,
notifyForMentionsIfMuted: undefined,
notifyForRepliesIfMuted: undefined,
} as const satisfies Partial<StorageAccessType>;
function renderUpdateDialog(
props: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
): JSX.Element {
@@ -267,7 +254,7 @@ export function SmartPreferences(): JSX.Element | null {
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const me = useSelector(getMe);
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
const otherTabsUnreadCount = useSelector(getOtherTabsUnreadCount);
const preferredWidthFromStorage = useSelector(getPreferredLeftPaneWidth);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const theme = useSelector(getTheme);
@@ -737,7 +724,7 @@ export function SmartPreferences(): JSX.Element | null {
const [hasAudioNotifications, onAudioNotificationsChange] = createItemsAccess(
'audio-notification',
NOTIFICATION_SETTING_DEFAULTS['audio-notification']
STORAGE_KEY_DEFAULTS['audio-notification']
);
const [hasAutoConvertEmoji, onAutoConvertEmojiChange] = createItemsAccess(
'autoConvertEmoji',
@@ -755,7 +742,7 @@ export function SmartPreferences(): JSX.Element | null {
);
const [hasCallNotifications, onCallNotificationsChange] = createItemsAccess(
'call-system-notification',
NOTIFICATION_SETTING_DEFAULTS['call-system-notification']
STORAGE_KEY_DEFAULTS['call-system-notification']
);
const [hasIncomingCallNotifications, onIncomingCallNotificationsChange] =
createItemsAccess('incoming-call-notification', true);
@@ -764,7 +751,7 @@ export function SmartPreferences(): JSX.Element | null {
const [hasCountMutedConversations, onCountMutedConversationsChange] =
createItemsAccess(
'badge-count-muted-conversations',
NOTIFICATION_SETTING_DEFAULTS['badge-count-muted-conversations'],
STORAGE_KEY_DEFAULTS['badge-count-muted-conversations'],
() => {
window.Whisper.events.emit('updateUnreadCount');
const account =
@@ -772,6 +759,17 @@ export function SmartPreferences(): JSX.Element | null {
account.captureChange('badge-count-muted-conversations');
}
);
const [unreadCountBadgeType, onUnreadCountBadgeTypeChange] =
createItemsAccess(
'unreadCountBadgeType',
STORAGE_KEY_DEFAULTS.unreadCountBadgeType,
() => {
const account =
window.ConversationController.getOurConversationOrThrow();
account.captureChange('unreadCountBadgeType');
window.Whisper.events.emit('updateUnreadCount');
}
);
const [hasHideMenuBar, onHideMenuBarChange] = createItemsAccess(
'hide-menu-bar',
false,
@@ -782,17 +780,17 @@ export function SmartPreferences(): JSX.Element | null {
);
const [hasMessageAudio, onMessageAudioChange] = createItemsAccess(
'audioMessage',
NOTIFICATION_SETTING_DEFAULTS.audioMessage
STORAGE_KEY_DEFAULTS.audioMessage
);
const [hasNotificationAttention, onNotificationAttentionChange] =
createItemsAccess(
'notification-draw-attention',
NOTIFICATION_SETTING_DEFAULTS['notification-draw-attention']
STORAGE_KEY_DEFAULTS['notification-draw-attention']
);
const [hasReactionNotifications, onReactionNotificationsChange] =
createItemsAccess(
'reaction-notification',
NOTIFICATION_SETTING_DEFAULTS['reaction-notification'],
STORAGE_KEY_DEFAULTS['reaction-notification'],
() => {
const account =
window.ConversationController.getOurConversationOrThrow();
@@ -802,13 +800,13 @@ export function SmartPreferences(): JSX.Element | null {
const [notificationContent, onNotificationContentChange] = createItemsAccess(
'notification-setting',
NOTIFICATION_SETTING_DEFAULTS['notification-setting']
STORAGE_KEY_DEFAULTS['notification-setting']
);
const hasNotifications = notificationContent !== 'off';
const onNotificationsChange = (value: boolean) => {
putItem(
'notification-setting',
value ? DEFAULT_NOTIFICATION_SETTING : 'off'
value ? STORAGE_KEY_DEFAULTS['notification-setting'] : 'off'
);
};
@@ -825,32 +823,27 @@ export function SmartPreferences(): JSX.Element | null {
const onResetNotificationSettings = () => {
// Reset global settings
onNotificationContentChange(
NOTIFICATION_SETTING_DEFAULTS['notification-setting']
);
onCallNotificationsChange(
NOTIFICATION_SETTING_DEFAULTS['call-system-notification']
);
onNotificationContentChange(STORAGE_KEY_DEFAULTS['notification-setting']);
onCallNotificationsChange(STORAGE_KEY_DEFAULTS['call-system-notification']);
onReactionNotificationsChange(
NOTIFICATION_SETTING_DEFAULTS['reaction-notification']
STORAGE_KEY_DEFAULTS['reaction-notification']
);
onNotificationAttentionChange(
NOTIFICATION_SETTING_DEFAULTS['notification-draw-attention']
STORAGE_KEY_DEFAULTS['notification-draw-attention']
);
onAudioNotificationsChange(
NOTIFICATION_SETTING_DEFAULTS['audio-notification']
);
onMessageAudioChange(NOTIFICATION_SETTING_DEFAULTS.audioMessage);
onAudioNotificationsChange(STORAGE_KEY_DEFAULTS['audio-notification']);
onMessageAudioChange(STORAGE_KEY_DEFAULTS.audioMessage);
onUnreadCountBadgeTypeChange(STORAGE_KEY_DEFAULTS.unreadCountBadgeType);
onCountMutedConversationsChange(
NOTIFICATION_SETTING_DEFAULTS['badge-count-muted-conversations']
STORAGE_KEY_DEFAULTS['badge-count-muted-conversations']
);
const account = window.ConversationController.getOurConversationOrThrow();
for (const itemKey of Object.values(NOTIFY_WHILE_MUTED_FIELDS)) {
if (itemStorage.get(itemKey) === NOTIFICATION_SETTING_DEFAULTS[itemKey]) {
if (itemStorage.get(itemKey) === STORAGE_KEY_DEFAULTS[itemKey]) {
continue;
}
drop(itemStorage.put(itemKey, NOTIFICATION_SETTING_DEFAULTS[itemKey]));
drop(itemStorage.put(itemKey, STORAGE_KEY_DEFAULTS[itemKey]));
account.captureChange(itemKey);
}
@@ -1164,12 +1157,13 @@ export function SmartPreferences(): JSX.Element | null {
onToggleNavTabsCollapse={toggleNavTabsCollapse}
onTypingIndicatorsChange={onTypingIndicatorsChange}
onUniversalExpireTimerChange={onUniversalExpireTimerChange}
onUnreadCountBadgeTypeChange={onUnreadCountBadgeTypeChange}
onWhoCanFindMeChange={onWhoCanFindMeChange}
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
onZoomFactorChange={onZoomFactorChange}
openFileInFolder={openFileInFolder}
osName={osName}
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
settingsLocation={settingsLocation}
pickLocalBackupFolder={pickLocalBackupFolder}
preferredSystemLocales={preferredSystemLocales}
@@ -1212,6 +1206,7 @@ export function SmartPreferences(): JSX.Element | null {
theme={theme}
themeSetting={themeSetting}
universalExpireTimer={universalExpireTimer}
unreadCountBadgeType={unreadCountBadgeType}
validateBackup={validateBackup}
whoCanFindMe={whoCanFindMe}
whoCanSeeMe={whoCanSeeMe}
+3 -3
View File
@@ -10,7 +10,7 @@ import { getMaximumOutgoingVideoSize } from '../../types/AttachmentSize.std.ts';
import { getValue, type ConfigKeyType } from '../../RemoteConfig.dom.ts';
import {
getMe,
getOtherTabsUnreadStats,
getOtherTabsUnreadCount,
} from '../selectors/conversations.dom.ts';
import { getIntl, getTheme } from '../selectors/user.std.ts';
import { getPreferredBadgeSelector } from '../selectors/badges.preload.ts';
@@ -65,7 +65,7 @@ export const SmartStoriesTab = memo(function SmartStoriesTab() {
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
const hasPendingUpdate = useSelector(getHasPendingUpdate);
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
const otherTabsUnreadCount = useSelector(getOtherTabsUnreadCount);
const remoteConfig = useSelector(getRemoteConfig);
const maxAttachmentVideoSize = getMaximumOutgoingVideoSize(
@@ -108,7 +108,7 @@ export const SmartStoriesTab = memo(function SmartStoriesTab() {
return (
<StoriesTab
otherTabsUnreadStats={otherTabsUnreadStats}
otherTabsUnreadCount={otherTabsUnreadCount}
addStoryData={addStoryData}
getPreferredBadge={getPreferredBadge}
hasFailedStorySends={hasFailedStorySends}
+1
View File
@@ -158,6 +158,7 @@ function* createRecords({
callsUseLessDataSetting: null,
allowSealedSenderFromAnyone: null,
allowAutomaticKeyVerification: null,
unreadBadgeType: null,
},
username: null,
usernameLink: null,
+75 -18
View File
@@ -12,6 +12,7 @@ import {
_shouldExcludeMuted,
countAllChatFoldersUnreadStats,
countAllConversationsUnreadStats,
getUnreadCountForBadge,
isConversationUnread,
} from '../../util/countUnreadStats.std.ts';
import type {
@@ -19,6 +20,7 @@ import type {
ConversationPropsForUnreadStats,
UnreadStatsIncludeMuted,
} from '../../util/countUnreadStats.std.ts';
import type { UnreadCountBadgeType } from '../../types/StorageKeys.std.ts';
import type { CurrentChatFolder } from '../../types/CurrentChatFolders.std.ts';
import { CurrentChatFolders } from '../../types/CurrentChatFolders.std.ts';
import type { ChatFolderId } from '../../types/ChatFolder.std.ts';
@@ -53,6 +55,7 @@ function mockChat(props: ChatProps): ConversationPropsForUnreadStats {
function mockStats(props: StatsProps): UnreadStats {
return {
unreadCount: 0,
unreadChatsCount: 0,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
...props,
@@ -141,22 +144,33 @@ describe('countUnreadStats', () => {
it('should count unreadCount', () => {
check({ unreadCount: undefined }, { unreadCount: 0 });
check({ unreadCount: 0 }, { unreadCount: 0 });
check({ unreadCount: 1 }, { unreadCount: 1 });
check({ unreadCount: 42 }, { unreadCount: 42 });
check({ unreadCount: 1 }, { unreadCount: 1, unreadChatsCount: 1 });
check({ unreadCount: 42 }, { unreadCount: 42, unreadChatsCount: 1 });
});
it('should count unreadMentionsCount', () => {
check({ unreadMentionsCount: undefined }, { unreadMentionsCount: 0 });
check({ unreadMentionsCount: 0 }, { unreadMentionsCount: 0 });
check({ unreadMentionsCount: 1 }, { unreadMentionsCount: 1 });
check({ unreadMentionsCount: 42 }, { unreadMentionsCount: 42 });
check(
{ unreadMentionsCount: 1 },
{ unreadMentionsCount: 1, unreadChatsCount: 1 }
);
check(
{ unreadMentionsCount: 42 },
{ unreadMentionsCount: 42, unreadChatsCount: 1 }
);
});
it('should count readChatsMarkedUnreadCount', () => {
const read = { readChatsMarkedUnreadCount: 1 };
const unread = { unreadCount: 42, readChatsMarkedUnreadCount: 0 };
const unread = {
unreadCount: 42,
unreadChatsCount: 1,
readChatsMarkedUnreadCount: 0,
};
const mentions = {
unreadMentionsCount: 42,
unreadChatsCount: 1,
readChatsMarkedUnreadCount: 0,
};
@@ -235,25 +249,29 @@ describe('countUnreadStats', () => {
check([read], { unreadCount: 0 });
check([read, read], { unreadCount: 0 });
check([read, unread], { unreadCount: 10 });
check([read, unread], { unreadCount: 10, unreadChatsCount: 1 });
check([unread], { unreadCount: 10 });
check([unread, unread], { unreadCount: 20 });
check([unread], { unreadCount: 10, unreadChatsCount: 1 });
check([unread, unread], { unreadCount: 20, unreadChatsCount: 2 });
check([mentions], { unreadMentionsCount: 10 });
check([mentions, mentions], { unreadMentionsCount: 20 });
check([mentions], { unreadMentionsCount: 10, unreadChatsCount: 1 });
check([mentions, mentions], {
unreadMentionsCount: 20,
unreadChatsCount: 2,
});
check([markedUnread], { readChatsMarkedUnreadCount: 1 });
check([markedUnread, markedUnread], { readChatsMarkedUnreadCount: 2 });
check([unreadAndMarkedUnread], {
unreadCount: 10,
unreadChatsCount: 1,
readChatsMarkedUnreadCount: 0,
});
});
it('should check if each conversation can be counted', () => {
const isCounted = { unreadCount: 20 };
const isNotCounted = { unreadCount: 10 };
const isCounted = { unreadCount: 20, unreadChatsCount: 2 };
const isNotCounted = { unreadCount: 10, unreadChatsCount: 1 };
const unread = { unreadCount: 10 };
const inactive = { ...unread, activeAt: 0 };
@@ -269,6 +287,42 @@ describe('countUnreadStats', () => {
});
});
describe('getUnreadCountForBadge', () => {
function check(
stats: StatsProps,
expected: Record<UnreadCountBadgeType, number>
) {
const unreadStats = mockStats(stats);
assert.equal(
getUnreadCountForBadge(unreadStats, 'unread-messages'),
expected['unread-messages']
);
assert.equal(
getUnreadCountForBadge(unreadStats, 'unread-chats'),
expected['unread-chats']
);
}
it('should count unread messages or unread chats', () => {
check({}, { 'unread-messages': 0, 'unread-chats': 0 });
check(
{ unreadCount: 20, unreadChatsCount: 2 },
{ 'unread-messages': 20, 'unread-chats': 2 }
);
});
it('should always include chats that are only marked unread', () => {
check(
{ readChatsMarkedUnreadCount: 3 },
{ 'unread-messages': 3, 'unread-chats': 3 }
);
check(
{ unreadCount: 20, unreadChatsCount: 2, readChatsMarkedUnreadCount: 3 },
{ 'unread-messages': 23, 'unread-chats': 5 }
);
});
});
describe('countAllChatFoldersUnreadStats', () => {
function check(
chats: ReadonlyArray<ChatProps>,
@@ -326,17 +380,20 @@ describe('countUnreadStats', () => {
check(chats, [{ folder: empty, stats: null }]);
check(chats, [
{ folder: all, stats: { unreadCount: 77 } },
{ folder: allGroups, stats: { unreadCount: 7 } },
{ folder: allDirect, stats: { unreadCount: 70 } },
{ folder: all, stats: { unreadCount: 77, unreadChatsCount: 4 } },
{ folder: allGroups, stats: { unreadCount: 7, unreadChatsCount: 2 } },
{ folder: allDirect, stats: { unreadCount: 70, unreadChatsCount: 2 } },
]);
check(
chats,
[
{ folder: all, stats: { unreadCount: 88 } },
{ folder: allGroups, stats: { unreadCount: 8 } },
{ folder: allDirect, stats: { unreadCount: 80 } },
{ folder: all, stats: { unreadCount: 88, unreadChatsCount: 6 } },
{ folder: allGroups, stats: { unreadCount: 8, unreadChatsCount: 3 } },
{
folder: allDirect,
stats: { unreadCount: 80, unreadChatsCount: 3 },
},
],
'force-include'
);
+18
View File
@@ -51,6 +51,8 @@ export type SentMediaQualitySettingType = 'standard' | 'high';
export type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
export type UnreadCountBadgeType = 'unread-messages' | 'unread-chats';
export type IdentityKeyMap = Record<
ServiceIdString,
{
@@ -194,6 +196,7 @@ export type StorageAccessType = {
preferredReactionEmoji: ReadonlyArray<Emoji.Variant>;
emojiSkinToneDefault: Emoji.SkinTone;
unreadCount: number;
unreadCountBadgeType: UnreadCountBadgeType;
'challenge:conversations': ReadonlyArray<RegisteredChallengeType>;
deviceNameEncrypted: boolean;
@@ -399,6 +402,7 @@ export const STORAGE_KEYS_TO_PRESERVE_AFTER_UNLINK = [
'showStickersIntroduction',
'emojiSkinToneDefault',
'textFormatting',
'unreadCountBadgeType',
'zoomFactor',
// Bookkeeping keys
@@ -647,3 +651,17 @@ export type AssertStorageUnlinkKeysAreExhaustive = AssertTrue<
keyof StorageAccessType
>
>;
export const STORAGE_KEY_DEFAULTS = {
'audio-notification': false,
'badge-count-muted-conversations': false,
'call-system-notification': true,
'notification-draw-attention': false,
'notification-setting': 'message',
'reaction-notification': true,
audioMessage: false,
notifyForCallsIfMuted: undefined,
notifyForMentionsIfMuted: undefined,
notifyForRepliesIfMuted: undefined,
unreadCountBadgeType: 'unread-messages',
} as const satisfies Partial<StorageAccessType>;
+28
View File
@@ -6,6 +6,7 @@ import { CurrentChatFolders } from '../types/CurrentChatFolders.std.ts';
import { isConversationMuted } from './isConversationMuted.std.ts';
import type { ConversationType } from '../state/ducks/conversations.preload.ts';
import type { UnreadCountBadgeType } from '../types/StorageKeys.std.ts';
import type { ChatFolderId } from '../types/ChatFolder.std.ts';
import type { NotificationProfileType } from '../types/NotificationProfile.std.ts';
@@ -19,6 +20,15 @@ type MutableUnreadStats = {
*/
unreadCount: number;
/**
* Number of countable conversations in the set that have at least one
* unread message.
*
* Note: Chats that are only marked unread are counted in
* `readChatsMarkedUnreadCount` instead.
*/
unreadChatsCount: number;
/**
* Total of `conversation.unreadMentionsCount`
* in all countable conversations in the set.
@@ -42,6 +52,7 @@ export type UnreadStats = Readonly<MutableUnreadStats>;
export function _createUnreadStats(): MutableUnreadStats {
return {
unreadCount: 0,
unreadChatsCount: 0,
unreadMentionsCount: 0,
readChatsMarkedUnreadCount: 0,
};
@@ -124,12 +135,29 @@ export function _countConversation(
if (hasUnreadCount) {
mutable.unreadCount += unreadCount;
mutable.unreadChatsCount += 1;
mutable.unreadMentionsCount += unreadMentionsCount;
} else if (markedUnread) {
mutable.readChatsMarkedUnreadCount += 1;
}
}
/**
* The number to show anywhere we are aggregating unreads across chats. Chats that are
* marked-unread count as 1, for either badge count type.
*/
export function getUnreadCountForBadge(
unreadStats: UnreadStats,
unreadCountBadgeType: UnreadCountBadgeType
): number {
const unreadCount =
unreadCountBadgeType === 'unread-chats'
? unreadStats.unreadChatsCount
: unreadStats.unreadCount;
return unreadCount + unreadStats.readChatsMarkedUnreadCount;
}
export function isConversationUnread(
conversation: ConversationPropsForUnreadStats,
options: UnreadStatsOptions
+1 -1
View File
@@ -51,7 +51,7 @@ export type IPCType = {
removeSetupMenuItems: () => unknown;
setAutoHideMenuBar: (value: boolean) => void;
setAutoLaunch: (value: boolean) => Promise<void>;
setBadge: (badge: number | 'marked-unread') => void;
setBadgeCount: (badgeCount: number) => void;
setMediaPermissions: (value: boolean) => Promise<void>;
setMediaCameraPermissions: (value: boolean) => Promise<void>;
setMenuBarVisibility: (value: boolean) => void;
+1 -1
View File
@@ -134,7 +134,7 @@ const IPC: IPCType = {
removeSetupMenuItems: () => ipc.send('remove-setup-menu-items'),
setAutoHideMenuBar: autoHide => ipc.send('set-auto-hide-menu-bar', autoHide),
setAutoLaunch: value => ipc.invoke('set-auto-launch', value),
setBadge: badge => ipc.send('set-badge', badge),
setBadgeCount: badgeCount => ipc.send('set-badge-count', badgeCount),
setMenuBarVisibility: visibility =>
ipc.send('set-menu-bar-visibility', visibility),
showDebugLog: (options?: { mode?: 'submit' | 'close' }) => {