Default disappearing message timeout fixes

This commit is contained in:
Fedor Indutny 2021-06-25 16:52:56 -07:00 committed by GitHub
parent c9415dcf67
commit cd28e71bc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 456 additions and 164 deletions

View File

@ -1805,7 +1805,7 @@
}, },
"disappearingMessages": { "disappearingMessages": {
"message": "Disappearing messages", "message": "Disappearing messages",
"description": "Conversation menu option to enable disappearing messages. Title of the settings section for Disappearing Messages" "description": "Conversation menu option to enable disappearing messages. Title of the settings section for Disappearing Messages. Label of the disappearing timer select in group creation flow"
}, },
"disappearingMessagesDisabled": { "disappearingMessagesDisabled": {
"message": "Disappearing messages disabled", "message": "Disappearing messages disabled",

View File

@ -56,7 +56,7 @@ const {
} = require('../../ts/components/conversation/StagedLinkPreview'); } = require('../../ts/components/conversation/StagedLinkPreview');
const { const {
DisappearingTimeDialog, DisappearingTimeDialog,
} = require('../../ts/components/conversation/DisappearingTimeDialog'); } = require('../../ts/components/DisappearingTimeDialog');
// State // State
const { createTimeline } = require('../../ts/state/roots/createTimeline'); const { createTimeline } = require('../../ts/state/roots/createTimeline');

View File

@ -2366,64 +2366,9 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
} }
// Module: Timer Notification // Module: Timer Notification
.module-timer-notification {
text-align: center;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-05;
}
}
.module-timer-notification__icon-container {
margin-left: auto;
margin-right: auto;
display: inline-flex;
flex-direction: row;
align-items: center;
margin-bottom: 4px;
}
.module-timer-notification__icon {
height: 20px;
width: 20px;
display: inline-block;
@include light-theme {
@include color-svg('../images/icons/v2/timer-24.svg', $color-gray-60);
}
@include dark-theme {
@include color-svg('../images/icons/v2/timer-24.svg', $color-gray-05);
}
}
.module-timer-notification__icon--disabled {
@include light-theme {
@include color-svg(
'../images/icons/v2/timer-disabled-24.svg',
$color-gray-60
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v2/timer-disabled-24.svg',
$color-gray-05
);
}
}
.module-timer-notification__icon-label {
margin-left: 6px;
// Didn't seem centered otherwise
margin-top: 1px;
}
// Module: Universal Timer Notification // Module: Universal Timer Notification
.module-timer-notification,
.module-universal-timer-notification { .module-universal-timer-notification {
text-align: center; text-align: center;
@ -2433,6 +2378,53 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
@include dark-theme { @include dark-theme {
color: $color-gray-05; color: $color-gray-05;
} }
&__icon-container {
margin-left: auto;
margin-right: auto;
display: inline-flex;
flex-direction: row;
align-items: center;
margin-bottom: 8px;
}
&__icon {
height: 20px;
width: 20px;
display: inline-block;
opacity: 0.6;
@include light-theme {
@include color-svg('../images/icons/v2/timer-24.svg', $color-gray-60);
}
@include dark-theme {
@include color-svg('../images/icons/v2/timer-24.svg', $color-gray-05);
}
&--disabled {
@include light-theme {
@include color-svg(
'../images/icons/v2/timer-disabled-24.svg',
$color-gray-60
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v2/timer-disabled-24.svg',
$color-gray-05
);
}
}
}
&__icon-label {
margin-left: 4px;
// Didn't seem centered otherwise
margin-top: 1px;
opacity: 0.8;
}
} }
.module-notification--with-click-handler { .module-notification--with-click-handler {
@ -3070,8 +3062,7 @@ button.module-conversation-details__action-button {
margin-right: 12px; margin-right: 12px;
} }
&__info, &__info {
&__right-info {
@include font-body-2; @include font-body-2;
margin-top: 4px; margin-top: 4px;
@ -3090,14 +3081,6 @@ button.module-conversation-details__action-button {
min-width: 143px; min-width: 143px;
} }
&__right-info {
position: absolute;
@include font-subtitle;
padding-left: 14px;
}
&__actions { &__actions {
margin-left: 12px; margin-left: 12px;
overflow: hidden; overflow: hidden;
@ -7005,6 +6988,21 @@ button.module-image__border-overlay:focus {
&__form { &__form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&__expire-timer {
display: flex;
flex-direction: row;
margin: 0 16px 16px 16px;
&__label {
margin-right: 12px;
}
.module-disappearing-timer-select {
width: 144px;
}
}
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
.module-disappearing-timer-select {
position: relative;
&__info {
position: absolute;
margin-top: 4px;
padding-left: 14px;
@include font-subtitle;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
}
}

View File

@ -43,6 +43,7 @@
@import './components/ConversationHeader.scss'; @import './components/ConversationHeader.scss';
@import './components/CustomColorEditor.scss'; @import './components/CustomColorEditor.scss';
@import './components/DisappearingTimeDialog.scss'; @import './components/DisappearingTimeDialog.scss';
@import './components/DisappearingTimerSelect.scss';
@import './components/EditConversationAttributesModal.scss'; @import './components/EditConversationAttributesModal.scss';
@import './components/ForwardMessageModal.scss'; @import './components/ForwardMessageModal.scss';
@import './components/GradientDial.scss'; @import './components/GradientDial.scss';

View File

@ -6,10 +6,10 @@ import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { DisappearingTimeDialog } from './DisappearingTimeDialog'; import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import { setup as setupI18n } from '../../../js/modules/i18n'; import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { EXPIRE_TIMERS } from '../../test-both/util/expireTimers'; import { EXPIRE_TIMERS } from '../test-both/util/expireTimers';
const story = storiesOf('Components/DisappearingTimeDialog', module); const story = storiesOf('Components/DisappearingTimeDialog', module);

View File

@ -4,10 +4,10 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { ConfirmationDialog } from '../ConfirmationDialog'; import { ConfirmationDialog } from './ConfirmationDialog';
import { Select } from '../Select'; import { Select } from './Select';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../types/Util';
import { Theme } from '../../util/theme'; import { Theme } from '../util/theme';
const CSS_MODULE = 'module-disappearing-time-dialog'; const CSS_MODULE = 'module-disappearing-time-dialog';

View File

@ -0,0 +1,37 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import { storiesOf } from '@storybook/react';
import { DisappearingTimerSelect } from './DisappearingTimerSelect';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const story = storiesOf('Components/DisappearingTimerSelect', module);
const i18n = setupI18n('en', enMessages);
type Props = {
initialValue: number;
};
const TimerSelectWrap: React.FC<Props> = ({ initialValue }) => {
const [value, setValue] = useState(initialValue);
return (
<DisappearingTimerSelect
i18n={i18n}
value={value}
onChange={newValue => setValue(newValue)}
/>
);
};
story.add('Initial value: 1 day', () => (
<TimerSelectWrap initialValue={24 * 3600} />
));
story.add('Initial value 3 days (Custom time)', () => (
<TimerSelectWrap initialValue={3 * 24 * 3600} />
));

View File

@ -0,0 +1,106 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState, ReactNode } from 'react';
import classNames from 'classnames';
import { LocalizerType } from '../types/Util';
import * as expirationTimer from '../util/expirationTimer';
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import { Select } from './Select';
const CSS_MODULE = 'module-disappearing-timer-select';
export type Props = {
i18n: LocalizerType;
value?: number;
onChange(value: number): void;
};
export const DisappearingTimerSelect: React.FC<Props> = (props: Props) => {
const { i18n, value = 0, onChange } = props;
const [isModalOpen, setIsModalOpen] = useState(false);
let expirationTimerOptions: ReadonlyArray<{
readonly value: number;
readonly text: string;
}> = expirationTimer.DEFAULT_DURATIONS_IN_SECONDS.map(seconds => {
const text = expirationTimer.format(i18n, seconds, {
capitalizeOff: true,
});
return {
value: seconds,
text,
};
});
const isCustomTimeSelected = !expirationTimer.DEFAULT_DURATIONS_SET.has(
value
);
const onSelectChange = (newValue: string) => {
const intValue = parseInt(newValue, 10);
if (intValue === -1) {
setIsModalOpen(true);
} else {
onChange(intValue);
}
};
// Custom time...
expirationTimerOptions = [
...expirationTimerOptions,
{
value: -1,
text: i18n(
isCustomTimeSelected
? 'selectedCustomDisappearingTimeOption'
: 'customDisappearingTimeOption'
),
},
];
let modalNode: ReactNode = null;
if (isModalOpen) {
modalNode = (
<DisappearingTimeDialog
i18n={i18n}
initialValue={value}
onSubmit={newValue => {
setIsModalOpen(false);
onChange(newValue);
}}
onClose={() => setIsModalOpen(false)}
/>
);
}
let info: ReactNode;
if (isCustomTimeSelected) {
info = (
<div className={`${CSS_MODULE}__info`}>
{expirationTimer.format(i18n, value)}
</div>
);
}
return (
<div
className={classNames(
CSS_MODULE,
isCustomTimeSelected ? `${CSS_MODULE}--custom-time` : false
)}
>
<Select
onChange={onSelectChange}
value={isCustomTimeSelected ? -1 : value}
options={expirationTimerOptions}
/>
{info}
{modalNode}
</div>
);
};

View File

@ -126,6 +126,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
setComposeSearchTerm: action('setComposeSearchTerm'), setComposeSearchTerm: action('setComposeSearchTerm'),
setComposeGroupAvatar: action('setComposeGroupAvatar'), setComposeGroupAvatar: action('setComposeGroupAvatar'),
setComposeGroupName: action('setComposeGroupName'), setComposeGroupName: action('setComposeGroupName'),
setComposeGroupExpireTimer: action('setComposeGroupExpireTimer'),
showArchivedConversations: action('showArchivedConversations'), showArchivedConversations: action('showArchivedConversations'),
showInbox: action('showInbox'), showInbox: action('showInbox'),
startComposing: action('startComposing'), startComposing: action('startComposing'),
@ -514,3 +515,53 @@ story.add('Captcha dialog: pending', () => (
})} })}
/> />
)); ));
// Set group metadata
story.add('Group Metadata: No Timer', () => (
<LeftPane
{...createProps({
modeSpecificProps: {
mode: LeftPaneMode.SetGroupMetadata,
groupAvatar: undefined,
groupName: 'Group 1',
groupExpireTimer: 0,
hasError: false,
isCreating: false,
selectedContacts: defaultConversations,
},
})}
/>
));
story.add('Group Metadata: Regular Timer', () => (
<LeftPane
{...createProps({
modeSpecificProps: {
mode: LeftPaneMode.SetGroupMetadata,
groupAvatar: undefined,
groupName: 'Group 1',
groupExpireTimer: 24 * 3600,
hasError: false,
isCreating: false,
selectedContacts: defaultConversations,
},
})}
/>
));
story.add('Group Metadata: Custom Timer', () => (
<LeftPane
{...createProps({
modeSpecificProps: {
mode: LeftPaneMode.SetGroupMetadata,
groupAvatar: undefined,
groupName: 'Group 1',
groupExpireTimer: 7 * 3600,
hasError: false,
isCreating: false,
selectedContacts: defaultConversations,
},
})}
/>
));

