Use DurationInSeconds for expireTimer
This commit is contained in:
parent
cf57c7aaf0
commit
6be69a7ba8
59 changed files with 411 additions and 216 deletions
|
@ -7,6 +7,7 @@ import { ConfirmationDialog } from './ConfirmationDialog';
|
|||
import { Select } from './Select';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import type { Theme } from '../util/theme';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
|
||||
const CSS_MODULE = 'module-disappearing-time-dialog';
|
||||
|
||||
|
@ -15,14 +16,14 @@ const DEFAULT_VALUE = 60;
|
|||
export type PropsType = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
theme?: Theme;
|
||||
initialValue?: number;
|
||||
onSubmit: (value: number) => void;
|
||||
initialValue?: DurationInSeconds;
|
||||
onSubmit: (value: DurationInSeconds) => void;
|
||||
onClose: () => void;
|
||||
}>;
|
||||
|
||||
const UNITS = ['seconds', 'minutes', 'hours', 'days', 'weeks'];
|
||||
|
||||
const UNIT_TO_MS = new Map<string, number>([
|
||||
const UNIT_TO_SEC = new Map<string, number>([
|
||||
['seconds', 1],
|
||||
['minutes', 60],
|
||||
['hours', 60 * 60],
|
||||
|
@ -50,14 +51,14 @@ export function DisappearingTimeDialog(props: PropsType): JSX.Element {
|
|||
let initialUnit = 'seconds';
|
||||
let initialUnitValue = 1;
|
||||
for (const unit of UNITS) {
|
||||
const ms = UNIT_TO_MS.get(unit) || 1;
|
||||
const sec = UNIT_TO_SEC.get(unit) || 1;
|
||||
|
||||
if (initialValue < ms) {
|
||||
if (initialValue < sec) {
|
||||
break;
|
||||
}
|
||||
|
||||
initialUnit = unit;
|
||||
initialUnitValue = Math.floor(initialValue / ms);
|
||||
initialUnitValue = Math.floor(initialValue / sec);
|
||||
}
|
||||
|
||||
const [unitValue, setUnitValue] = useState(initialUnitValue);
|
||||
|
@ -84,7 +85,11 @@ export function DisappearingTimeDialog(props: PropsType): JSX.Element {
|
|||
text: i18n('DisappearingTimeDialog__set'),
|
||||
style: 'affirmative',
|
||||
action() {
|
||||
onSubmit(unitValue * (UNIT_TO_MS.get(unit) || 1));
|
||||
onSubmit(
|
||||
DurationInSeconds.fromSeconds(
|
||||
unitValue * (UNIT_TO_SEC.get(unit) ?? 1)
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, { useState } from 'react';
|
|||
|
||||
import { DisappearingTimerSelect } from './DisappearingTimerSelect';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
|
||||
export default {
|
||||
|
@ -23,7 +24,7 @@ const TimerSelectWrap: React.FC<Props> = ({ initialValue }) => {
|
|||
return (
|
||||
<DisappearingTimerSelect
|
||||
i18n={i18n}
|
||||
value={value}
|
||||
value={DurationInSeconds.fromSeconds(value)}
|
||||
onChange={newValue => setValue(newValue)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ import classNames from 'classnames';
|
|||
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import * as expirationTimer from '../util/expirationTimer';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
|
||||
|
||||
import { Select } from './Select';
|
||||
|
@ -16,17 +17,17 @@ const CSS_MODULE = 'module-disappearing-timer-select';
|
|||
export type Props = {
|
||||
i18n: LocalizerType;
|
||||
|
||||
value?: number;
|
||||
onChange(value: number): void;
|
||||
value?: DurationInSeconds;
|
||||
onChange(value: DurationInSeconds): void;
|
||||
};
|
||||
|
||||
export const DisappearingTimerSelect: React.FC<Props> = (props: Props) => {
|
||||
const { i18n, value = 0, onChange } = props;
|
||||
const { i18n, value = DurationInSeconds.ZERO, onChange } = props;
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
let expirationTimerOptions: ReadonlyArray<{
|
||||
readonly value: number;
|
||||
readonly value: DurationInSeconds;
|
||||
readonly text: string;
|
||||
}> = expirationTimer.DEFAULT_DURATIONS_IN_SECONDS.map(seconds => {
|
||||
const text = expirationTimer.format(i18n, seconds, {
|
||||
|
@ -42,7 +43,7 @@ export const DisappearingTimerSelect: React.FC<Props> = (props: Props) => {
|
|||
!expirationTimer.DEFAULT_DURATIONS_SET.has(value);
|
||||
|
||||
const onSelectChange = (newValue: string) => {
|
||||
const intValue = parseInt(newValue, 10);
|
||||
const intValue = DurationInSeconds.fromSeconds(parseInt(newValue, 10));
|
||||
if (intValue === -1) {
|
||||
setIsModalOpen(true);
|
||||
} else {
|
||||
|
@ -54,7 +55,7 @@ export const DisappearingTimerSelect: React.FC<Props> = (props: Props) => {
|
|||
expirationTimerOptions = [
|
||||
...expirationTimerOptions,
|
||||
{
|
||||
value: -1,
|
||||
value: DurationInSeconds.fromSeconds(-1),
|
||||
text: i18n(
|
||||
isCustomTimeSelected
|
||||
? 'selectedCustomDisappearingTimeOption'
|
||||
|
|
|
@ -13,6 +13,7 @@ import { CrashReportDialog } from './CrashReportDialog';
|
|||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import { MessageSearchResult } from './conversationList/MessageSearchResult';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { ThemeType } from '../types/Util';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
|
@ -953,7 +954,7 @@ export const GroupMetadataNoTimer = (): JSX.Element => (
|
|||
mode: LeftPaneMode.SetGroupMetadata,
|
||||
groupAvatar: undefined,
|
||||
groupName: 'Group 1',
|
||||
groupExpireTimer: 0,
|
||||
groupExpireTimer: DurationInSeconds.ZERO,
|
||||
hasError: false,
|
||||
isCreating: false,
|
||||
isEditingAvatar: false,
|
||||
|
@ -975,7 +976,7 @@ export const GroupMetadataRegularTimer = (): JSX.Element => (
|
|||
mode: LeftPaneMode.SetGroupMetadata,
|
||||
groupAvatar: undefined,
|
||||
groupName: 'Group 1',
|
||||
groupExpireTimer: 24 * 3600,
|
||||
groupExpireTimer: DurationInSeconds.DAY,
|
||||
hasError: false,
|
||||
isCreating: false,
|
||||
isEditingAvatar: false,
|
||||
|
@ -997,7 +998,7 @@ export const GroupMetadataCustomTimer = (): JSX.Element => (
|
|||
mode: LeftPaneMode.SetGroupMetadata,
|
||||
groupAvatar: undefined,
|
||||
groupName: 'Group 1',
|
||||
groupExpireTimer: 7 * 3600,
|
||||
groupExpireTimer: DurationInSeconds.fromHours(7),
|
||||
hasError: false,
|
||||
isCreating: false,
|
||||
isEditingAvatar: false,
|
||||
|
|
|
@ -28,6 +28,7 @@ import { ScrollBehavior } from '../types/Util';
|
|||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||
import { usePrevious } from '../hooks/usePrevious';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
import type { WidthBreakpoint } from './_util';
|
||||
import { getConversationListWidthBreakpoint } from './_util';
|
||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||
|
@ -106,7 +107,7 @@ export type PropsType = {
|
|||
savePreferredLeftPaneWidth: (_: number) => void;
|
||||
searchInConversation: (conversationId: string) => unknown;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => void;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
setComposeGroupExpireTimer: (_: DurationInSeconds) => void;
|
||||
setComposeGroupName: (_: string) => void;
|
||||
setComposeSearchTerm: (composeSearchTerm: string) => void;
|
||||
showArchivedConversations: () => void;
|
||||
|
|
|
@ -12,6 +12,7 @@ import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
|
|||
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
|
||||
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
|
||||
import { objectMap } from '../util/objectMap';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -107,7 +108,7 @@ const getDefaultArgs = (): PropsDataType => ({
|
|||
selectedSpeaker: availableSpeakers[1],
|
||||
shouldShowStoriesSettings: true,
|
||||
themeSetting: 'system',
|
||||
universalExpireTimer: 3600,
|
||||
universalExpireTimer: DurationInSeconds.HOUR,
|
||||
whoCanFindMe: PhoneNumberDiscoverability.Discoverable,
|
||||
whoCanSeeMe: PhoneNumberSharingMode.Everybody,
|
||||
zoomFactor: 1,
|
||||
|
@ -186,7 +187,7 @@ BlockedMany.args = {
|
|||
|
||||
export const CustomUniversalExpireTimer = Template.bind({});
|
||||
CustomUniversalExpireTimer.args = {
|
||||
universalExpireTimer: 9000,
|
||||
universalExpireTimer: DurationInSeconds.fromSeconds(9000),
|
||||
};
|
||||
CustomUniversalExpireTimer.story = {
|
||||
name: 'Custom universalExpireTimer',
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
DEFAULT_DURATIONS_SET,
|
||||
format as formatExpirationTimer,
|
||||
} from '../util/expirationTimer';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
import { useUniqueId } from '../hooks/useUniqueId';
|
||||
import { useTheme } from '../hooks/useTheme';
|
||||
|
@ -76,7 +77,7 @@ export type PropsDataType = {
|
|||
selectedMicrophone?: AudioDevice;
|
||||
selectedSpeaker?: AudioDevice;
|
||||
themeSetting: ThemeSettingType;
|
||||
universalExpireTimer: number;
|
||||
universalExpireTimer: DurationInSeconds;
|
||||
whoCanFindMe: PhoneNumberDiscoverability;
|
||||
whoCanSeeMe: PhoneNumberSharingMode;
|
||||
zoomFactor: ZoomFactorType;
|
||||
|
@ -280,7 +281,7 @@ export const Preferences = ({
|
|||
setGlobalDefaultConversationColor,
|
||||
shouldShowStoriesSettings,
|
||||
themeSetting,
|
||||
universalExpireTimer = 0,
|
||||
universalExpireTimer = DurationInSeconds.ZERO,
|
||||
whoCanFindMe,
|
||||
whoCanSeeMe,
|
||||
zoomFactor,
|
||||
|
@ -954,7 +955,7 @@ export const Preferences = ({
|
|||
{
|
||||
value: isCustomDisappearingMessageValue
|
||||
? universalExpireTimer
|
||||
: -1,
|
||||
: DurationInSeconds.fromSeconds(-1),
|
||||
text: isCustomDisappearingMessageValue
|
||||
? formatExpirationTimer(i18n, universalExpireTimer)
|
||||
: i18n('selectedCustomDisappearingTimeOption'),
|
||||
|
|
|
@ -13,9 +13,10 @@ import { Intl } from './Intl';
|
|||
import { Modal } from './Modal';
|
||||
import { SendStatus } from '../messages/MessageSendState';
|
||||
import { Theme } from '../util/theme';
|
||||
import { formatDateTimeLong } from '../util/timestamp';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { ThemeType } from '../types/Util';
|
||||
import { Time } from './Time';
|
||||
import { formatDateTimeLong } from '../util/timestamp';
|
||||
import { groupBy } from '../util/mapUtil';
|
||||
import { format as formatRelativeTime } from '../util/expirationTimer';
|
||||
|
||||
|
@ -189,7 +190,7 @@ export const StoryDetailsModal = ({
|
|||
}
|
||||
|
||||
const timeRemaining = expirationTimestamp
|
||||
? expirationTimestamp - Date.now()
|
||||
? DurationInSeconds.fromMillis(expirationTimestamp - Date.now())
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
|
@ -254,7 +255,7 @@ export const StoryDetailsModal = ({
|
|||
id="StoryDetailsModal__disappears-in"
|
||||
components={[
|
||||
<span className="StoryDetailsModal__debugger__button__text">
|
||||
{formatRelativeTime(i18n, timeRemaining / 1000, {
|
||||
{formatRelativeTime(i18n, timeRemaining, {
|
||||
largest: 2,
|
||||
})}
|
||||
</span>,
|
||||
|
|
|
@ -9,6 +9,7 @@ import { action } from '@storybook/addon-actions';
|
|||
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
|
||||
import { getRandomColor } from '../../test-both/helpers/getRandomColor';
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import { StorybookThemeContext } from '../../../.storybook/StorybookThemeContext';
|
||||
import {
|
||||
|
@ -152,7 +153,7 @@ export const PrivateConvo = (): JSX.Element => {
|
|||
phoneNumber: '(202) 555-0005',
|
||||
type: 'direct',
|
||||
id: '7',
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
acceptedMessageRequest: true,
|
||||
},
|
||||
},
|
||||
|
@ -165,7 +166,7 @@ export const PrivateConvo = (): JSX.Element => {
|
|||
phoneNumber: '(202) 555-0005',
|
||||
type: 'direct',
|
||||
id: '8',
|
||||
expireTimer: 300,
|
||||
expireTimer: DurationInSeconds.fromSeconds(300),
|
||||
acceptedMessageRequest: true,
|
||||
isVerified: true,
|
||||
canChangeTimer: true,
|
||||
|
@ -231,7 +232,7 @@ export const Group = (): JSX.Element => {
|
|||
phoneNumber: '',
|
||||
id: '11',
|
||||
type: 'group',
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
acceptedMessageRequest: true,
|
||||
outgoingCallButtonStyle: OutgoingCallButtonStyle.JustVideo,
|
||||
},
|
||||
|
@ -247,7 +248,7 @@ export const Group = (): JSX.Element => {
|
|||
id: '12',
|
||||
type: 'group',
|
||||
left: true,
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
acceptedMessageRequest: true,
|
||||
outgoingCallButtonStyle: OutgoingCallButtonStyle.JustVideo,
|
||||
},
|
||||
|
@ -262,7 +263,7 @@ export const Group = (): JSX.Element => {
|
|||
phoneNumber: '',
|
||||
id: '13',
|
||||
type: 'group',
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
acceptedMessageRequest: true,
|
||||
outgoingCallButtonStyle: OutgoingCallButtonStyle.Join,
|
||||
},
|
||||
|
@ -277,7 +278,7 @@ export const Group = (): JSX.Element => {
|
|||
phoneNumber: '',
|
||||
id: '14',
|
||||
type: 'group',
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
acceptedMessageRequest: true,
|
||||
outgoingCallButtonStyle: OutgoingCallButtonStyle.JustVideo,
|
||||
muteExpiresAt: Infinity,
|
||||
|
|
|
@ -28,6 +28,7 @@ import * as expirationTimer from '../../util/expirationTimer';
|
|||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
||||
import { isConversationMuted } from '../../util/isConversationMuted';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import {
|
||||
useStartCallShortcuts,
|
||||
useKeyboardShortcuts,
|
||||
|
@ -79,7 +80,7 @@ export type PropsDataType = {
|
|||
|
||||
export type PropsActionsType = {
|
||||
onSetMuteNotifications: (seconds: number) => void;
|
||||
onSetDisappearingMessages: (seconds: number) => void;
|
||||
onSetDisappearingMessages: (seconds: DurationInSeconds) => void;
|
||||
onDeleteMessages: () => void;
|
||||
onSearchInConversation: () => void;
|
||||
onOutgoingAudioCallInConversation: () => void;
|
||||
|
@ -406,8 +407,8 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
|||
|
||||
const expireDurations: ReadonlyArray<ReactNode> = [
|
||||
...expirationTimer.DEFAULT_DURATIONS_IN_SECONDS,
|
||||
-1,
|
||||
].map((seconds: number) => {
|
||||
DurationInSeconds.fromSeconds(-1),
|
||||
].map(seconds => {
|
||||
let text: string;
|
||||
|
||||
if (seconds === -1) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { SendStatus } from '../../messages/MessageSendState';
|
|||
import { WidthBreakpoint } from '../_util';
|
||||
import * as log from '../../logging/log';
|
||||
import { formatDateTimeLong } from '../../util/timestamp';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import { format as formatRelativeTime } from '../../util/expirationTimer';
|
||||
|
||||
export type Contact = Pick<
|
||||
|
@ -302,7 +303,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
} = this.props;
|
||||
|
||||
const timeRemaining = expirationTimestamp
|
||||
? expirationTimestamp - Date.now()
|
||||
? DurationInSeconds.fromMillis(expirationTimestamp - Date.now())
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
|
@ -422,7 +423,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
{i18n('MessageDetail--disappears-in')}
|
||||
</td>
|
||||
<td>
|
||||
{formatRelativeTime(i18n, timeRemaining / 1000, {
|
||||
{formatRelativeTime(i18n, timeRemaining, {
|
||||
largest: 2,
|
||||
})}
|
||||
</td>
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import * as moment from 'moment';
|
||||
import { times } from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { text, boolean, number } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import type { PropsType } from './Timeline';
|
||||
import { Timeline } from './Timeline';
|
||||
|
@ -135,7 +135,7 @@ const items: Record<string, TimelineItemType> = {
|
|||
type: 'timerNotification',
|
||||
data: {
|
||||
disabled: false,
|
||||
expireTimer: moment.duration(2, 'hours').asSeconds(),
|
||||
expireTimer: DurationInSeconds.fromHours(2),
|
||||
title: "It's Me",
|
||||
type: 'fromMe',
|
||||
},
|
||||
|
@ -145,7 +145,7 @@ const items: Record<string, TimelineItemType> = {
|
|||
type: 'timerNotification',
|
||||
data: {
|
||||
disabled: false,
|
||||
expireTimer: moment.duration(2, 'hours').asSeconds(),
|
||||
expireTimer: DurationInSeconds.fromHours(2),
|
||||
title: '(202) 555-0000',
|
||||
type: 'fromOther',
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import { action } from '@storybook/addon-actions';
|
|||
|
||||
import { EmojiPicker } from '../emoji/EmojiPicker';
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import type { PropsType as TimelineItemProps } from './TimelineItem';
|
||||
import { TimelineItem } from './TimelineItem';
|
||||
|
@ -43,7 +44,10 @@ const renderContact = (conversationId: string) => (
|
|||
);
|
||||
|
||||
const renderUniversalTimerNotification = () => (
|
||||
<UniversalTimerNotification i18n={i18n} expireTimer={3600} />
|
||||
<UniversalTimerNotification
|
||||
i18n={i18n}
|
||||
expireTimer={DurationInSeconds.HOUR}
|
||||
/>
|
||||
);
|
||||
|
||||
const getDefaultProps = () => ({
|
||||
|
@ -138,7 +142,7 @@ export const Notification = (): JSX.Element => {
|
|||
type: 'timerNotification',
|
||||
data: {
|
||||
phoneNumber: '(202) 555-0000',
|
||||
expireTimer: 60,
|
||||
expireTimer: DurationInSeconds.MINUTE,
|
||||
...getDefaultConversation(),
|
||||
type: 'fromOther',
|
||||
},
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import * as moment from 'moment';
|
||||
import { boolean, number, select, text } from '@storybook/addon-knobs';
|
||||
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import type { Props } from './TimerNotification';
|
||||
import { TimerNotification } from './TimerNotification';
|
||||
|
@ -34,16 +34,19 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
}
|
||||
: {
|
||||
disabled: false,
|
||||
expireTimer: number(
|
||||
'expireTimer',
|
||||
('expireTimer' in overrideProps ? overrideProps.expireTimer : 0) || 0
|
||||
expireTimer: DurationInSeconds.fromMillis(
|
||||
number(
|
||||
'expireTimer',
|
||||
('expireTimer' in overrideProps ? overrideProps.expireTimer : 0) ||
|
||||
0
|
||||
)
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
export const SetByOther = (): JSX.Element => {
|
||||
const props = createProps({
|
||||
expireTimer: moment.duration(1, 'hour').asSeconds(),
|
||||
expireTimer: DurationInSeconds.fromHours(1),
|
||||
type: 'fromOther',
|
||||
title: 'Mr. Fire',
|
||||
});
|
||||
|
@ -61,7 +64,7 @@ export const SetByOtherWithALongName = (): JSX.Element => {
|
|||
const longName = '🦴🧩📴'.repeat(50);
|
||||
|
||||
const props = createProps({
|
||||
expireTimer: moment.duration(1, 'hour').asSeconds(),
|
||||
expireTimer: DurationInSeconds.fromHours(1),
|
||||
type: 'fromOther',
|
||||
title: longName,
|
||||
});
|
||||
|
@ -81,7 +84,7 @@ SetByOtherWithALongName.story = {
|
|||
|
||||
export const SetByYou = (): JSX.Element => {
|
||||
const props = createProps({
|
||||
expireTimer: moment.duration(1, 'hour').asSeconds(),
|
||||
expireTimer: DurationInSeconds.fromHours(1),
|
||||
type: 'fromMe',
|
||||
title: 'Mr. Fire',
|
||||
});
|
||||
|
@ -97,7 +100,7 @@ export const SetByYou = (): JSX.Element => {
|
|||
|
||||
export const SetBySync = (): JSX.Element => {
|
||||
const props = createProps({
|
||||
expireTimer: moment.duration(1, 'hour').asSeconds(),
|
||||
expireTimer: DurationInSeconds.fromHours(1),
|
||||
type: 'fromSync',
|
||||
title: 'Mr. Fire',
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { SystemMessage } from './SystemMessage';
|
|||
import { Intl } from '../Intl';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import * as expirationTimer from '../../util/expirationTimer';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
export type TimerNotificationType =
|
||||
|
@ -27,7 +28,7 @@ export type PropsData = {
|
|||
| { disabled: true }
|
||||
| {
|
||||
disabled: false;
|
||||
expireTimer: number;
|
||||
expireTimer: DurationInSeconds;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -18,34 +18,34 @@ const i18n = setupI18n('en', enMessages);
|
|||
export const Seconds = (): JSX.Element => (
|
||||
<UniversalTimerNotification
|
||||
i18n={i18n}
|
||||
expireTimer={EXPIRE_TIMERS[0].value / 1000}
|
||||
expireTimer={EXPIRE_TIMERS[0].value}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Minutes = (): JSX.Element => (
|
||||
<UniversalTimerNotification
|
||||
i18n={i18n}
|
||||
expireTimer={EXPIRE_TIMERS[1].value / 1000}
|
||||
expireTimer={EXPIRE_TIMERS[1].value}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Hours = (): JSX.Element => (
|
||||
<UniversalTimerNotification
|
||||
i18n={i18n}
|
||||
expireTimer={EXPIRE_TIMERS[2].value / 1000}
|
||||
expireTimer={EXPIRE_TIMERS[2].value}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Days = (): JSX.Element => (
|
||||
<UniversalTimerNotification
|
||||
i18n={i18n}
|
||||
expireTimer={EXPIRE_TIMERS[3].value / 1000}
|
||||
expireTimer={EXPIRE_TIMERS[3].value}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Weeks = (): JSX.Element => (
|
||||
<UniversalTimerNotification
|
||||
i18n={i18n}
|
||||
expireTimer={EXPIRE_TIMERS[4].value / 1000}
|
||||
expireTimer={EXPIRE_TIMERS[4].value}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -6,10 +6,11 @@ import React from 'react';
|
|||
import { SystemMessage } from './SystemMessage';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import * as expirationTimer from '../../util/expirationTimer';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
|
||||
export type Props = {
|
||||
i18n: LocalizerType;
|
||||
expireTimer: number;
|
||||
expireTimer: DurationInSeconds;
|
||||
};
|
||||
|
||||
export const UniversalTimerNotification: React.FC<Props> = props => {
|
||||
|
|
|
@ -16,6 +16,7 @@ import type { ConversationType } from '../../../state/ducks/conversations';
|
|||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { makeFakeLookupConversationWithoutUuid } from '../../../test-both/helpers/fakeLookupConversationWithoutUuid';
|
||||
import { ThemeType } from '../../../types/Util';
|
||||
import { DurationInSeconds } from '../../../util/durations';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -35,7 +36,10 @@ const conversation: ConversationType = getDefaultConversation({
|
|||
|
||||
const allCandidateContacts = times(10, () => getDefaultConversation());
|
||||
|
||||
const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
|
||||
const createProps = (
|
||||
hasGroupLink = false,
|
||||
expireTimer?: DurationInSeconds
|
||||
): Props => ({
|
||||
addMembers: async () => {
|
||||
action('addMembers');
|
||||
},
|
||||
|
@ -194,7 +198,7 @@ export const GroupEditable = (): JSX.Element => {
|
|||
};
|
||||
|
||||
export const GroupEditableWithCustomDisappearingTimeout = (): JSX.Element => {
|
||||
const props = createProps(false, 3 * 24 * 60 * 60);
|
||||
const props = createProps(false, DurationInSeconds.fromDays(3));
|
||||
|
||||
return <ConversationDetails {...props} canEditGroupInfo />;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import type { LocalizerType, ThemeType } from '../../../types/Util';
|
|||
import type { MediaItemType } from '../../../types/MediaItem';
|
||||
import type { BadgeType } from '../../../badges/types';
|
||||
import { missingCaseError } from '../../../util/missingCaseError';
|
||||
import { DurationInSeconds } from '../../../util/durations';
|
||||
|
||||
import { DisappearingTimerSelect } from '../../DisappearingTimerSelect';
|
||||
|
||||
|
@ -79,7 +80,7 @@ export type StateProps = {
|
|||
memberships: Array<GroupV2Membership>;
|
||||
pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||
pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||
setDisappearingMessages: (seconds: number) => void;
|
||||
setDisappearingMessages: (seconds: DurationInSeconds) => void;
|
||||
showAllMedia: () => void;
|
||||
showChatColorEditor: () => void;
|
||||
showGroupLinkManagement: () => void;
|
||||
|
@ -410,7 +411,7 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
right={
|
||||
<DisappearingTimerSelect
|
||||
i18n={i18n}
|
||||
value={conversation.expireTimer || 0}
|
||||
value={conversation.expireTimer || DurationInSeconds.ZERO}
|
||||
onChange={setDisappearingMessages}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
ReplaceAvatarActionType,
|
||||
SaveAvatarToDiskActionType,
|
||||
} from '../../types/Avatar';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import type { ShowConversationType } from '../../state/ducks/conversations';
|
||||
|
||||
export enum FindDirection {
|
||||
|
@ -73,7 +74,7 @@ export abstract class LeftPaneHelper<T> {
|
|||
i18n: LocalizerType;
|
||||
removeSelectedContact: (_: string) => unknown;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
setComposeGroupExpireTimer: (_: DurationInSeconds) => void;
|
||||
setComposeGroupName: (_: string) => unknown;
|
||||
toggleComposeEditingAvatar: () => unknown;
|
||||
}>
|
||||
|
|
|
@ -10,6 +10,7 @@ import { RowType } from '../ConversationList';
|
|||
import type { ContactListItemConversationType } from '../conversationList/ContactListItem';
|
||||
import { DisappearingTimerSelect } from '../DisappearingTimerSelect';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import { Alert } from '../Alert';
|
||||
import { AvatarEditor } from '../AvatarEditor';
|
||||
import { AvatarPreview } from '../AvatarPreview';
|
||||
|
@ -28,7 +29,7 @@ import { AvatarColors } from '../../types/Colors';
|
|||
export type LeftPaneSetGroupMetadataPropsType = {
|
||||
groupAvatar: undefined | Uint8Array;
|
||||
groupName: string;
|
||||
groupExpireTimer: number;
|
||||
groupExpireTimer: DurationInSeconds;
|
||||
hasError: boolean;
|
||||
isCreating: boolean;
|
||||
isEditingAvatar: boolean;
|
||||
|
@ -41,7 +42,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
|
||||
private readonly groupName: string;
|
||||
|
||||
private readonly groupExpireTimer: number;
|
||||
private readonly groupExpireTimer: DurationInSeconds;
|
||||
|
||||
private readonly hasError: boolean;
|
||||
|
||||
|
@ -128,7 +129,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
createGroup: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
setComposeGroupExpireTimer: (_: DurationInSeconds) => void;
|
||||
setComposeGroupName: (_: string) => unknown;
|
||||
toggleComposeEditingAvatar: () => unknown;
|
||||
}>): ReactChild {
|
||||
|
|
24
ts/groups.ts
24
ts/groups.ts
|
@ -23,7 +23,7 @@ import dataInterface from './sql/Client';
|
|||
import { toWebSafeBase64, fromWebSafeBase64 } from './util/webSafeBase64';
|
||||
import { assertDev, strictAssert } from './util/assert';
|
||||
import { isMoreRecentThan } from './util/timestamp';
|
||||
import * as durations from './util/durations';
|
||||
import { MINUTE, DurationInSeconds } from './util/durations';
|
||||
import { normalizeUuid } from './util/normalizeUuid';
|
||||
import { dropNull } from './util/dropNull';
|
||||
import type {
|
||||
|
@ -854,7 +854,7 @@ export function buildDisappearingMessagesTimerChange({
|
|||
expireTimer,
|
||||
group,
|
||||
}: {
|
||||
expireTimer: number;
|
||||
expireTimer: DurationInSeconds;
|
||||
group: ConversationAttributesType;
|
||||
}): Proto.GroupChange.Actions {
|
||||
const actions = new Proto.GroupChange.Actions();
|
||||
|
@ -1458,7 +1458,7 @@ export async function modifyGroupV2({
|
|||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const timeoutTime = startTime + durations.MINUTE;
|
||||
const timeoutTime = startTime + MINUTE;
|
||||
|
||||
const MAX_ATTEMPTS = 5;
|
||||
|
||||
|
@ -1725,7 +1725,7 @@ export async function createGroupV2(
|
|||
options: Readonly<{
|
||||
name: string;
|
||||
avatar: undefined | Uint8Array;
|
||||
expireTimer: undefined | number;
|
||||
expireTimer: undefined | DurationInSeconds;
|
||||
conversationIds: Array<string>;
|
||||
avatars?: Array<AvatarDataType>;
|
||||
refreshedCredentials?: boolean;
|
||||
|
@ -2904,7 +2904,7 @@ type MaybeUpdatePropsType = Readonly<{
|
|||
groupChange?: WrappedGroupChangeType;
|
||||
}>;
|
||||
|
||||
const FIVE_MINUTES = 5 * durations.MINUTE;
|
||||
const FIVE_MINUTES = 5 * MINUTE;
|
||||
|
||||
export async function waitThenMaybeUpdateGroup(
|
||||
options: MaybeUpdatePropsType,
|
||||
|
@ -4625,7 +4625,7 @@ function extractDiffs({
|
|||
Boolean(current.expireTimer) &&
|
||||
old.expireTimer !== current.expireTimer)
|
||||
) {
|
||||
const expireTimer = current.expireTimer || 0;
|
||||
const expireTimer = current.expireTimer || DurationInSeconds.ZERO;
|
||||
log.info(
|
||||
`extractDiffs/${logId}: generating change notifcation for new ${expireTimer} timer`
|
||||
);
|
||||
|
@ -4977,9 +4977,9 @@ async function applyGroupChange({
|
|||
disappearingMessagesTimer &&
|
||||
disappearingMessagesTimer.content === 'disappearingMessagesDuration'
|
||||
) {
|
||||
result.expireTimer = dropNull(
|
||||
disappearingMessagesTimer.disappearingMessagesDuration
|
||||
);
|
||||
const duration = disappearingMessagesTimer.disappearingMessagesDuration;
|
||||
result.expireTimer =
|
||||
duration == null ? undefined : DurationInSeconds.fromSeconds(duration);
|
||||
} else {
|
||||
log.warn(
|
||||
`applyGroupChange/${logId}: Clearing group expireTimer due to missing data.`
|
||||
|
@ -5335,9 +5335,9 @@ async function applyGroupState({
|
|||
disappearingMessagesTimer &&
|
||||
disappearingMessagesTimer.content === 'disappearingMessagesDuration'
|
||||
) {
|
||||
result.expireTimer = dropNull(
|
||||
disappearingMessagesTimer.disappearingMessagesDuration
|
||||
);
|
||||
const duration = disappearingMessagesTimer.disappearingMessagesDuration;
|
||||
result.expireTimer =
|
||||
duration == null ? undefined : DurationInSeconds.fromSeconds(duration);
|
||||
} else {
|
||||
result.expireTimer = undefined;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import type {
|
|||
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
|
||||
export async function sendDirectExpirationTimerUpdate(
|
||||
conversation: ConversationModel,
|
||||
|
@ -77,7 +78,11 @@ export async function sendDirectExpirationTimerUpdate(
|
|||
const sendType = 'expirationTimerUpdate';
|
||||
const flags = Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
|
||||
const proto = await messaging.getContentMessage({
|
||||
expireTimer,
|
||||
// `expireTimer` is already in seconds
|
||||
expireTimer:
|
||||
expireTimer === undefined
|
||||
? undefined
|
||||
: DurationInSeconds.fromSeconds(expireTimer),
|
||||
flags,
|
||||
profileKey,
|
||||
recipients: conversation.getRecipients(),
|
||||
|
|
|
@ -36,6 +36,7 @@ import { ourProfileKeyService } from '../../services/ourProfileKey';
|
|||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||
import { isConversationAccepted } from '../../util/isConversationAccepted';
|
||||
import { sendToGroup } from '../../util/sendToGroup';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
export async function sendNormalMessage(
|
||||
|
@ -466,7 +467,7 @@ async function getMessageSendData({
|
|||
body: undefined | string;
|
||||
contact?: Array<ContactWithHydratedAvatar>;
|
||||
deletedForEveryoneTimestamp: undefined | number;
|
||||
expireTimer: undefined | number;
|
||||
expireTimer: undefined | DurationInSeconds;
|
||||
mentions: undefined | BodyRangesType;
|
||||
messageTimestamp: number;
|
||||
preview: Array<LinkPreviewType>;
|
||||
|
|
7
ts/model-types.d.ts
vendored
7
ts/model-types.d.ts
vendored
|
@ -32,6 +32,7 @@ import type { LinkPreviewType } from './types/message/LinkPreviews';
|
|||
import type { StickerType } from './types/Stickers';
|
||||
import type { StorySendMode } from './types/Stories';
|
||||
import type { MIMEType } from './types/MIME';
|
||||
import type { DurationInSeconds } from './util/durations';
|
||||
|
||||
import AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
||||
import MemberRoleEnum = Proto.Member.Role;
|
||||
|
@ -130,7 +131,7 @@ export type MessageAttributesType = {
|
|||
deletedForEveryoneTimestamp?: number;
|
||||
errors?: Array<CustomError>;
|
||||
expirationStartTimestamp?: number | null;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
groupMigration?: GroupMigrationType;
|
||||
group_update?: GroupV1Update;
|
||||
hasAttachments?: boolean | 0 | 1;
|
||||
|
@ -198,7 +199,7 @@ export type MessageAttributesType = {
|
|||
};
|
||||
|
||||
expirationTimerUpdate?: {
|
||||
expireTimer: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
fromSync?: unknown;
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
|
@ -381,7 +382,7 @@ export type ConversationAttributesType = {
|
|||
} | null;
|
||||
avatars?: Array<AvatarDataType>;
|
||||
description?: string;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
membersV2?: Array<GroupV2MemberType>;
|
||||
pendingMembersV2?: Array<GroupV2PendingMemberType>;
|
||||
pendingAdminApprovalV2?: Array<GroupV2PendingAdminApprovalType>;
|
||||
|
|
|
@ -80,7 +80,7 @@ import { updateConversationsWithUuidLookup } from '../updateConversationsWithUui
|
|||
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||
import { SendStatus } from '../messages/MessageSendState';
|
||||
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
||||
import * as durations from '../util/durations';
|
||||
import { MINUTE, DurationInSeconds } from '../util/durations';
|
||||
import {
|
||||
concat,
|
||||
filter,
|
||||
|
@ -155,7 +155,7 @@ const {
|
|||
getNewerMessagesByConversation,
|
||||
} = window.Signal.Data;
|
||||
|
||||
const FIVE_MINUTES = durations.MINUTE * 5;
|
||||
const FIVE_MINUTES = MINUTE * 5;
|
||||
|
||||
const JOB_REPORTING_THRESHOLD_MS = 25;
|
||||
const SEND_REPORTING_THRESHOLD_MS = 25;
|
||||
|
@ -471,7 +471,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
async updateExpirationTimerInGroupV2(
|
||||
seconds?: number
|
||||
seconds?: DurationInSeconds
|
||||
): Promise<Proto.GroupChange.Actions | undefined> {
|
||||
const idLog = this.idForLogging();
|
||||
const current = this.get('expireTimer');
|
||||
|
@ -485,7 +485,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
return window.Signal.Groups.buildDisappearingMessagesTimerChange({
|
||||
expireTimer: seconds || 0,
|
||||
expireTimer: seconds || DurationInSeconds.ZERO,
|
||||
group: this.attributes,
|
||||
});
|
||||
}
|
||||
|
@ -1382,7 +1382,7 @@ export class ConversationModel extends window.Backbone
|
|||
if (!this.newMessageQueue) {
|
||||
this.newMessageQueue = new PQueue({
|
||||
concurrency: 1,
|
||||
timeout: durations.MINUTE * 30,
|
||||
timeout: MINUTE * 30,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3944,7 +3944,7 @@ export class ConversationModel extends window.Backbone
|
|||
);
|
||||
|
||||
let expirationStartTimestamp: number | undefined;
|
||||
let expireTimer: number | undefined;
|
||||
let expireTimer: DurationInSeconds | undefined;
|
||||
|
||||
// If it's a group story reply then let's match the expiration timers
|
||||
// with the parent story's expiration.
|
||||
|
@ -3952,7 +3952,7 @@ export class ConversationModel extends window.Backbone
|
|||
const parentStory = await getMessageById(storyId);
|
||||
expirationStartTimestamp =
|
||||
parentStory?.expirationStartTimestamp || Date.now();
|
||||
expireTimer = parentStory?.expireTimer || durations.DAY;
|
||||
expireTimer = parentStory?.expireTimer || DurationInSeconds.DAY;
|
||||
} else {
|
||||
await this.maybeApplyUniversalTimer();
|
||||
expireTimer = this.get('expireTimer');
|
||||
|
@ -4431,7 +4431,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
async updateExpirationTimer(
|
||||
providedExpireTimer: number | undefined,
|
||||
providedExpireTimer: DurationInSeconds | undefined,
|
||||
{
|
||||
reason,
|
||||
receivedAt,
|
||||
|
@ -4479,7 +4479,7 @@ export class ConversationModel extends window.Backbone
|
|||
);
|
||||
}
|
||||
|
||||
let expireTimer: number | undefined = providedExpireTimer;
|
||||
let expireTimer: DurationInSeconds | undefined = providedExpireTimer;
|
||||
let source = providedSource;
|
||||
if (this.get('left')) {
|
||||
return false;
|
||||
|
|
|
@ -172,7 +172,7 @@ import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
|
|||
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import { downloadAttachment } from '../util/downloadAttachment';
|
||||
import type { StickerWithHydratedData } from '../types/Stickers';
|
||||
import { SECOND } from '../util/durations';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import dataInterface from '../sql/Client';
|
||||
|
||||
function isSameUuid(
|
||||
|
@ -566,7 +566,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
const expireTimer = this.get('expireTimer');
|
||||
const expirationStartTimestamp = this.get('expirationStartTimestamp');
|
||||
const expirationLength = isNumber(expireTimer)
|
||||
? expireTimer * SECOND
|
||||
? DurationInSeconds.toMillis(expireTimer)
|
||||
: undefined;
|
||||
const expirationTimestamp = expirationTimer.calculateExpirationTimestamp({
|
||||
expireTimer,
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
} from '../util/universalExpireTimer';
|
||||
import { ourProfileKeyService } from './ourProfileKey';
|
||||
import { isGroupV1, isGroupV2 } from '../util/whatTypeOfConversation';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { isValidUuid, UUID, UUIDKind } from '../types/UUID';
|
||||
import * as preferredReactionEmoji from '../reactions/preferredReactionEmoji';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
@ -1178,7 +1179,9 @@ export async function mergeAccountRecord(
|
|||
window.storage.put('preferredReactionEmoji', rawPreferredReactionEmoji);
|
||||
}
|
||||
|
||||
setUniversalExpireTimer(universalExpireTimer || 0);
|
||||
setUniversalExpireTimer(
|
||||
DurationInSeconds.fromSeconds(universalExpireTimer || 0)
|
||||
);
|
||||
|
||||
const PHONE_NUMBER_SHARING_MODE_ENUM =
|
||||
Proto.AccountRecord.PhoneNumberSharingMode;
|
||||
|
|
|
@ -15,6 +15,7 @@ import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
|||
import { isNotNil } from '../util/isNotNil';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { dropNull } from '../util/dropNull';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { isGroup } from '../util/whatTypeOfConversation';
|
||||
import { SIGNAL_ACI } from '../types/SignalConversation';
|
||||
|
||||
|
@ -142,7 +143,7 @@ export function getStoriesForRedux(): Array<StoryDataType> {
|
|||
async function repairUnexpiredStories(): Promise<void> {
|
||||
strictAssert(storyData, 'Could not load stories');
|
||||
|
||||
const DAY_AS_SECONDS = durations.DAY / 1000;
|
||||
const DAY_AS_SECONDS = DurationInSeconds.fromDays(1);
|
||||
|
||||
const storiesWithExpiry = storyData
|
||||
.filter(
|
||||
|
@ -155,9 +156,11 @@ async function repairUnexpiredStories(): Promise<void> {
|
|||
.map(story => ({
|
||||
...story,
|
||||
expirationStartTimestamp: Math.min(story.timestamp, Date.now()),
|
||||
expireTimer: Math.min(
|
||||
Math.floor((story.timestamp + durations.DAY - Date.now()) / 1000),
|
||||
DAY_AS_SECONDS
|
||||
expireTimer: DurationInSeconds.fromMillis(
|
||||
Math.min(
|
||||
Math.floor(story.timestamp + durations.DAY - Date.now()),
|
||||
durations.DAY
|
||||
)
|
||||
),
|
||||
}));
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as log from '../../logging/log';
|
|||
import { calling } from '../../services/calling';
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
import { assertDev, strictAssert } from '../../util/assert';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import * as universalExpireTimer from '../../util/universalExpireTimer';
|
||||
import type {
|
||||
ShowSendAnywayDialogActionType,
|
||||
|
@ -178,7 +179,7 @@ export type ConversationType = {
|
|||
accessControlMembers?: number;
|
||||
announcementsOnly?: boolean;
|
||||
announcementsOnlyReady?: boolean;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
memberships?: Array<{
|
||||
uuid: UUIDStringType;
|
||||
isAdmin: boolean;
|
||||
|
@ -293,7 +294,7 @@ export type PreJoinConversationType = {
|
|||
type ComposerGroupCreationState = {
|
||||
groupAvatar: undefined | Uint8Array;
|
||||
groupName: string;
|
||||
groupExpireTimer: number;
|
||||
groupExpireTimer: DurationInSeconds;
|
||||
maximumGroupSizeModalState: OneTimeModalState;
|
||||
recommendedGroupSizeModalState: OneTimeModalState;
|
||||
selectedConversationIds: Array<string>;
|
||||
|
@ -712,7 +713,7 @@ type SetComposeGroupNameActionType = {
|
|||
};
|
||||
type SetComposeGroupExpireTimerActionType = {
|
||||
type: 'SET_COMPOSE_GROUP_EXPIRE_TIMER';
|
||||
payload: { groupExpireTimer: number };
|
||||
payload: { groupExpireTimer: DurationInSeconds };
|
||||
};
|
||||
type SetComposeSearchTermActionType = {
|
||||
type: 'SET_COMPOSE_SEARCH_TERM';
|
||||
|
@ -1907,7 +1908,7 @@ function setComposeGroupName(groupName: string): SetComposeGroupNameActionType {
|
|||
}
|
||||
|
||||
function setComposeGroupExpireTimer(
|
||||
groupExpireTimer: number
|
||||
groupExpireTimer: DurationInSeconds
|
||||
): SetComposeGroupExpireTimerActionType {
|
||||
return {
|
||||
type: 'SET_COMPOSE_GROUP_EXPIRE_TIMER',
|
||||
|
@ -3509,7 +3510,7 @@ export function reducer(
|
|||
let maximumGroupSizeModalState: OneTimeModalState;
|
||||
let groupName: string;
|
||||
let groupAvatar: undefined | Uint8Array;
|
||||
let groupExpireTimer: number;
|
||||
let groupExpireTimer: DurationInSeconds;
|
||||
let userAvatarData = getDefaultAvatars(true);
|
||||
|
||||
switch (state.composer?.step) {
|
||||
|
|
|
@ -36,6 +36,7 @@ import { markViewed } from '../../services/MessageUpdater';
|
|||
import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
||||
import { replaceIndex } from '../../util/replaceIndex';
|
||||
import { showToast } from '../../util/showToast';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import { hasFailed, isDownloaded, isDownloading } from '../../types/Attachment';
|
||||
import {
|
||||
getConversationSelector,
|
||||
|
@ -79,7 +80,7 @@ export type StoryDataType = {
|
|||
| 'type'
|
||||
> & {
|
||||
// don't want the fields to be optional as in MessageAttributesType
|
||||
expireTimer: number | undefined;
|
||||
expireTimer: DurationInSeconds | undefined;
|
||||
expirationStartTimestamp: number | undefined;
|
||||
};
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import type { UUIDStringType } from '../../types/UUID';
|
|||
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
||||
import { isSignalConnection } from '../../util/getSignalConnections';
|
||||
import { sortByTitle } from '../../util/sortByTitle';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import {
|
||||
isDirectConversation,
|
||||
isGroupV1,
|
||||
|
@ -634,7 +635,7 @@ const getGroupCreationComposerState = createSelector(
|
|||
): {
|
||||
groupName: string;
|
||||
groupAvatar: undefined | Uint8Array;
|
||||
groupExpireTimer: number;
|
||||
groupExpireTimer: DurationInSeconds;
|
||||
selectedConversationIds: Array<string>;
|
||||
} => {
|
||||
switch (composerState?.step) {
|
||||
|
@ -649,7 +650,7 @@ const getGroupCreationComposerState = createSelector(
|
|||
return {
|
||||
groupName: '',
|
||||
groupAvatar: undefined,
|
||||
groupExpireTimer: 0,
|
||||
groupExpireTimer: DurationInSeconds.ZERO,
|
||||
selectedConversationIds: [],
|
||||
};
|
||||
}
|
||||
|
@ -668,7 +669,7 @@ export const getComposeGroupName = createSelector(
|
|||
|
||||
export const getComposeGroupExpireTimer = createSelector(
|
||||
getGroupCreationComposerState,
|
||||
(composerState): number => composerState.groupExpireTimer
|
||||
(composerState): DurationInSeconds => composerState.groupExpireTimer
|
||||
);
|
||||
|
||||
export const getComposeSelectedContacts = createSelector(
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { UUIDStringType } from '../../types/UUID';
|
|||
import { DEFAULT_CONVERSATION_COLOR } from '../../types/Colors';
|
||||
import { getPreferredReactionEmoji as getPreferredReactionEmojiFromStoredValue } from '../../reactions/preferredReactionEmoji';
|
||||
import { isBeta } from '../../util/version';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import { getUserNumber, getUserACI } from './user';
|
||||
|
||||
const DEFAULT_PREFERRED_LEFT_PANE_WIDTH = 320;
|
||||
|
@ -42,7 +43,8 @@ export const getPinnedConversationIds = createSelector(
|
|||
|
||||
export const getUniversalExpireTimer = createSelector(
|
||||
getItems,
|
||||
(state: ItemsStateType): number => state[UNIVERSAL_EXPIRE_TIMER_ITEM] || 0
|
||||
(state: ItemsStateType): DurationInSeconds =>
|
||||
DurationInSeconds.fromSeconds(state[UNIVERSAL_EXPIRE_TIMER_ITEM] || 0)
|
||||
);
|
||||
|
||||
const isRemoteConfigFlagEnabled = (
|
||||
|
|
|
@ -93,7 +93,7 @@ import {
|
|||
} from '../../messages/MessageSendState';
|
||||
import * as log from '../../logging/log';
|
||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||
import { DAY, HOUR, SECOND } from '../../util/durations';
|
||||
import { DAY, HOUR, DurationInSeconds } from '../../util/durations';
|
||||
import { getStoryReplyText } from '../../util/getStoryReplyText';
|
||||
import { isIncoming, isOutgoing, isStory } from '../../messages/helpers';
|
||||
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
||||
|
@ -628,7 +628,9 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)(
|
|||
}: GetPropsForMessageOptions
|
||||
): ShallowPropsType => {
|
||||
const { expireTimer, expirationStartTimestamp, conversationId } = message;
|
||||
const expirationLength = expireTimer ? expireTimer * SECOND : undefined;
|
||||
const expirationLength = expireTimer
|
||||
? DurationInSeconds.toMillis(expireTimer)
|
||||
: undefined;
|
||||
|
||||
const conversation = getConversation(message, conversationSelector);
|
||||
const isGroup = conversation.type === 'group';
|
||||
|
@ -1107,10 +1109,26 @@ function getPropsForTimerNotification(
|
|||
const sourceId = sourceUuid || source;
|
||||
const formattedContact = conversationSelector(sourceId);
|
||||
|
||||
// Pacify typescript
|
||||
type MaybeExpireTimerType =
|
||||
| { disabled: true }
|
||||
| {
|
||||
disabled: false;
|
||||
expireTimer: DurationInSeconds;
|
||||
};
|
||||
|
||||
const maybeExpireTimer: MaybeExpireTimerType = disabled
|
||||
? {
|
||||
disabled: true,
|
||||
}
|
||||
: {
|
||||
disabled: false,
|
||||
expireTimer,
|
||||
};
|
||||
|
||||
const basicProps = {
|
||||
...formattedContact,
|
||||
disabled,
|
||||
expireTimer,
|
||||
...maybeExpireTimer,
|
||||
type: 'fromOther' as const,
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
getPreferredBadgeSelector,
|
||||
} from '../selectors/badges';
|
||||
import { assertDev } from '../../util/assert';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||
import type { SmartChooseGroupMembersModalPropsType } from './ChooseGroupMembersModal';
|
||||
|
@ -39,7 +40,7 @@ export type SmartConversationDetailsProps = {
|
|||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
||||
conversationId: string;
|
||||
loadRecentMediaItems: (limit: number) => void;
|
||||
setDisappearingMessages: (seconds: number) => void;
|
||||
setDisappearingMessages: (seconds: DurationInSeconds) => void;
|
||||
showAllMedia: () => void;
|
||||
showChatColorEditor: () => void;
|
||||
showGroupLinkManagement: () => void;
|
||||
|
|
|
@ -25,6 +25,7 @@ import { mapDispatchToProps } from '../actions';
|
|||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||
import type { DurationInSeconds } from '../../util/durations';
|
||||
|
||||
export type OwnProps = {
|
||||
id: string;
|
||||
|
@ -37,7 +38,7 @@ export type OwnProps = {
|
|||
onOutgoingAudioCallInConversation: () => void;
|
||||
onOutgoingVideoCallInConversation: () => void;
|
||||
onSearchInConversation: () => void;
|
||||
onSetDisappearingMessages: (seconds: number) => void;
|
||||
onSetDisappearingMessages: (seconds: DurationInSeconds) => void;
|
||||
onSetMuteNotifications: (seconds: number) => void;
|
||||
onSetPin: (value: boolean) => void;
|
||||
onShowAllMedia: () => void;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { ComposerStep } from '../../state/ducks/conversationsEnums';
|
||||
import { OneTimeModalState } from '../../groups/toggleSelectedContactForGroupAddition';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
|
||||
export const defaultStartDirectConversationComposerState = {
|
||||
step: ComposerStep.StartDirectConversation as const,
|
||||
|
@ -16,7 +17,7 @@ export const defaultChooseGroupMembersComposerState = {
|
|||
uuidFetchState: {},
|
||||
groupAvatar: undefined,
|
||||
groupName: '',
|
||||
groupExpireTimer: 0,
|
||||
groupExpireTimer: DurationInSeconds.ZERO,
|
||||
maximumGroupSizeModalState: OneTimeModalState.NeverShown,
|
||||
recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
|
||||
selectedConversationIds: [],
|
||||
|
@ -28,7 +29,7 @@ export const defaultSetGroupMetadataComposerState = {
|
|||
isEditingAvatar: false,
|
||||
groupAvatar: undefined,
|
||||
groupName: '',
|
||||
groupExpireTimer: 0,
|
||||
groupExpireTimer: DurationInSeconds.ZERO,
|
||||
maximumGroupSizeModalState: OneTimeModalState.NeverShown,
|
||||
recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
|
||||
selectedConversationIds: [],
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { assert } from 'chai';
|
||||
import * as moment from 'moment';
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import esMessages from '../../../_locales/es/messages.json';
|
||||
import nbMessages from '../../../_locales/nb/messages.json';
|
||||
|
@ -24,7 +25,7 @@ describe('expiration timer utilities', () => {
|
|||
});
|
||||
|
||||
it('includes 1 hour as seconds', () => {
|
||||
const oneHour = moment.duration(1, 'hour').asSeconds();
|
||||
const oneHour = DurationInSeconds.fromHours(1);
|
||||
assert.include(DEFAULT_DURATIONS_IN_SECONDS, oneHour);
|
||||
});
|
||||
});
|
||||
|
@ -37,7 +38,7 @@ describe('expiration timer utilities', () => {
|
|||
});
|
||||
|
||||
it('handles no duration', () => {
|
||||
assert.strictEqual(format(i18n, 0), 'off');
|
||||
assert.strictEqual(format(i18n, DurationInSeconds.ZERO), 'off');
|
||||
});
|
||||
|
||||
it('formats durations', () => {
|
||||
|
@ -59,22 +60,31 @@ describe('expiration timer utilities', () => {
|
|||
[moment.duration(3, 'w').asSeconds(), '3 weeks'],
|
||||
[moment.duration(52, 'w').asSeconds(), '52 weeks'],
|
||||
]).forEach((expected, input) => {
|
||||
assert.strictEqual(format(i18n, input), expected);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(input)),
|
||||
expected
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('formats other languages successfully', () => {
|
||||
const esI18n = setupI18n('es', esMessages);
|
||||
assert.strictEqual(format(esI18n, 120), '2 minutos');
|
||||
assert.strictEqual(
|
||||
format(esI18n, DurationInSeconds.fromSeconds(120)),
|
||||
'2 minutos'
|
||||
);
|
||||
|
||||
const zhCnI18n = setupI18n('zh-CN', zhCnMessages);
|
||||
assert.strictEqual(format(zhCnI18n, 60), '1 分钟');
|
||||
assert.strictEqual(
|
||||
format(zhCnI18n, DurationInSeconds.fromSeconds(60)),
|
||||
'1 分钟'
|
||||
);
|
||||
|
||||
// The underlying library supports the "pt" locale, not the "pt_BR" locale. That's
|
||||
// what we're testing here.
|
||||
const ptBrI18n = setupI18n('pt_BR', ptBrMessages);
|
||||
assert.strictEqual(
|
||||
format(ptBrI18n, moment.duration(5, 'days').asSeconds()),
|
||||
format(ptBrI18n, DurationInSeconds.fromDays(5)),
|
||||
'5 dias'
|
||||
);
|
||||
|
||||
|
@ -83,7 +93,7 @@ describe('expiration timer utilities', () => {
|
|||
[setupI18n('nb', nbMessages), setupI18n('nn', nlMessages)].forEach(
|
||||
norwegianI18n => {
|
||||
assert.strictEqual(
|
||||
format(norwegianI18n, moment.duration(6, 'hours').asSeconds()),
|
||||
format(norwegianI18n, DurationInSeconds.fromHours(6)),
|
||||
'6 timer'
|
||||
);
|
||||
}
|
||||
|
@ -92,41 +102,76 @@ describe('expiration timer utilities', () => {
|
|||
|
||||
it('falls back to English if the locale is not supported', () => {
|
||||
const badI18n = setupI18n('bogus', {});
|
||||
assert.strictEqual(format(badI18n, 120), '2 minutes');
|
||||
assert.strictEqual(
|
||||
format(badI18n, DurationInSeconds.fromSeconds(120)),
|
||||
'2 minutes'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles a "mix" of units gracefully', () => {
|
||||
// We don't expect there to be a "mix" of units, but we shouldn't choke if a bad
|
||||
// client gives us an unexpected timestamp.
|
||||
const mix = moment
|
||||
.duration(6, 'days')
|
||||
.add(moment.duration(2, 'hours'))
|
||||
.asSeconds();
|
||||
const mix = DurationInSeconds.fromSeconds(
|
||||
moment.duration(6, 'days').add(moment.duration(2, 'hours')).asSeconds()
|
||||
);
|
||||
assert.strictEqual(format(i18n, mix), '6 days, 2 hours');
|
||||
});
|
||||
|
||||
it('handles negative numbers gracefully', () => {
|
||||
// The proto helps enforce non-negative numbers by specifying a u32, but because
|
||||
// JavaScript lacks such a type, we test it here.
|
||||
assert.strictEqual(format(i18n, -1), '1 second');
|
||||
assert.strictEqual(format(i18n, -120), '2 minutes');
|
||||
assert.strictEqual(format(i18n, -0), 'off');
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-1)),
|
||||
'1 second'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-120)),
|
||||
'2 minutes'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-0)),
|
||||
'off'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles fractional seconds gracefully', () => {
|
||||
// The proto helps enforce integer numbers by specifying a u32, but this function
|
||||
// shouldn't choke if bad data is passed somehow.
|
||||
assert.strictEqual(format(i18n, 4.2), '4 seconds');
|
||||
assert.strictEqual(format(i18n, 4.8), '4 seconds');
|
||||
assert.strictEqual(format(i18n, 0.2), '1 second');
|
||||
assert.strictEqual(format(i18n, 0.8), '1 second');
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(4.2)),
|
||||
'4 seconds'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(4.8)),
|
||||
'4 seconds'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(0.2)),
|
||||
'1 second'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(0.8)),
|
||||
'1 second'
|
||||
);
|
||||
|
||||
// If multiple things go wrong and we pass a fractional negative number, we still
|
||||
// shouldn't explode.
|
||||
assert.strictEqual(format(i18n, -4.2), '4 seconds');
|
||||
assert.strictEqual(format(i18n, -4.8), '4 seconds');
|
||||
assert.strictEqual(format(i18n, -0.2), '1 second');
|
||||
assert.strictEqual(format(i18n, -0.8), '1 second');
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-4.2)),
|
||||
'4 seconds'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-4.8)),
|
||||
'4 seconds'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-0.2)),
|
||||
'1 second'
|
||||
);
|
||||
assert.strictEqual(
|
||||
format(i18n, DurationInSeconds.fromSeconds(-0.8)),
|
||||
'1 second'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as durations from '../../util/durations';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
|
||||
export type TestExpireTimer = Readonly<{ value: number; label: string }>;
|
||||
export type TestExpireTimer = Readonly<{
|
||||
value: DurationInSeconds;
|
||||
label: string;
|
||||
}>;
|
||||
|
||||
export const EXPIRE_TIMERS: ReadonlyArray<TestExpireTimer> = [
|
||||
{ value: 42 * durations.SECOND, label: '42 seconds' },
|
||||
|
@ -11,4 +15,9 @@ export const EXPIRE_TIMERS: ReadonlyArray<TestExpireTimer> = [
|
|||
{ value: 1 * durations.HOUR, label: '1 hour' },
|
||||
{ value: 6 * durations.DAY, label: '6 days' },
|
||||
{ value: 3 * durations.WEEK, label: '3 weeks' },
|
||||
];
|
||||
].map(({ value, label }) => {
|
||||
return {
|
||||
value: DurationInSeconds.fromMillis(value),
|
||||
label,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ import { assert } from 'chai';
|
|||
import dataInterface from '../../sql/Client';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
|
||||
|
@ -342,7 +343,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationTimerUpdate: {
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
source: 'you',
|
||||
},
|
||||
sent_at: now + 1,
|
||||
|
@ -355,7 +356,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationTimerUpdate: {
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
fromSync: true,
|
||||
},
|
||||
sent_at: now + 2,
|
||||
|
@ -391,7 +392,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationTimerUpdate: {
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
source: 'you',
|
||||
fromSync: false,
|
||||
},
|
||||
|
@ -405,7 +406,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationTimerUpdate: {
|
||||
expireTimer: 10,
|
||||
expireTimer: DurationInSeconds.fromSeconds(10),
|
||||
fromSync: true,
|
||||
},
|
||||
sent_at: now + 2,
|
||||
|
@ -450,7 +451,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationStartTimestamp: now - 2 * 1000,
|
||||
expireTimer: 1,
|
||||
expireTimer: DurationInSeconds.fromSeconds(1),
|
||||
sent_at: now + 2,
|
||||
received_at: now + 2,
|
||||
timestamp: now + 2,
|
||||
|
@ -484,7 +485,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationStartTimestamp: now,
|
||||
expireTimer: 30,
|
||||
expireTimer: DurationInSeconds.fromSeconds(30),
|
||||
sent_at: now + 1,
|
||||
received_at: now + 1,
|
||||
timestamp: now + 1,
|
||||
|
@ -495,7 +496,7 @@ describe('sql/conversationSummary', () => {
|
|||
type: 'outgoing',
|
||||
conversationId,
|
||||
expirationStartTimestamp: now - 2 * 1000,
|
||||
expireTimer: 1,
|
||||
expireTimer: DurationInSeconds.fromSeconds(1),
|
||||
sent_at: now + 2,
|
||||
received_at: now + 2,
|
||||
timestamp: now + 2,
|
||||
|
|
|
@ -8,6 +8,7 @@ import { UUID } from '../../types/UUID';
|
|||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
import type { ReactionType } from '../../types/Reactions';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import type { MessageAttributesType } from '../../model-types.d';
|
||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
|
||||
|
@ -331,7 +332,7 @@ describe('sql/markRead', () => {
|
|||
const start = Date.now();
|
||||
const readAt = start + 20;
|
||||
const conversationId = getUuid();
|
||||
const expireTimer = 15;
|
||||
const expireTimer = DurationInSeconds.fromSeconds(15);
|
||||
const ourUuid = getUuid();
|
||||
|
||||
const message1: MessageAttributesType = {
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { ConversationType } from '../../../state/ducks/conversations';
|
|||
import type { MessageAttributesType } from '../../../model-types.d';
|
||||
import type { StateType as RootStateType } from '../../../state/reducer';
|
||||
import type { UUIDStringType } from '../../../types/UUID';
|
||||
import { DAY } from '../../../util/durations';
|
||||
import { DurationInSeconds } from '../../../util/durations';
|
||||
import { TEXT_ATTACHMENT, IMAGE_JPEG } from '../../../types/MIME';
|
||||
import { ReadStatus } from '../../../messages/MessageReadStatus';
|
||||
import {
|
||||
|
@ -74,7 +74,7 @@ describe('both/state/ducks/stories', () => {
|
|||
return {
|
||||
conversationId,
|
||||
expirationStartTimestamp: now,
|
||||
expireTimer: 1 * DAY,
|
||||
expireTimer: DurationInSeconds.DAY,
|
||||
messageId,
|
||||
readStatus: ReadStatus.Unread,
|
||||
timestamp: now - timestampDelta,
|
||||
|
@ -538,7 +538,7 @@ describe('both/state/ducks/stories', () => {
|
|||
? ourConversationId
|
||||
: groupConversationId,
|
||||
expirationStartTimestamp: now,
|
||||
expireTimer: 1 * DAY,
|
||||
expireTimer: DurationInSeconds.DAY,
|
||||
messageId,
|
||||
readStatus: ReadStatus.Unread,
|
||||
sendStateByConversationId: {},
|
||||
|
|
|
@ -5,13 +5,14 @@ import { assert } from 'chai';
|
|||
import * as sinon from 'sinon';
|
||||
import { RowType } from '../../../components/ConversationList';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { DurationInSeconds } from '../../../util/durations';
|
||||
|
||||
import { LeftPaneSetGroupMetadataHelper } from '../../../components/leftPane/LeftPaneSetGroupMetadataHelper';
|
||||
|
||||
function getComposeState() {
|
||||
return {
|
||||
groupAvatar: undefined,
|
||||
groupExpireTimer: 0,
|
||||
groupExpireTimer: DurationInSeconds.ZERO,
|
||||
groupName: '',
|
||||
hasError: false,
|
||||
isCreating: false,
|
||||
|
|
|
@ -7,23 +7,27 @@ import protobuf from '../protobuf/wrap';
|
|||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
import Avatar = Proto.ContactDetails.IAvatar;
|
||||
|
||||
const { Reader } = protobuf;
|
||||
|
||||
type OptionalAvatar = { avatar?: Avatar | null };
|
||||
type OptionalFields = { avatar?: Avatar | null; expireTimer?: number | null };
|
||||
|
||||
type DecoderBase<Message extends OptionalAvatar> = {
|
||||
type DecoderBase<Message extends OptionalFields> = {
|
||||
decodeDelimited(reader: protobuf.Reader): Message | undefined;
|
||||
};
|
||||
|
||||
export type MessageWithAvatar<Message extends OptionalAvatar> = Omit<
|
||||
type HydratedAvatar = Avatar & { data: Uint8Array };
|
||||
|
||||
type MessageWithAvatar<Message extends OptionalFields> = Omit<
|
||||
Message,
|
||||
'avatar'
|
||||
> & {
|
||||
avatar?: (Avatar & { data: Uint8Array }) | null;
|
||||
avatar?: HydratedAvatar;
|
||||
expireTimer?: DurationInSeconds;
|
||||
};
|
||||
|
||||
export type ModifiedGroupDetails = MessageWithAvatar<Proto.GroupDetails>;
|
||||
|
@ -32,7 +36,7 @@ export type ModifiedContactDetails = MessageWithAvatar<Proto.ContactDetails>;
|
|||
|
||||
/* eslint-disable @typescript-eslint/brace-style -- Prettier conflicts with ESLint */
|
||||
abstract class ParserBase<
|
||||
Message extends OptionalAvatar,
|
||||
Message extends OptionalFields,
|
||||
Decoder extends DecoderBase<Message>,
|
||||
Result
|
||||
> implements Iterable<Result>
|
||||
|
@ -57,28 +61,33 @@ abstract class ParserBase<
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (!proto.avatar) {
|
||||
return {
|
||||
...proto,
|
||||
avatar: null,
|
||||
let avatar: HydratedAvatar | undefined;
|
||||
if (proto.avatar) {
|
||||
const attachmentLen = proto.avatar.length ?? 0;
|
||||
const avatarData = this.reader.buf.slice(
|
||||
this.reader.pos,
|
||||
this.reader.pos + attachmentLen
|
||||
);
|
||||
this.reader.skip(attachmentLen);
|
||||
|
||||
avatar = {
|
||||
...proto.avatar,
|
||||
|
||||
data: avatarData,
|
||||
};
|
||||
}
|
||||
|
||||
const attachmentLen = proto.avatar.length ?? 0;
|
||||
const avatarData = this.reader.buf.slice(
|
||||
this.reader.pos,
|
||||
this.reader.pos + attachmentLen
|
||||
);
|
||||
this.reader.skip(attachmentLen);
|
||||
let expireTimer: DurationInSeconds | undefined;
|
||||
|
||||
if (proto.expireTimer != null) {
|
||||
expireTimer = DurationInSeconds.fromSeconds(proto.expireTimer);
|
||||
}
|
||||
|
||||
return {
|
||||
...proto,
|
||||
|
||||
avatar: {
|
||||
...proto.avatar,
|
||||
|
||||
data: avatarData,
|
||||
},
|
||||
avatar,
|
||||
expireTimer,
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(
|
||||
|
@ -118,6 +127,7 @@ export class GroupBuffer extends ParserBase<
|
|||
if (!proto.members) {
|
||||
return proto;
|
||||
}
|
||||
|
||||
return {
|
||||
...proto,
|
||||
members: proto.members.map((member, i) => {
|
||||
|
|
|
@ -45,6 +45,7 @@ import { normalizeUuid } from '../util/normalizeUuid';
|
|||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||
import { Zone } from '../util/Zone';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { deriveMasterKeyFromGroupV1, bytesToUuid } from '../Crypto';
|
||||
import type { DownloadedAttachmentType } from '../types/Attachment';
|
||||
import { Address } from '../types/Address';
|
||||
|
@ -2058,7 +2059,7 @@ export default class MessageReceiver
|
|||
attachments,
|
||||
preview,
|
||||
canReplyToStory: Boolean(msg.allowsReplies),
|
||||
expireTimer: durations.DAY / 1000,
|
||||
expireTimer: DurationInSeconds.DAY,
|
||||
flags: 0,
|
||||
groupV2,
|
||||
isStory: true,
|
||||
|
|
|
@ -65,6 +65,7 @@ import { concat, isEmpty, map } from '../util/iterables';
|
|||
import type { SendTypesType } from '../util/handleMessageSend';
|
||||
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
|
||||
import { uuidToBytes } from '../util/uuidToBytes';
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import * as log from '../logging/log';
|
||||
import type { Avatar, EmbeddedContactType } from '../types/EmbeddedContact';
|
||||
|
@ -174,7 +175,7 @@ export type MessageOptionsType = {
|
|||
attachments?: ReadonlyArray<AttachmentType> | null;
|
||||
body?: string;
|
||||
contact?: Array<ContactWithHydratedAvatar>;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
flags?: number;
|
||||
group?: {
|
||||
id: string;
|
||||
|
@ -198,7 +199,7 @@ export type GroupSendOptionsType = {
|
|||
attachments?: Array<AttachmentType>;
|
||||
contact?: Array<ContactWithHydratedAvatar>;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
flags?: number;
|
||||
groupCallUpdate?: GroupCallUpdateType;
|
||||
groupV1?: GroupV1InfoType;
|
||||
|
@ -221,7 +222,7 @@ class Message {
|
|||
|
||||
contact?: Array<ContactWithHydratedAvatar>;
|
||||
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
|
||||
flags?: number;
|
||||
|
||||
|
@ -1358,7 +1359,7 @@ export default class MessageSender {
|
|||
contact?: Array<ContactWithHydratedAvatar>;
|
||||
contentHint: number;
|
||||
deletedForEveryoneTimestamp: number | undefined;
|
||||
expireTimer: number | undefined;
|
||||
expireTimer: DurationInSeconds | undefined;
|
||||
groupId: string | undefined;
|
||||
identifier: string;
|
||||
messageText: string | undefined;
|
||||
|
|
3
ts/textsecure/Types.d.ts
vendored
3
ts/textsecure/Types.d.ts
vendored
|
@ -7,6 +7,7 @@ import type { UUID, UUIDStringType } from '../types/UUID';
|
|||
import type { TextAttachmentType } from '../types/Attachment';
|
||||
import type { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import type { MIMEType } from '../types/MIME';
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
|
||||
export {
|
||||
IdentityKeyType,
|
||||
|
@ -207,7 +208,7 @@ export type ProcessedDataMessage = {
|
|||
group?: ProcessedGroupContext;
|
||||
groupV2?: ProcessedGroupV2Context;
|
||||
flags: number;
|
||||
expireTimer: number;
|
||||
expireTimer: DurationInSeconds;
|
||||
profileKey?: string;
|
||||
timestamp: number;
|
||||
quote?: ProcessedQuote;
|
||||
|
|
|
@ -28,7 +28,7 @@ import type {
|
|||
import { WarnOnlyError } from './Errors';
|
||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import { APPLICATION_OCTET_STREAM, stringToMIMEType } from '../types/MIME';
|
||||
import { SECOND } from '../util/durations';
|
||||
import { SECOND, DurationInSeconds } from '../util/durations';
|
||||
|
||||
const FLAGS = Proto.DataMessage.Flags;
|
||||
export const ATTACHMENT_MAX = 32;
|
||||
|
@ -299,7 +299,7 @@ export function processDataMessage(
|
|||
group: processGroupContext(message.group),
|
||||
groupV2: processGroupV2Context(message.groupV2),
|
||||
flags: message.flags ?? 0,
|
||||
expireTimer: message.expireTimer ?? 0,
|
||||
expireTimer: DurationInSeconds.fromSeconds(message.expireTimer ?? 0),
|
||||
profileKey:
|
||||
message.profileKey && message.profileKey.length > 0
|
||||
? Bytes.toBase64(message.profileKey)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
import type { AttachmentType } from './Attachment';
|
||||
import type { EmbeddedContactType } from './EmbeddedContact';
|
||||
import type { IndexableBoolean, IndexablePresence } from './IndexedDB';
|
||||
|
@ -30,7 +31,7 @@ export type IncomingMessage = Readonly<
|
|||
body?: string;
|
||||
decrypted_at?: number;
|
||||
errors?: Array<Error>;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
messageTimer?: number; // deprecated
|
||||
isViewOnce?: number;
|
||||
flags?: number;
|
||||
|
@ -54,7 +55,7 @@ export type OutgoingMessage = Readonly<
|
|||
|
||||
// Optional
|
||||
body?: string;
|
||||
expireTimer?: number;
|
||||
expireTimer?: DurationInSeconds;
|
||||
messageTimer?: number; // deprecated
|
||||
isViewOnce?: number;
|
||||
synced: boolean;
|
||||
|
@ -88,7 +89,7 @@ export type SharedMessageProperties = Readonly<{
|
|||
export type ExpirationTimerUpdate = Partial<
|
||||
Readonly<{
|
||||
expirationTimerUpdate: Readonly<{
|
||||
expireTimer: number;
|
||||
expireTimer: DurationInSeconds;
|
||||
fromSync: boolean;
|
||||
source: string; // PhoneNumber
|
||||
}>;
|
||||
|
|
|
@ -34,6 +34,7 @@ import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
|
|||
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
|
||||
import { assertDev } from './assert';
|
||||
import * as durations from './durations';
|
||||
import type { DurationInSeconds } from './durations';
|
||||
import { isPhoneNumberSharingEnabled } from './isPhoneNumberSharingEnabled';
|
||||
import {
|
||||
parseE164FromSignalDotMeHash,
|
||||
|
@ -66,7 +67,7 @@ export type IPCEventsValuesType = {
|
|||
spellCheck: boolean;
|
||||
systemTraySetting: SystemTraySetting;
|
||||
themeSetting: ThemeType;
|
||||
universalExpireTimer: number;
|
||||
universalExpireTimer: DurationInSeconds;
|
||||
zoomFactor: ZoomFactorType;
|
||||
storyViewReceiptsEnabled: boolean;
|
||||
|
||||
|
|
39
ts/util/durations/duration-in-seconds.ts
Normal file
39
ts/util/durations/duration-in-seconds.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as Constants from './constants';
|
||||
|
||||
export type DurationInSeconds = number & {
|
||||
// eslint-disable-next-line camelcase
|
||||
__time_difference_in_seconds: never;
|
||||
};
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-redeclare */
|
||||
export namespace DurationInSeconds {
|
||||
export const fromMillis = (ms: number): DurationInSeconds =>
|
||||
(ms / Constants.SECOND) as DurationInSeconds;
|
||||
export const fromSeconds = (seconds: number): DurationInSeconds =>
|
||||
seconds as DurationInSeconds;
|
||||
export const fromMinutes = (m: number): DurationInSeconds =>
|
||||
((m * Constants.MINUTE) / Constants.SECOND) as DurationInSeconds;
|
||||
export const fromHours = (h: number): DurationInSeconds =>
|
||||
((h * Constants.HOUR) / Constants.SECOND) as DurationInSeconds;
|
||||
export const fromDays = (d: number): DurationInSeconds =>
|
||||
((d * Constants.DAY) / Constants.SECOND) as DurationInSeconds;
|
||||
export const fromWeeks = (d: number): DurationInSeconds =>
|
||||
((d * Constants.WEEK) / Constants.SECOND) as DurationInSeconds;
|
||||
export const fromMonths = (d: number): DurationInSeconds =>
|
||||
((d * Constants.MONTH) / Constants.SECOND) as DurationInSeconds;
|
||||
|
||||
export const toSeconds = (d: DurationInSeconds): number => d;
|
||||
export const toMillis = (d: DurationInSeconds): number =>
|
||||
d * Constants.SECOND;
|
||||
export const toHours = (d: DurationInSeconds): number =>
|
||||
(d * Constants.SECOND) / Constants.HOUR;
|
||||
|
||||
export const ZERO = DurationInSeconds.fromSeconds(0);
|
||||
export const HOUR = DurationInSeconds.fromHours(1);
|
||||
export const MINUTE = DurationInSeconds.fromMinutes(1);
|
||||
export const DAY = DurationInSeconds.fromDays(1);
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-namespace, @typescript-eslint/no-redeclare */
|
5
ts/util/durations/index.ts
Normal file
5
ts/util/durations/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export * from './constants';
|
||||
export { DurationInSeconds } from './duration-in-seconds';
|
|
@ -1,26 +1,25 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as moment from 'moment';
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
import type { Unit } from 'humanize-duration';
|
||||
import { isNumber } from 'lodash';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { SECOND } from './durations';
|
||||
import { SECOND, DurationInSeconds } from './durations';
|
||||
|
||||
const SECONDS_PER_WEEK = 604800;
|
||||
export const DEFAULT_DURATIONS_IN_SECONDS: ReadonlyArray<number> = [
|
||||
0,
|
||||
moment.duration(4, 'weeks').asSeconds(),
|
||||
moment.duration(1, 'week').asSeconds(),
|
||||
moment.duration(1, 'day').asSeconds(),
|
||||
moment.duration(8, 'hours').asSeconds(),
|
||||
moment.duration(1, 'hour').asSeconds(),
|
||||
moment.duration(5, 'minutes').asSeconds(),
|
||||
moment.duration(30, 'seconds').asSeconds(),
|
||||
export const DEFAULT_DURATIONS_IN_SECONDS: ReadonlyArray<DurationInSeconds> = [
|
||||
DurationInSeconds.ZERO,
|
||||
DurationInSeconds.fromWeeks(4),
|
||||
DurationInSeconds.fromWeeks(1),
|
||||
DurationInSeconds.fromDays(1),
|
||||
DurationInSeconds.fromHours(8),
|
||||
DurationInSeconds.fromHours(1),
|
||||
DurationInSeconds.fromMinutes(5),
|
||||
DurationInSeconds.fromSeconds(30),
|
||||
];
|
||||
|
||||
export const DEFAULT_DURATIONS_SET: ReadonlySet<number> = new Set<number>(
|
||||
export const DEFAULT_DURATIONS_SET: ReadonlySet<DurationInSeconds> = new Set(
|
||||
DEFAULT_DURATIONS_IN_SECONDS
|
||||
);
|
||||
|
||||
|
@ -31,7 +30,7 @@ export type FormatOptions = {
|
|||
|
||||
export function format(
|
||||
i18n: LocalizerType,
|
||||
dirtySeconds?: number,
|
||||
dirtySeconds?: DurationInSeconds,
|
||||
{ capitalizeOff = false, largest }: FormatOptions = {}
|
||||
): string {
|
||||
let seconds = Math.abs(dirtySeconds || 0);
|
||||
|
@ -66,7 +65,7 @@ export function format(
|
|||
const defaultUnits: Array<Unit> =
|
||||
seconds % SECONDS_PER_WEEK === 0 ? ['w'] : ['d', 'h', 'm', 's'];
|
||||
|
||||
return humanizeDuration(seconds * 1000, {
|
||||
return humanizeDuration(seconds * SECOND, {
|
||||
// if we have an explict `largest` specified,
|
||||
// allow it to pick from all the units
|
||||
units: largest ? allUnits : defaultUnits,
|
||||
|
@ -82,10 +81,10 @@ export function calculateExpirationTimestamp({
|
|||
expireTimer,
|
||||
expirationStartTimestamp,
|
||||
}: {
|
||||
expireTimer: number | undefined;
|
||||
expireTimer: DurationInSeconds | undefined;
|
||||
expirationStartTimestamp: number | undefined | null;
|
||||
}): number | undefined {
|
||||
return isNumber(expirationStartTimestamp) && isNumber(expireTimer)
|
||||
? expirationStartTimestamp + expireTimer * SECOND
|
||||
? expirationStartTimestamp + DurationInSeconds.toMillis(expireTimer)
|
||||
: undefined;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { DAY } from './durations';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { isNotNil } from './isNotNil';
|
||||
import { DurationInSeconds } from './durations';
|
||||
import { markViewed } from '../services/MessageUpdater';
|
||||
import { storageServiceUploadJob } from '../services/storage';
|
||||
|
||||
|
@ -29,7 +29,7 @@ export async function markOnboardingStoryAsRead(): Promise<void> {
|
|||
}
|
||||
|
||||
message.set({
|
||||
expireTimer: DAY,
|
||||
expireTimer: DurationInSeconds.DAY,
|
||||
});
|
||||
|
||||
message.set(markViewed(message.attributes, storyReadDate));
|
||||
|
|
|
@ -7,7 +7,6 @@ import type { SendStateByConversationId } from '../messages/MessageSendState';
|
|||
import type { UUIDStringType } from '../types/UUID';
|
||||
import * as log from '../logging/log';
|
||||
import dataInterface from '../sql/Client';
|
||||
import { DAY, SECOND } from './durations';
|
||||
import { MY_STORY_ID, StorySendMode } from '../types/Stories';
|
||||
import { getStoriesBlocked } from './stories';
|
||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||
|
@ -24,6 +23,7 @@ import { incrementMessageCounter } from './incrementMessageCounter';
|
|||
import { isGroupV2 } from './whatTypeOfConversation';
|
||||
import { isNotNil } from './isNotNil';
|
||||
import { collect } from './iterables';
|
||||
import { DurationInSeconds } from './durations';
|
||||
|
||||
export async function sendStoryMessage(
|
||||
listIds: Array<string>,
|
||||
|
@ -158,7 +158,7 @@ export async function sendStoryMessage(
|
|||
return window.Signal.Migrations.upgradeMessageSchema({
|
||||
attachments,
|
||||
conversationId: ourConversation.id,
|
||||
expireTimer: DAY / SECOND,
|
||||
expireTimer: DurationInSeconds.DAY,
|
||||
expirationStartTimestamp: Date.now(),
|
||||
id: UUID.generate().toString(),
|
||||
readStatus: ReadStatus.Read,
|
||||
|
@ -262,7 +262,7 @@ export async function sendStoryMessage(
|
|||
attachments,
|
||||
canReplyToStory: true,
|
||||
conversationId: group.id,
|
||||
expireTimer: DAY / SECOND,
|
||||
expireTimer: DurationInSeconds.DAY,
|
||||
expirationStartTimestamp: Date.now(),
|
||||
id: UUID.generate().toString(),
|
||||
readStatus: ReadStatus.Read,
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { DurationInSeconds } from './durations';
|
||||
|
||||
export const ITEM_NAME = 'universalExpireTimer';
|
||||
|
||||
export function get(): number {
|
||||
return window.storage.get(ITEM_NAME) || 0;
|
||||
export function get(): DurationInSeconds {
|
||||
return DurationInSeconds.fromSeconds(window.storage.get(ITEM_NAME) || 0);
|
||||
}
|
||||
|
||||
export function set(newValue: number | undefined): Promise<void> {
|
||||
return window.storage.put(ITEM_NAME, newValue || 0);
|
||||
export function set(newValue: DurationInSeconds | undefined): Promise<void> {
|
||||
return window.storage.put(ITEM_NAME, newValue || DurationInSeconds.ZERO);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
isGroupV1,
|
||||
} from '../util/whatTypeOfConversation';
|
||||
import { findAndFormatContact } from '../util/findAndFormatContact';
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
import { getPreferredBadgeSelector } from '../state/selectors/badges';
|
||||
import {
|
||||
canReply,
|
||||
|
@ -347,7 +348,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
const conversationHeaderProps = {
|
||||
id: this.model.id,
|
||||
|
||||
onSetDisappearingMessages: (seconds: number) =>
|
||||
onSetDisappearingMessages: (seconds: DurationInSeconds) =>
|
||||
this.setDisappearingMessages(seconds),
|
||||
onDeleteMessages: () => this.destroyMessages(),
|
||||
onSearchInConversation: () => {
|
||||
|
@ -2260,7 +2261,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
);
|
||||
}
|
||||
|
||||
async setDisappearingMessages(seconds: number): Promise<void> {
|
||||
async setDisappearingMessages(seconds: DurationInSeconds): Promise<void> {
|
||||
const { model }: { model: ConversationModel } = this;
|
||||
|
||||
const valueToSet = seconds > 0 ? seconds : undefined;
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
shouldMinimizeToSystemTray,
|
||||
} from '../../types/SystemTraySetting';
|
||||
import { awaitObject } from '../../util/awaitObject';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import { createSetting, createCallback } from '../../util/preload';
|
||||
import { startInteractionMode } from '../startInteractionMode';
|
||||
|
||||
|
@ -215,6 +216,10 @@ const renderPreferences = async () => {
|
|||
const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } =
|
||||
getSystemTraySettingValues(systemTraySetting);
|
||||
|
||||
const onUniversalExpireTimerChange = reRender(
|
||||
settingUniversalExpireTimer.setValue
|
||||
);
|
||||
|
||||
const props = {
|
||||
// Settings
|
||||
availableCameras,
|
||||
|
@ -250,7 +255,7 @@ const renderPreferences = async () => {
|
|||
selectedMicrophone,
|
||||
selectedSpeaker,
|
||||
themeSetting,
|
||||
universalExpireTimer,
|
||||
universalExpireTimer: DurationInSeconds.fromSeconds(universalExpireTimer),
|
||||
whoCanFindMe,
|
||||
whoCanSeeMe,
|
||||
zoomFactor,
|
||||
|
@ -347,9 +352,11 @@ const renderPreferences = async () => {
|
|||
onSelectedSpeakerChange: reRender(settingAudioOutput.setValue),
|
||||
onSpellCheckChange: reRender(settingSpellCheck.setValue),
|
||||
onThemeChange: reRender(settingTheme.setValue),
|
||||
onUniversalExpireTimerChange: reRender(
|
||||
settingUniversalExpireTimer.setValue
|
||||
),
|
||||
onUniversalExpireTimerChange: (newValue: number): Promise<void> => {
|
||||
return onUniversalExpireTimerChange(
|
||||
DurationInSeconds.fromSeconds(newValue)
|
||||
);
|
||||
},
|
||||
|
||||
// Zoom factor change doesn't require immediate rerender since it will:
|
||||
// 1. Update the zoom factor in the main window
|
||||
|
|
Loading…
Reference in a new issue