Hidden Typing & Read Receipts (#2230)
authorAjay Bura <32841439+ajbura@users.noreply.github.com>
Wed, 26 Feb 2025 10:44:53 +0000 (21:44 +1100)
committerGitHub <noreply@github.com>
Wed, 26 Feb 2025 10:44:53 +0000 (21:44 +1100)
* add hide activity toggle

* stop sending/receiving typing status

* send private read receipt when setting toggle is activated

* prevent showing read-receipt when feature toggle in on

21 files changed:
src/app/features/room-nav/RoomNavItem.tsx
src/app/features/room/Room.tsx
src/app/features/room/RoomInput.tsx
src/app/features/room/RoomTimeline.tsx
src/app/features/room/RoomView.tsx
src/app/features/room/RoomViewFollowing.css.ts
src/app/features/room/RoomViewFollowing.tsx
src/app/features/room/RoomViewHeader.tsx
src/app/features/room/message/Message.tsx
src/app/features/settings/general/General.tsx
src/app/pages/client/direct/Direct.tsx
src/app/pages/client/home/Home.tsx
src/app/pages/client/inbox/Notifications.tsx
src/app/pages/client/sidebar/DirectTab.tsx
src/app/pages/client/sidebar/HomeTab.tsx
src/app/pages/client/sidebar/SpaceTabs.tsx
src/app/pages/client/space/Space.tsx
src/app/state/room/roomToUnread.ts
src/app/state/settings.ts
src/app/state/typingMembers.ts
src/client/action/notifications.ts

index ef59bf986da5f5874a8e7271c60076559b44e09f..ffff0f45f788932092e4b6ed4a9124fd4ae1494e 100644 (file)
@@ -39,6 +39,8 @@ import { getMatrixToRoom } from '../../plugins/matrix-to';
 import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
 import { getViaServers } from '../../plugins/via-servers';
 import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
+import { useSetting } from '../../state/hooks/settings';
+import { settingsAtom } from '../../state/settings';
 
 type RoomNavItemMenuProps = {
   room: Room;
@@ -47,13 +49,14 @@ type RoomNavItemMenuProps = {
 const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
   ({ room, requestClose }, ref) => {
     const mx = useMatrixClient();
+    const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
     const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
     const powerLevels = usePowerLevels(room);
     const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
     const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
 
     const handleMarkAsRead = () => {
-      markAsRead(mx, room.roomId);
+      markAsRead(mx, room.roomId, hideActivity);
       requestClose();
     };
 
index ee3e702740596649364aee2c0ce0669823c0d81d..ffc218579a82897b81fb2ef48248f005b569809b 100644 (file)
@@ -20,6 +20,7 @@ export function Room() {
   const mx = useMatrixClient();
 
   const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const screenSize = useScreenSizeContext();
   const powerLevels = usePowerLevels(room);
   const members = useRoomMembers(mx, room.roomId);
@@ -29,10 +30,10 @@ export function Room() {
     useCallback(
       (evt) => {
         if (isKeyHotkey('escape', evt)) {
-          markAsRead(mx, room.roomId);
+          markAsRead(mx, room.roomId, hideActivity);
         }
       },
-      [mx, room.roomId]
+      [mx, room.roomId, hideActivity]
     )
   );
 
index faaba90c22c8e9517e7a417639c2dbbb4ed69183..97f805950b9a28a686404ba1512bc5d4a7cd63e0 100644 (file)
@@ -127,6 +127,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
     const useAuthentication = useMediaAuthentication();
     const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
     const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
+    const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
     const commands = useCommands(mx, room);
     const emojiBtnRef = useRef<HTMLButtonElement>(null);
     const roomToParents = useAtomValue(roomToParentsAtom);
@@ -382,7 +383,9 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
           return;
         }
 
-        sendTypingStatus(!isEmptyEditor(editor));
+        if (!hideActivity) {
+          sendTypingStatus(!isEmptyEditor(editor));
+        }
 
         const prevWordRange = getPrevWorldRange(editor);
         const query = prevWordRange
@@ -390,7 +393,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
           : undefined;
         setAutocompleteQuery(query);
       },
-      [editor, sendTypingStatus]
+      [editor, sendTypingStatus, hideActivity]
     );
 
     const handleCloseAutocomplete = useCallback(() => {
index f6854b43130e72548aed7e14b8a374f86933eb3e..4bcdadbc321045bd159a936d1757569b2db1c773 100644 (file)
@@ -424,6 +424,7 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
 export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
   const mx = useMatrixClient();
   const useAuthentication = useMediaAuthentication();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
   const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
   const [hideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents');
@@ -589,7 +590,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
             // Check if the document is in focus (user is actively viewing the app),
             // and either there are no unread messages or the latest message is from the current user.
             // If either condition is met, trigger the markAsRead function to send a read receipt.
-            requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!));
+            requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!, hideActivity));
           }
 
           if (!document.hasFocus() && !unreadInfo) {
@@ -613,7 +614,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
           setUnreadInfo(getRoomUnreadInfo(room));
         }
       },