View File

@ -98,6 +98,7 @@ export type PropsType = {
setComposeSearchTerm: (composeSearchTerm: string) => void; setComposeSearchTerm: (composeSearchTerm: string) => void;
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => void; setComposeGroupAvatar: (_: undefined | ArrayBuffer) => void;
setComposeGroupName: (_: string) => void; setComposeGroupName: (_: string) => void;
setComposeGroupExpireTimer: (_: number) => void;
showArchivedConversations: () => void; showArchivedConversations: () => void;
showInbox: () => void; showInbox: () => void;
startComposing: () => void; startComposing: () => void;
@ -139,6 +140,7 @@ export const LeftPane: React.FC<PropsType> = ({
setComposeSearchTerm, setComposeSearchTerm,
setComposeGroupAvatar, setComposeGroupAvatar,
setComposeGroupName, setComposeGroupName,
setComposeGroupExpireTimer,
showArchivedConversations, showArchivedConversations,
showInbox, showInbox,
startComposing, startComposing,
@ -342,6 +344,7 @@ export const LeftPane: React.FC<PropsType> = ({
i18n, i18n,
setComposeGroupAvatar, setComposeGroupAvatar,
setComposeGroupName, setComposeGroupName,
setComposeGroupExpireTimer,
onChangeComposeSearchTerm: event => { onChangeComposeSearchTerm: event => {
setComposeSearchTerm(event.target.value); setComposeSearchTerm(event.target.value);
}, },

View File

@ -13,7 +13,7 @@ import {
} from 'react-contextmenu'; } from 'react-contextmenu';
import { Emojify } from './Emojify'; import { Emojify } from './Emojify';
import { DisappearingTimeDialog } from './DisappearingTimeDialog'; import { DisappearingTimeDialog } from '../DisappearingTimeDialog';
import { Avatar, AvatarSize } from '../Avatar'; import { Avatar, AvatarSize } from '../Avatar';
import { InContactsIcon } from '../InContactsIcon'; import { InContactsIcon } from '../InContactsIcon';

View File

@ -11,6 +11,8 @@ export type Props = {
expireTimer: number; expireTimer: number;
}; };
const CSS_MODULE = 'module-universal-timer-notification';
export const UniversalTimerNotification: React.FC<Props> = props => { export const UniversalTimerNotification: React.FC<Props> = props => {
const { i18n, expireTimer } = props; const { i18n, expireTimer } = props;
@ -18,11 +20,19 @@ export const UniversalTimerNotification: React.FC<Props> = props => {
return null; return null;
} }
const timeValue = expirationTimer.format(i18n, expireTimer);
return ( return (
<div className="module-universal-timer-notification"> <div className={CSS_MODULE}>
{i18n('UniversalTimerNotification__text', { <div className={`${CSS_MODULE}__icon-container`}>
timeValue: expirationTimer.format(i18n, expireTimer), <div className={`${CSS_MODULE}__icon`} />
})} <div className={`${CSS_MODULE}__icon-label`}>{timeValue}</div>
</div>
<div className={`${CSS_MODULE}__message`}>
{i18n('UniversalTimerNotification__text', {
timeValue,
})}
</div>
</div> </div>
); );
}; };

View File

@ -5,15 +5,12 @@ import React, { useState, ReactNode } from 'react';
import { ConversationType } from '../../../state/ducks/conversations'; import { ConversationType } from '../../../state/ducks/conversations';
import { assert } from '../../../util/assert'; import { assert } from '../../../util/assert';
import * as expirationTimer from '../../../util/expirationTimer';
import { LocalizerType } from '../../../types/Util'; import { LocalizerType } from '../../../types/Util';
import { MediaItemType } from '../../LightboxGallery'; import { MediaItemType } from '../../LightboxGallery';
import { missingCaseError } from '../../../util/missingCaseError'; import { missingCaseError } from '../../../util/missingCaseError';
import { Select } from '../../Select'; import { DisappearingTimerSelect } from '../../DisappearingTimerSelect';
import { DisappearingTimeDialog } from '../DisappearingTimeDialog';
import { PanelRow } from './PanelRow'; import { PanelRow } from './PanelRow';
import { PanelSection } from './PanelSection'; import { PanelSection } from './PanelSection';
@ -39,7 +36,6 @@ enum ModalState {
EditingGroupDescription, EditingGroupDescription,
EditingGroupTitle, EditingGroupTitle,
AddingGroupMembers, AddingGroupMembers,
CustomDisappearingTimeout,
} }
export type StateProps = { export type StateProps = {
@ -114,15 +110,6 @@ export const ConversationDetails: React.ComponentType<Props> = ({
setAddGroupMembersRequestState, setAddGroupMembersRequestState,
] = useState<RequestState>(RequestState.Inactive); ] = useState<RequestState>(RequestState.Inactive);
const updateExpireTimer = (value: string) => {
const intValue = parseInt(value, 10);
if (intValue === -1) {
setModalState(ModalState.CustomDisappearingTimeout);
} else {
setDisappearingMessages(intValue);
}
};
if (conversation === undefined) { if (conversation === undefined) {
throw new Error('ConversationDetails rendered without a conversation'); throw new Error('ConversationDetails rendered without a conversation');
} }
@ -218,55 +205,10 @@ export const ConversationDetails: React.ComponentType<Props> = ({
/> />
); );
break; break;
case ModalState.CustomDisappearingTimeout:
modalNode = (
<DisappearingTimeDialog
i18n={i18n}
initialValue={conversation.expireTimer}
onSubmit={value => {
setModalState(ModalState.NothingOpen);
setDisappearingMessages(value);
}}
onClose={() => setModalState(ModalState.NothingOpen)}
/>
);
break;
default: default:
throw missingCaseError(modalState); throw missingCaseError(modalState);
} }
const expireTimer: number = conversation.expireTimer || 0;
let expirationTimerOptions: ReadonlyArray<{
readonly value: number;
readonly text: string;
}> = expirationTimer.DEFAULT_DURATIONS_IN_SECONDS.map(seconds => {
const text = expirationTimer.format(i18n, seconds, {
capitalizeOff: true,
});
return {
value: seconds,
text,
};
});
const isCustomTimeSelected = !expirationTimer.DEFAULT_DURATIONS_SET.has(
expireTimer
);
// Custom time...
expirationTimerOptions = [
...expirationTimerOptions,
{
value: -1,
text: i18n(
isCustomTimeSelected
? 'selectedCustomDisappearingTimeOption'
: 'customDisappearingTimeOption'
),
},
];
return ( return (
<div className="conversation-details-panel"> <div className="conversation-details-panel">
<ConversationDetailsHeader <ConversationDetailsHeader
@ -297,17 +239,12 @@ export const ConversationDetails: React.ComponentType<Props> = ({
info={i18n('ConversationDetails--disappearing-messages-info')} info={i18n('ConversationDetails--disappearing-messages-info')}
label={i18n('ConversationDetails--disappearing-messages-label')} label={i18n('ConversationDetails--disappearing-messages-label')}
right={ right={
<Select <DisappearingTimerSelect
onChange={updateExpireTimer} i18n={i18n}
value={isCustomTimeSelected ? -1 : expireTimer} value={conversation.expireTimer || 0}
options={expirationTimerOptions} onChange={setDisappearingMessages}
/> />
} }
rightInfo={
isCustomTimeSelected
? expirationTimer.format(i18n, expireTimer)
: undefined
}
/> />
) : null} ) : null}
<PanelRow <PanelRow

View File

@ -13,7 +13,6 @@ export type Props = {
label: string | React.ReactNode; label: string | React.ReactNode;
info?: string; info?: string;
right?: string | React.ReactNode; right?: string | React.ReactNode;
rightInfo?: string;
actions?: React.ReactNode; actions?: React.ReactNode;
onClick?: () => void; onClick?: () => void;
}; };
@ -28,7 +27,6 @@ export const PanelRow: React.ComponentType<Props> = ({
label, label,
info, info,
right, right,
rightInfo,
actions, actions,
onClick, onClick,
}) => { }) => {
@ -39,14 +37,7 @@ export const PanelRow: React.ComponentType<Props> = ({
<div>{label}</div> <div>{label}</div>
{info !== undefined ? <div className={bem('info')}>{info}</div> : null} {info !== undefined ? <div className={bem('info')}>{info}</div> : null}
</div> </div>
{right !== undefined ? ( {right !== undefined ? <div className={bem('right')}>{right}</div> : null}
<div className={bem('right')}>
{right}
{rightInfo !== undefined ? (
<div className={bem('right-info')}>{rightInfo}</div>
) : null}
</div>
) : null}
{actions !== undefined ? ( {actions !== undefined ? (
<div className={alwaysShowActions ? '' : bem('actions')}>{actions}</div> <div className={alwaysShowActions ? '' : bem('actions')}>{actions}</div>
) : null} ) : null}

View File

@ -50,6 +50,7 @@ export abstract class LeftPaneHelper<T> {
i18n: LocalizerType; i18n: LocalizerType;
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown; setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown;
setComposeGroupName: (_: string) => unknown; setComposeGroupName: (_: string) => unknown;
setComposeGroupExpireTimer: (_: number) => void;
onChangeComposeSearchTerm: ( onChangeComposeSearchTerm: (
event: ChangeEvent<HTMLInputElement> event: ChangeEvent<HTMLInputElement>
) => unknown; ) => unknown;

View File

@ -6,6 +6,7 @@ import React, { ReactChild } from 'react';
import { LeftPaneHelper } from './LeftPaneHelper'; import { LeftPaneHelper } from './LeftPaneHelper';
import { Row, RowType } from '../ConversationList'; import { Row, RowType } from '../ConversationList';
import { PropsDataType as ContactListItemPropsType } from '../conversationList/ContactListItem'; import { PropsDataType as ContactListItemPropsType } from '../conversationList/ContactListItem';
import { DisappearingTimerSelect } from '../DisappearingTimerSelect';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { AvatarInput } from '../AvatarInput'; import { AvatarInput } from '../AvatarInput';
import { Alert } from '../Alert'; import { Alert } from '../Alert';
@ -16,6 +17,7 @@ import { GroupTitleInput } from '../GroupTitleInput';
export type LeftPaneSetGroupMetadataPropsType = { export type LeftPaneSetGroupMetadataPropsType = {
groupAvatar: undefined | ArrayBuffer; groupAvatar: undefined | ArrayBuffer;
groupName: string; groupName: string;
groupExpireTimer: number;
hasError: boolean; hasError: boolean;
isCreating: boolean; isCreating: boolean;
selectedContacts: ReadonlyArray<ContactListItemPropsType>; selectedContacts: ReadonlyArray<ContactListItemPropsType>;
@ -28,6 +30,8 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
private readonly groupName: string; private readonly groupName: string;
private readonly groupExpireTimer: number;
private readonly hasError: boolean; private readonly hasError: boolean;
private readonly isCreating: boolean; private readonly isCreating: boolean;
@ -37,6 +41,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
constructor({ constructor({
groupAvatar, groupAvatar,
groupName, groupName,
groupExpireTimer,
isCreating, isCreating,
hasError, hasError,
selectedContacts, selectedContacts,
@ -45,6 +50,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
this.groupAvatar = groupAvatar; this.groupAvatar = groupAvatar;
this.groupName = groupName; this.groupName = groupName;
this.groupExpireTimer = groupExpireTimer;
this.hasError = hasError; this.hasError = hasError;
this.isCreating = isCreating; this.isCreating = isCreating;
this.selectedContacts = selectedContacts; this.selectedContacts = selectedContacts;
@ -89,12 +95,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
createGroup, createGroup,
i18n, i18n,
setComposeGroupAvatar, setComposeGroupAvatar,
setComposeGroupExpireTimer,
setComposeGroupName, setComposeGroupName,
}: Readonly<{ }: Readonly<{
clearGroupCreationError: () => unknown; clearGroupCreationError: () => unknown;
createGroup: () => unknown; createGroup: () => unknown;
i18n: LocalizerType; i18n: LocalizerType;
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown; setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown;
setComposeGroupExpireTimer: (_: number) => void;
setComposeGroupName: (_: string) => unknown; setComposeGroupName: (_: string) => unknown;
}>): ReactChild { }>): ReactChild {
const disabled = this.isCreating; const disabled = this.isCreating;
@ -128,6 +136,17 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
value={this.groupName} value={this.groupName}
/> />
<section className="module-left-pane__header__form__expire-timer">
<div className="module-left-pane__header__form__expire-timer__label">
{i18n('disappearingMessages')}
</div>
<DisappearingTimerSelect
i18n={i18n}
value={this.groupExpireTimer}
onChange={setComposeGroupExpireTimer}
/>
</section>
{this.hasError && ( {this.hasError && (
<Alert <Alert
body={i18n('setGroupMetadata__error-message')} body={i18n('setGroupMetadata__error-message')}

View File

@ -48,7 +48,6 @@ import {
getClientZkGroupCipher, getClientZkGroupCipher,
getClientZkProfileOperations, getClientZkProfileOperations,
} from './util/zkgroup'; } from './util/zkgroup';
import * as universalExpireTimer from './util/universalExpireTimer';
import { import {
computeHash, computeHash,
deriveMasterKeyFromGroupV1, deriveMasterKeyFromGroupV1,
@ -1477,10 +1476,12 @@ export async function fetchMembershipProof({
export async function createGroupV2({ export async function createGroupV2({
name, name,
avatar, avatar,
expireTimer,
conversationIds, conversationIds,
}: Readonly<{ }: Readonly<{
name: string; name: string;
avatar: undefined | ArrayBuffer; avatar: undefined | ArrayBuffer;
expireTimer: undefined | number;
conversationIds: Array<string>; conversationIds: Array<string>;
}>): Promise<ConversationModel> { }>): Promise<ConversationModel> {
// Ensure we have the credentials we need before attempting GroupsV2 operations // Ensure we have the credentials we need before attempting GroupsV2 operations
@ -1709,7 +1710,6 @@ export async function createGroupV2({
window.MessageController.register(model.id, model); window.MessageController.register(model.id, model);
conversation.trigger('newmessage', model); conversation.trigger('newmessage', model);
const expireTimer = universalExpireTimer.get();
if (expireTimer) { if (expireTimer) {
await conversation.updateExpirationTimer(expireTimer); await conversation.updateExpirationTimer(expireTimer);
} }

View File

@ -20,6 +20,7 @@ import * as groups from '../../groups';
import { calling } from '../../services/calling'; import { calling } from '../../services/calling';
import { getOwn } from '../../util/getOwn'; import { getOwn } from '../../util/getOwn';
import { assert } from '../../util/assert'; import { assert } from '../../util/assert';
import * as universalExpireTimer from '../../util/universalExpireTimer';
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
import { import {
@ -208,9 +209,9 @@ export type PreJoinConversationType = {
}; };
export enum ComposerStep { export enum ComposerStep {
StartDirectConversation, StartDirectConversation = 'StartDirectConversation',
ChooseGroupMembers, ChooseGroupMembers = 'ChooseGroupMembers',
SetGroupMetadata, SetGroupMetadata = 'SetGroupMetadata',
} }
export enum OneTimeModalState { export enum OneTimeModalState {
@ -222,6 +223,7 @@ export enum OneTimeModalState {
type ComposerGroupCreationState = { type ComposerGroupCreationState = {
groupAvatar: undefined | ArrayBuffer; groupAvatar: undefined | ArrayBuffer;
groupName: string; groupName: string;
groupExpireTimer: number;
maximumGroupSizeModalState: OneTimeModalState; maximumGroupSizeModalState: OneTimeModalState;
recommendedGroupSizeModalState: OneTimeModalState; recommendedGroupSizeModalState: OneTimeModalState;
selectedConversationIds: Array<string>; selectedConversationIds: Array<string>;
@ -557,6 +559,10 @@ type SetComposeGroupNameActionType = {
type: 'SET_COMPOSE_GROUP_NAME'; type: 'SET_COMPOSE_GROUP_NAME';
payload: { groupName: string }; payload: { groupName: string };
}; };
type SetComposeGroupExpireTimerActionType = {
type: 'SET_COMPOSE_GROUP_EXPIRE_TIMER';
payload: { groupExpireTimer: number };
};
type SetComposeSearchTermActionType = { type SetComposeSearchTermActionType = {
type: 'SET_COMPOSE_SEARCH_TERM'; type: 'SET_COMPOSE_SEARCH_TERM';
payload: { searchTerm: string }; payload: { searchTerm: string };
@ -625,6 +631,7 @@ export type ConversationActionType =
| SelectedConversationChangedActionType | SelectedConversationChangedActionType
| SetComposeGroupAvatarActionType | SetComposeGroupAvatarActionType
| SetComposeGroupNameActionType | SetComposeGroupNameActionType
| SetComposeGroupExpireTimerActionType
| SetComposeSearchTermActionType | SetComposeSearchTermActionType
| SetConversationHeaderTitleActionType | SetConversationHeaderTitleActionType
| SetIsNearBottomActionType | SetIsNearBottomActionType
@ -679,6 +686,7 @@ export const actions = {
selectMessage, selectMessage,
setComposeGroupAvatar, setComposeGroupAvatar,
setComposeGroupName, setComposeGroupName,
setComposeGroupExpireTimer,
setComposeSearchTerm, setComposeSearchTerm,
setIsNearBottom, setIsNearBottom,
setLoadCountdownStart, setLoadCountdownStart,
@ -903,6 +911,7 @@ function createGroup(): ThunkAction<
const conversation = await groups.createGroupV2({ const conversation = await groups.createGroupV2({
name: composer.groupName.trim(), name: composer.groupName.trim(),
avatar: composer.groupAvatar, avatar: composer.groupAvatar,
expireTimer: composer.groupExpireTimer,
conversationIds: composer.selectedConversationIds, conversationIds: composer.selectedConversationIds,
}); });
dispatch({ dispatch({
@ -1192,6 +1201,15 @@ function setComposeGroupName(groupName: string): SetComposeGroupNameActionType {
}; };
} }
function setComposeGroupExpireTimer(
groupExpireTimer: number
): SetComposeGroupExpireTimerActionType {
return {
type: 'SET_COMPOSE_GROUP_EXPIRE_TIMER',
payload: { groupExpireTimer },
};
}
function setComposeSearchTerm( function setComposeSearchTerm(
searchTerm: string searchTerm: string
): SetComposeSearchTermActionType { ): SetComposeSearchTermActionType {
@ -2346,6 +2364,7 @@ export function reducer(
let maximumGroupSizeModalState: OneTimeModalState; let maximumGroupSizeModalState: OneTimeModalState;
let groupName: string; let groupName: string;
let groupAvatar: undefined | ArrayBuffer; let groupAvatar: undefined | ArrayBuffer;
let groupExpireTimer: number;
switch (state.composer?.step) { switch (state.composer?.step) {
case ComposerStep.ChooseGroupMembers: case ComposerStep.ChooseGroupMembers:
@ -2357,6 +2376,7 @@ export function reducer(
maximumGroupSizeModalState, maximumGroupSizeModalState,
groupName, groupName,
groupAvatar, groupAvatar,
groupExpireTimer,
} = state.composer); } = state.composer);
break; break;
default: default:
@ -2364,6 +2384,7 @@ export function reducer(
recommendedGroupSizeModalState = OneTimeModalState.NeverShown; recommendedGroupSizeModalState = OneTimeModalState.NeverShown;
maximumGroupSizeModalState = OneTimeModalState.NeverShown; maximumGroupSizeModalState = OneTimeModalState.NeverShown;
groupName = ''; groupName = '';
groupExpireTimer = universalExpireTimer.get();
break; break;
} }
@ -2379,6 +2400,7 @@ export function reducer(
maximumGroupSizeModalState, maximumGroupSizeModalState,
groupName, groupName,
groupAvatar, groupAvatar,
groupExpireTimer,
}, },
}; };
} }
@ -2398,6 +2420,7 @@ export function reducer(
...pick(composer, [ ...pick(composer, [
'groupAvatar', 'groupAvatar',
'groupName', 'groupName',
'groupExpireTimer',
'maximumGroupSizeModalState', 'maximumGroupSizeModalState',
'recommendedGroupSizeModalState', 'recommendedGroupSizeModalState',
'selectedConversationIds', 'selectedConversationIds',
@ -2453,6 +2476,25 @@ export function reducer(
} }
} }
if (action.type === 'SET_COMPOSE_GROUP_EXPIRE_TIMER') {
const { composer } = state;
switch (composer?.step) {
case ComposerStep.ChooseGroupMembers:
case ComposerStep.SetGroupMetadata:
return {
...state,
composer: {
...composer,
groupExpireTimer: action.payload.groupExpireTimer,
},
};
default:
assert(false, 'Setting compose group name at this step is a no-op');
return state;
}
}
if (action.type === 'SET_COMPOSE_SEARCH_TERM') { if (action.type === 'SET_COMPOSE_SEARCH_TERM') {
const { composer } = state; const { composer } = state;
if (!composer) { if (!composer) {

View File

@ -518,6 +518,7 @@ const getGroupCreationComposerState = createSelector(
): { ): {
groupName: string; groupName: string;
groupAvatar: undefined | ArrayBuffer; groupAvatar: undefined | ArrayBuffer;
groupExpireTimer: number;
selectedConversationIds: Array<string>; selectedConversationIds: Array<string>;
} => { } => {
switch (composerState?.step) { switch (composerState?.step) {
@ -532,6 +533,7 @@ const getGroupCreationComposerState = createSelector(
return { return {
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
selectedConversationIds: [], selectedConversationIds: [],
}; };
} }
@ -548,6 +550,11 @@ export const getComposeGroupName = createSelector(
(composerState): string => composerState.groupName (composerState): string => composerState.groupName
); );
export const getComposeGroupExpireTimer = createSelector(
getGroupCreationComposerState,
(composerState): number => composerState.groupExpireTimer
);
export const getComposeSelectedContacts = createSelector( export const getComposeSelectedContacts = createSelector(
getConversationLookup, getConversationLookup,
getGroupCreationComposerState, getGroupCreationComposerState,

View File

@ -23,6 +23,7 @@ import {
getFilteredComposeGroups, getFilteredComposeGroups,
getComposeGroupAvatar, getComposeGroupAvatar,
getComposeGroupName, getComposeGroupName,
getComposeGroupExpireTimer,
getComposeSelectedContacts, getComposeSelectedContacts,
getComposerConversationSearchTerm, getComposerConversationSearchTerm,
getComposerStep, getComposerStep,
@ -129,6 +130,7 @@ const getModeSpecificProps = (
mode: LeftPaneMode.SetGroupMetadata, mode: LeftPaneMode.SetGroupMetadata,
groupAvatar: getComposeGroupAvatar(state), groupAvatar: getComposeGroupAvatar(state),
groupName: getComposeGroupName(state), groupName: getComposeGroupName(state),
groupExpireTimer: getComposeGroupExpireTimer(state),
hasError: hasGroupCreationError(state), hasError: hasGroupCreationError(state),
isCreating: isCreatingGroup(state), isCreating: isCreatingGroup(state),
selectedContacts: getComposeSelectedContacts(state), selectedContacts: getComposeSelectedContacts(state),

View File

@ -325,6 +325,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}, },
}; };
@ -346,6 +347,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -388,6 +390,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: false as const, hasError: false as const,
}, },
@ -409,6 +412,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: true as const, hasError: true as const,
}, },
@ -449,6 +453,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: true as const, hasError: true as const,
}, },
@ -470,6 +475,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: true as const, isCreating: true as const,
hasError: false as const, hasError: false as const,
}, },
@ -1080,6 +1086,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}, },
user: { user: {
@ -1135,6 +1142,7 @@ describe('both/state/selectors/conversations', () => {
cantAddContactIdForModal: undefined, cantAddContactIdForModal: undefined,
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -1159,6 +1167,7 @@ describe('both/state/selectors/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -1596,6 +1605,7 @@ describe('both/state/selectors/conversations', () => {
cantAddContactIdForModal: undefined, cantAddContactIdForModal: undefined,
searchTerm: 'to be cleared', searchTerm: 'to be cleared',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.Showing, maximumGroupSizeModalState: OneTimeModalState.Showing,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -1621,6 +1631,7 @@ describe('both/state/selectors/conversations', () => {
cantAddContactIdForModal: undefined, cantAddContactIdForModal: undefined,
searchTerm: 'to be cleared', searchTerm: 'to be cleared',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.Showing, recommendedGroupSizeModalState: OneTimeModalState.Showing,
@ -1650,6 +1661,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -1671,6 +1683,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: new Uint8Array([1, 2, 3]).buffer, groupAvatar: new Uint8Array([1, 2, 3]).buffer,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -1697,6 +1710,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'foo bar', groupName: 'foo bar',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -1730,6 +1744,7 @@ describe('both/state/selectors/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'foo bar', groupName: 'foo bar',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },

View File

@ -454,6 +454,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: undefined, cantAddContactIdForModal: undefined,
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -482,6 +483,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: true as const, hasError: true as const,
}, },
@ -517,6 +519,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -567,6 +570,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.Showing, maximumGroupSizeModalState: OneTimeModalState.Showing,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -592,6 +596,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -612,6 +617,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.Shown, maximumGroupSizeModalState: OneTimeModalState.Shown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -634,6 +640,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.Showing, recommendedGroupSizeModalState: OneTimeModalState.Showing,
@ -659,6 +666,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -679,6 +687,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: 'abc123', cantAddContactIdForModal: 'abc123',
searchTerm: '', searchTerm: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.Shown, recommendedGroupSizeModalState: OneTimeModalState.Shown,
@ -703,6 +712,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'Foo Bar Group', groupName: 'Foo Bar Group',
groupAvatar: new Uint8Array([1, 2, 3]).buffer, groupAvatar: new Uint8Array([1, 2, 3]).buffer,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: true as const, hasError: true as const,
}, },
@ -748,6 +758,7 @@ describe('both/state/ducks/conversations', () => {
sinon.assert.calledWith(createGroupStub, { sinon.assert.calledWith(createGroupStub, {
name: 'Foo Bar Group', name: 'Foo Bar Group',
avatar: new Uint8Array([1, 2, 3]).buffer, avatar: new Uint8Array([1, 2, 3]).buffer,
expireTimer: 0,
conversationIds: ['abc123'], conversationIds: ['abc123'],
}); });
}); });
@ -1205,6 +1216,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'foo', groupName: 'foo',
groupAvatar: new ArrayBuffer(2), groupAvatar: new ArrayBuffer(2),
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: false as const, hasError: false as const,
}, },
@ -1230,6 +1242,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'foo', groupName: 'foo',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: false as const, hasError: false as const,
}, },
@ -1255,6 +1268,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false as const, isCreating: false as const,
hasError: false as const, hasError: false as const,
}, },
@ -1424,6 +1438,7 @@ describe('both/state/ducks/conversations', () => {
cantAddContactIdForModal: undefined, cantAddContactIdForModal: undefined,
searchTerm: 'to be cleared', searchTerm: 'to be cleared',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
recommendedGroupSizeModalState: OneTimeModalState.NeverShown, recommendedGroupSizeModalState: OneTimeModalState.NeverShown,
@ -1451,6 +1466,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -1515,6 +1531,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1530,6 +1547,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = showChooseGroupMembers(); const action = showChooseGroupMembers();
@ -1549,6 +1567,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'Foo Bar Group', groupName: 'Foo Bar Group',
groupAvatar: new Uint8Array([4, 2]).buffer, groupAvatar: new Uint8Array([4, 2]).buffer,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -1566,6 +1585,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'Foo Bar Group', groupName: 'Foo Bar Group',
groupAvatar: new Uint8Array([4, 2]).buffer, groupAvatar: new Uint8Array([4, 2]).buffer,
groupExpireTimer: 0,
}); });
}); });
@ -1584,6 +1604,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1605,6 +1626,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
}); });
@ -1622,6 +1644,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = startSettingGroupMetadata(); const action = startSettingGroupMetadata();
@ -1634,6 +1657,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false, hasError: false,
}); });
@ -1651,6 +1675,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'Foo Bar Group', groupName: 'Foo Bar Group',
groupAvatar: new Uint8Array([6, 9]).buffer, groupAvatar: new Uint8Array([6, 9]).buffer,
groupExpireTimer: 0,
}, },
}; };
const action = startSettingGroupMetadata(); const action = startSettingGroupMetadata();
@ -1663,6 +1688,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'Foo Bar Group', groupName: 'Foo Bar Group',
groupAvatar: new Uint8Array([6, 9]).buffer, groupAvatar: new Uint8Array([6, 9]).buffer,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}); });
@ -1678,6 +1704,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: 'Foo Bar Group', groupName: 'Foo Bar Group',
groupAvatar: new Uint8Array([4, 2]).buffer, groupAvatar: new Uint8Array([4, 2]).buffer,
groupExpireTimer: 0,
isCreating: false, isCreating: false,
hasError: false as const, hasError: false as const,
}, },
@ -1731,6 +1758,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const one = reducer(zero, getAction('abc', zero)); const one = reducer(zero, getAction('abc', zero));
@ -1745,6 +1773,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1760,6 +1789,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction('abc', state); const action = getAction('abc', state);
@ -1774,6 +1804,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1792,6 +1823,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(newUuid, state); const action = getAction(newUuid, state);
@ -1806,6 +1838,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1824,6 +1857,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(newUuid, state); const action = getAction(newUuid, state);
@ -1838,6 +1872,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1858,6 +1893,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(uuid(), state); const action = getAction(uuid(), state);
@ -1881,6 +1917,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(newUuid, state); const action = getAction(newUuid, state);
@ -1895,6 +1932,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.Showing, maximumGroupSizeModalState: OneTimeModalState.Showing,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1913,6 +1951,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.Shown, maximumGroupSizeModalState: OneTimeModalState.Shown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(newUuid, state); const action = getAction(newUuid, state);
@ -1927,6 +1966,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.Shown, maximumGroupSizeModalState: OneTimeModalState.Shown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}); });
}); });
@ -1942,6 +1982,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(uuid(), state); const action = getAction(uuid(), state);
@ -1969,6 +2010,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(uuid(), state); const action = getAction(uuid(), state);
@ -1995,6 +2037,7 @@ describe('both/state/ducks/conversations', () => {
maximumGroupSizeModalState: OneTimeModalState.NeverShown, maximumGroupSizeModalState: OneTimeModalState.NeverShown,
groupName: '', groupName: '',
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
}, },
}; };
const action = getAction(uuid(), state); const action = getAction(uuid(), state);

