mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-09-20 08:45:21 +01:00
Support new unread badge count type
This commit is contained in:
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -18,11 +18,7 @@ export default {
|
||||
},
|
||||
args: {
|
||||
i18n,
|
||||
otherTabsUnreadStats: {
|
||||
unreadCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
readChatsMarkedUnreadCount: 0,
|
||||
},
|
||||
otherTabsUnreadCount: 0,
|
||||
isStaging: false,
|
||||
hasPendingUpdate: false,
|
||||
hasFailedStorySends: false,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -158,6 +158,7 @@ function* createRecords({
|
||||
callsUseLessDataSetting: null,
|
||||
allowSealedSenderFromAnyone: null,
|
||||
allowAutomaticKeyVerification: null,
|
||||
unreadBadgeType: null,
|
||||
},
|
||||
username: null,
|
||||
usernameLink: null,
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
-1
@@ -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;
|
||||
|
||||
@@ -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' }) => {
|
||||
|
||||
Reference in New Issue
Block a user