-      [mx, room, unreadInfo]
+      [mx, room, unreadInfo, hideActivity]
     )
   );
 
@@ -682,15 +683,15 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
   const tryAutoMarkAsRead = useCallback(() => {
     const readUptoEventId = readUptoEventIdRef.current;
     if (!readUptoEventId) {
-      requestAnimationFrame(() => markAsRead(mx, room.roomId));
+      requestAnimationFrame(() => markAsRead(mx, room.roomId, hideActivity));
       return;
     }
     const evtTimeline = getEventTimeline(room, readUptoEventId);
     const latestTimeline = evtTimeline && getFirstLinkedTimeline(evtTimeline, Direction.Forward);
     if (latestTimeline === room.getLiveTimeline()) {
-      requestAnimationFrame(() => markAsRead(mx, room.roomId));
+      requestAnimationFrame(() => markAsRead(mx, room.roomId, hideActivity));
     }
-  }, [mx, room]);
+  }, [mx, room, hideActivity]);
 
   const debounceSetAtBottom = useDebounce(
     useCallback((entry: IntersectionObserverEntry) => {
@@ -872,7 +873,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
   };
 
   const handleMarkAsRead = () => {
-    markAsRead(mx, room.roomId);
+    markAsRead(mx, room.roomId, hideActivity);
   };
 
   const handleOpenReply: MouseEventHandler = useCallback(
@@ -1047,6 +1048,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
                 />
               )
             }
+            hideReadReceipts={hideActivity}
           >
             {mEvent.isRedacted() ? (
               <RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
@@ -1119,6 +1121,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
                 />
               )
             }
+            hideReadReceipts={hideActivity}
           >
             <EncryptedContent mEvent={mEvent}>
               {() => {
@@ -1215,6 +1218,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
                 />
               )
             }
+            hideReadReceipts={hideActivity}
           >
             {mEvent.isRedacted() ? (
               <RedactedContent reason={mEvent.getUnsigned().redacted_because?.content.reason} />
@@ -1256,6 +1260,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
             highlight={highlighted}
             messageSpacing={messageSpacing}
             canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
+            hideReadReceipts={hideActivity}
           >
             <EventContent
               messageLayout={messageLayout}
@@ -1291,6 +1296,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
             highlight={highlighted}
             messageSpacing={messageSpacing}
             canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
+            hideReadReceipts={hideActivity}
           >
             <EventContent
               messageLayout={messageLayout}
@@ -1327,6 +1333,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
             highlight={highlighted}
             messageSpacing={messageSpacing}
             canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
+            hideReadReceipts={hideActivity}
           >
             <EventContent
               messageLayout={messageLayout}
@@ -1363,6 +1370,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
             highlight={highlighted}
             messageSpacing={messageSpacing}
             canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
+            hideReadReceipts={hideActivity}
           >
             <EventContent
               messageLayout={messageLayout}
@@ -1401,6 +1409,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
           highlight={highlighted}
           messageSpacing={messageSpacing}
           canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
+          hideReadReceipts={hideActivity}
         >
           <EventContent
             messageLayout={messageLayout}
@@ -1444,6 +1453,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
           highlight={highlighted}
           messageSpacing={messageSpacing}
           canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
+          hideReadReceipts={hideActivity}
         >
           <EventContent
             messageLayout={messageLayout}
index b72d2685588833166710a32fdb40881a5b07b2cb..0eb6bff1c09b4a9d9962f8bce96bcd782cfc7aa6 100644 (file)
@@ -13,12 +13,14 @@ import { RoomTimeline } from './RoomTimeline';
 import { RoomViewTyping } from './RoomViewTyping';
 import { RoomTombstone } from './RoomTombstone';
 import { RoomInput } from './RoomInput';
-import { RoomViewFollowing } from './RoomViewFollowing';
+import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
 import { Page } from '../../components/page';
 import { RoomViewHeader } from './RoomViewHeader';
 import { useKeyDown } from '../../hooks/useKeyDown';
 import { editableActiveElement } from '../../utils/dom';
 import navigation from '../../../client/state/navigation';
+import { settingsAtom } from '../../state/settings';
+import { useSetting } from '../../state/hooks/settings';
 
 const FN_KEYS_REGEX = /^F\d+$/;
 const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
@@ -57,6 +59,8 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
   const roomInputRef = useRef<HTMLDivElement>(null);
   const roomViewRef = useRef<HTMLDivElement>(null);
 
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
+
   const { roomId } = room;
   const editor = useEditor();
 
@@ -133,7 +137,7 @@ export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
             </>
           )}
         </div>
-        <RoomViewFollowing room={room} />
+        {hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />}
       </Box>
     </Page>
   );
