diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx
index a0869b615..90ea50e5c 100644
--- a/src/app/organisms/settings/Settings.jsx
+++ b/src/app/organisms/settings/Settings.jsx
@@ -7,7 +7,7 @@ import settings from '../../../client/state/settings';
import navigation from '../../../client/state/navigation';
import {
toggleSystemTheme, toggleMarkdown, toggleMembershipEvents, toggleNickAvatarEvents,
- toggleNotifications, toggleNotificationSounds,
+ toggleNotifications, toggleNotificationSounds, toggleReadReceipts,
} from '../../../client/action/settings';
import { usePermission } from '../../hooks/usePermission';
@@ -44,6 +44,10 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import CinnySVG from '../../../../public/res/svg/cinny.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
+let capabilities = {
+ privateReadReceipts: false,
+};
+
function AppearanceSection() {
const [, updateState] = useState({});
@@ -188,6 +192,7 @@ function EmojiSection() {
}
function SecuritySection() {
+ const [, updateState] = useState({});
return (
@@ -217,6 +222,33 @@ function SecuritySection() {
)}
/>
+
+ Presence
+ { toggleReadReceipts(); updateState({}); }}
+ />
+ )}
+ content={(
+ <>
+
+ Let other people know what messages you read.
+
+ {!capabilities.privateReadReceipts
+ && (
+
+ Making your read receipts private requires a compatible homeserver.
+
+ )}
+ >
+ )}
+ />
+
);
}
@@ -320,9 +352,20 @@ function useWindowToggle(setSelectedTab) {
return [isOpen, requestClose];
}
+async function getCapabilities() {
+ const mx = initMatrix.matrixClient;
+ capabilities = {
+ privateReadReceipts: (await (Promise.all([
+ mx.doesServerSupportUnstableFeature('org.matrix.msc2285.stable'),
+ mx.isVersionSupported('v1.4')])
+ )).some((res) => res === true),
+ };
+}
+
function Settings() {
const [selectedTab, setSelectedTab] = useState(tabItems[0]);
const [isOpen, requestClose] = useWindowToggle(setSelectedTab);
+ useEffect(getCapabilities, []);
const handleTabChange = (tabItem) => setSelectedTab(tabItem);
const handleLogout = async () => {
diff --git a/src/client/action/notifications.js b/src/client/action/notifications.js
index a869632a8..83b93367d 100644
--- a/src/client/action/notifications.js
+++ b/src/client/action/notifications.js
@@ -1,4 +1,10 @@
import initMatrix from '../initMatrix';
+import settings from '../state/settings';
+
+const ReceiptType = {
+ Read: 'm.read',
+ ReadPrivate: 'm.read.private',
+};
// eslint-disable-next-line import/prefer-default-export
export async function markAsRead(roomId) {
@@ -22,5 +28,6 @@ export async function markAsRead(roomId) {
const latestEvent = getLatestValidEvent();
if (latestEvent === null) return;
- await mx.sendReadReceipt(latestEvent);
+ const receiptType = settings.sendReadReceipts ? ReceiptType.Read : ReceiptType.ReadPrivate;
+ await mx.sendReadReceipt(latestEvent, receiptType);
}
diff --git a/src/client/action/settings.js b/src/client/action/settings.js
index 7b539c8d7..8d77f3f80 100644
--- a/src/client/action/settings.js
+++ b/src/client/action/settings.js
@@ -42,3 +42,9 @@ export function toggleNotificationSounds() {
type: cons.actions.settings.TOGGLE_NOTIFICATION_SOUNDS,
});
}
+
+export function toggleReadReceipts() {
+ appDispatcher.dispatch({
+ type: cons.actions.settings.TOGGLE_READ_RECEIPTS,
+ });
+}
diff --git a/src/client/state/cons.js b/src/client/state/cons.js
index 8d9fda543..438626222 100644
--- a/src/client/state/cons.js
+++ b/src/client/state/cons.js
@@ -78,6 +78,7 @@ const cons = {
TOGGLE_NICKAVATAR_EVENT: 'TOGGLE_NICKAVATAR_EVENT',
TOGGLE_NOTIFICATIONS: 'TOGGLE_NOTIFICATIONS',
TOGGLE_NOTIFICATION_SOUNDS: 'TOGGLE_NOTIFICATION_SOUNDS',
+ TOGGLE_READ_RECEIPTS: 'TOGGLE_READ_RECEIPTS',
},
},
events: {
@@ -150,6 +151,7 @@ const cons = {
NICKAVATAR_EVENTS_TOGGLED: 'NICKAVATAR_EVENTS_TOGGLED',
NOTIFICATIONS_TOGGLED: 'NOTIFICATIONS_TOGGLED',
NOTIFICATION_SOUNDS_TOGGLED: 'NOTIFICATION_SOUNDS_TOGGLED',
+ READ_RECEIPTS_TOGGLED: 'READ_RECEIPTS_TOGGLED',
},
},
};
diff --git a/src/client/state/settings.js b/src/client/state/settings.js
index af2e279ad..498e38f01 100644
--- a/src/client/state/settings.js
+++ b/src/client/state/settings.js
@@ -33,6 +33,7 @@ class Settings extends EventEmitter {
this.hideNickAvatarEvents = this.getHideNickAvatarEvents();
this._showNotifications = this.getShowNotifications();
this.isNotificationSounds = this.getIsNotificationSounds();
+ this.sendReadReceipts = this.getSendReadReceipts();
this.darkModeQueryList = window.matchMedia('(prefers-color-scheme: dark)');
@@ -153,6 +154,15 @@ class Settings extends EventEmitter {
return settings.isNotificationSounds;
}
+ getSendReadReceipts() {
+ if (typeof this.sendReadReceipts === 'boolean') return this.sendReadReceipts;
+
+ const settings = getSettings();
+ if (settings === null) return true;
+ if (typeof settings.sendReadReceipts === 'undefined') return true;
+ return settings.sendReadReceipts;
+ }
+
setter(action) {
const actions = {
[cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => {
@@ -192,6 +202,11 @@ class Settings extends EventEmitter {
setSettings('isNotificationSounds', this.isNotificationSounds);
this.emit(cons.events.settings.NOTIFICATION_SOUNDS_TOGGLED, this.isNotificationSounds);
},
+ [cons.actions.settings.TOGGLE_READ_RECEIPTS]: () => {
+ this.sendReadReceipts = !this.sendReadReceipts;
+ setSettings('sendReadReceipts', this.sendReadReceipts);
+ this.emit(cons.events.settings.READ_RECEIPTS_TOGGLED, this.sendReadReceipts);
+ },
};
actions[action.type]?.();