Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: readonly conversation indication #17369

Merged
merged 2 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1166,7 +1166,7 @@
"ongoingGroupAudioCall": "Ongoing conference call with {{conversationName}}.",
"ongoingGroupVideoCall": "Ongoing video conference call with {{conversationName}}, your camera is {{cameraStatus}}.",
"ongoingVideoCall": "Ongoing video call with {{conversationName}}, your camera is {{cameraStatus}}.",
"otherUserNotSupportMLSMsg": "You can't communicate with [bold]{{participantName}}[/bold] anymore, as you two now use different protocols. When [bold]{{participantName}}[/bold] gets an update, you can call and send messages and files again.",
"otherUserNotSupportMLSMsg": "You can't communicate with {{participantName}} anymore, as you two now use different protocols. When {{participantName}} gets an update, you can call and send messages and files again.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is localisation in German done for those? Might be worth it to cherrypick the localisation commit too if we want to avoid the [bold] tag to appear as a string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When merged to dev, these changes were made automatically for German

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess it will happen automatically here as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see, it didn't work out that way on master last time :|
Let's worry about it when we get there 🤞

"participantDevicesDetailHeadline": "Verify that this matches the fingerprint shown on [bold]{{user}}’s device[/bold].",
"participantDevicesDetailHowTo": "How do I do that?",
"participantDevicesDetailResetSession": "Reset session",
Expand Down Expand Up @@ -1377,7 +1377,7 @@
"searchTrySearch": "Find people by\nname or username",
"searchTrySearchFederation": "Find people in Wire by name or\n@username\n\nFind people from another domain\nby @username@domainname",
"searchTrySearchLearnMore": "Learn more",
"selfNotSupportMLSMsgPart1": "You can't communicate with [bold]{{selfUserName}}[/bold] anymore, as your device doesn't support the suitable protocol.",
"selfNotSupportMLSMsgPart1": "You can't communicate with {{selfUserName}} anymore, as your device doesn't support the suitable protocol.",
"selfNotSupportMLSMsgPart2": "to call, and send messages and files again.",
"selfProfileImageAlt": "Your profile picture",
"servicesOptionsTitle": "Services",
Expand Down
31 changes: 9 additions & 22 deletions src/script/components/Conversation/Conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {showWarningModal} from 'Components/Modals/utils/showWarningModal';
import {TitleBar} from 'Components/TitleBar';
import {CallState} from 'src/script/calling/CallState';
import {Config} from 'src/script/Config';
import {CONVERSATION_READONLY_STATE} from 'src/script/conversation/ConversationRepository';
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
import {allowsAllFiles, getFileExtensionOrName, hasAllowedExtension} from 'Util/FileTypeUtil';
import {isHittingUploadLimit} from 'Util/isHittingUploadLimit';
Expand All @@ -45,7 +44,7 @@ import {safeMailOpen, safeWindowOpen} from 'Util/SanitizationUtil';
import {formatBytes, incomingCssClass, removeAnimationsClass} from 'Util/util';

import {useReadReceiptSender} from './hooks/useReadReceipt';
import {ReadOnlyConversationMessage} from './ReadOnlyConversationMessage';
import {ReadOnlyConversationMessage} from './ReadOnlyConversationMessage/ReadOnlyConversationMessage';
import {checkFileSharingPermission} from './utils/checkFileSharingPermission';

import {ConversationState} from '../../conversation/ConversationState';
Expand Down Expand Up @@ -103,19 +102,11 @@ export const Conversation = ({
'isFileSharingSendingEnabled',
]);

const {
is1to1,
isRequest,
readOnlyState,
display_name: displayName,
} = useKoSubscribableChildren(activeConversation!, ['is1to1', 'isRequest', 'display_name', 'readOnlyState']);

const showReadOnlyConversationMessage =
readOnlyState !== null &&
[
CONVERSATION_READONLY_STATE.READONLY_ONE_TO_ONE_OTHER_UNSUPPORTED_MLS,
CONVERSATION_READONLY_STATE.READONLY_ONE_TO_ONE_SELF_UNSUPPORTED_MLS,
].includes(readOnlyState);
const {is1to1, isRequest, isReadOnlyConversation} = useKoSubscribableChildren(activeConversation!, [
'is1to1',
'isRequest',
'isReadOnlyConversation',
]);

const inTeam = teamState.isInTeam(selfUser);

Expand Down Expand Up @@ -488,7 +479,7 @@ export const Conversation = ({
callActions={mainViewModel.calling.callActions}
openRightSidebar={openRightSidebar}
isRightSidebarOpen={isRightSidebarOpen}
isReadOnlyConversation={showReadOnlyConversationMessage}
isReadOnlyConversation={isReadOnlyConversation}
/>

{activeCalls.map(call => {
Expand Down Expand Up @@ -539,12 +530,8 @@ export const Conversation = ({
/>

{isConversationLoaded &&
(showReadOnlyConversationMessage ? (
<ReadOnlyConversationMessage
state={readOnlyState}
handleMLSUpdate={reloadApp}
displayName={displayName}
/>
(isReadOnlyConversation ? (
<ReadOnlyConversationMessage reloadApp={reloadApp} conversation={activeConversation} />
) : (
<InputBar
key={activeConversation?.id}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {render} from '@testing-library/react';
import {ConnectionStatus} from '@wireapp/api-client/lib/connection';
import {CONVERSATION_TYPE} from '@wireapp/api-client/lib/conversation';

import {withTheme} from 'src/script/auth/util/test/TestUtil';
import {ConnectionEntity} from 'src/script/connection/ConnectionEntity';
import {CONVERSATION_READONLY_STATE} from 'src/script/conversation/ConversationRepository';
import {Conversation} from 'src/script/entity/Conversation';
import {User} from 'src/script/entity/User';

import {ReadOnlyConversationMessage} from './ReadOnlyConversationMessage';

const generateConversation = (
readOnlyState: CONVERSATION_READONLY_STATE | null = null,
is1To1WithBlockedUser = false,
userName = 'John Doe',
) => {
const conversation = new Conversation();
conversation.type(CONVERSATION_TYPE.ONE_TO_ONE);
conversation.readOnlyState(readOnlyState);

const connection = new ConnectionEntity();

if (is1To1WithBlockedUser) {
connection.status(ConnectionStatus.BLOCKED);
}

const user = new User('user-id', 'user-domain');
user.name(userName);
conversation.participating_user_ets([user]);
conversation.participating_user_ids([user.qualifiedId]);

user.connection(connection);
connection.userId = user.qualifiedId;

return conversation;
};

describe('ReadOnlyConversationMessage', () => {
it('renders mls is not supported by the other user', () => {
const conversation = generateConversation(
CONVERSATION_READONLY_STATE.READONLY_ONE_TO_ONE_OTHER_UNSUPPORTED_MLS,
false,
);

const {getByText} = render(
withTheme(<ReadOnlyConversationMessage conversation={conversation} reloadApp={() => {}} />),
);

expect(getByText('otherUserNotSupportMLSMsg')).toBeDefined();
});

it('renders mls is not supported by the self user', () => {
const conversation = generateConversation(
CONVERSATION_READONLY_STATE.READONLY_ONE_TO_ONE_SELF_UNSUPPORTED_MLS,
false,
);

const reloadAppMock = jest.fn();

const {getByText} = render(
withTheme(<ReadOnlyConversationMessage reloadApp={reloadAppMock} conversation={conversation} />),
);

const reloadButton = getByText('downloadLatestMLS');

expect(getByText('selfNotSupportMLSMsgPart1')).toBeDefined();
expect(reloadButton).toBeDefined();
expect(getByText('selfNotSupportMLSMsgPart2')).toBeDefined();

reloadButton.click();

expect(reloadAppMock).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {FC, ReactNode} from 'react';

import {Link, LinkVariant} from '@wireapp/react-ui-kit';

import {Icon} from 'Components/Icon';
import {CONVERSATION_READONLY_STATE} from 'src/script/conversation/ConversationRepository';
import {Conversation} from 'src/script/entity/Conversation';
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
import {t} from 'Util/LocalizerUtil';
import {replaceReactComponents} from 'Util/LocalizerUtil/ReactLocalizerUtil';

interface ReadOnlyConversationMessageProps {
reloadApp: () => void;
conversation: Conversation;
}

export const ReadOnlyConversationMessage: FC<ReadOnlyConversationMessageProps> = ({conversation, reloadApp}) => {
const {
readOnlyState,
is1to1,
participating_user_ets: participatingUserEts,
} = useKoSubscribableChildren(conversation, ['readOnlyState', 'is1to1', 'participating_user_ets']);

const user = (is1to1 && participatingUserEts[0]) || null;

if (!user) {
// This should never happen for 1:1 conversations
return null;
}

if (readOnlyState) {
switch (readOnlyState) {
case CONVERSATION_READONLY_STATE.READONLY_ONE_TO_ONE_OTHER_UNSUPPORTED_MLS:
return (
<ReadOnlyConversationMessageBase>
<span>
{replaceReactComponents(t('otherUserNotSupportMLSMsg'), [
{
exactMatch: '{{participantName}}',
render: () => <strong>{user.name()}</strong>,
},
])}
</span>
</ReadOnlyConversationMessageBase>
);
case CONVERSATION_READONLY_STATE.READONLY_ONE_TO_ONE_SELF_UNSUPPORTED_MLS:
return (
<ReadOnlyConversationMessageBase>
<span>
{replaceReactComponents(t('selfNotSupportMLSMsgPart1'), [
{
exactMatch: '{{selfUserName}}',
render: () => <strong>{user.name()}</strong>,
},
])}
</span>
<>
{' '}
<Link
css={{fontSize: 'var(--font-size-small)', fontWeight: 'var(--font-weight-semibold)'}}
onClick={reloadApp}
variant={LinkVariant.PRIMARY}
data-uie-name="do-update-mls"
>
{t('downloadLatestMLS')}
</Link>{' '}
<span>{t('selfNotSupportMLSMsgPart2')}</span>
</>
</ReadOnlyConversationMessageBase>
);
}
}

return null;
};

const ReadOnlyConversationMessageBase = ({children}: {children: ReactNode}) => {
return (
<div className="readonly-message-header readonly-message-container">
<div className="readonly-message-header-icon readonly-message-header-icon--svg">
<div>
<Icon.Info />
</div>
</div>
<p className="readonly-message-header-label" data-uie-name="element-readonly-conversation">
{children}
</p>
</div>
);
};
4 changes: 4 additions & 0 deletions src/script/entity/Conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class Conversation {
public readonly readOnlyState: ko.Observable<CONVERSATION_READONLY_STATE | null>;
private readonly incomingMessages: ko.ObservableArray<Message>;
public readonly isProteusTeam1to1: ko.PureComputed<boolean>;
public readonly isReadOnlyConversation: ko.PureComputed<boolean>;
public readonly last_server_timestamp: ko.Observable<number>;
private readonly logger: Logger;
public readonly mutedState: ko.Observable<number>;
Expand Down Expand Up @@ -242,6 +243,9 @@ export class Conversation {
otherMembersLength: this.participating_user_ids().length,
}),
);

this.isReadOnlyConversation = ko.pureComputed(() => this.readOnlyState() !== null);

this.isGroup = ko.pureComputed(() => {
const isGroupConversation = this.type() === CONVERSATION_TYPE.REGULAR;
return isGroupConversation && !this.isProteusTeam1to1();
Expand Down
Loading
Loading