index 0a0358e0c6a95a4dc6ee7e31ccf0a21224dccd2b..18b53ac920adc901e96bc22e19c6a260cf7ab89b 100644 (file)
@@ -1,6 +1,14 @@
+import { style } from '@vanilla-extract/css';
 import { recipe } from '@vanilla-extract/recipes';
 import { DefaultReset, color, config, toRem } from 'folds';
 
+export const RoomViewFollowingPlaceholder = style([
+  DefaultReset,
+  {
+    height: toRem(28),
+  },
+]);
+
 export const RoomViewFollowing = recipe({
   base: [
     DefaultReset,
index 58d3f64f59f0b807c76a9cc7b2f8b37f3d2f533e..5a96e6ad47afa95a2462b03ac7002ae1c06a567e 100644 (file)
@@ -24,6 +24,10 @@ import { useRoomEventReaders } from '../../hooks/useRoomEventReaders';
 import { EventReaders } from '../../components/event-readers';
 import { stopPropagation } from '../../utils/keyboard';
 
+export function RoomViewFollowingPlaceholder() {
+  return <div className={css.RoomViewFollowingPlaceholder} />;
+}
+
 export type RoomViewFollowingProps = {
   room: Room;
 };
index 7ee1d302956b3a216dbe9f14249a8a9f595d8266..deac935ec692864d3f5d05a838cc557442546552 100644 (file)
@@ -33,7 +33,7 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer';
 import { StateEvent } from '../../../types/matrix/room';
 import { useMatrixClient } from '../../hooks/useMatrixClient';
 import { useRoom } from '../../hooks/useRoom';
-import { useSetSetting } from '../../state/hooks/settings';
+import { useSetSetting, useSetting } from '../../state/hooks/settings';
 import { settingsAtom } from '../../state/settings';
 import { useSpaceOptionally } from '../../hooks/useSpace';
 import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
@@ -64,13 +64,14 @@ type RoomMenuProps = {
 };
 const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose }, ref) => {
   const mx = useMatrixClient();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
   const powerLevels = usePowerLevelsContext();
   const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
   const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
 
   const handleMarkAsRead = () => {
-    markAsRead(mx, room.roomId);
+    markAsRead(mx, room.roomId, hideActivity);
     requestClose();
   };
 
index bde03eb2cfbd985b5373d407b413f65350a3beb5..d6709a97c488689a3ba8a4147afec0def805de56 100644 (file)
@@ -671,6 +671,7 @@ export type MessageProps = {
   onReactionToggle: (targetEventId: string, key: string, shortcode?: string) => void;
   reply?: ReactNode;
   reactions?: ReactNode;
+  hideReadReceipts?: boolean;
 };
 export const Message = as<'div', MessageProps>(
   (
@@ -695,6 +696,7 @@ export const Message = as<'div', MessageProps>(
       onEditId,
       reply,
       reactions,
+      hideReadReceipts,
       children,
       ...props
     },
@@ -992,11 +994,13 @@ export const Message = as<'div', MessageProps>(
                               </Text>
                             </MenuItem>
                           )}
-                          <MessageReadReceiptItem
-                            room={room}
-                            eventId={mEvent.getId() ?? ''}
-                            onClose={closeMenu}
-                          />
+                          {!hideReadReceipts && (
+                            <MessageReadReceiptItem
+                              room={room}
+                              eventId={mEvent.getId() ?? ''}
+                              onClose={closeMenu}
+                            />
+                          )}
                           <MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
                           <MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
                           {canPinEvent && (
@@ -1071,9 +1075,23 @@ export type EventProps = {
   highlight: boolean;
   canDelete?: boolean;
   messageSpacing: MessageSpacing;
+  hideReadReceipts?: boolean;
 };
 export const Event = as<'div', EventProps>(
-  ({ className, room, mEvent, highlight, canDelete, messageSpacing, children, ...props }, ref) => {
+  (
+    {
+      className,
+      room,
+      mEvent,
+      highlight,
+      canDelete,
+      messageSpacing,
+      hideReadReceipts,
+      children,
+      ...props
+    },
+    ref
+  ) => {
     const mx = useMatrixClient();
     const [hover, setHover] = useState(false);
     const { hoverProps } = useHover({ onHoverChange: setHover });
@@ -1138,11 +1156,13 @@ export const Event = as<'div', EventProps>(
                     >
                       <Menu {...props} ref={ref}>
                         <Box direction="Column" gap="100" className={css.MessageMenuGroup}>
-                          <MessageReadReceiptItem
-                            room={room}
-                            eventId={mEvent.getId() ?? ''}
-                            onClose={closeMenu}
-                          />
+                          {!hideReadReceipts && (
+                            <MessageReadReceiptItem
+                              room={room}
+                              eventId={mEvent.getId() ?? ''}
+                              onClose={closeMenu}
+                            />
+                          )}
                           <MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
                           <MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
                         </Box>
index d58c99caf30347e04ca1588c13b193f7a0ba6f49..569cd41008489bc95ba9590f495ca3d0e40b6070 100644 (file)
@@ -344,6 +344,7 @@ function Appearance() {
 function Editor() {
   const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
   const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
+  const [hideActivity, setHideActivity] = useSetting(settingsAtom, 'hideActivity');
 
   return (
     <Box direction="Column" gap="100">
@@ -363,6 +364,13 @@ function Editor() {
           after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />}
         />
       </SequenceCard>
+      <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
+        <SettingTile
+          title="Hide Typing & Read Receipts"
+          description="Turn off both typing status and read receipts to keep your activity private."
+          after={<Switch variant="Primary" value={hideActivity} onChange={setHideActivity} />}
+        />
+      </SequenceCard>
     </Box>
   );
 }
@@ -555,7 +563,13 @@ function Messages() {
       <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
         <SettingTile
           title="Disable Media Auto Load"
-          after={<Switch variant="Primary" value={!mediaAutoLoad} onChange={(v) => setMediaAutoLoad(!v)} />}
+          after={
+            <Switch
+              variant="Primary"
+              value={!mediaAutoLoad}
+              onChange={(v) => setMediaAutoLoad(!v)}
+            />
+          }
         />
       </SequenceCard>
       <SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
index cd19e33e80e864f6c23a65192575026044055d2e..5e79921471854065ef285ccd669fca275b6f5a61 100644 (file)
@@ -45,18 +45,21 @@ import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCatego
 import { useRoomsUnread } from '../../../state/hooks/unread';
 import { markAsRead } from '../../../../client/action/notifications';
 import { stopPropagation } from '../../../utils/keyboard';
+import { useSetting } from '../../../state/hooks/settings';
+import { settingsAtom } from '../../../state/settings';
 
 type DirectMenuProps = {
   requestClose: () => void;
 };
 const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => {
   const mx = useMatrixClient();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const orphanRooms = useDirectRooms();
   const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
 
   const handleMarkAsRead = () => {
     if (!unread) return;
-    orphanRooms.forEach((rId) => markAsRead(mx, rId));
+    orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
     requestClose();
   };
 
index f9923f462bf05ab6ce636508e8f83e7a186b5ff7..fa5e68ab148c5cf89df4cb8a66a7ba6b5ad93df7 100644 (file)
@@ -48,18 +48,21 @@ import { useRoomsUnread } from '../../../state/hooks/unread';
 import { markAsRead } from '../../../../client/action/notifications';
 import { useClosedNavCategoriesAtom } from '../../../state/hooks/closedNavCategories';
 import { stopPropagation } from '../../../utils/keyboard';
+import { useSetting } from '../../../state/hooks/settings';
+import { settingsAtom } from '../../../state/settings';
 
 type HomeMenuProps = {
   requestClose: () => void;
 };
 const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
   const orphanRooms = useHomeRooms();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
   const mx = useMatrixClient();
 
   const handleMarkAsRead = () => {
     if (!unread) return;
-    orphanRooms.forEach((rId) => markAsRead(mx, rId));
+    orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
     requestClose();
   };
 
index 0c832b0946cd26833780929580e7e08ba4f46fdd..722ce5d30888b0ff32d0863916a392cc682cfc65 100644 (file)
@@ -182,6 +182,7 @@ type RoomNotificationsGroupProps = {
   notifications: INotification[];
   mediaAutoLoad?: boolean;
   urlPreview?: boolean;
+  hideActivity: boolean;
   onOpen: (roomId: string, eventId: string) => void;
 };
 function RoomNotificationsGroupComp({
@@ -189,6 +190,7 @@ function RoomNotificationsGroupComp({
   notifications,
   mediaAutoLoad,
   urlPreview,
+  hideActivity,
   onOpen,
 }: RoomNotificationsGroupProps) {
   const mx = useMatrixClient();
@@ -362,7 +364,7 @@ function RoomNotificationsGroupComp({
     onOpen(room.roomId, eventId);
   };
   const handleMarkAsRead = () => {
-    markAsRead(mx, room.roomId);
+    markAsRead(mx, room.roomId, hideActivity);
   };
 
   return (
@@ -496,6 +498,7 @@ const DEFAULT_REFRESH_MS = 7000;
 
 export function Notifications() {
   const mx = useMatrixClient();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
   const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
   const screenSize = useScreenSizeContext();
@@ -656,6 +659,7 @@ export function Notifications() {
                           notifications={group.notifications}
                           mediaAutoLoad={mediaAutoLoad}
                           urlPreview={urlPreview}
+                          hideActivity={hideActivity}
                           onOpen={navigateRoom}
                         />
                       </VirtualTile>
index 849fc3656bf6d104c581a854fc481626f8a1b29c..bd8090d3dd69440b993835925726871eea9a2b20 100644 (file)
@@ -23,18 +23,21 @@ import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
 import { useDirectRooms } from '../direct/useDirectRooms';
 import { markAsRead } from '../../../../client/action/notifications';
 import { stopPropagation } from '../../../utils/keyboard';
+import { settingsAtom } from '../../../state/settings';
+import { useSetting } from '../../../state/hooks/settings';
 
 type DirectMenuProps = {
   requestClose: () => void;
 };
 const DirectMenu = forwardRef<HTMLDivElement, DirectMenuProps>(({ requestClose }, ref) => {
   const orphanRooms = useDirectRooms();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
   const mx = useMatrixClient();
 
   const handleMarkAsRead = () => {
     if (!unread) return;
-    orphanRooms.forEach((rId) => markAsRead(mx, rId));
+    orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
     requestClose();
   };
 
index dcb0a49862c434b43dae544bbc3821441482aa17..c8a80280a8a260cceb4f1373c2c3afd87522d1e0 100644 (file)
@@ -24,18 +24,21 @@ import { useNavToActivePathAtom } from '../../../state/hooks/navToActivePath';
 import { useHomeRooms } from '../home/useHomeRooms';
 import { markAsRead } from '../../../../client/action/notifications';
 import { stopPropagation } from '../../../utils/keyboard';
+import { useSetting } from '../../../state/hooks/settings';
+import { settingsAtom } from '../../../state/settings';
 
 type HomeMenuProps = {
   requestClose: () => void;
 };
 const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
   const orphanRooms = useHomeRooms();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
   const mx = useMatrixClient();
 
   const handleMarkAsRead = () => {
     if (!unread) return;
-    orphanRooms.forEach((rId) => markAsRead(mx, rId));
+    orphanRooms.forEach((rId) => markAsRead(mx, rId, hideActivity));
     requestClose();
   };
 
index 343afae47e5f7614745d2391d77b5f7b246f8727..96e3b9adbcafad67d118c091992de33066b7baa1 100644 (file)
@@ -88,6 +88,8 @@ import { getMatrixToRoom } from '../../../plugins/matrix-to';
 import { getViaServers } from '../../../plugins/via-servers';
 import { getRoomAvatarUrl } from '../../../utils/room';
 import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
+import { useSetting } from '../../../state/hooks/settings';
+import { settingsAtom } from '../../../state/settings';
 
 type SpaceMenuProps = {
   room: Room;
@@ -97,6 +99,7 @@ type SpaceMenuProps = {
 const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
   ({ room, requestClose, onUnpin }, ref) => {
     const mx = useMatrixClient();
+    const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
     const roomToParents = useAtomValue(roomToParentsAtom);
     const powerLevels = usePowerLevels(room);
     const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
@@ -110,7 +113,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(
     const unread = useRoomsUnread(allChild, roomToUnreadAtom);
 
     const handleMarkAsRead = () => {
-      allChild.forEach((childRoomId) => markAsRead(mx, childRoomId));
+      allChild.forEach((childRoomId) => markAsRead(mx, childRoomId, hideActivity));
       requestClose();
     };
 
@@ -227,18 +230,18 @@ const useDraggableItem = (
     return !target
       ? undefined
       : draggable({
-        element: target,
-        dragHandle,
-        getInitialData: () => ({ item }),
-        onDragStart: () => {
-          setDragging(true);
-          onDragging?.(item);
-        },
-        onDrop: () => {
-          setDragging(false);
-          onDragging?.(undefined);
-        },
-      });
+          element: target,
+          dragHandle,
+          getInitialData: () => ({ item }),
+          onDragStart: () => {
+            setDragging(true);
+            onDragging?.(item);
+          },
+          onDrop: () => {
+            setDragging(false);
+            onDragging?.(undefined);
+          },
+        });
   }, [targetRef, dragHandleRef, item, onDragging]);
 
   return dragging;
@@ -388,9 +391,9 @@ function SpaceTab({
     () =>
       folder
         ? {
-          folder,
-          spaceId: space.roomId,
-        }
+            folder,
+            spaceId: space.roomId,
+          }
         : space.roomId,
     [folder, space]
   );
index c8e2b78356a14df815d7db4f22a18dbb0d3cbf27..1714d8ee02bc41812ee32356b316a960d7d98506 100644 (file)
@@ -69,6 +69,8 @@ import { StateEvent } from '../../../../types/matrix/room';
 import { stopPropagation } from '../../../utils/keyboard';
 import { getMatrixToRoom } from '../../../plugins/matrix-to';
 import { getViaServers } from '../../../plugins/via-servers';
+import { useSetting } from '../../../state/hooks/settings';
+import { settingsAtom } from '../../../state/settings';
 
 type SpaceMenuProps = {
   room: Room;
@@ -76,6 +78,7 @@ type SpaceMenuProps = {
 };
 const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClose }, ref) => {
   const mx = useMatrixClient();
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
   const roomToParents = useAtomValue(roomToParentsAtom);
   const powerLevels = usePowerLevels(room);
   const { getPowerLevel, canDoAction } = usePowerLevelsAPI(powerLevels);
@@ -89,7 +92,7 @@ const SpaceMenu = forwardRef<HTMLDivElement, SpaceMenuProps>(({ room, requestClo
   const unread = useRoomsUnread(allChild, roomToUnreadAtom);
 
   const handleMarkAsRead = () => {
-    allChild.forEach((childRoomId) => markAsRead(mx, childRoomId));
+    allChild.forEach((childRoomId) => markAsRead(mx, childRoomId, hideActivity));
     requestClose();
   };
 
index 8cb9d958476d8840a0781b361d75f112ae5319fb..bf99fe346c1c2391ce04088135d00d946356b70b 100644 (file)
@@ -228,20 +228,18 @@ export const useBindRoomToUnreadAtom = (
 
   useEffect(() => {
     const handleReceipt = (mEvent: MatrixEvent, room: Room) => {
-      if (mEvent.getType() === 'm.receipt') {
-        const myUserId = mx.getUserId();
-        if (!myUserId) return;
-        if (room.isSpaceRoom()) return;
-        const content = mEvent.getContent<ReceiptContent>();
+      const myUserId = mx.getUserId();
+      if (!myUserId) return;
+      if (room.isSpaceRoom()) return;
+      const content = mEvent.getContent<ReceiptContent>();
 
-        const isMyReceipt = Object.keys(content).find((eventId) =>
-          (Object.keys(content[eventId]) as ReceiptType[]).find(
-            (receiptType) => content[eventId][receiptType][myUserId]
-          )
-        );
-        if (isMyReceipt) {
-          setUnreadAtom({ type: 'DELETE', roomId: room.roomId });
-        }
+      const isMyReceipt = Object.keys(content).find((eventId) =>
+        (Object.keys(content[eventId]) as ReceiptType[]).find(
+          (receiptType) => content[eventId][receiptType][myUserId]
+        )
+      );
+      if (isMyReceipt) {
+        setUnreadAtom({ type: 'DELETE', roomId: room.roomId });
       }
     };
     mx.on(RoomEvent.Receipt, handleReceipt);
index ac47e78b218d6184775908d8b5bdb8ed60a027e7..9d979195515ae1145959649a4d08195841ade104 100644 (file)
@@ -17,6 +17,7 @@ export interface Settings {
   editorToolbar: boolean;
   twitterEmoji: boolean;
   pageZoom: number;
+  hideActivity: boolean;
 
   isPeopleDrawer: boolean;
   memberSortFilterIndex: number;
@@ -45,6 +46,7 @@ const defaultSettings: Settings = {
   editorToolbar: false,
   twitterEmoji: false,
   pageZoom: 100,
+  hideActivity: false,
 
   isPeopleDrawer: true,
   memberSortFilterIndex: 0,
index 55bf8f62837c61f3c89c16421ca3336326f6cf48..e94ba9726334cc4476370e42f55956682ccf584c 100644 (file)
@@ -2,6 +2,8 @@ import produce from 'immer';
 import { atom, useSetAtom } from 'jotai';
 import { MatrixClient, RoomMemberEvent, RoomMemberEventHandlerMap } from 'matrix-js-sdk';
 import { useEffect } from 'react';
+import { useSetting } from './hooks/settings';
+import { settingsAtom } from './settings';
 
 export const TYPING_TIMEOUT_MS = 5000; // 5 seconds
 
@@ -127,12 +129,16 @@ export const useBindRoomIdToTypingMembersAtom = (
   typingMembersAtom: typeof roomIdToTypingMembersAtom
 ) => {
   const setTypingMembers = useSetAtom(typingMembersAtom);
+  const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
 
   useEffect(() => {
     const handleTypingEvent: RoomMemberEventHandlerMap[RoomMemberEvent.Typing] = (
       event,
       member
     ) => {
+      if (hideActivity) {
+        return;
+      }
       setTypingMembers({
         type: member.typing ? 'PUT' : 'DELETE',
         roomId: member.roomId,
@@ -145,5 +151,5 @@ export const useBindRoomIdToTypingMembersAtom = (
     return () => {
       mx.removeListener(RoomMemberEvent.Typing, handleTypingEvent);
     };
-  }, [mx, setTypingMembers]);
+  }, [mx, setTypingMembers, hideActivity]);
 };
index 17ea1ed614c4c33e58af8fb5bd48b14484cd6b70..a23bd1a41b8817342d287e3f6be4739e3a76fb62 100644 (file)
@@ -1,6 +1,6 @@
-import { MatrixClient } from "matrix-js-sdk";
+import { MatrixClient, ReceiptType } from 'matrix-js-sdk';
 
-export async function markAsRead(mx: MatrixClient, roomId: string) {
+export async function markAsRead(mx: MatrixClient, roomId: string, privateReceipt: boolean) {
   const room = mx.getRoom(roomId);
   if (!room) return;
 
@@ -19,5 +19,8 @@ export async function markAsRead(mx: MatrixClient, roomId: string) {
   const latestEvent = getLatestValidEvent();
   if (latestEvent === null) return;
 
-  await mx.sendReadReceipt(latestEvent);
+  await mx.sendReadReceipt(
+    latestEvent,
+    privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read
+  );
 }