View File

@ -14,6 +14,7 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
const showChooseGroupMembers = sinon.fake(); const showChooseGroupMembers = sinon.fake();
const helper = new LeftPaneSetGroupMetadataHelper({ const helper = new LeftPaneSetGroupMetadataHelper({
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
hasError: false, hasError: false,
isCreating: false, isCreating: false,
@ -29,6 +30,7 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
it("returns undefined (i.e., you can't go back) if a request is active", () => { it("returns undefined (i.e., you can't go back) if a request is active", () => {
const helper = new LeftPaneSetGroupMetadataHelper({ const helper = new LeftPaneSetGroupMetadataHelper({
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: 'Foo Bar', groupName: 'Foo Bar',
hasError: false, hasError: false,
isCreating: true, isCreating: true,
@ -46,6 +48,7 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
assert.strictEqual( assert.strictEqual(
new LeftPaneSetGroupMetadataHelper({ new LeftPaneSetGroupMetadataHelper({
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
hasError: false, hasError: false,
isCreating: false, isCreating: false,
@ -59,6 +62,7 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
assert.strictEqual( assert.strictEqual(
new LeftPaneSetGroupMetadataHelper({ new LeftPaneSetGroupMetadataHelper({
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
hasError: false, hasError: false,
isCreating: false, isCreating: false,
@ -77,6 +81,7 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
assert.isUndefined( assert.isUndefined(
new LeftPaneSetGroupMetadataHelper({ new LeftPaneSetGroupMetadataHelper({
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
hasError: false, hasError: false,
isCreating: false, isCreating: false,
@ -92,6 +97,7 @@ describe('LeftPaneSetGroupMetadataHelper', () => {
]; ];
const helper = new LeftPaneSetGroupMetadataHelper({ const helper = new LeftPaneSetGroupMetadataHelper({
groupAvatar: undefined, groupAvatar: undefined,
groupExpireTimer: 0,
groupName: '', groupName: '',
hasError: false, hasError: false,
isCreating: false, isCreating: false,

2
ts/window.d.ts vendored
View File

@ -107,7 +107,7 @@ import { MessageDetail } from './components/conversation/MessageDetail';
import { ProgressModal } from './components/ProgressModal'; import { ProgressModal } from './components/ProgressModal';
import { Quote } from './components/conversation/Quote'; import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview'; import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/conversation/DisappearingTimeDialog'; import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
import { MIMEType } from './types/MIME'; import { MIMEType } from './types/MIME';
import { AttachmentType } from './types/Attachment'; import { AttachmentType } from './types/Attachment';
import { ElectronLocaleType } from './util/mapToSupportLocale'; import { ElectronLocaleType } from './util/mapToSupportLocale';