feat: do not approve a convo before sending the first message

but still sync it's state through the createdAt with libsession util
pull/2620/head
Audric Ackermann 3 years ago
parent faeb95fefd
commit 55a2767fce

@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useCallback, useContext } from 'react'; import React, { useCallback } from 'react';
import { contextMenu } from 'react-contexify'; import { contextMenu } from 'react-contexify';
import { Avatar, AvatarSize } from '../../avatar/Avatar'; import { Avatar, AvatarSize } from '../../avatar/Avatar';
@ -7,8 +7,8 @@ import { Avatar, AvatarSize } from '../../avatar/Avatar';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import {
openConversationWithMessages,
ReduxConversationType, ReduxConversationType,
openConversationWithMessages,
} from '../../../state/ducks/conversations'; } from '../../../state/ducks/conversations';
import { updateUserDetailsModal } from '../../../state/ducks/modalDialog'; import { updateUserDetailsModal } from '../../../state/ducks/modalDialog';
@ -24,18 +24,13 @@ import {
import { isSearching } from '../../../state/selectors/search'; import { isSearching } from '../../../state/selectors/search';
import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation'; import { useSelectedConversationKey } from '../../../state/selectors/selectedConversation';
import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu'; import { MemoConversationListItemContextMenu } from '../../menu/ConversationListItemContextMenu';
import { ContextConversationProvider, useConvoIdFromContext } from './ConvoIdContext';
import { ConversationListItemHeaderItem } from './HeaderItem'; import { ConversationListItemHeaderItem } from './HeaderItem';
import { MessageItem } from './MessageItem'; import { MessageItem } from './MessageItem';
// tslint:disable-next-line: no-empty-interface // tslint:disable-next-line: no-empty-interface
export type ConversationListItemProps = Pick<ReduxConversationType, 'id'>; export type ConversationListItemProps = Pick<ReduxConversationType, 'id'>;
/**
* This React context is used to share deeply in the tree of the ConversationListItem what is the ID we are currently rendering.
* This is to avoid passing the prop to all the subtree component
*/
export const ContextConversationId = React.createContext('');
type PropsHousekeeping = { type PropsHousekeeping = {
style?: Object; style?: Object;
}; };
@ -48,7 +43,7 @@ const Portal = ({ children }: { children: any }) => {
}; };
const AvatarItem = () => { const AvatarItem = () => {
const conversationId = useContext(ContextConversationId); const conversationId = useConvoIdFromContext();
const userName = useConversationUsername(conversationId); const userName = useConversationUsername(conversationId);
const isPrivate = useIsPrivate(conversationId); const isPrivate = useIsPrivate(conversationId);
const avatarPath = useAvatarPath(conversationId); const avatarPath = useAvatarPath(conversationId);
@ -107,7 +102,7 @@ const ConversationListItem = (props: Props) => {
); );
return ( return (
<ContextConversationId.Provider value={conversationId}> <ContextConversationProvider value={conversationId}>
<div key={key}> <div key={key}>
<div <div
role="button" role="button"
@ -141,7 +136,7 @@ const ConversationListItem = (props: Props) => {
<MemoConversationListItemContextMenu triggerId={triggerId} /> <MemoConversationListItemContextMenu triggerId={triggerId} />
</Portal> </Portal>
</div> </div>
</ContextConversationId.Provider> </ContextConversationProvider>
); );
}; };

@ -0,0 +1,14 @@
import React, { useContext } from 'react';
/**
* This React context is used to share deeply in the tree of the ConversationListItem what is the ID we are currently rendering.
* This is to avoid passing the prop to all the subtree component
*/
const ContextConversationId = React.createContext('');
export const ContextConversationProvider = ContextConversationId.Provider;
export function useConvoIdFromContext() {
const convoId = useContext(ContextConversationId);
return convoId;
}

