diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 6cf84a30be..f2347b3a16 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -63,6 +63,18 @@ "message": "Membership is pending", "description": "Shown below the group name when selecting a group to invite a contact to, when the group item is disabled" }, + "Preferences__sent-media-quality": { + "message": "Sent media quality", + "description": "Title for the sent media quality setting" + }, + "sentMediaQualityStandard": { + "message": "Standard", + "description": "Label text for standard media quality option" + }, + "sentMediaQualityHigh": { + "message": "High", + "description": "Label text for high media quality option" + }, "softwareAcknowledgments": { "message": "Software Acknowledgments", "description": "Shown in the about box for the link to software acknowledgments" diff --git a/ts/components/Preferences.stories.tsx b/ts/components/Preferences.stories.tsx index 6af02a269a..b749451fef 100644 --- a/ts/components/Preferences.stories.tsx +++ b/ts/components/Preferences.stories.tsx @@ -107,6 +107,7 @@ const getDefaultArgs = (): PropsDataType => ({ selectedMicrophone: availableMicrophones[0], selectedSpeaker: availableSpeakers[1], shouldShowStoriesSettings: true, + sentMediaQualitySetting: 'standard', themeSetting: 'system', universalExpireTimer: DurationInSeconds.HOUR, whoCanFindMe: PhoneNumberDiscoverability.Discoverable, @@ -158,6 +159,7 @@ export default { onSelectedCameraChange: { action: true }, onSelectedMicrophoneChange: { action: true }, onSelectedSpeakerChange: { action: true }, + onSentMediaQualityChange: { action: true }, onSpellCheckChange: { action: true }, onThemeChange: { action: true }, onUniversalExpireTimerChange: { action: true }, diff --git a/ts/components/Preferences.tsx b/ts/components/Preferences.tsx index 39d1ca2874..b966dbf0a5 100644 --- a/ts/components/Preferences.tsx +++ b/ts/components/Preferences.tsx @@ -9,8 +9,9 @@ import type { AudioDevice } from 'ringrtc'; import type { MediaDeviceSettings } from '../types/Calling'; import type { - ZoomFactorType, NotificationSettingType, + SentMediaQualitySettingType, + ZoomFactorType, } from '../types/Storage.d'; import type { ThemeSettingType } from '../types/StorageUIKeys'; import { Button, ButtonVariant } from './Button'; @@ -24,7 +25,11 @@ import type { DefaultConversationColorType, } from '../types/Colors'; import { DisappearingTimeDialog } from './DisappearingTimeDialog'; -import type { LocalizerType, ThemeType } from '../types/Util'; +import type { + LocalizerType, + SentMediaQualityType, + ThemeType, +} from '../types/Util'; import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability'; import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode'; import { Select } from './Select'; @@ -76,6 +81,7 @@ export type PropsDataType = { selectedCamera?: string; selectedMicrophone?: AudioDevice; selectedSpeaker?: AudioDevice; + sentMediaQualitySetting: SentMediaQualitySettingType; themeSetting: ThemeSettingType; universalExpireTimer: DurationInSeconds; whoCanFindMe: PhoneNumberDiscoverability; @@ -149,6 +155,7 @@ type PropsFunctionType = { onSelectedCameraChange: SelectChangeHandlerType; onSelectedMicrophoneChange: SelectChangeHandlerType; onSelectedSpeakerChange: SelectChangeHandlerType; + onSentMediaQualityChange: SelectChangeHandlerType; onSpellCheckChange: CheckboxChangeHandlerType; onThemeChange: SelectChangeHandlerType; onUniversalExpireTimerChange: SelectChangeHandlerType; @@ -267,6 +274,7 @@ export function Preferences({ onSelectedCameraChange, onSelectedMicrophoneChange, onSelectedSpeakerChange, + onSentMediaQualityChange, onSpellCheckChange, onThemeChange, onUniversalExpireTimerChange, @@ -278,6 +286,7 @@ export function Preferences({ selectedCamera, selectedMicrophone, selectedSpeaker, + sentMediaQualitySetting, setGlobalDefaultConversationColor, shouldShowStoriesSettings, themeSetting, @@ -535,6 +544,25 @@ export function Preferences({ name="linkPreviews" onChange={noop} /> + + } + /> {isSyncSupported && ( diff --git a/ts/main/settingsChannel.ts b/ts/main/settingsChannel.ts index d640389e85..93387833f6 100644 --- a/ts/main/settingsChannel.ts +++ b/ts/main/settingsChannel.ts @@ -85,6 +85,7 @@ export class SettingsChannel extends EventEmitter { this.installSetting('audioNotification'); this.installSetting('countMutedConversations'); + this.installSetting('sentMediaQualitySetting'); this.installSetting('spellCheck', { isEphemeral: true, }); diff --git a/ts/state/ducks/composer.ts b/ts/state/ducks/composer.ts index 517e3bb455..d95bcf5bce 100644 --- a/ts/state/ducks/composer.ts +++ b/ts/state/ducks/composer.ts @@ -36,7 +36,7 @@ export type ComposerStateType = { linkPreviewLoading: boolean; linkPreviewResult?: LinkPreviewType; quotedMessage?: Pick; - shouldSendHighQualityAttachments: boolean; + shouldSendHighQualityAttachments?: boolean; }; // Actions @@ -287,7 +287,6 @@ export function getEmptyState(): ComposerStateType { return { attachments: [], linkPreviewLoading: false, - shouldSendHighQualityAttachments: false, }; } @@ -306,7 +305,7 @@ export function reducer( attachments, ...(attachments.length ? {} - : { shouldSendHighQualityAttachments: false }), + : { shouldSendHighQualityAttachments: undefined }), }; } diff --git a/ts/state/smart/CompositionArea.tsx b/ts/state/smart/CompositionArea.tsx index abf0c41afa..450f4b409a 100644 --- a/ts/state/smart/CompositionArea.tsx +++ b/ts/state/smart/CompositionArea.tsx @@ -93,7 +93,10 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => { // MediaEditor imageToBlurHash, // MediaQualitySelector - shouldSendHighQualityAttachments, + shouldSendHighQualityAttachments: + shouldSendHighQualityAttachments !== undefined + ? shouldSendHighQualityAttachments + : window.storage.get('sent-media-quality') === 'high', // StagedLinkPreview linkPreviewLoading, linkPreviewResult, diff --git a/ts/test-both/state/ducks/composer_test.ts b/ts/test-both/state/ducks/composer_test.ts index 2e02bff921..519fea6c8a 100644 --- a/ts/test-both/state/ducks/composer_test.ts +++ b/ts/test-both/state/ducks/composer_test.ts @@ -82,7 +82,7 @@ describe('both/state/ducks/composer', () => { assert.deepEqual(state.attachments, attachments); assert.deepEqual(state.attachments, attachments); - assert.isFalse(state.shouldSendHighQualityAttachments); + assert.isUndefined(state.shouldSendHighQualityAttachments); }); it('does not update redux if the conversation is not selected', () => { @@ -122,7 +122,7 @@ describe('both/state/ducks/composer', () => { const { setMediaQualitySetting } = actions; const state = getEmptyState(); - assert.isFalse(state.shouldSendHighQualityAttachments); + assert.isUndefined(state.shouldSendHighQualityAttachments); const nextState = reducer(state, setMediaQualitySetting(true)); diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index e342a46d46..efa3eb9a17 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -30,6 +30,8 @@ export type SerializedCertificateType = { export type ZoomFactorType = 0.75 | 1 | 1.25 | 1.5 | 2 | number; +export type SentMediaQualitySettingType = 'standard' | 'high'; + export type NotificationSettingType = 'message' | 'name' | 'count' | 'off'; export type IdentityKeyMap = Record< @@ -57,6 +59,7 @@ export type StorageAccessType = { 'notification-draw-attention': boolean; 'notification-setting': NotificationSettingType; 'read-receipt-setting': boolean; + 'sent-media-quality': SentMediaQualitySettingType; 'spell-check': boolean; 'theme-setting': ThemeSettingType; attachmentMigration_isComplete: boolean; diff --git a/ts/types/Util.ts b/ts/types/Util.ts index a960f5d98c..37d49f189b 100644 --- a/ts/types/Util.ts +++ b/ts/types/Util.ts @@ -53,6 +53,11 @@ export type LocalizerType = { getLocale(): string; }; +export enum SentMediaQualityType { + 'standard' = 'standard', + 'high' = 'high', +} + export enum ThemeType { 'light' = 'light', 'dark' = 'dark', diff --git a/ts/util/createIPCEvents.tsx b/ts/util/createIPCEvents.tsx index b10063cccb..df0c27e990 100644 --- a/ts/util/createIPCEvents.tsx +++ b/ts/util/createIPCEvents.tsx @@ -45,6 +45,7 @@ import { lookupConversationWithoutUuid } from './lookupConversationWithoutUuid'; import * as log from '../logging/log'; import { deleteAllMyStories } from './deleteAllMyStories'; +type SentMediaQualityType = 'standard' | 'high'; type ThemeType = 'light' | 'dark' | 'system'; type NotificationSettingType = 'message' | 'name' | 'count' | 'off'; @@ -65,6 +66,7 @@ export type IPCEventsValuesType = { preferredAudioInputDevice: AudioDevice | undefined; preferredAudioOutputDevice: AudioDevice | undefined; preferredVideoInputDevice: string | undefined; + sentMediaQualitySetting: SentMediaQualityType; spellCheck: boolean; systemTraySetting: SystemTraySetting; themeSetting: ThemeType; @@ -298,6 +300,10 @@ export function createIPCEvents( window.storage.get('auto-download-update', true), setAutoDownloadUpdate: value => window.storage.put('auto-download-update', value), + getSentMediaQualitySetting: () => + window.storage.get('sent-media-quality', 'standard'), + setSentMediaQualitySetting: value => + window.storage.put('sent-media-quality', value), getThemeSetting: () => window.storage.get('theme-setting', 'system'), setThemeSetting: value => { const promise = window.storage.put('theme-setting', value); diff --git a/ts/views/conversation_view.tsx b/ts/views/conversation_view.tsx index e1e1b97631..db6cf393e6 100644 --- a/ts/views/conversation_view.tsx +++ b/ts/views/conversation_view.tsx @@ -2563,9 +2563,15 @@ export class ConversationView extends window.Backbone.View { ).filter(isNotNil); } + const shouldSendHighQualityAttachments = window.reduxStore + ? window.reduxStore.getState().composer.shouldSendHighQualityAttachments + : undefined; + const sendHQImages = - window.reduxStore && - window.reduxStore.getState().composer.shouldSendHighQualityAttachments; + shouldSendHighQualityAttachments !== undefined + ? shouldSendHighQualityAttachments + : window.storage.get('sent-media-quality') === 'high'; + const sendDelta = Date.now() - this.sendStart; log.info('Send pre-checks took', sendDelta, 'milliseconds'); diff --git a/ts/windows/preload.ts b/ts/windows/preload.ts index 5f7dc53519..de2cad6232 100644 --- a/ts/windows/preload.ts +++ b/ts/windows/preload.ts @@ -61,6 +61,7 @@ installSetting('notificationDrawAttention'); installSetting('notificationSetting'); installSetting('spellCheck'); installSetting('systemTraySetting'); +installSetting('sentMediaQualitySetting'); installSetting('themeSetting'); installSetting('universalExpireTimer'); installSetting('zoomFactor'); diff --git a/ts/windows/settings/preload.ts b/ts/windows/settings/preload.ts index ad4ad7a3cf..7a7b76e057 100644 --- a/ts/windows/settings/preload.ts +++ b/ts/windows/settings/preload.ts @@ -43,6 +43,7 @@ const settingNotificationDrawAttention = createSetting( const settingNotificationSetting = createSetting('notificationSetting'); const settingRelayCalls = createSetting('alwaysRelayCalls'); const settingSpellCheck = createSetting('spellCheck'); +const settingSentMediaQuality = createSetting('sentMediaQualitySetting'); const settingTheme = createSetting('themeSetting'); const settingSystemTraySetting = createSetting('systemTraySetting'); @@ -157,6 +158,7 @@ const renderPreferences = async () => { selectedCamera, selectedMicrophone, selectedSpeaker, + sentMediaQualitySetting, systemTraySetting, themeSetting, universalExpireTimer, @@ -195,6 +197,7 @@ const renderPreferences = async () => { selectedCamera: settingVideoInput.getValue(), selectedMicrophone: settingAudioInput.getValue(), selectedSpeaker: settingAudioOutput.getValue(), + sentMediaQualitySetting: settingSentMediaQuality.getValue(), systemTraySetting: settingSystemTraySetting.getValue(), themeSetting: settingTheme.getValue(), universalExpireTimer: settingUniversalExpireTimer.getValue(), @@ -254,6 +257,7 @@ const renderPreferences = async () => { selectedCamera, selectedMicrophone, selectedSpeaker, + sentMediaQualitySetting, themeSetting, universalExpireTimer: DurationInSeconds.fromSeconds(universalExpireTimer), whoCanFindMe, @@ -350,6 +354,7 @@ const renderPreferences = async () => { onSelectedCameraChange: reRender(settingVideoInput.setValue), onSelectedMicrophoneChange: reRender(settingAudioInput.setValue), onSelectedSpeakerChange: reRender(settingAudioOutput.setValue), + onSentMediaQualityChange: reRender(settingSentMediaQuality.setValue), onSpellCheckChange: reRender(settingSpellCheck.setValue), onThemeChange: reRender(settingTheme.setValue), onUniversalExpireTimerChange: (newValue: number): Promise => {