Universal Disappearing Messages

This commit is contained in:
Fedor Indutny 2021-06-01 13:45:43 -07:00 committed by GitHub
parent c63871d71b
commit 19f8042cd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 1224 additions and 191 deletions

View file

@ -13,6 +13,7 @@ import {
} from 'react-contextmenu';
import { Emojify } from './Emojify';
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
import { Avatar, AvatarSize } from '../Avatar';
import { InContactsIcon } from '../InContactsIcon';
@ -92,10 +93,18 @@ export type PropsType = PropsDataType &
PropsActionsType &
PropsHousekeepingType;
enum ModalState {
NothingOpen,
CustomDisappearingTimeout,
}
type StateType = {
isNarrow: boolean;
modalState: ModalState;
};
const TIMER_ITEM_CLASS = 'module-ConversationHeader__disappearing-timer__item';
export class ConversationHeader extends React.Component<PropsType, StateType> {
private showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
@ -106,7 +115,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
public constructor(props: PropsType) {
super(props);
this.state = { isNarrow: false };
this.state = { isNarrow: false, modalState: ModalState.NothingOpen };
this.menuTriggerRef = React.createRef();
this.showMenuBound = this.showMenu.bind(this);
@ -355,6 +364,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
i18n,
acceptedMessageRequest,
canChangeTimer,
expireTimer,
isArchived,
isMe,
isPinned,
@ -427,23 +437,60 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
const hasGV2AdminEnabled = isGroup && groupVersion === 2;
const isActiveExpireTimer = (value: number): boolean => {
if (!expireTimer) {
return value === 0;
}
// Custom time...
if (value === -1) {
return !expirationTimer.DEFAULT_DURATIONS_SET.has(expireTimer);
}
return value === expireTimer;
};
const expireDurations: ReadonlyArray<ReactNode> = [
...expirationTimer.DEFAULT_DURATIONS_IN_SECONDS,
-1,
].map((seconds: number) => {
let text: string;
if (seconds === -1) {
text = i18n('customDisappearingTimeOption');
} else {
text = expirationTimer.format(i18n, seconds, {
capitalizeOff: true,
});
}
const onDurationClick = () => {
if (seconds === -1) {
this.setState({
modalState: ModalState.CustomDisappearingTimeout,
});
} else {
onSetDisappearingMessages(seconds);
}
};
return (
<MenuItem key={seconds} onClick={onDurationClick}>
<div
className={classNames(
TIMER_ITEM_CLASS,
isActiveExpireTimer(seconds) && `${TIMER_ITEM_CLASS}--active`
)}
>
{text}
</div>
</MenuItem>
);
});
return (
<ContextMenu id={triggerId}>
{disableTimerChanges ? null : (
<SubMenu title={disappearingTitle}>
{expirationTimer.DEFAULT_DURATIONS_IN_SECONDS.map(
(seconds: number) => (
<MenuItem
key={seconds}
onClick={() => {
onSetDisappearingMessages(seconds);
}}
>
{expirationTimer.format(i18n, seconds)}
</MenuItem>
)
)}
</SubMenu>
<SubMenu title={disappearingTitle}>{expireDurations}</SubMenu>
)}
<SubMenu title={muteTitle}>
{muteOptions.map(item => (
@ -578,36 +625,64 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
}
public render(): ReactNode {
const { id, isSMSOnly } = this.props;
const { isNarrow } = this.state;
const {
id,
isSMSOnly,
i18n,
onSetDisappearingMessages,
expireTimer,
} = this.props;
const { isNarrow, modalState } = this.state;
const triggerId = `conversation-${id}`;
let modalNode: ReactNode;
if (modalState === ModalState.NothingOpen) {
modalNode = undefined;
} else if (modalState === ModalState.CustomDisappearingTimeout) {
modalNode = (
<DisappearingTimeDialog
i18n={i18n}
initialValue={expireTimer}
onSubmit={value => {
this.setState({ modalState: ModalState.NothingOpen });
onSetDisappearingMessages(value);
}}
onClose={() => this.setState({ modalState: ModalState.NothingOpen })}
/>
);
} else {
throw missingCaseError(modalState);
}
return (
<Measure
bounds
onResize={({ bounds }) => {
if (!bounds || !bounds.width) {
return;
}
this.setState({ isNarrow: bounds.width < 500 });
}}
>
{({ measureRef }) => (
<div
className={classNames('module-ConversationHeader', {
'module-ConversationHeader--narrow': isNarrow,
})}
ref={measureRef}
>
{this.renderBackButton()}
{this.renderHeader()}
{!isSMSOnly && this.renderOutgoingCallButtons()}
{this.renderSearchButton()}
{this.renderMoreButton(triggerId)}
{this.renderMenu(triggerId)}
</div>
)}
</Measure>
<>
{modalNode}
<Measure
bounds
onResize={({ bounds }) => {
if (!bounds || !bounds.width) {
return;
}
this.setState({ isNarrow: bounds.width < 500 });
}}
>
{({ measureRef }) => (
<div
className={classNames('module-ConversationHeader', {
'module-ConversationHeader--narrow': isNarrow,
})}
ref={measureRef}
>
{this.renderBackButton()}
{this.renderHeader()}
{!isSMSOnly && this.renderOutgoingCallButtons()}
{this.renderSearchButton()}
{this.renderMoreButton(triggerId)}
{this.renderMenu(triggerId)}
</div>
)}
</Measure>
</>
);
}
}