diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index a0869b61..90ea50e5 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 a869632a..83b93367 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 7b539c8d..8d77f3f8 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 86ddab98..7bbdaea4 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 32f55fcc..ff40f453 100644 --- a/src/client/state/settings.js +++ b/src/client/state/settings.js @@ -30,6 +30,7 @@ class Settings extends EventEmitter { this.hideNickAvatarEvents = this.getHideNickAvatarEvents(); this._showNotifications = this.getShowNotifications(); this.isNotificationSounds = this.getIsNotificationSounds(); + this.sendReadReceipts = this.getSendReadReceipts(); this.isTouchScreenDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0); } @@ -147,6 +148,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]: () => { @@ -186,6 +196,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]?.();