diff --git a/stylesheets/_session_left_pane.scss b/stylesheets/_session_left_pane.scss
index ade13d4d4..982d7a4d7 100644
--- a/stylesheets/_session_left_pane.scss
+++ b/stylesheets/_session_left_pane.scss
@@ -249,6 +249,14 @@ $session-compose-margin: 20px;
margin-bottom: 3rem;
flex-shrink: 0;
}
+
+ .message-request-list__container {
+ width: 100%;
+
+ .session-button {
+ margin: $session-margin-xs 0;
+ }
+ }
}
}
.module-search-results {
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index cdb1921f3..99d1a1d07 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -27,6 +27,10 @@ import { useSelector } from 'react-redux';
import { SectionType } from '../state/ducks/section';
import { getFocusedSection } from '../state/selectors/section';
import { ConversationNotificationSettingType } from '../models/conversation';
+import { Flex } from './basic/Flex';
+import { SessionButton } from './session/SessionButton';
+import { getConversationById } from '../data/data';
+import { getConversationController } from '../session/conversations';
// tslint:disable-next-line: no-empty-interface
export interface ConversationListItemProps extends ReduxConversationType {}
@@ -42,6 +46,7 @@ export const StyledConversationListItemIconWrapper = styled.div`
type PropsHousekeeping = {
style?: Object;
+ isMessageRequest?: boolean;
};
// tslint:disable: use-simple-attributes
@@ -261,6 +266,7 @@ const ConversationListItem = (props: Props) => {
avatarPath,
isPrivate,
currentNotificationSetting,
+ isMessageRequest,
} = props;
const triggerId = `conversation-item-${conversationId}-ctxmenu`;
const key = `conversation-item-${conversationId}`;
@@ -277,15 +283,32 @@ const ConversationListItem = (props: Props) => {
[conversationId]
);
+ /**
+ * deletes the conversation
+ */
+ const handleConversationDecline = async () => {
+ await getConversationController().deleteContact(conversationId);
+ };
+
+ /**
+ * marks the conversation as approved.
+ */
+ const handleConversationAccept = async () => {
+ const convo = await getConversationById(conversationId);
+ convo?.setIsApproved(true);
+ console.warn('convo marked as approved');
+ console.warn({ convo });
+ };
+
return (
{
- e.stopPropagation();
- e.preventDefault();
- }}
+ // onMouseUp={e => {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // }}
onContextMenu={(e: any) => {
contextMenu.show({
id: triggerId,
@@ -327,6 +350,16 @@ const ConversationListItem = (props: Props) => {
unreadCount={unreadCount || 0}
lastMessage={lastMessage}
/>
+ {isMessageRequest ? (
+
+ Decline
+ Accept
+
+ ) : null}
diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx
index 23a3112eb..fc6871017 100644
--- a/ts/components/session/LeftPaneMessageSection.tsx
+++ b/ts/components/session/LeftPaneMessageSection.tsx
@@ -28,6 +28,7 @@ import { onsNameRegex } from '../../session/snode_api/SNodeAPI';
import { SNodeAPI } from '../../session/snode_api';
import { clearSearch, search, updateSearchTerm } from '../../state/ducks/search';
import _ from 'lodash';
+import { MessageRequestsBanner } from './MessageRequestsBanner';
export interface Props {
searchTerm: string;
@@ -95,6 +96,15 @@ export class LeftPaneMessageSection extends React.Component {
throw new Error('render: must provided conversations if no search results are provided');
}
+ // TODO: make selectors for this instead.
+ // TODO: only filter conversations if setting for requests is applied
+ const approvedConversations = conversations.filter(c => c.isApproved === true);
+ console.warn({ approvedConversations });
+ const messageRequestsEnabled =
+ window.inboxStore?.getState().userConfig.messageRequests === true;
+
+ const conversationsForList = messageRequestsEnabled ? approvedConversations : conversations;
+
const length = conversations.length;
const listKey = 0;
// Note: conversations is not a known prop for List, but it is required to ensure that
@@ -106,7 +116,7 @@ export class LeftPaneMessageSection extends React.Component {
{({ height, width }) => (
{
onChange={this.updateSearch}
placeholder={window.i18n('searchFor...')}
/>
- message requests
+
{this.renderList()}
{this.renderBottomButtons()}
diff --git a/ts/components/session/MessageRequestsBanner.tsx b/ts/components/session/MessageRequestsBanner.tsx
new file mode 100644
index 000000000..8e5fcb2ce
--- /dev/null
+++ b/ts/components/session/MessageRequestsBanner.tsx
@@ -0,0 +1,98 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import styled from 'styled-components';
+import { getLeftPaneLists } from '../../state/selectors/conversations';
+import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
+
+const StyledMessageRequestBanner = styled.div`
+ border-left: var(--border-unread);
+ height: 64px;
+ width: 100%;
+ max-width: 300px;
+ display: flex;
+ flex-direction: row;
+ padding: 8px 16px;
+ align-items: center;
+ cursor: pointer;
+
+ transition: var(--session-transition-duration);
+
+ &:hover {
+ background: var(--color-clickable-hovered);
+ }
+`;
+
+const StyledMessageRequestBannerHeader = styled.span`
+ font-weight: bold;
+ font-size: 15px;
+ color: var(--color-text-subtle);
+ padding-left: var(--margin-xs);
+ margin-inline-start: 12px;
+ margin-top: var(--margin-sm);
+ line-height: 18px;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+`;
+
+const StyledCircleIcon = styled.div`
+ padding-left: var(--margin-xs);
+`;
+
+const StyledUnreadCounter = styled.div`
+ font-weight: bold;
+ border-radius: 50%;
+ background-color: var(--color-clickable-hovered);
+ margin-left: 10px;
+ width: 20px;
+ height: 20px;
+ line-height: 25px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+`;
+
+const StyledGridContainer = styled.div`
+ border: solid 1px black;
+ display: flex;
+ width: 36px;
+ height: 36px;
+ align-items: center;
+ border-radius: 50%;
+ justify-content: center;
+ background-color: var(--color-conversation-item-has-unread);
+`;
+
+export const CirclularIcon = (props: { iconType: SessionIconType; iconSize: SessionIconSize }) => {
+ const { iconSize, iconType } = props;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export const MessageRequestsBanner = (props: { handleOnClick: () => any }) => {
+ const { handleOnClick } = props;
+ const convos = useSelector(getLeftPaneLists).conversations;
+ const pendingRequests = convos.filter(c => c.isApproved !== true) || [];
+
+ return (
+
+
+ Message Requests
+
+ {pendingRequests.length}
+
+
+ );
+};
diff --git a/ts/components/session/SessionClosableOverlay.tsx b/ts/components/session/SessionClosableOverlay.tsx
index c643762be..a396d62ca 100644
--- a/ts/components/session/SessionClosableOverlay.tsx
+++ b/ts/components/session/SessionClosableOverlay.tsx
@@ -292,79 +292,30 @@ export class SessionClosableOverlay extends React.Component {
}
}
+
+
const MessageRequestList = () => {
// get all conversations with (accepted / known)
- // const convos = useSelector(getConversationLookup);
-
const lists = useSelector(getLeftPaneLists);
- const conversationx = lists?.conversations as Array;
- console.warn({ conversationx });
-
- // console.warn({ convos });
- // const allConversations = getConversationController().getConversations();
- // const messageRequests = allConversations.filter(convo => convo.get('isApproved') !== true);
-
+ const unapprovedConversations = lists?.conversations.filter(c => {
+ return !c.isApproved;
+ }) as Array;
return (
- <>
- {/* {messageRequests.map(convoOfMessage => { */}
- {conversationx.map(convoOfMessage => {
- return ;
+
+ {unapprovedConversations.map(conversation => {
+ return ;
})}
- >
+
);
};
// const MessageRequestListItem = (props: { conversation: ConversationModel }) => {
const MessageRequestListItem = (props: { conversation: ConversationListItemProps }) => {
const { conversation } = props;
- // const { id: conversationId } = conversation;
-
- // TODO: add hovering
- // TODO: add styling
-
- /**
- * open the conversation selected
- */
- // const openConvo = useCallback(
- // async (e: React.MouseEvent) => {
- // // mousedown is invoked sooner than onClick, but for both right and left click
- // if (e.button === 0) {
- // await openConversationWithMessages({ conversationKey: conversationId });
- // }
- // },
- // [conversationId]
- // );
-
- // /**
- // * show basic highlight animation
- // */
- // const handleMouseOver = () => {
- // console.warn('hovered');
- // };
-
return (
- // {
- // e.stopPropagation();
- // e.preventDefault();
- // }}
- // // className="message-request__item"
-
- // // className={classNames(
- // // 'module-conversation-list-item',
- // // unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
- // // unreadCount && unreadCount > 0 && mentionedUs
- // // ? 'module-conversation-list-item--mentioned-us'
- // // : null,
- // // isSelected ? 'module-conversation-list-item--is-selected' : null,
- // // isBlocked ? 'module-conversation-list-item--is-blocked' : null
- // // )}
- // >
- // {conversation.getName()}
- //
-
-
+
);
};
diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx
index 05db39c86..5c6228ee0 100644
--- a/ts/components/session/settings/SessionSettings.tsx
+++ b/ts/components/session/settings/SessionSettings.tsx
@@ -17,7 +17,11 @@ import {
import { shell } from 'electron';
import { mapDispatchToProps } from '../../../state/actions';
import { unblockConvoById } from '../../../interactions/conversationInteractions';
-import { toggleAudioAutoplay } from '../../../state/ducks/userConfig';
+import {
+ disableMessageRequests,
+ enableMessageRequests,
+ toggleAudioAutoplay,
+} from '../../../state/ducks/userConfig';
import { sessionPassword, updateConfirmModal } from '../../../state/ducks/modalDialog';
import { PasswordAction } from '../../dialog/SessionPasswordDialog';
import { SessionIconButton } from '../icon';
@@ -406,7 +410,28 @@ class SettingsViewInner extends React.Component {
comparisonValue: undefined,
onClick: undefined,
},
+ {
+ id: 'message-request-setting',
+ title: 'Message Requests', // TODO: translation
+ description: 'Enable Message Request Inbox',
+ hidden: false,
+ type: SessionSettingType.Toggle,
+ category: SessionSettingCategory.Appearance,
+ setFn: () => {
+ window.inboxStore?.dispatch(toggleAudioAutoplay());
+ if (window.inboxStore?.getState().userConfig.messageRequests) {
+ window.inboxStore?.dispatch(disableMessageRequests());
+ } else {
+ window.inboxStore?.dispatch(enableMessageRequests());
+ }
+ },
+ content: {
+ defaultValue: window.inboxStore?.getState().userConfig.audioAutoplay,
+ },
+ comparisonValue: undefined,
+ onClick: undefined,
+ },
{
id: 'notification-setting',
title: window.i18n('notificationSettingsDialog'),
diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts
index 3704170a2..8919c9afe 100644
--- a/ts/models/conversation.ts
+++ b/ts/models/conversation.ts
@@ -714,6 +714,12 @@ export class ConversationModel extends Backbone.Model {
const sentAt = message.get('sent_at');
+ // TODO: for debuggong
+ if (message.get('body')?.includes('unapprove')) {
+ console.warn('setting to unapprove');
+ await this.setIsApproved(false);
+ }
+
if (!sentAt) {
throw new Error('sendMessageJob() sent_at must be set.');
}
@@ -771,6 +777,7 @@ export class ConversationModel extends Backbone.Model {
const chatMessagePrivate = new VisibleMessage(chatMessageParams);
await getMessageQueue().sendToPubKey(destinationPubkey, chatMessagePrivate);
+ // this.setIsApproved(true); // consider the conversation approved even if the message fails to send
return;
}
@@ -1384,7 +1391,7 @@ export class ConversationModel extends Backbone.Model {
public async setIsApproved(value: boolean) {
if (value !== this.get('isApproved')) {
this.set({
- isApproved: true,
+ isApproved: value,
});
await this.commit();
}
diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts
index 08f3e39a0..3e0988f87 100644
--- a/ts/session/conversations/ConversationController.ts
+++ b/ts/session/conversations/ConversationController.ts
@@ -99,6 +99,7 @@ export class ConversationController {
const create = async () => {
try {
+ debugger;
await saveConversation(conversation.attributes);
} catch (error) {
window?.log?.error(
diff --git a/ts/state/ducks/userConfig.tsx b/ts/state/ducks/userConfig.tsx
index af64b2cb4..ea34b9cfd 100644
--- a/ts/state/ducks/userConfig.tsx
+++ b/ts/state/ducks/userConfig.tsx
@@ -7,11 +7,13 @@ import { createSlice } from '@reduxjs/toolkit';
export interface UserConfigState {
audioAutoplay: boolean;
showRecoveryPhrasePrompt: boolean;
+ messageRequests: boolean;
}
export const initialUserConfigState = {
audioAutoplay: false,
showRecoveryPhrasePrompt: true,
+ messageRequests: true,
};
const userConfigSlice = createSlice({
@@ -24,9 +26,20 @@ const userConfigSlice = createSlice({
disableRecoveryPhrasePrompt: state => {
state.showRecoveryPhrasePrompt = false;
},
+ disableMessageRequests: state => {
+ state.messageRequests = false;
+ },
+ enableMessageRequests: state => {
+ state.messageRequests = true;
+ },
},
});
const { actions, reducer } = userConfigSlice;
-export const { toggleAudioAutoplay, disableRecoveryPhrasePrompt } = actions;
+export const {
+ toggleAudioAutoplay,
+ disableRecoveryPhrasePrompt,
+ disableMessageRequests,
+ enableMessageRequests,
+} = actions;
export const userConfigReducer = reducer;