@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useContext } from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { Data } from '../../../data/data'; import { Data } from '../../../data/data';
@ -20,12 +20,12 @@ import { isSearching } from '../../../state/selectors/search';
import { getIsMessageSection } from '../../../state/selectors/section'; import { getIsMessageSection } from '../../../state/selectors/section';
import { Timestamp } from '../../conversation/Timestamp'; import { Timestamp } from '../../conversation/Timestamp';
import { SessionIcon } from '../../icon'; import { SessionIcon } from '../../icon';
import { ContextConversationId } from './ConversationListItem'; import { useConvoIdFromContext } from './ConvoIdContext';
import { UserItem } from './UserItem'; import { UserItem } from './UserItem';
const NotificationSettingIcon = () => { const NotificationSettingIcon = () => {
const isMessagesSection = useSelector(getIsMessageSection); const isMessagesSection = useSelector(getIsMessageSection);
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const convoSetting = useConversationPropsById(convoId)?.currentNotificationSetting; const convoSetting = useConversationPropsById(convoId)?.currentNotificationSetting;
if (!isMessagesSection) { if (!isMessagesSection) {
@ -66,7 +66,7 @@ const StyledConversationListItemIconWrapper = styled.div`
`; `;
const PinIcon = () => { const PinIcon = () => {
const conversationId = useContext(ContextConversationId); const conversationId = useConvoIdFromContext();
const isMessagesSection = useSelector(getIsMessageSection); const isMessagesSection = useSelector(getIsMessageSection);
const isPinned = useIsPinned(conversationId); const isPinned = useIsPinned(conversationId);
@ -165,7 +165,7 @@ const UnreadCount = ({ convoId }: { convoId: string }) => {
}; };
export const ConversationListItemHeaderItem = () => { export const ConversationListItemHeaderItem = () => {
const conversationId = useContext(ContextConversationId); const conversationId = useConvoIdFromContext();
const isSearchingMode = useSelector(isSearching); const isSearchingMode = useSelector(isSearching);

@ -1,19 +1,19 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useContext } from 'react';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React from 'react';
import { useSelector } from 'react-redux';
import { import {
useConversationPropsById, useConversationPropsById,
useHasUnread, useHasUnread,
useIsPrivate, useIsPrivate,
useIsTyping, useIsTyping,
} from '../../../hooks/useParamSelector'; } from '../../../hooks/useParamSelector';
import { MessageBody } from '../../conversation/message/message-content/MessageBody';
import { OutgoingMessageStatus } from '../../conversation/message/message-content/OutgoingMessageStatus';
import { TypingAnimation } from '../../conversation/TypingAnimation';
import { ContextConversationId } from './ConversationListItem';
import { useSelector } from 'react-redux';
import { isSearching } from '../../../state/selectors/search'; import { isSearching } from '../../../state/selectors/search';
import { getIsMessageRequestOverlayShown } from '../../../state/selectors/section'; import { getIsMessageRequestOverlayShown } from '../../../state/selectors/section';
import { TypingAnimation } from '../../conversation/TypingAnimation';
import { MessageBody } from '../../conversation/message/message-content/MessageBody';
import { OutgoingMessageStatus } from '../../conversation/message/message-content/OutgoingMessageStatus';
import { useConvoIdFromContext } from './ConvoIdContext';
function useLastMessageFromConvo(convoId: string) { function useLastMessageFromConvo(convoId: string) {
const convoProps = useConversationPropsById(convoId); const convoProps = useConversationPropsById(convoId);
@ -24,7 +24,7 @@ function useLastMessageFromConvo(convoId: string) {
} }
export const MessageItem = () => { export const MessageItem = () => {
const conversationId = useContext(ContextConversationId); const conversationId = useConvoIdFromContext();
const lastMessage = useLastMessageFromConvo(conversationId); const lastMessage = useLastMessageFromConvo(conversationId);
const isGroup = !useIsPrivate(conversationId); const isGroup = !useIsPrivate(conversationId);

@ -1,4 +1,4 @@
import React, { useContext } from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { import {
useConversationRealName, useConversationRealName,
@ -9,10 +9,10 @@ import {
import { PubKey } from '../../../session/types'; import { PubKey } from '../../../session/types';
import { isSearching } from '../../../state/selectors/search'; import { isSearching } from '../../../state/selectors/search';
import { ContactName } from '../../conversation/ContactName'; import { ContactName } from '../../conversation/ContactName';
import { ContextConversationId } from './ConversationListItem'; import { useConvoIdFromContext } from './ConvoIdContext';
export const UserItem = () => { export const UserItem = () => {
const conversationId = useContext(ContextConversationId); const conversationId = useConvoIdFromContext();
// we want to show the nickname in brackets if a nickname is set for search results // we want to show the nickname in brackets if a nickname is set for search results
const isSearchResultsMode = useSelector(isSearching); const isSearchResultsMode = useSelector(isSearching);

@ -63,15 +63,13 @@ export const OverlayMessage = () => {
); );
// we now want to show a conversation we just started on the leftpane, even if we did not send a message to it yet // we now want to show a conversation we just started on the leftpane, even if we did not send a message to it yet
if (!convo.isActive() || !convo.isApproved() || convo.isHidden()) { if (!convo.isActive() || convo.isHidden()) {
// bump the timestamp only if we were not active before // bump the timestamp only if we were not active before
if (!convo.isActive()) { if (!convo.isActive()) {
convo.set({ active_at: Date.now() }); convo.set({ active_at: Date.now() });
} }
await convo.unhideIfNeeded(false); await convo.unhideIfNeeded(false);
// TODO find a way to make this not a hack to show it in the list
convo.set({ isApproved: true });
await convo.commit(); await convo.commit();
} }

@ -22,7 +22,6 @@ import {
} from '../../state/selectors/selectedConversation'; } from '../../state/selectors/selectedConversation';
import { getTimerOptions } from '../../state/selectors/timerOptions'; import { getTimerOptions } from '../../state/selectors/timerOptions';
import { LocalizerKeys } from '../../types/LocalizerKeys'; import { LocalizerKeys } from '../../types/LocalizerKeys';
import { ContextConversationId } from '../leftpane/conversation-list-item/ConversationListItem';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer'; import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
import { import {
AddModeratorsMenuItem, AddModeratorsMenuItem,
@ -40,6 +39,7 @@ import {
UnbanMenuItem, UnbanMenuItem,
UpdateGroupNameMenuItem, UpdateGroupNameMenuItem,
} from './Menu'; } from './Menu';
import { ContextConversationProvider } from '../leftpane/conversation-list-item/ConvoIdContext';
export type PropsConversationHeaderMenu = { export type PropsConversationHeaderMenu = {
triggerId: string; triggerId: string;
@ -62,7 +62,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
} }
return ( return (
<ContextConversationId.Provider value={convoId}> <ContextConversationProvider value={convoId}>
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation={animation.fade}>
<DisappearingMessageMenuItem /> <DisappearingMessageMenuItem />
@ -83,7 +83,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
<ShowUserDetailsMenuItem /> <ShowUserDetailsMenuItem />
</Menu> </Menu>
</SessionContextMenuContainer> </SessionContextMenuContainer>
</ContextConversationId.Provider> </ContextConversationProvider>
); );
}; };

@ -1,7 +1,13 @@
import React from 'react';
import { animation, Menu } from 'react-contexify';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react';
import { animation, Item, Menu } from 'react-contexify';
import { useSelector } from 'react-redux';
import { useIsPinned, useIsPrivate, useIsPrivateAndFriend } from '../../hooks/useParamSelector';
import { getConversationController } from '../../session/conversations';
import { getIsMessageSection } from '../../state/selectors/section';
import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
import { import {
AcceptMsgRequestMenuItem, AcceptMsgRequestMenuItem,
BanMenuItem, BanMenuItem,
@ -17,11 +23,9 @@ import {
LeaveGroupMenuItem, LeaveGroupMenuItem,
MarkAllReadMenuItem, MarkAllReadMenuItem,
MarkConversationUnreadMenuItem, MarkConversationUnreadMenuItem,
PinConversationMenuItem,
ShowUserDetailsMenuItem, ShowUserDetailsMenuItem,
UnbanMenuItem, UnbanMenuItem,
} from './Menu'; } from './Menu';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
export type PropsContextConversationItem = { export type PropsContextConversationItem = {
triggerId: string; triggerId: string;
@ -33,17 +37,22 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
return ( return (
<SessionContextMenuContainer> <SessionContextMenuContainer>
<Menu id={triggerId} animation={animation.fade}> <Menu id={triggerId} animation={animation.fade}>
{/* Message request related actions */}
<AcceptMsgRequestMenuItem /> <AcceptMsgRequestMenuItem />
<DeclineMsgRequestMenuItem /> <DeclineMsgRequestMenuItem />
<DeclineAndBlockMsgRequestMenuItem /> <DeclineAndBlockMsgRequestMenuItem />
{/* Generic actions */}
<PinConversationMenuItem /> <PinConversationMenuItem />
<BlockMenuItem /> <BlockMenuItem />
<CopyMenuItem /> <CopyMenuItem />
{/* Read state actions */}
<MarkAllReadMenuItem /> <MarkAllReadMenuItem />
<MarkConversationUnreadMenuItem /> <MarkConversationUnreadMenuItem />
<DeleteMessagesMenuItem />
{/* Nickname actions */}
<ChangeNicknameMenuItem /> <ChangeNicknameMenuItem />
<ClearNicknameMenuItem /> <ClearNicknameMenuItem />
<DeleteMessagesMenuItem /> {/* Communities actions */}
<BanMenuItem /> <BanMenuItem />
<UnbanMenuItem /> <UnbanMenuItem />
<InviteContactMenuItem /> <InviteContactMenuItem />
@ -62,3 +71,23 @@ export const MemoConversationListItemContextMenu = React.memo(
ConversationListItemContextMenu, ConversationListItemContextMenu,
propsAreEqual propsAreEqual
); );
export const PinConversationMenuItem = (): JSX.Element | null => {
const conversationId = useConvoIdFromContext();
const isMessagesSection = useSelector(getIsMessageSection);
const isPrivateAndFriend = useIsPrivateAndFriend(conversationId);
const isPrivate = useIsPrivate(conversationId);
const isPinned = useIsPinned(conversationId);
if (isMessagesSection && (!isPrivate || (isPrivate && isPrivateAndFriend))) {
const conversation = getConversationController().get(conversationId);
const togglePinConversation = async () => {
await conversation?.togglePinned();
};
const menuText = isPinned ? window.i18n('unpinConversation') : window.i18n('pinConversation');
return <Item onClick={togglePinConversation}>{menuText}</Item>;
}
return null;
};

@ -1,4 +1,4 @@
import React, { useContext } from 'react'; import React from 'react';
import { Item } from 'react-contexify'; import { Item } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
@ -12,7 +12,6 @@ import {
useIsKickedFromGroup, useIsKickedFromGroup,
useIsLeft, useIsLeft,
useIsMe, useIsMe,
useIsPinned,
useIsPrivate, useIsPrivate,
useIsPrivateAndFriend, useIsPrivateAndFriend,
useIsPublic, useIsPublic,
@ -47,7 +46,7 @@ import {
useSelectedIsPrivateFriend, useSelectedIsPrivateFriend,
} from '../../state/selectors/selectedConversation'; } from '../../state/selectors/selectedConversation';
import { SessionButtonColor } from '../basic/SessionButton'; import { SessionButtonColor } from '../basic/SessionButton';
import { ContextConversationId } from '../leftpane/conversation-list-item/ConversationListItem'; import { useConvoIdFromContext } from '../leftpane/conversation-list-item/ConvoIdContext';
function showDeleteContact( function showDeleteContact(
isGroup: boolean, isGroup: boolean,
@ -84,7 +83,7 @@ function showInviteContact(isPublic: boolean): boolean {
/** Menu items standardized */ /** Menu items standardized */
export const InviteContactMenuItem = (): JSX.Element | null => { export const InviteContactMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
if (showInviteContact(isPublic)) { if (showInviteContact(isPublic)) {
@ -101,31 +100,13 @@ export const InviteContactMenuItem = (): JSX.Element | null => {
return null; return null;
}; };
export const PinConversationMenuItem = (): JSX.Element | null => {
const conversationId = useContext(ContextConversationId);
const isMessagesSection = useSelector(getIsMessageSection);
const isRequest = useIsIncomingRequest(conversationId);
const isPinned = useIsPinned(conversationId);
if (isMessagesSection && !isRequest) {
const conversation = getConversationController().get(conversationId);
const togglePinConversation = async () => {
await conversation?.togglePinned();
};
const menuText = isPinned ? window.i18n('unpinConversation') : window.i18n('pinConversation');
return <Item onClick={togglePinConversation}>{menuText}</Item>;
}
return null;
};
export const MarkConversationUnreadMenuItem = (): JSX.Element | null => { export const MarkConversationUnreadMenuItem = (): JSX.Element | null => {
const conversationId = useContext(ContextConversationId); const conversationId = useConvoIdFromContext();
const isMessagesSection = useSelector(getIsMessageSection); const isMessagesSection = useSelector(getIsMessageSection);
const isRequest = useIsIncomingRequest(conversationId); const isPrivate = useIsPrivate(conversationId);
const isPrivateAndFriend = useIsPrivateAndFriend(conversationId);
if (isMessagesSection && !isRequest) { if (isMessagesSection && (!isPrivate || (isPrivate && isPrivateAndFriend))) {
const conversation = getConversationController().get(conversationId); const conversation = getConversationController().get(conversationId);
const markUnread = async () => { const markUnread = async () => {
@ -139,7 +120,7 @@ export const MarkConversationUnreadMenuItem = (): JSX.Element | null => {
export const DeleteContactMenuItem = () => { export const DeleteContactMenuItem = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isLeft = useIsLeft(convoId); const isLeft = useIsLeft(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
@ -182,7 +163,7 @@ export const DeleteContactMenuItem = () => {
}; };
export const LeaveGroupMenuItem = () => { export const LeaveGroupMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isLeft = useIsLeft(convoId); const isLeft = useIsLeft(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
@ -205,7 +186,7 @@ export const LeaveGroupMenuItem = () => {
export const ShowUserDetailsMenuItem = () => { export const ShowUserDetailsMenuItem = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
const avatarPath = useAvatarPath(convoId); const avatarPath = useAvatarPath(convoId);
const userName = useConversationUsername(convoId) || convoId; const userName = useConversationUsername(convoId) || convoId;
@ -233,7 +214,7 @@ export const ShowUserDetailsMenuItem = () => {
}; };
export const UpdateGroupNameMenuItem = () => { export const UpdateGroupNameMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const left = useIsLeft(convoId); const left = useIsLeft(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
const weAreAdmin = useWeAreAdmin(convoId); const weAreAdmin = useWeAreAdmin(convoId);
@ -253,7 +234,7 @@ export const UpdateGroupNameMenuItem = () => {
}; };
export const RemoveModeratorsMenuItem = (): JSX.Element | null => { export const RemoveModeratorsMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
const weAreAdmin = useWeAreAdmin(convoId); const weAreAdmin = useWeAreAdmin(convoId);
@ -273,7 +254,7 @@ export const RemoveModeratorsMenuItem = (): JSX.Element | null => {
}; };
export const AddModeratorsMenuItem = (): JSX.Element | null => { export const AddModeratorsMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
const weAreAdmin = useWeAreAdmin(convoId); const weAreAdmin = useWeAreAdmin(convoId);
@ -293,7 +274,7 @@ export const AddModeratorsMenuItem = (): JSX.Element | null => {
}; };
export const UnbanMenuItem = (): JSX.Element | null => { export const UnbanMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
const weAreAdmin = useWeAreAdmin(convoId); const weAreAdmin = useWeAreAdmin(convoId);
@ -313,7 +294,7 @@ export const UnbanMenuItem = (): JSX.Element | null => {
}; };
export const BanMenuItem = (): JSX.Element | null => { export const BanMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId); const isKickedFromGroup = useIsKickedFromGroup(convoId);
const weAreAdmin = useWeAreAdmin(convoId); const weAreAdmin = useWeAreAdmin(convoId);
@ -333,7 +314,7 @@ export const BanMenuItem = (): JSX.Element | null => {
}; };
export const CopyMenuItem = (): JSX.Element | null => { export const CopyMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isPublic = useIsPublic(convoId); const isPublic = useIsPublic(convoId);
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
const isBlinded = useIsBlinded(convoId); const isBlinded = useIsBlinded(convoId);
@ -356,7 +337,7 @@ export const CopyMenuItem = (): JSX.Element | null => {
}; };
export const MarkAllReadMenuItem = (): JSX.Element | null => { export const MarkAllReadMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isIncomingRequest = useIsIncomingRequest(convoId); const isIncomingRequest = useIsIncomingRequest(convoId);
if (!isIncomingRequest) { if (!isIncomingRequest) {
return ( return (
@ -374,7 +355,7 @@ export function isRtlBody(): boolean {
} }
export const BlockMenuItem = (): JSX.Element | null => { export const BlockMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isMe = useIsMe(convoId); const isMe = useIsMe(convoId);
const isBlocked = useIsBlocked(convoId); const isBlocked = useIsBlocked(convoId);
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
@ -391,7 +372,7 @@ export const BlockMenuItem = (): JSX.Element | null => {
}; };
export const ClearNicknameMenuItem = (): JSX.Element | null => { export const ClearNicknameMenuItem = (): JSX.Element | null => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isMe = useIsMe(convoId); const isMe = useIsMe(convoId);
const hasNickname = useHasNickname(convoId); const hasNickname = useHasNickname(convoId);
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
@ -407,7 +388,7 @@ export const ClearNicknameMenuItem = (): JSX.Element | null => {
}; };
export const ChangeNicknameMenuItem = () => { export const ChangeNicknameMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isMe = useIsMe(convoId); const isMe = useIsMe(convoId);
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
const isPrivateAndFriend = useSelectedIsPrivateFriend(); const isPrivateAndFriend = useSelectedIsPrivateFriend();
@ -428,7 +409,7 @@ export const ChangeNicknameMenuItem = () => {
}; };
export const DeleteMessagesMenuItem = () => { export const DeleteMessagesMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
if (!convoId) { if (!convoId) {
return null; return null;
@ -446,7 +427,7 @@ export const DeleteMessagesMenuItem = () => {
}; };
export const AcceptMsgRequestMenuItem = () => { export const AcceptMsgRequestMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isRequest = useIsIncomingRequest(convoId); const isRequest = useIsIncomingRequest(convoId);
const convo = getConversationController().get(convoId); const convo = getConversationController().get(convoId);
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
@ -468,7 +449,7 @@ export const AcceptMsgRequestMenuItem = () => {
}; };
export const DeclineMsgRequestMenuItem = () => { export const DeclineMsgRequestMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isRequest = useIsIncomingRequest(convoId); const isRequest = useIsIncomingRequest(convoId);
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);
const selected = useSelectedConversationKey(); const selected = useSelectedConversationKey();
@ -493,7 +474,7 @@ export const DeclineMsgRequestMenuItem = () => {
}; };
export const DeclineAndBlockMsgRequestMenuItem = () => { export const DeclineAndBlockMsgRequestMenuItem = () => {
const convoId = useContext(ContextConversationId); const convoId = useConvoIdFromContext();
const isRequest = useIsIncomingRequest(convoId); const isRequest = useIsIncomingRequest(convoId);
const selected = useSelectedConversationKey(); const selected = useSelectedConversationKey();
const isPrivate = useIsPrivate(convoId); const isPrivate = useIsPrivate(convoId);

@ -32,7 +32,7 @@ const BlockedEntriesRoundedContainer = styled.div`
const BlockedContactsSection = styled.div` const BlockedContactsSection = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 80px;
background: var(--settings-tab-background-color); background: var(--settings-tab-background-color);
color: var(--settings-tab-text-color); color: var(--settings-tab-text-color);

@ -134,14 +134,12 @@ export async function declineConversationWithoutConfirm({
}) { }) {
const conversationToDecline = getConversationController().get(conversationId); const conversationToDecline = getConversationController().get(conversationId);
if (!conversationToDecline || conversationToDecline.isApproved()) { if (!conversationToDecline || !conversationToDecline.isApproved()) {
window?.log?.info('Conversation is already declined.'); window?.log?.info('Conversation is already declined.');
return; return;
} }
// we mark the conversation as inactive. This way it wont' show up in the UI. // Note: do not set the active_at undefined as this would make that conversation not synced with the libsession wrapper
// we cannot delete it completely on desktop, because we might need the convo details for sogs/group convos.
conversationToDecline.set('active_at', undefined);
await conversationToDecline.setIsApproved(false, false); await conversationToDecline.setIsApproved(false, false);
await conversationToDecline.setDidApproveMe(false, false); await conversationToDecline.setDidApproveMe(false, false);
// this will update the value in the wrapper if needed but not remove the entry if we want it gone. The remove is done below with removeContactFromWrapper // this will update the value in the wrapper if needed but not remove the entry if we want it gone. The remove is done below with removeContactFromWrapper
@ -154,7 +152,7 @@ export async function declineConversationWithoutConfirm({
if ( if (
conversationToDecline.isPrivate() && conversationToDecline.isPrivate() &&
!SessionUtilContact.isContactToStoreInContactsWrapper(conversationToDecline) !SessionUtilContact.isContactToStoreInWrapper(conversationToDecline)
) { ) {
await SessionUtilContact.removeContactFromWrapper(conversationToDecline.id); await SessionUtilContact.removeContactFromWrapper(conversationToDecline.id);
} }

@ -2256,12 +2256,12 @@ export async function commitConversationAndRefreshWrapper(id: string) {
switch (variant) { switch (variant) {
case 'UserConfig': case 'UserConfig':
if (SessionUtilUserProfile.isUserProfileToStoreInContactsWrapper(convo.id)) { if (SessionUtilUserProfile.isUserProfileToStoreInWrapper(convo.id)) {
await SessionUtilUserProfile.insertUserProfileIntoWrapper(convo.id); await SessionUtilUserProfile.insertUserProfileIntoWrapper(convo.id);
} }
break; break;
case 'ContactsConfig': case 'ContactsConfig':
if (SessionUtilContact.isContactToStoreInContactsWrapper(convo)) { if (SessionUtilContact.isContactToStoreInWrapper(convo)) {
await SessionUtilContact.insertContactFromDBIntoWrapperAndRefresh(convo.id); await SessionUtilContact.insertContactFromDBIntoWrapperAndRefresh(convo.id);
} }
break; break;

@ -1610,7 +1610,7 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
* Setup up the Contacts Wrapper with all the contact details which needs to be stored in it. * Setup up the Contacts Wrapper with all the contact details which needs to be stored in it.
*/ */
// this filter is based on the `isContactToStoreInContactsWrapper` function. Note, blocked contacts won't be added to the wrapper at first, but will on the first start // this filter is based on the `isContactToStoreInWrapper` function. Note, blocked contacts won't be added to the wrapper at first, but will on the first start
const contactsToWriteInWrapper = db const contactsToWriteInWrapper = db
.prepare( .prepare(
`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE type = 'private' AND active_at > 0 AND NOT hidden AND (didApproveMe OR isApproved) AND id <> '$us' AND id NOT LIKE '15%' ;` `SELECT * FROM ${CONVERSATIONS_TABLE} WHERE type = 'private' AND active_at > 0 AND NOT hidden AND (didApproveMe OR isApproved) AND id <> '$us' AND id NOT LIKE '15%' ;`

@ -142,7 +142,6 @@ async function handleContactsUpdate(result: IncomingConfResult): Promise<Incomin
// our profile update comes from our userProfile, not from the contacts wrapper. // our profile update comes from our userProfile, not from the contacts wrapper.
continue; continue;
} }
const contactConvo = await getConversationController().getOrCreateAndWait( const contactConvo = await getConversationController().getOrCreateAndWait(
wrapperConvo.id, wrapperConvo.id,
ConversationTypeEnum.PRIVATE ConversationTypeEnum.PRIVATE
@ -175,11 +174,6 @@ async function handleContactsUpdate(result: IncomingConfResult): Promise<Incomin
await contactConvo.updateExpireTimer(wrapperConvo.expirationTimerSeconds); await contactConvo.updateExpireTimer(wrapperConvo.expirationTimerSeconds);
changes = true; changes = true;
} }
console.warn(
`contactConvo.id: ${contactConvo}; wrapperConvo.createdAtSeconds:${
wrapperConvo.createdAtSeconds
}; current:${contactConvo.get('active_at')}`
);
// we want to set the active_at to the created_at timestamp if active_at is unset, so that it shows up in our list. // we want to set the active_at to the created_at timestamp if active_at is unset, so that it shows up in our list.
if (!contactConvo.get('active_at') && wrapperConvo.createdAtSeconds) { if (!contactConvo.get('active_at') && wrapperConvo.createdAtSeconds) {

@ -326,7 +326,7 @@ export class ConversationController {
case 'UserConfig': case 'UserConfig':
break; break;
case 'ContactsConfig': case 'ContactsConfig':
if (SessionUtilContact.isContactToStoreInContactsWrapper(convo)) { if (SessionUtilContact.isContactToStoreInWrapper(convo)) {
await SessionUtilContact.refreshMappedValue(convo.id, true); await SessionUtilContact.refreshMappedValue(convo.id, true);
} }
break; break;

@ -31,7 +31,7 @@ const mappedContactWrapperValues = new Map<string, ContactInfo>();
async function insertAllContactsIntoContactsWrapper() { async function insertAllContactsIntoContactsWrapper() {
const idsToInsert = getConversationController() const idsToInsert = getConversationController()
.getConversations() .getConversations()
.filter(isContactToStoreInContactsWrapper) .filter(isContactToStoreInWrapper)
.map(m => m.id); .map(m => m.id);
window.log.debug(`ContactsWrapper keep tracks of ${idsToInsert.length} contacts: ${idsToInsert}`); window.log.debug(`ContactsWrapper keep tracks of ${idsToInsert.length} contacts: ${idsToInsert}`);
@ -44,17 +44,13 @@ async function insertAllContactsIntoContactsWrapper() {
} }
/** /**
* Returns true if that conversation is not us, is private, is not blinded and has either the * Returns true if that conversation is not us, is private, is not blinded.
* `isApproved` or `didApproveMe` field set. *
* So that would be all the private conversations we either sent or receive a message from, not blinded * We want to sync the message request status so we need to allow a contact even if it's not approved, did not approve us and is not blocked.
*/ */
function isContactToStoreInContactsWrapper(convo: ConversationModel): boolean { function isContactToStoreInWrapper(convo: ConversationModel): boolean {
return ( return (
!convo.isMe() && !convo.isMe() && convo.isPrivate() && convo.isActive() && !PubKey.hasBlindedPrefix(convo.id)
convo.isPrivate() &&
convo.isActive() &&
!PubKey.hasBlindedPrefix(convo.id) &&
(convo.isApproved() || convo.didApproveMe() || convo.isBlocked())
); );
} }
// TODOLATER should we allow a blinded pubkey to be in the contact wrapper when we blocked it (can we block a blinded message request?) // TODOLATER should we allow a blinded pubkey to be in the contact wrapper when we blocked it (can we block a blinded message request?)
@ -70,8 +66,10 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
return; return;
} }
if (!isContactToStoreInContactsWrapper(foundConvo)) { if (!isContactToStoreInWrapper(foundConvo)) {
// window.log.info(`insertContactFromDBIntoWrapperAndRefresh: convo ${id} should not be saved. Skipping`); console.warn(
`insertContactFromDBIntoWrapperAndRefresh: convo ${id} should not be saved. Skipping`
);
return; return;
} }
@ -148,7 +146,7 @@ async function removeContactFromWrapper(id: string) {
} }
} }
export const SessionUtilContact = { export const SessionUtilContact = {
isContactToStoreInContactsWrapper, isContactToStoreInWrapper,
insertAllContactsIntoContactsWrapper, insertAllContactsIntoContactsWrapper,
insertContactFromDBIntoWrapperAndRefresh, insertContactFromDBIntoWrapperAndRefresh,
removeContactFromWrapper, removeContactFromWrapper,

@ -61,15 +61,15 @@ async function insertAllConvoInfoVolatileIntoWrapper() {
function isConvoToStoreInWrapper(convo: ConversationModel): boolean { function isConvoToStoreInWrapper(convo: ConversationModel): boolean {
return ( return (
SessionUtilUserGroups.isUserGroupToStoreInWrapper(convo) || // this checks for community & legacy group SessionUtilUserGroups.isUserGroupToStoreInWrapper(convo) || // this checks for community & legacy group
SessionUtilContact.isContactToStoreInContactsWrapper(convo) || // this checks for contacts SessionUtilContact.isContactToStoreInWrapper(convo) || // this checks for contacts
SessionUtilUserProfile.isUserProfileToStoreInContactsWrapper(convo.id) // this checks for our own pubkey, as we want to keep track of the read state for the Note To Self SessionUtilUserProfile.isUserProfileToStoreInWrapper(convo.id) // this checks for our own pubkey, as we want to keep track of the read state for the Note To Self
); );
} }
function getConvoType(convo: ConversationModel): ConvoVolatileType { function getConvoType(convo: ConversationModel): ConvoVolatileType {
const convoType: ConvoVolatileType = const convoType: ConvoVolatileType =
SessionUtilContact.isContactToStoreInContactsWrapper(convo) || SessionUtilContact.isContactToStoreInWrapper(convo) ||
SessionUtilUserProfile.isUserProfileToStoreInContactsWrapper(convo.id) SessionUtilUserProfile.isUserProfileToStoreInWrapper(convo.id)
? '1o1' ? '1o1'
: SessionUtilUserGroups.isCommunityToStoreInWrapper(convo) : SessionUtilUserGroups.isCommunityToStoreInWrapper(convo)
? 'Community' ? 'Community'

@ -5,7 +5,7 @@ import { getConversationController } from '../../conversations';
import { fromHexToArray } from '../String'; import { fromHexToArray } from '../String';
async function insertUserProfileIntoWrapper(convoId: string) { async function insertUserProfileIntoWrapper(convoId: string) {
if (!isUserProfileToStoreInContactsWrapper(convoId)) { if (!isUserProfileToStoreInWrapper(convoId)) {
return; return;
} }
const us = UserUtils.getOurPubKeyStrFromCache(); const us = UserUtils.getOurPubKeyStrFromCache();
@ -27,7 +27,7 @@ async function insertUserProfileIntoWrapper(convoId: string) {
} }
} }
function isUserProfileToStoreInContactsWrapper(convoId: string) { function isUserProfileToStoreInWrapper(convoId: string) {
try { try {
const us = UserUtils.getOurPubKeyStrFromCache(); const us = UserUtils.getOurPubKeyStrFromCache();
return convoId === us; return convoId === us;
@ -38,5 +38,5 @@ function isUserProfileToStoreInContactsWrapper(convoId: string) {
export const SessionUtilUserProfile = { export const SessionUtilUserProfile = {
insertUserProfileIntoWrapper, insertUserProfileIntoWrapper,
isUserProfileToStoreInContactsWrapper, isUserProfileToStoreInWrapper,
}; };

@ -377,7 +377,7 @@ export const fetchTopMessagesForConversation = createAsyncThunk(
const mostRecentMessage = await Data.getLastMessageInConversation(conversationKey); const mostRecentMessage = await Data.getLastMessageInConversation(conversationKey);
if (!oldestMessage || oldestMessage.id === oldTopMessageId) { if (!oldestMessage || oldestMessage.id === oldTopMessageId) {
window.log.info('fetchTopMessagesForConversation: we are already at the top'); window.log.debug('fetchTopMessagesForConversation: we are already at the top');
return null; return null;
} }
const messagesProps = await getMessages({ const messagesProps = await getMessages({
@ -414,7 +414,7 @@ export const fetchBottomMessagesForConversation = createAsyncThunk(
const mostRecentMessage = await Data.getLastMessageInConversation(conversationKey); const mostRecentMessage = await Data.getLastMessageInConversation(conversationKey);
if (!mostRecentMessage || mostRecentMessage.id === oldBottomMessageId) { if (!mostRecentMessage || mostRecentMessage.id === oldBottomMessageId) {
window.log.info('fetchBottomMessagesForConversation: we are already at the bottom'); window.log.debug('fetchBottomMessagesForConversation: we are already at the bottom');
return null; return null;
} }
const messagesProps = await getMessages({ const messagesProps = await getMessages({

@ -288,11 +288,20 @@ const _getLeftPaneLists = (
directConversations.push(conversation); directConversations.push(conversation);
} }
if ( const isPrivateButHidden =
(conversation.isPrivate && !conversation.isApproved) || conversation.isPrivate &&
(conversation.isPrivate &&
conversation.priority && conversation.priority &&
conversation.priority <= CONVERSATION_PRIORITIES.default) // a hidden contact conversation is only visible from the contact list, not from the global conversation list conversation.priority <= CONVERSATION_PRIORITIES.default;
/**
* When getting a contact from a linked device, before he sent a message, the approved field is false, but a createdAt is used as activeAt
*/
const isPrivateUnapprovedButActive =
conversation.isPrivate && !conversation.isApproved && !conversation.activeAt;
if (
isPrivateUnapprovedButActive ||
isPrivateButHidden // a hidden contact conversation is only visible from the contact list, not from the global conversation list
) { ) {
// dont increase unread counter, don't push to convo list. // dont increase unread counter, don't push to convo list.
continue; continue;

@ -324,7 +324,7 @@ describe('libsession_contacts', () => {
it('excludes ourselves', () => { it('excludes ourselves', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ ...validArgs, id: ourNumber } as any) new ConversationModel({ ...validArgs, id: ourNumber } as any)
) )
).to.be.eq(false); ).to.be.eq(false);
@ -332,7 +332,7 @@ describe('libsession_contacts', () => {
it('excludes non private', () => { it('excludes non private', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ ...validArgs, type: ConversationTypeEnum.GROUP } as any) new ConversationModel({ ...validArgs, type: ConversationTypeEnum.GROUP } as any)
) )
).to.be.eq(false); ).to.be.eq(false);
@ -340,7 +340,7 @@ describe('libsession_contacts', () => {
it('includes private', () => { it('includes private', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ ...validArgs, type: ConversationTypeEnum.PRIVATE } as any) new ConversationModel({ ...validArgs, type: ConversationTypeEnum.PRIVATE } as any)
) )
).to.be.eq(true); ).to.be.eq(true);
@ -348,7 +348,7 @@ describe('libsession_contacts', () => {
it('includes hidden private', () => { it('includes hidden private', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ new ConversationModel({
...validArgs, ...validArgs,
type: ConversationTypeEnum.PRIVATE, type: ConversationTypeEnum.PRIVATE,
@ -360,7 +360,7 @@ describe('libsession_contacts', () => {
it('excludes blinded', () => { it('excludes blinded', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ new ConversationModel({
...validArgs, ...validArgs,
type: ConversationTypeEnum.PRIVATE, type: ConversationTypeEnum.PRIVATE,
@ -370,21 +370,34 @@ describe('libsession_contacts', () => {
).to.be.eq(false); ).to.be.eq(false);
}); });
it('excludes non approved by us nor did approveme', () => { it('excludes non approved by us nor did approveme and not active', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ new ConversationModel({
...validArgs, ...validArgs,
didApproveMe: false, didApproveMe: false,
isApproved: false, isApproved: false,
active_at: undefined,
} as any) } as any)
) )
).to.be.eq(false); ).to.be.eq(false);
}); });
it('includes non approved by us nor did approveme but active', () => {
expect(
SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({
...validArgs,
didApproveMe: false,
isApproved: false,
} as any)
)
).to.be.eq(true);
});
it('includes approved only by us ', () => { it('includes approved only by us ', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ new ConversationModel({
...validArgs, ...validArgs,
didApproveMe: false, didApproveMe: false,
@ -396,7 +409,7 @@ describe('libsession_contacts', () => {
it('excludes not active ', () => { it('excludes not active ', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ new ConversationModel({
...validArgs, ...validArgs,
didApproveMe: false, didApproveMe: false,
@ -409,7 +422,7 @@ describe('libsession_contacts', () => {
it('includes approved only by them ', () => { it('includes approved only by them ', () => {
expect( expect(
SessionUtilContact.isContactToStoreInContactsWrapper( SessionUtilContact.isContactToStoreInWrapper(
new ConversationModel({ new ConversationModel({
...validArgs, ...validArgs,
didApproveMe: true, didApproveMe: true,

Loading…
Cancel
Save