Release Note Channel: Mute/Unmute UI, hide UI elements
Co-authored-by: yash-signal <yash@signal.org>
This commit is contained in:
parent
141cf2d482
commit
d93f488cbb
17 changed files with 305 additions and 150 deletions
|
@ -29,6 +29,8 @@
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title-contact-icon {
|
&__title-contact-icon {
|
||||||
|
|
30
stylesheets/components/SignalConversationMuteToggle.scss
Normal file
30
stylesheets/components/SignalConversationMuteToggle.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
@use '../mixins';
|
||||||
|
@use '../variables';
|
||||||
|
|
||||||
|
.SignalConversationMuteToggle {
|
||||||
|
@include mixins.light-theme() {
|
||||||
|
background-color: variables.$color-white;
|
||||||
|
border-top: 0.5px solid variables.$color-black-alpha-16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include mixins.dark-theme() {
|
||||||
|
background-color: variables.$color-gray-95;
|
||||||
|
border-top: 0.5px solid variables.$color-gray-65;
|
||||||
|
}
|
||||||
|
|
||||||
|
height: variables.$header-height;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
color: variables.$color-ultramarine-light;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
@include mixins.button-reset;
|
||||||
|
}
|
||||||
|
}
|
|
@ -159,6 +159,7 @@
|
||||||
@use 'components/SendStoryModal.scss';
|
@use 'components/SendStoryModal.scss';
|
||||||
@use 'components/ShortcutGuide.scss';
|
@use 'components/ShortcutGuide.scss';
|
||||||
@use 'components/SignalConnectionsModal.scss';
|
@use 'components/SignalConnectionsModal.scss';
|
||||||
|
@use 'components/SignalConversationMuteToggle.scss';
|
||||||
@use 'components/Slider.scss';
|
@use 'components/Slider.scss';
|
||||||
@use 'components/SpinnerV2.scss';
|
@use 'components/SpinnerV2.scss';
|
||||||
@use 'components/StagedLinkPreview.scss';
|
@use 'components/StagedLinkPreview.scss';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import type { Meta } from '@storybook/react';
|
import type { Meta } from '@storybook/react';
|
||||||
import { IMAGE_JPEG } from '../types/MIME';
|
import { IMAGE_JPEG } from '../types/MIME';
|
||||||
|
@ -125,6 +125,10 @@ export default {
|
||||||
selectedMessageIds: undefined,
|
selectedMessageIds: undefined,
|
||||||
toggleSelectMode: action('toggleSelectMode'),
|
toggleSelectMode: action('toggleSelectMode'),
|
||||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||||
|
// Signal Conversation
|
||||||
|
isSignalConversation: false,
|
||||||
|
isMuted: false,
|
||||||
|
setMuteExpiration: action('setMuteExpiration'),
|
||||||
},
|
},
|
||||||
} satisfies Meta<Props>;
|
} satisfies Meta<Props>;
|
||||||
|
|
||||||
|
@ -263,3 +267,21 @@ export function NoFormattingMenu(args: Props): JSX.Element {
|
||||||
<CompositionArea {...args} theme={theme} isFormattingEnabled={false} />
|
<CompositionArea {...args} theme={theme} isFormattingEnabled={false} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SignalConversationMuteToggle(args: Props): JSX.Element {
|
||||||
|
const theme = useContext(StorybookThemeContext);
|
||||||
|
const [isMuted, setIsMuted] = useState(true);
|
||||||
|
|
||||||
|
function setIsMutedByTime(_: string, muteExpiresAt: number) {
|
||||||
|
setIsMuted(muteExpiresAt > Date.now());
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<CompositionArea
|
||||||
|
{...args}
|
||||||
|
theme={theme}
|
||||||
|
isSignalConversation
|
||||||
|
isMuted={isMuted}
|
||||||
|
setMuteExpiration={setIsMutedByTime}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ import type { ShowToastAction } from '../state/ducks/toast';
|
||||||
import type { DraftEditMessageType } from '../model-types.d';
|
import type { DraftEditMessageType } from '../model-types.d';
|
||||||
import type { ForwardMessagesPayload } from '../state/ducks/globalModals';
|
import type { ForwardMessagesPayload } from '../state/ducks/globalModals';
|
||||||
import { ForwardMessagesModalType } from './ForwardMessagesModal';
|
import { ForwardMessagesModalType } from './ForwardMessagesModal';
|
||||||
|
import { SignalConversationMuteToggle } from './conversation/SignalConversationMuteToggle';
|
||||||
|
|
||||||
export type OwnProps = Readonly<{
|
export type OwnProps = Readonly<{
|
||||||
acceptedMessageRequest: boolean | null;
|
acceptedMessageRequest: boolean | null;
|
||||||
|
@ -118,6 +119,7 @@ export type OwnProps = Readonly<{
|
||||||
recordingState: RecordingState;
|
recordingState: RecordingState;
|
||||||
messageCompositionId: string;
|
messageCompositionId: string;
|
||||||
shouldHidePopovers: boolean | null;
|
shouldHidePopovers: boolean | null;
|
||||||
|
isMuted: boolean;
|
||||||
isSmsOnlyOrUnregistered: boolean | null;
|
isSmsOnlyOrUnregistered: boolean | null;
|
||||||
left: boolean | null;
|
left: boolean | null;
|
||||||
linkPreviewLoading: boolean;
|
linkPreviewLoading: boolean;
|
||||||
|
@ -130,6 +132,7 @@ export type OwnProps = Readonly<{
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
files: ReadonlyArray<File>;
|
files: ReadonlyArray<File>;
|
||||||
}) => unknown;
|
}) => unknown;
|
||||||
|
setMuteExpiration(conversationId: string, muteExpiresAt: number): unknown;
|
||||||
setMediaQualitySetting(conversationId: string, isHQ: boolean): unknown;
|
setMediaQualitySetting(conversationId: string, isHQ: boolean): unknown;
|
||||||
sendStickerMessage(
|
sendStickerMessage(
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -239,6 +242,7 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
imageToBlurHash,
|
imageToBlurHash,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isSignalConversation,
|
isSignalConversation,
|
||||||
|
isMuted,
|
||||||
isActive,
|
isActive,
|
||||||
lastEditableMessageId,
|
lastEditableMessageId,
|
||||||
messageCompositionId,
|
messageCompositionId,
|
||||||
|
@ -254,6 +258,7 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
shouldHidePopovers,
|
shouldHidePopovers,
|
||||||
showToast,
|
showToast,
|
||||||
theme,
|
theme,
|
||||||
|
setMuteExpiration,
|
||||||
|
|
||||||
// AttachmentList
|
// AttachmentList
|
||||||
draftAttachments,
|
draftAttachments,
|
||||||
|
@ -737,8 +742,14 @@ export const CompositionArea = memo(function CompositionArea({
|
||||||
useEscapeHandling(handleEscape);
|
useEscapeHandling(handleEscape);
|
||||||
|
|
||||||
if (isSignalConversation) {
|
if (isSignalConversation) {
|
||||||
// TODO DESKTOP-4547
|
return (
|
||||||
return <div />;
|
<SignalConversationMuteToggle
|
||||||
|
conversationId={conversationId}
|
||||||
|
isMuted={isMuted}
|
||||||
|
i18n={i18n}
|
||||||
|
setMuteExpiration={setMuteExpiration}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedMessageIds != null) {
|
if (selectedMessageIds != null) {
|
||||||
|
|
|
@ -47,6 +47,7 @@ function HeaderInfoTitle({
|
||||||
type,
|
type,
|
||||||
i18n,
|
i18n,
|
||||||
isMe,
|
isMe,
|
||||||
|
isSignalConversation,
|
||||||
headerRef,
|
headerRef,
|
||||||
}: {
|
}: {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
|
@ -54,8 +55,18 @@ function HeaderInfoTitle({
|
||||||
type: ConversationTypeType;
|
type: ConversationTypeType;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
|
isSignalConversation: boolean;
|
||||||
headerRef: React.RefObject<HTMLDivElement>;
|
headerRef: React.RefObject<HTMLDivElement>;
|
||||||
}) {
|
}) {
|
||||||
|
if (isSignalConversation) {
|
||||||
|
return (
|
||||||
|
<div className="module-ConversationHeader__header__info__title">
|
||||||
|
<UserText text={title} />
|
||||||
|
<span className="ContactModal__official-badge" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
return (
|
return (
|
||||||
<div className="module-ConversationHeader__header__info__title">
|
<div className="module-ConversationHeader__header__info__title">
|
||||||
|
@ -294,6 +305,7 @@ export const ConversationHeader = memo(function ConversationHeader({
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onViewUserStories={onViewUserStories}
|
onViewUserStories={onViewUserStories}
|
||||||
onViewConversationDetails={onViewConversationDetails}
|
onViewConversationDetails={onViewConversationDetails}
|
||||||
|
isSignalConversation={isSignalConversation ?? false}
|
||||||
/>
|
/>
|
||||||
{!isSmsOnlyOrUnregistered && !isSignalConversation && (
|
{!isSmsOnlyOrUnregistered && !isSignalConversation && (
|
||||||
<OutgoingCallButtons
|
<OutgoingCallButtons
|
||||||
|
@ -415,6 +427,7 @@ function HeaderContent({
|
||||||
i18n,
|
i18n,
|
||||||
sharedGroupNames,
|
sharedGroupNames,
|
||||||
theme,
|
theme,
|
||||||
|
isSignalConversation,
|
||||||
onViewUserStories,
|
onViewUserStories,
|
||||||
onViewConversationDetails,
|
onViewConversationDetails,
|
||||||
}: {
|
}: {
|
||||||
|
@ -425,6 +438,7 @@ function HeaderContent({
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
sharedGroupNames: ReadonlyArray<string>;
|
sharedGroupNames: ReadonlyArray<string>;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
|
isSignalConversation: boolean;
|
||||||
onViewUserStories: () => void;
|
onViewUserStories: () => void;
|
||||||
onViewConversationDetails: () => void;
|
onViewConversationDetails: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
@ -476,6 +490,7 @@ function HeaderContent({
|
||||||
type={conversation.type}
|
type={conversation.type}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
isMe={conversation.isMe}
|
||||||
|
isSignalConversation={isSignalConversation}
|
||||||
headerRef={headerRef}
|
headerRef={headerRef}
|
||||||
/>
|
/>
|
||||||
{(conversation.expireTimer != null || conversation.isVerified) && (
|
{(conversation.expireTimer != null || conversation.isVerified) && (
|
||||||
|
|
34
ts/components/conversation/SignalConversationMuteToggle.tsx
Normal file
34
ts/components/conversation/SignalConversationMuteToggle.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type { LocalizerType } from '../../types/I18N';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isMuted: boolean;
|
||||||
|
i18n: LocalizerType;
|
||||||
|
setMuteExpiration: (conversationId: string, muteExpiresAt: number) => unknown;
|
||||||
|
conversationId: string;
|
||||||
|
};
|
||||||
|
export function SignalConversationMuteToggle({
|
||||||
|
isMuted,
|
||||||
|
i18n,
|
||||||
|
setMuteExpiration,
|
||||||
|
conversationId,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const onMuteToggleClicked = () => {
|
||||||
|
setMuteExpiration(conversationId, isMuted ? 0 : Number.MAX_SAFE_INTEGER);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SignalConversationMuteToggle">
|
||||||
|
<button
|
||||||
|
onClick={onMuteToggleClicked}
|
||||||
|
type="button"
|
||||||
|
className="SignalConversationMuteToggle__text"
|
||||||
|
>
|
||||||
|
{isMuted ? i18n('icu:unmute') : i18n('icu:mute')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ const createProps = (
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
isGroup: true,
|
isGroup: true,
|
||||||
|
isSignalConversation: false,
|
||||||
leaveGroup: action('leaveGroup'),
|
leaveGroup: action('leaveGroup'),
|
||||||
loadRecentMediaItems: action('loadRecentMediaItems'),
|
loadRecentMediaItems: action('loadRecentMediaItems'),
|
||||||
memberships: times(32, i => ({
|
memberships: times(32, i => ({
|
||||||
|
@ -244,3 +245,11 @@ export function InAnotherCallIndividual(): JSX.Element {
|
||||||
|
|
||||||
return <ConversationDetails {...props} hasActiveCall isGroup={false} />;
|
return <ConversationDetails {...props} hasActiveCall isGroup={false} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SignalConversation(): JSX.Element {
|
||||||
|
const props = createProps();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConversationDetails {...props} isSignalConversation isGroup={false} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ export type StateProps = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
isGroup: boolean;
|
isGroup: boolean;
|
||||||
|
isSignalConversation: boolean;
|
||||||
groupsInCommon: ReadonlyArray<ConversationType>;
|
groupsInCommon: ReadonlyArray<ConversationType>;
|
||||||
maxGroupSize: number;
|
maxGroupSize: number;
|
||||||
maxRecommendedGroupSize: number;
|
maxRecommendedGroupSize: number;
|
||||||
|
@ -181,6 +182,7 @@ export function ConversationDetails({
|
||||||
i18n,
|
i18n,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isGroup,
|
isGroup,
|
||||||
|
isSignalConversation,
|
||||||
leaveGroup,
|
leaveGroup,
|
||||||
loadRecentMediaItems,
|
loadRecentMediaItems,
|
||||||
memberships,
|
memberships,
|
||||||
|
@ -397,6 +399,7 @@ export function ConversationDetails({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={conversation.isMe}
|
isMe={conversation.isMe}
|
||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
|
isSignalConversation={isSignalConversation}
|
||||||
membersCount={conversation.membersCount ?? null}
|
membersCount={conversation.membersCount ?? null}
|
||||||
startEditing={(isGroupTitle: boolean) => {
|
startEditing={(isGroupTitle: boolean) => {
|
||||||
setModalState(
|
setModalState(
|
||||||
|
@ -424,7 +427,7 @@ export function ConversationDetails({
|
||||||
{i18n('icu:ConversationDetails__HeaderButton--Message')}
|
{i18n('icu:ConversationDetails__HeaderButton--Message')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!conversation.isMe && (
|
{!conversation.isMe && !isSignalConversation && (
|
||||||
<>
|
<>
|
||||||
<ConversationDetailsCallButton
|
<ConversationDetailsCallButton
|
||||||
hasActiveCall={hasActiveCall}
|
hasActiveCall={hasActiveCall}
|
||||||
|
@ -477,152 +480,157 @@ export function ConversationDetails({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PanelSection>
|
{!isSignalConversation && (
|
||||||
{!isGroup || canEditGroupInfo ? (
|
<PanelSection>
|
||||||
<PanelRow
|
{!isGroup || canEditGroupInfo ? (
|
||||||
icon={
|
<PanelRow
|
||||||
<ConversationDetailsIcon
|
icon={
|
||||||
ariaLabel={i18n(
|
<ConversationDetailsIcon
|
||||||
'icu:ConversationDetails--disappearing-messages-label'
|
ariaLabel={i18n(
|
||||||
)}
|
'icu:ConversationDetails--disappearing-messages-label'
|
||||||
icon={IconType.timer}
|
)}
|
||||||
/>
|
icon={IconType.timer}
|
||||||
}
|
/>
|
||||||
info={
|
}
|
||||||
isGroup
|
info={
|
||||||
? i18n(
|
isGroup
|
||||||
'icu:ConversationDetails--disappearing-messages-info--group'
|
? i18n(
|
||||||
)
|
'icu:ConversationDetails--disappearing-messages-info--group'
|
||||||
: i18n(
|
)
|
||||||
'icu:ConversationDetails--disappearing-messages-info--direct'
|
: i18n(
|
||||||
)
|
'icu:ConversationDetails--disappearing-messages-info--direct'
|
||||||
}
|
)
|
||||||
label={i18n('icu:ConversationDetails--disappearing-messages-label')}
|
}
|
||||||
right={
|
label={i18n(
|
||||||
<DisappearingTimerSelect
|
'icu:ConversationDetails--disappearing-messages-label'
|
||||||
i18n={i18n}
|
)}
|
||||||
value={conversation.expireTimer || DurationInSeconds.ZERO}
|
right={
|
||||||
onChange={value =>
|
<DisappearingTimerSelect
|
||||||
setDisappearingMessages(conversation.id, value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{canHaveNicknameAndNote(conversation) && (
|
|
||||||
<PanelRow
|
|
||||||
icon={
|
|
||||||
<ConversationDetailsIcon
|
|
||||||
ariaLabel={i18n('icu:ConversationDetails--nickname-label')}
|
|
||||||
icon={IconType.edit}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={i18n('icu:ConversationDetails--nickname-label')}
|
|
||||||
onClick={onOpenEditNicknameAndNoteModal}
|
|
||||||
actions={
|
|
||||||
(conversation.nicknameGivenName ||
|
|
||||||
conversation.nicknameFamilyName ||
|
|
||||||
conversation.note) && (
|
|
||||||
<ContextMenu
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
portalToRoot
|
value={conversation.expireTimer || DurationInSeconds.ZERO}
|
||||||
popperOptions={{
|
onChange={value =>
|
||||||
placement: 'bottom',
|
setDisappearingMessages(conversation.id, value)
|
||||||
strategy: 'absolute',
|
}
|
||||||
}}
|
/>
|
||||||
menuOptions={[
|
}
|
||||||
{
|
/>
|
||||||
icon: 'ConversationDetails--nickname-actions--delete',
|
) : null}
|
||||||
label: i18n(
|
{canHaveNicknameAndNote(conversation) && (
|
||||||
'icu:ConversationDetails--nickname-actions--delete'
|
<PanelRow
|
||||||
),
|
icon={
|
||||||
onClick: () => {
|
<ConversationDetailsIcon
|
||||||
setModalState(ModalState.ConfirmDeleteNicknameAndNote);
|
ariaLabel={i18n('icu:ConversationDetails--nickname-label')}
|
||||||
|
icon={IconType.edit}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n('icu:ConversationDetails--nickname-label')}
|
||||||
|
onClick={onOpenEditNicknameAndNoteModal}
|
||||||
|
actions={
|
||||||
|
(conversation.nicknameGivenName ||
|
||||||
|
conversation.nicknameFamilyName ||
|
||||||
|
conversation.note) && (
|
||||||
|
<ContextMenu
|
||||||
|
i18n={i18n}
|
||||||
|
portalToRoot
|
||||||
|
popperOptions={{
|
||||||
|
placement: 'bottom',
|
||||||
|
strategy: 'absolute',
|
||||||
|
}}
|
||||||
|
menuOptions={[
|
||||||
|
{
|
||||||
|
icon: 'ConversationDetails--nickname-actions--delete',
|
||||||
|
label: i18n(
|
||||||
|
'icu:ConversationDetails--nickname-actions--delete'
|
||||||
|
),
|
||||||
|
onClick: () => {
|
||||||
|
setModalState(
|
||||||
|
ModalState.ConfirmDeleteNicknameAndNote
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
>
|
||||||
>
|
{({ onClick }) => {
|
||||||
{({ onClick }) => {
|
return (
|
||||||
return (
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
className="ConversationDetails--nickname-actions"
|
||||||
className="ConversationDetails--nickname-actions"
|
onClick={onClick}
|
||||||
onClick={onClick}
|
>
|
||||||
>
|
<span className="ConversationDetails--nickname-actions-label">
|
||||||
<span className="ConversationDetails--nickname-actions-label">
|
{i18n('icu:ConversationDetails--nickname-actions')}
|
||||||
{i18n('icu:ConversationDetails--nickname-actions')}
|
</span>
|
||||||
</span>
|
</button>
|
||||||
</button>
|
);
|
||||||
);
|
}}
|
||||||
|
</ContextMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedNavTab === NavTab.Chats && (
|
||||||
|
<PanelRow
|
||||||
|
icon={
|
||||||
|
<ConversationDetailsIcon
|
||||||
|
ariaLabel={i18n('icu:showChatColorEditor')}
|
||||||
|
icon={IconType.color}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={i18n('icu:showChatColorEditor')}
|
||||||
|
onClick={() => {
|
||||||
|
pushPanelForConversation({
|
||||||
|
type: PanelType.ChatColorEditor,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
right={
|
||||||
|
<div
|
||||||
|
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${conversation.conversationColor}`}
|
||||||
|
style={{
|
||||||
|
...getCustomColorStyle(conversation.customColor),
|
||||||
}}
|
}}
|
||||||
</ContextMenu>
|
/>
|
||||||
)
|
}
|
||||||
}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
{isGroup && (
|
||||||
{selectedNavTab === NavTab.Chats && (
|
<PanelRow
|
||||||
<PanelRow
|
icon={
|
||||||
icon={
|
<ConversationDetailsIcon
|
||||||
<ConversationDetailsIcon
|
ariaLabel={i18n('icu:ConversationDetails--notifications')}
|
||||||
ariaLabel={i18n('icu:showChatColorEditor')}
|
icon={IconType.notifications}
|
||||||
icon={IconType.color}
|
/>
|
||||||
/>
|
}
|
||||||
}
|
label={i18n('icu:ConversationDetails--notifications')}
|
||||||
label={i18n('icu:showChatColorEditor')}
|
onClick={() =>
|
||||||
onClick={() => {
|
pushPanelForConversation({
|
||||||
pushPanelForConversation({
|
type: PanelType.NotificationSettings,
|
||||||
type: PanelType.ChatColorEditor,
|
})
|
||||||
});
|
}
|
||||||
}}
|
right={
|
||||||
right={
|
conversation.muteExpiresAt
|
||||||
<div
|
? getMutedUntilText(conversation.muteExpiresAt, i18n)
|
||||||
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${conversation.conversationColor}`}
|
: undefined
|
||||||
style={{
|
}
|
||||||
...getCustomColorStyle(conversation.customColor),
|
/>
|
||||||
}}
|
)}
|
||||||
/>
|
{!isGroup && !conversation.isMe && (
|
||||||
}
|
<PanelRow
|
||||||
/>
|
onClick={() => toggleSafetyNumberModal(conversation.id)}
|
||||||
)}
|
icon={
|
||||||
{isGroup && (
|
<ConversationDetailsIcon
|
||||||
<PanelRow
|
ariaLabel={i18n('icu:ConversationDetails__viewSafetyNumber')}
|
||||||
icon={
|
icon={IconType.verify}
|
||||||
<ConversationDetailsIcon
|
/>
|
||||||
ariaLabel={i18n('icu:ConversationDetails--notifications')}
|
}
|
||||||
icon={IconType.notifications}
|
label={
|
||||||
/>
|
<div className="ConversationDetails__safety-number">
|
||||||
}
|
{i18n('icu:ConversationDetails__viewSafetyNumber')}
|
||||||
label={i18n('icu:ConversationDetails--notifications')}
|
</div>
|
||||||
onClick={() =>
|
}
|
||||||
pushPanelForConversation({
|
/>
|
||||||
type: PanelType.NotificationSettings,
|
)}
|
||||||
})
|
</PanelSection>
|
||||||
}
|
)}
|
||||||
right={
|
|
||||||
conversation.muteExpiresAt
|
|
||||||
? getMutedUntilText(conversation.muteExpiresAt, i18n)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isGroup && !conversation.isMe && (
|
|
||||||
<PanelRow
|
|
||||||
onClick={() => toggleSafetyNumberModal(conversation.id)}
|
|
||||||
icon={
|
|
||||||
<ConversationDetailsIcon
|
|
||||||
ariaLabel={i18n('icu:ConversationDetails__viewSafetyNumber')}
|
|
||||||
icon={IconType.verify}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<div className="ConversationDetails__safety-number">
|
|
||||||
{i18n('icu:ConversationDetails__viewSafetyNumber')}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</PanelSection>
|
|
||||||
|
|
||||||
{isGroup && (
|
{isGroup && (
|
||||||
<ConversationDetailsMembershipList
|
<ConversationDetailsMembershipList
|
||||||
canAddNewMembers={canAddNewMembers}
|
canAddNewMembers={canAddNewMembers}
|
||||||
|
@ -705,7 +713,7 @@ export function ConversationDetails({
|
||||||
showLightbox={showLightbox}
|
showLightbox={showLightbox}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isGroup && !conversation.isMe && (
|
{!isGroup && !conversation.isMe && !isSignalConversation && (
|
||||||
<ConversationDetailsGroups
|
<ConversationDetailsGroups
|
||||||
contactId={conversation.id}
|
contactId={conversation.id}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
|
|
@ -44,6 +44,7 @@ function Wrapper(overrideProps: Partial<Props>) {
|
||||||
membersCount={0}
|
membersCount={0}
|
||||||
isGroup
|
isGroup
|
||||||
isMe={false}
|
isMe={false}
|
||||||
|
isSignalConversation={false}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
toggleAboutContactModal={action('toggleAboutContactModal')}
|
toggleAboutContactModal={action('toggleAboutContactModal')}
|
||||||
{...overrideProps}
|
{...overrideProps}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export type Props = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGroup: boolean;
|
isGroup: boolean;
|
||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
|
isSignalConversation: boolean;
|
||||||
membersCount: number | null;
|
membersCount: number | null;
|
||||||
startEditing: (isGroupTitle: boolean) => void;
|
startEditing: (isGroupTitle: boolean) => void;
|
||||||
toggleAboutContactModal: (contactId: string) => void;
|
toggleAboutContactModal: (contactId: string) => void;
|
||||||
|
@ -44,6 +45,7 @@ export function ConversationDetailsHeader({
|
||||||
i18n,
|
i18n,
|
||||||
isGroup,
|
isGroup,
|
||||||
isMe,
|
isMe,
|
||||||
|
isSignalConversation,
|
||||||
membersCount,
|
membersCount,
|
||||||
startEditing,
|
startEditing,
|
||||||
toggleAboutContactModal,
|
toggleAboutContactModal,
|
||||||
|
@ -194,6 +196,13 @@ export function ConversationDetailsHeader({
|
||||||
<span className="ContactModal__official-badge__large" />
|
<span className="ContactModal__official-badge__large" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (isSignalConversation) {
|
||||||
|
title = (
|
||||||
|
<div className="ConversationDetailsHeader__title">
|
||||||
|
<UserText text={conversation.title} />
|
||||||
|
<span className="ContactModal__official-badge__large" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else if (isGroup) {
|
} else if (isGroup) {
|
||||||
title = (
|
title = (
|
||||||
<div className="ConversationDetailsHeader__title">
|
<div className="ConversationDetailsHeader__title">
|
||||||
|
|
|
@ -396,6 +396,7 @@ export class ReleaseNotesFetcher {
|
||||||
|
|
||||||
await this.#scheduleForNextRun();
|
await this.#scheduleForNextRun();
|
||||||
this.setTimeoutForNextRun();
|
this.setTimeoutForNextRun();
|
||||||
|
window.SignalCI?.handleEvent('release_notes_fetcher_complete', {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorString =
|
const errorString =
|
||||||
error instanceof HTTPError
|
error instanceof HTTPError
|
||||||
|
|
|
@ -67,6 +67,7 @@ import { useToastActions } from '../ducks/toast';
|
||||||
import { isShowingAnyModal } from '../selectors/globalModals';
|
import { isShowingAnyModal } from '../selectors/globalModals';
|
||||||
import { isConversationEverUnregistered } from '../../util/isConversationUnregistered';
|
import { isConversationEverUnregistered } from '../../util/isConversationUnregistered';
|
||||||
import { isDirectConversation } from '../../util/whatTypeOfConversation';
|
import { isDirectConversation } from '../../util/whatTypeOfConversation';
|
||||||
|
import { isConversationMuted } from '../../util/isConversationMuted';
|
||||||
|
|
||||||
function renderSmartCompositionRecording(
|
function renderSmartCompositionRecording(
|
||||||
recProps: SmartCompositionRecordingProps
|
recProps: SmartCompositionRecordingProps
|
||||||
|
@ -232,6 +233,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
toggleSelectMode,
|
toggleSelectMode,
|
||||||
scrollToMessage,
|
scrollToMessage,
|
||||||
setMessageToEdit,
|
setMessageToEdit,
|
||||||
|
setMuteExpiration,
|
||||||
showConversation,
|
showConversation,
|
||||||
} = useConversationsActions();
|
} = useConversationsActions();
|
||||||
const { cancelRecording, completeRecording, startRecording, errorRecording } =
|
const { cancelRecording, completeRecording, startRecording, errorRecording } =
|
||||||
|
@ -325,7 +327,6 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
(isConversationSMSOnly(conversation) ||
|
(isConversationSMSOnly(conversation) ||
|
||||||
isConversationEverUnregistered(conversation))
|
isConversationEverUnregistered(conversation))
|
||||||
}
|
}
|
||||||
isSignalConversation={isSignalConversation(conversation)}
|
|
||||||
isFetchingUUID={conversation.isFetchingUUID ?? null}
|
isFetchingUUID={conversation.isFetchingUUID ?? null}
|
||||||
isMissingMandatoryProfileSharing={isMissingRequiredProfileSharing(
|
isMissingMandatoryProfileSharing={isMissingRequiredProfileSharing(
|
||||||
conversation
|
conversation
|
||||||
|
@ -335,6 +336,10 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
blockConversation={blockConversation}
|
blockConversation={blockConversation}
|
||||||
reportSpam={reportSpam}
|
reportSpam={reportSpam}
|
||||||
deleteConversation={deleteConversation}
|
deleteConversation={deleteConversation}
|
||||||
|
// Signal Conversation
|
||||||
|
isSignalConversation={isSignalConversation(conversation)}
|
||||||
|
isMuted={isConversationMuted(conversation)}
|
||||||
|
setMuteExpiration={setMuteExpiration}
|
||||||
// Groups
|
// Groups
|
||||||
groupVersion={conversation.groupVersion ?? null}
|
groupVersion={conversation.groupVersion ?? null}
|
||||||
isGroupV1AndDisabled={conversation.isGroupV1AndDisabled ?? null}
|
isGroupV1AndDisabled={conversation.isGroupV1AndDisabled ?? null}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { useCallingActions } from '../ducks/calling';
|
||||||
import { useSearchActions } from '../ducks/search';
|
import { useSearchActions } from '../ducks/search';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useLightboxActions } from '../ducks/lightbox';
|
import { useLightboxActions } from '../ducks/lightbox';
|
||||||
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
|
|
||||||
export type SmartConversationDetailsProps = {
|
export type SmartConversationDetailsProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -193,6 +194,7 @@ export const SmartConversationDetails = memo(function SmartConversationDetails({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
isGroup={isGroup}
|
isGroup={isGroup}
|
||||||
|
isSignalConversation={isSignalConversation(conversation)}
|
||||||
leaveGroup={leaveGroup}
|
leaveGroup={leaveGroup}
|
||||||
loadRecentMediaItems={loadRecentMediaItems}
|
loadRecentMediaItems={loadRecentMediaItems}
|
||||||
maxGroupSize={maxGroupSize}
|
maxGroupSize={maxGroupSize}
|
||||||
|
|
|
@ -129,6 +129,10 @@ export class App extends EventEmitter {
|
||||||
return this.#waitForEvent('receipts');
|
return this.#waitForEvent('receipts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async waitForReleaseNotesFetcher(): Promise<void> {
|
||||||
|
return this.#waitForEvent('release_notes_fetcher_complete');
|
||||||
|
}
|
||||||
|
|
||||||
public async waitForStorageService(): Promise<StorageServiceInfoType> {
|
public async waitForStorageService(): Promise<StorageServiceInfoType> {
|
||||||
return this.#waitForEvent('storageServiceComplete');
|
return this.#waitForEvent('storageServiceComplete');
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ describe('release notes', function (this: Mocha.Suite) {
|
||||||
it('shows release notes with an image and body ranges', async () => {
|
it('shows release notes with an image and body ranges', async () => {
|
||||||
const firstWindow = await app.getWindow();
|
const firstWindow = await app.getWindow();
|
||||||
|
|
||||||
|
await app.waitForReleaseNotesFetcher();
|
||||||
await firstWindow.evaluate('window.SignalCI.resetReleaseNotesFetcher()');
|
await firstWindow.evaluate('window.SignalCI.resetReleaseNotesFetcher()');
|
||||||
|
|
||||||
await app.close();
|
await app.close();
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function isSignalConversation(conversation: {
|
||||||
const { id, serviceId } = conversation;
|
const { id, serviceId } = conversation;
|
||||||
|
|
||||||
if (serviceId) {
|
if (serviceId) {
|
||||||
return serviceId === SIGNAL_ACI;
|
return isSignalServiceId(serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.ConversationController.isSignalConversationId(id);
|
return window.ConversationController.isSignalConversationId(id);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue