Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add option to stop sending read receipts (delabs MSC2285: private read receipts) #8629

Merged
merged 40 commits into from
Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7110ced
Add tooltip
SimonBrandner May 17, 2022
a244da7
Add disabled tooltip
SimonBrandner May 17, 2022
bbf3a60
Delabs MSC2285: Private read receipts
SimonBrandner May 17, 2022
53c50ea
i18n
SimonBrandner May 17, 2022
dea9866
Update snaps
SimonBrandner May 17, 2022
1d9916b
Use a turnary operator
SimonBrandner May 18, 2022
c77d2f6
Fix typo
SimonBrandner May 19, 2022
3c04bc4
`disabledTooltip` -> `disabledTooltipText`
SimonBrandner May 19, 2022
c1f4370
Improve test utils
SimonBrandner May 21, 2022
10c14f6
Allow `=`
SimonBrandner May 21, 2022
1984bae
Add `TimelinePanel-tests`
SimonBrandner May 21, 2022
e8d1069
Improve formatting
SimonBrandner May 25, 2022
18aad85
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner May 25, 2022
e62a8f2
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Jun 10, 2022
7c61400
Move `Presence` settings to the `Preferences` tab
SimonBrandner Jun 13, 2022
0abf5ef
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Jun 13, 2022
c46607f
Revert "Add tooltip"
SimonBrandner Jun 14, 2022
fdd7065
Move disabled tooltip to microcopy
SimonBrandner Jun 14, 2022
31fa423
Update copy
SimonBrandner Jun 14, 2022
2a3c15d
Fix tests
SimonBrandner Jun 14, 2022
9926bc0
Make categories private
SimonBrandner Jun 15, 2022
356eba9
Add `sendReadReceipts` as a comment
SimonBrandner Jun 15, 2022
4ac6156
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Jun 15, 2022
eeb2e77
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Jun 24, 2022
b7bc7fc
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Jul 14, 2022
fded4fa
Switch to stable prefixes for MSC2285
SimonBrandner Jul 14, 2022
5c3f27e
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Jul 30, 2022
578d24c
Make sure `inNodeView()` works in tests
SimonBrandner Aug 3, 2022
db69f9e
Switch to stable `msc2285`
SimonBrandner Aug 3, 2022
db72558
Write tests
SimonBrandner Aug 3, 2022
769b14d
Merge branch 'SimonBrandner/feat/disable-rr' into SimonBrandner/feat/…
SimonBrandner Aug 3, 2022
f9e38d9
Show MSC2285 both if stable and unstable
SimonBrandner Aug 4, 2022
e61b634
Support both stabel and unstable private RRs
SimonBrandner Aug 4, 2022
81e709a
Delint
SimonBrandner Aug 4, 2022
7ca38f6
Merge remote-tracking branch 'upstream/develop' into SimonBrandner/fe…
SimonBrandner Aug 4, 2022
6afd527
Add comment
SimonBrandner Aug 5, 2022
b4110b1
Remove duplicate line
SimonBrandner Aug 5, 2022
bcfd67d
Use try-catch
SimonBrandner Aug 5, 2022
2176a11
Add mising mock
SimonBrandner Aug 5, 2022
b953689
Update string
SimonBrandner Aug 5, 2022
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
23 changes: 12 additions & 11 deletions src/components/structures/TimelinePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -935,25 +935,26 @@ class TimelinePanel extends React.Component<IProps, IState> {
this.lastRMSentEventId = this.state.readMarkerEventId;

const roomId = this.props.timelineSet.room.roomId;
const hiddenRR = SettingsStore.getValue("feature_hidden_read_receipts", roomId);
const sendRRs = SettingsStore.getValue("sendReadReceipts", roomId);
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved

debuglog(
`Sending Read Markers for ${this.props.timelineSet.room.roomId}: `,
`rm=${this.state.readMarkerEventId} `,
`rr=${sendRRs ? lastReadEvent?.getId() : null} `,
`prr=${lastReadEvent?.getId()}`,

debuglog('Sending Read Markers for ',
this.props.timelineSet.room.roomId,
'rm', this.state.readMarkerEventId,
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
' hidden:' + hiddenRR,
);
MatrixClientPeg.get().setRoomReadMarkers(
roomId,
this.state.readMarkerEventId,
hiddenRR ? null : lastReadEvent, // Could be null, in which case no RR is sent
lastReadEvent, // Could be null, in which case no private RR is sent
sendRRs ? lastReadEvent : null, // Public read receipt (could be null)
lastReadEvent, // Private read receipt (could be null)
).catch((e) => {
// /read_markers API is not implemented on this HS, fallback to just RR
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
return MatrixClientPeg.get().sendReadReceipt(
lastReadEvent,
hiddenRR ? ReceiptType.ReadPrivate : ReceiptType.Read,
sendRRs ? ReceiptType.Read : ReceiptType.ReadPrivate,
).catch((e) => {
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
logger.error(e);
this.lastRRSentEventId = undefined;
Expand Down Expand Up @@ -1549,8 +1550,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
const isNodeInView = (node) => {
if (node) {
const boundingRect = node.getBoundingClientRect();
if ((allowPartial && boundingRect.top < wrapperRect.bottom) ||
(!allowPartial && boundingRect.bottom < wrapperRect.bottom)) {
if ((allowPartial && boundingRect.top <= wrapperRect.bottom) ||
(!allowPartial && boundingRect.bottom <= wrapperRect.bottom)) {
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/views/elements/SettingsFlag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface IProps {
// XXX: once design replaces all toggles make this the default
useCheckbox?: boolean;
disabled?: boolean;
disabledTooltipText?: string;
onChange?(checked: boolean): void;
}

Expand Down Expand Up @@ -103,6 +104,7 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
onChange={this.onChange}
disabled={this.props.disabled || !canChange}
aria-label={label}
tooltip={this.props.disabled ? this.props.disabledTooltipText : null}
/>
</div>
);
Expand Down
14 changes: 12 additions & 2 deletions src/components/views/elements/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import React from "react";
import classNames from "classnames";

import AccessibleButton from "./AccessibleButton";
import useHover from "../../../hooks/useHover";
import Tooltip from "./Tooltip";

interface IProps {
// Whether or not this toggle is in the 'on' position.
Expand All @@ -27,12 +29,17 @@ interface IProps {
// Whether or not the user can interact with the switch
disabled: boolean;

// The tooltip to show on hover
tooltip?: string;

// Called when the checked state changes. First argument will be the new state.
onChange(checked: boolean): void;
}

// Controlled Toggle Switch element, written with Accessibility in mind
export default ({ checked, disabled = false, onChange, ...props }: IProps) => {
export default ({ checked, disabled = false, onChange, tooltip, ...props }: IProps) => {
const [hovered, hoverProps] = useHover();

const _onClick = () => {
if (disabled) return;
onChange(!checked);
Expand All @@ -45,14 +52,17 @@ export default ({ checked, disabled = false, onChange, ...props }: IProps) => {
});

return (
<AccessibleButton {...props}
<AccessibleButton
{...props}
{...hoverProps}
className={classes}
onClick={_onClick}
role="switch"
aria-checked={checked}
aria-disabled={disabled}
>
<div className="mx_ToggleSwitch_ball" />
{ hovered && tooltip && <Tooltip label={tooltip} /> }
</AccessibleButton>
);
};
16 changes: 0 additions & 16 deletions src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,18 @@ export class LabsSettingToggle extends React.Component<ILabsSettingToggleProps>
}

interface IState {
showHiddenReadReceipts: boolean;
showJumpToDate: boolean;
}

export default class LabsUserSettingsTab extends React.Component<{}, IState> {
constructor(props: {}) {
super(props);

MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc2285").then((showHiddenReadReceipts) => {
this.setState({ showHiddenReadReceipts });
});

MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc3030").then((showJumpToDate) => {
this.setState({ showJumpToDate });
});

this.state = {
showHiddenReadReceipts: false,
showJumpToDate: false,
};
}
Expand Down Expand Up @@ -113,16 +107,6 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
/>,
);

if (this.state.showHiddenReadReceipts) {
groups.getOrCreate(LabGroup.Messaging, []).push(
<SettingsFlag
key="feature_hidden_read_receipts"
name="feature_hidden_read_receipts"
level={SettingLevel.DEVICE}
/>,
);
}

if (this.state.showJumpToDate) {
groups.getOrCreate(LabGroup.Messaging, []).push(
<SettingsFlag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
'MessageComposerInput.autoReplaceEmoji',
'MessageComposerInput.useMarkdown',
'MessageComposerInput.suggestEmoji',
'sendTypingNotifications',
'MessageComposerInput.ctrlEnterToSend',
'MessageComposerInput.surroundWith',
'MessageComposerInput.showStickersButton',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ interface IState {
waitingUnignored: string[];
managingInvites: boolean;
invitedRoomIds: Set<string>;
canDisableReadReceipts: boolean;
}

export default class SecurityUserSettingsTab extends React.Component<IProps, IState> {
Expand All @@ -84,10 +85,15 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
// Get rooms we're invited to
const invitedRoomIds = new Set(this.getInvitedRooms().map(room => room.roomId));

MatrixClientPeg.get().doesServerSupportUnstableFeature("org.matrix.msc2285").then((canDisableReadReceipts) => {
this.setState({ canDisableReadReceipts });
});

this.state = {
ignoredUserIds: MatrixClientPeg.get().getIgnoredUsers(),
waitingUnignored: [],
managingInvites: false,
canDisableReadReceipts: false,
invitedRoomIds,
};
}
Expand Down Expand Up @@ -294,7 +300,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
</div>;
}

let privacySection;
let analytics;
if (Analytics.canEnable() || PosthogAnalytics.instance.isEnabled()) {
const onClickAnalyticsLearnMore = () => {
if (PosthogAnalytics.instance.isEnabled()) {
Expand All @@ -306,31 +312,28 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
Analytics.showDetailsModal();
}
};
privacySection = <React.Fragment>
<div className="mx_SettingsTab_heading">{ _t("Privacy") }</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{ _t("Analytics") }</span>
<div className="mx_SettingsTab_subsectionText">
<p>
{ _t("Share anonymous data to help us identify issues. Nothing personal. " +
"No third parties.") }
</p>
<p>
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={onClickAnalyticsLearnMore}>
{ _t("Learn more") }
</AccessibleButton>
</p>
</div>
{
PosthogAnalytics.instance.isEnabled() ?
<SettingsFlag name="pseudonymousAnalyticsOptIn"
level={SettingLevel.ACCOUNT}
onChange={this.updateAnalytics} /> :
<SettingsFlag name="analyticsOptIn"
level={SettingLevel.DEVICE}
onChange={this.updateAnalytics} />
}
analytics = <React.Fragment>
<span className="mx_SettingsTab_subheading">{ _t("Analytics") }</span>
<div className="mx_SettingsTab_subsectionText">
<p>
{ _t("Share anonymous data to help us identify issues. Nothing personal. " +
"No third parties.") }
</p>
<p>
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={onClickAnalyticsLearnMore}>
{ _t("Learn more") }
</AccessibleButton>
</p>
</div>
{
PosthogAnalytics.instance.isEnabled() ?
<SettingsFlag name="pseudonymousAnalyticsOptIn"
level={SettingLevel.ACCOUNT}
onChange={this.updateAnalytics} /> :
<SettingsFlag name="analyticsOptIn"
level={SettingLevel.DEVICE}
onChange={this.updateAnalytics} />
}
</React.Fragment>;
}

Expand Down Expand Up @@ -372,7 +375,19 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
{ crossSigning }
<CryptographyPanel />
</div>
{ privacySection }
<div className="mx_SettingsTab_heading">{ _t("Privacy") }</div>
<div className="mx_SettingsTab_section">
{ analytics }
<span className="mx_SettingsTab_subheading">{ _t("Presence") }</span>
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
<SettingsFlag name="sendTypingNotifications" level={SettingLevel.ACCOUNT} />
<SettingsFlag
disabled={!this.state.canDisableReadReceipts}
disabledTooltipText={_t("Your server doesn't support disabling read receipts")}
name="sendReadReceipts"
level={SettingLevel.ACCOUNT}
/>
</div>

{ advancedSection }
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@
"To leave, just return to this page or click on the beta badge when you search.": "To leave, just return to this page or click on the beta badge when you search.",
"Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)",
"Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)",
"Don't send read receipts": "Don't send read receipts",
"Send read receipts": "Send read receipts",
"Right-click message context menu": "Right-click message context menu",
"Location sharing - pin drop": "Location sharing - pin drop",
"Live Location Sharing (temporary implementation: locations persist in room history)": "Live Location Sharing (temporary implementation: locations persist in room history)",
Expand Down Expand Up @@ -1522,10 +1522,12 @@
"Cross-signing": "Cross-signing",
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
"Okay": "Okay",
"Privacy": "Privacy",
"Share anonymous data to help us identify issues. Nothing personal. No third parties.": "Share anonymous data to help us identify issues. Nothing personal. No third parties.",
"Where you're signed in": "Where you're signed in",
"Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Manage your signed-in devices below. A device's name is visible to people you communicate with.",
"Privacy": "Privacy",
"Presence": "Presence",
"Your server doesn't support disabling read receipts": "Your server doesn't support disabling read receipts",
"Sidebar": "Sidebar",
"Spaces to show": "Spaces to show",
"Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.": "Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.",
Expand Down
8 changes: 4 additions & 4 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: null,
},
"feature_hidden_read_receipts": {
supportedLevels: LEVELS_FEATURE,
displayName: _td("Don't send read receipts"),
default: false,
"sendReadReceipts": {
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Send read receipts"),
default: true,
},
"feature_message_right_click_context_menu": {
isFeature: true,
Expand Down
Loading