WIP message requesting. Banner styling finished.

This commit is contained in:
Warrick Corfe-Tan 2021-10-20 17:49:14 +11:00
parent 123e68c167
commit c3f20aceb2
9 changed files with 217 additions and 71 deletions

View File

@ -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 {

View File

@ -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 (
<div key={key}>
<div
role="button"
onMouseDown={openConvo}
onMouseUp={e => {
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 ? (
<Flex
className="module-conversation-list-item__button-container"
container={true}
flexDirection="row"
>
<SessionButton onClick={handleConversationDecline}>Decline</SessionButton>
<SessionButton onClick={handleConversationAccept}>Accept</SessionButton>
</Flex>
) : null}
</div>
</div>
<Portal>

View File

@ -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<Props, State> {
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<Props, State> {
{({ height, width }) => (
<List
className="module-left-pane__virtual-list"
conversations={conversations}
conversations={conversationsForList}
height={height}
rowCount={length}
rowHeight={64}
@ -157,7 +167,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
onChange={this.updateSearch}
placeholder={window.i18n('searchFor...')}
/>
<div onClick={this.handleMessageRequestsClick}>message requests</div>
<MessageRequestsBanner handleOnClick={this.handleMessageRequestsClick} />
{this.renderList()}
{this.renderBottomButtons()}
</div>

View File

@ -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 (
<StyledCircleIcon>
<StyledGridContainer>
<SessionIcon
iconType={iconType}
iconSize={iconSize}
iconColor={'var(--color-text-subtle)'}
/>
</StyledGridContainer>
</StyledCircleIcon>
);
};
export const MessageRequestsBanner = (props: { handleOnClick: () => any }) => {
const { handleOnClick } = props;
const convos = useSelector(getLeftPaneLists).conversations;
const pendingRequests = convos.filter(c => c.isApproved !== true) || [];
return (
<StyledMessageRequestBanner onClick={handleOnClick}>
<CirclularIcon iconType={'bell'} iconSize="medium" />
<StyledMessageRequestBannerHeader>Message Requests</StyledMessageRequestBannerHeader>
<StyledUnreadCounter>
<div>{pendingRequests.length}</div>
</StyledUnreadCounter>
</StyledMessageRequestBanner>
);
};

View File

@ -292,79 +292,30 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
}
}
const MessageRequestList = () => {
// get all conversations with (accepted / known)
// const convos = useSelector(getConversationLookup);
const lists = useSelector(getLeftPaneLists);
const conversationx = lists?.conversations as Array<ConversationListItemProps>;
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<ConversationListItemProps>;
return (
<>
{/* {messageRequests.map(convoOfMessage => { */}
{conversationx.map(convoOfMessage => {
return <MessageRequestListItem conversation={convoOfMessage} />;
<div className="message-request-list__container">
{unapprovedConversations.map(conversation => {
return <MessageRequestListItem conversation={conversation} />;
})}
</>
</div>
);
};
// 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<HTMLDivElement>) => {
// // 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 (
// <div
// onMouseOver={handleMouseOver}
// onMouseDown={openConvo}
// onMouseUp={e => {
// 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()}
// </div>
<MemoConversationListItemWithDetails {...conversation}></MemoConversationListItemWithDetails>
<MemoConversationListItemWithDetails
isMessageRequest={true}
{...conversation}
></MemoConversationListItemWithDetails>
);
};

View File

@ -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<SettingsViewProps, State> {
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'),

View File

@ -714,6 +714,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
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<ConversationAttributes> {
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<ConversationAttributes> {
public async setIsApproved(value: boolean) {
if (value !== this.get('isApproved')) {
this.set({
isApproved: true,
isApproved: value,
});
await this.commit();
}

View File

@ -99,6 +99,7 @@ export class ConversationController {
const create = async () => {
try {
debugger;
await saveConversation(conversation.attributes);
} catch (error) {
window?.log?.error(

View File

@ -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;