From afae456d15448b090014253b359f2fc37a16991a Mon Sep 17 00:00:00 2001 From: Nicholas La Roux Date: Mon, 24 Nov 2025 17:31:15 -0500 Subject: [PATCH] Add time format preference with system, 12, and 24 options --- _locales/en/messages.json | 16 ++++++++ app/main.main.ts | 41 +++++++++++++++---- ts/components/Preferences.dom.stories.tsx | 12 ++++++ ts/components/Preferences.dom.tsx | 36 ++++++++++++++++ ts/main/settingsChannel.main.ts | 1 + ts/state/smart/Preferences.preload.tsx | 18 ++++++++ .../util/hourCyclePreference_test.std.ts | 29 +++++++++++++ .../util/hourCycleSetting_test.std.ts | 24 +++++++++++ ts/util/createIPCEvents.preload.ts | 10 +++++ ts/util/preload.preload.ts | 3 ++ ts/windows/preload.preload.ts | 1 + 11 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 ts/test-node/util/hourCyclePreference_test.std.ts create mode 100644 ts/test-node/util/hourCycleSetting_test.std.ts diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 87cd3c1c9f..e17991a075 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3012,6 +3012,22 @@ "messageformat": "System", "description": "Label text for system theme" }, + "icu:Preferences--time-format": { + "messageformat": "Time format", + "description": "Header for time format settings" + }, + "icu:timeFormatSystem": { + "messageformat": "System", + "description": "Label text for system locale based time format" + }, + "icu:timeFormat12Hour": { + "messageformat": "12-hour", + "description": "Label text for 12-hour time format" + }, + "icu:timeFormat24Hour": { + "messageformat": "24-hour", + "description": "Label text for 24-hour time format" + }, "icu:noteToSelf": { "messageformat": "Note to Self", "description": "Name for the conversation with your own phone number" diff --git a/app/main.main.ts b/app/main.main.ts index 69bcf0a137..fc38516e8e 100644 --- a/app/main.main.ts +++ b/app/main.main.ts @@ -363,6 +363,21 @@ async function getResolvedThemeSetting( return ThemeType[theme]; } +type HourCycleSettingType = 'system' | '12' | '24'; + +async function getHourCycleSetting(): Promise { + const value = ephemeralConfig.get('hour-cycle-preference'); + if (value === '12' || value === '24' || value === 'system') { + log.info('got fast hour-cycle-preference value', value); + return value; + } + + // Default to 'system' if setting doesn't exist or is invalid + ephemeralConfig.set('hour-cycle-preference', 'system'); + log.info('initializing hour-cycle-preference setting', 'system'); + return 'system'; +} + type GetBackgroundColorOptionsType = GetThemeSettingOptionsType & Readonly<{ signalColors?: boolean; @@ -473,16 +488,28 @@ function getResolvedMessagesLocale(): LocaleType { return resolvedTranslationsLocale; } -function getHourCyclePreference(): HourCyclePreference { - if (process.platform !== 'darwin') { - return HourCyclePreference.UnknownPreference; +async function getHourCyclePreference(): Promise { + const userSetting = await getHourCycleSetting(); + if (userSetting === '12') { + return HourCyclePreference.Prefer12; } - if (systemPreferences.getUserDefault('AppleICUForce24HourTime', 'boolean')) { + if (userSetting === '24') { return HourCyclePreference.Prefer24; } - if (systemPreferences.getUserDefault('AppleICUForce12HourTime', 'boolean')) { - return HourCyclePreference.Prefer12; + + if (OS.isMacOS()) { + if ( + systemPreferences.getUserDefault('AppleICUForce24HourTime', 'boolean') + ) { + return HourCyclePreference.Prefer24; + } + if ( + systemPreferences.getUserDefault('AppleICUForce12HourTime', 'boolean') + ) { + return HourCyclePreference.Prefer12; + } } + return HourCyclePreference.UnknownPreference; } @@ -2082,7 +2109,7 @@ app.on('ready', async () => { localeOverride = await getLocaleOverrideSetting(); - const hourCyclePreference = getHourCyclePreference(); + const hourCyclePreference = await getHourCyclePreference(); log.info(`app.ready: hour cycle preference: ${hourCyclePreference}`); log.info('app.ready: preferred system locales:', preferredSystemLocales); diff --git a/ts/components/Preferences.dom.stories.tsx b/ts/components/Preferences.dom.stories.tsx index e785e3f408..7db84f1be6 100644 --- a/ts/components/Preferences.dom.stories.tsx +++ b/ts/components/Preferences.dom.stories.tsx @@ -494,6 +494,7 @@ export default { sentMediaQualitySetting: 'standard', shouldShowUpdateDialog: false, themeSetting: 'system', + hourCyclePreference: 'system', theme: ThemeType.light, universalExpireTimer: DurationInSeconds.HOUR, whoCanFindMe: PhoneNumberDiscoverability.Discoverable, @@ -591,6 +592,7 @@ export default { onStartUpdate: action('onStartUpdate'), onTextFormattingChange: action('onTextFormattingChange'), onThemeChange: action('onThemeChange'), + onHourCycleChange: action('onHourCycleChange'), onToggleNavTabsCollapse: action('onToggleNavTabsCollapse'), onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'), onWhoCanSeeMeChange: action('onWhoCanSeeMeChange'), @@ -666,6 +668,16 @@ export const Appearance = Template.bind({}); Appearance.args = { settingsLocation: { page: SettingsPage.Appearance }, }; +export const Appearance12HourFormat = Template.bind({}); +Appearance12HourFormat.args = { + settingsLocation: { page: SettingsPage.Appearance }, + hourCyclePreference: '12', +}; +export const Appearance24HourFormat = Template.bind({}); +Appearance24HourFormat.args = { + settingsLocation: { page: SettingsPage.Appearance }, + hourCyclePreference: '24', +}; export const Chats = Template.bind({}); Chats.args = { settingsLocation: { page: SettingsPage.Chats }, diff --git a/ts/components/Preferences.dom.tsx b/ts/components/Preferences.dom.tsx index 5311101fa2..91468be6f8 100644 --- a/ts/components/Preferences.dom.tsx +++ b/ts/components/Preferences.dom.tsx @@ -77,6 +77,7 @@ import type { SentMediaQualityType, ThemeType, } from '../types/Util.std.js'; +import type { HourCycleSettingType } from '../util/preload.preload.js'; import type { BackupMediaDownloadStatusType, BackupsSubscriptionType, @@ -162,6 +163,7 @@ export type PropsDataType = { selectedSpeaker?: AudioDevice; sentMediaQualitySetting: SentMediaQualitySettingType; themeSetting: ThemeSettingType | undefined; + hourCyclePreference: HourCycleSettingType | undefined; universalExpireTimer: DurationInSeconds; whoCanFindMe: PhoneNumberDiscoverability; whoCanSeeMe: PhoneNumberSharingMode; @@ -320,6 +322,7 @@ type PropsFunctionType = { onSpellCheckChange: CheckboxChangeHandlerType; onTextFormattingChange: CheckboxChangeHandlerType; onThemeChange: SelectChangeHandlerType; + onHourCycleChange: SelectChangeHandlerType; onToggleNavTabsCollapse: (navTabsCollapsed: boolean) => void; onUniversalExpireTimerChange: SelectChangeHandlerType; onWhoCanSeeMeChange: SelectChangeHandlerType; @@ -486,6 +489,7 @@ export function Preferences({ onSpellCheckChange, onTextFormattingChange, onThemeChange, + onHourCycleChange, onToggleNavTabsCollapse, onUniversalExpireTimerChange, onWhoCanSeeMeChange, @@ -526,6 +530,7 @@ export function Preferences({ localeOverride, theme, themeSetting, + hourCyclePreference, universalExpireTimer = DurationInSeconds.ZERO, validateBackup, whoCanFindMe, @@ -539,6 +544,7 @@ export function Preferences({ }: PropsType): JSX.Element { const storiesId = useId(); const themeSelectId = useId(); + const hourCycleSelectId = useId(); const zoomSelectId = useId(); const languageId = useId(); @@ -1035,6 +1041,36 @@ export function Preferences({ {i18n('icu:Preferences__LanguageModal__Restart__Description')} )} + + {i18n('icu:Preferences--time-format')} + + } + right={ +