mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Updating message recieving
This commit is contained in:
parent
d823e2a758
commit
28a0d82ea2
18 changed files with 323 additions and 257 deletions
|
@ -567,6 +567,11 @@
|
|||
? `${message.get('source')}.${message.get('sourceDevice')}`
|
||||
: `${message.source}.${message.sourceDevice}`;
|
||||
this.clearContactTypingTimer(identifier);
|
||||
|
||||
const model = this.addSingleMessage(message);
|
||||
MessageController.register(model.id, model);
|
||||
|
||||
this.trigger('change');
|
||||
},
|
||||
addSingleMessage(message, setToExpire = true) {
|
||||
const model = this.messageCollection.add(message, { merge: true });
|
||||
|
|
|
@ -65,8 +65,6 @@
|
|||
const generateProps = () => {
|
||||
if (this.isExpirationTimerUpdate()) {
|
||||
this.propsForTimerNotification = this.getPropsForTimerNotification();
|
||||
} else if (this.isKeyChange()) {
|
||||
this.propsForSafetyNumberNotification = this.getPropsForSafetyNumberNotification();
|
||||
} else if (this.isVerifiedChange()) {
|
||||
this.propsForVerificationNotification = this.getPropsForVerificationNotification();
|
||||
} else if (this.isEndSession()) {
|
||||
|
@ -344,19 +342,6 @@
|
|||
|
||||
return basicProps;
|
||||
},
|
||||
getPropsForSafetyNumberNotification() {
|
||||
const conversation = this.getConversation();
|
||||
const isGroup = conversation && !conversation.isPrivate();
|
||||
const phoneNumber = this.get('key_changed');
|
||||
const onVerify = () =>
|
||||
this.trigger('show-identity', this.findContact(phoneNumber));
|
||||
|
||||
return {
|
||||
isGroup,
|
||||
contact: this.findAndFormatContact(phoneNumber),
|
||||
onVerify,
|
||||
};
|
||||
},
|
||||
getPropsForVerificationNotification() {
|
||||
const type = this.get('verified') ? 'markVerified' : 'markNotVerified';
|
||||
const isLocal = this.get('local');
|
||||
|
|
|
@ -124,9 +124,6 @@ const { Quote } = require('../../ts/components/conversation/Quote');
|
|||
const {
|
||||
ResetSessionNotification,
|
||||
} = require('../../ts/components/conversation/ResetSessionNotification');
|
||||
const {
|
||||
SafetyNumberNotification,
|
||||
} = require('../../ts/components/conversation/SafetyNumberNotification');
|
||||
const {
|
||||
StagedLinkPreview,
|
||||
} = require('../../ts/components/conversation/StagedLinkPreview');
|
||||
|
@ -323,7 +320,6 @@ exports.setup = (options = {}) => {
|
|||
MessageDetail,
|
||||
Quote,
|
||||
ResetSessionNotification,
|
||||
SafetyNumberNotification,
|
||||
StagedLinkPreview,
|
||||
TimerNotification,
|
||||
Types: {
|
||||
|
|
|
@ -49,11 +49,6 @@
|
|||
Component: Components.TimerNotification,
|
||||
props: this.model.propsForTimerNotification,
|
||||
};
|
||||
} else if (this.model.propsForSafetyNumberNotification) {
|
||||
return {
|
||||
Component: Components.SafetyNumberNotification,
|
||||
props: this.model.propsForSafetyNumberNotification,
|
||||
};
|
||||
} else if (this.model.propsForVerificationNotification) {
|
||||
return {
|
||||
Component: Components.VerificationNotification,
|
||||
|
|
|
@ -40,6 +40,7 @@ $composition-container-height: 60px;
|
|||
|
||||
.conversation-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
|
@ -65,14 +66,6 @@ $composition-container-height: 60px;
|
|||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&__options-pane {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
@ -102,7 +95,7 @@ $composition-container-height: 60px;
|
|||
display: flex;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin: 0px $session-margin-md;
|
||||
padding: 0px $session-margin-md;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: $main-view-header-height;
|
||||
|
@ -138,15 +131,25 @@ $composition-container-height: 60px;
|
|||
flex-direction: column;
|
||||
position: relative;
|
||||
background-color: $session-background;
|
||||
outline: none;
|
||||
|
||||
.conversation-messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
background-color: inherit;
|
||||
outline: none;
|
||||
|
||||
&__blocking-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-info-panel {
|
||||
|
@ -167,22 +170,6 @@ $composition-container-height: 60px;
|
|||
}
|
||||
}
|
||||
|
||||
.messages-wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
&--blocking-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
.messages-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -351,7 +351,6 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
public renderSelectionOverlay() {
|
||||
const {
|
||||
onDeleteSelectedMessages,
|
||||
onResetSession,
|
||||
onCloseOverlay,
|
||||
isPublic,
|
||||
i18n,
|
||||
|
@ -367,7 +366,7 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
<SessionIconButton
|
||||
iconType={SessionIconType.Exit}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
onClick={onResetSession}
|
||||
onClick={onCloseOverlay}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -394,17 +393,15 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
{this.renderBackButton()}
|
||||
<div className="module-conversation-header__title-container">
|
||||
<div className="module-conversation-header__title-flex">
|
||||
{this.renderOptions(triggerId)}
|
||||
{!selectionMode && this.renderOptions(triggerId)}
|
||||
{this.renderTitle()}
|
||||
{/* This might be redundant as we show the title in the title: */}
|
||||
{/*isPrivateGroup ? this.renderMemberCount() : null*/}
|
||||
</div>
|
||||
</div>
|
||||
{!isKickedFromGroup && this.renderExpirationLength()}
|
||||
|
||||
{!this.props.isRss && this.renderAvatar()}
|
||||
{!this.props.isRss && !selectionMode && this.renderAvatar()}
|
||||
|
||||
{this.renderMenu(triggerId)}
|
||||
{!selectionMode && this.renderMenu(triggerId)}
|
||||
</div>
|
||||
|
||||
{selectionMode && this.renderSelectionOverlay()}
|
||||
|
|
|
@ -1186,7 +1186,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
|
||||
// User clicked on message body
|
||||
const target = event.target as HTMLDivElement;
|
||||
if (target.className === 'text-selectable') {
|
||||
if (!multiSelectMode && target.className === 'text-selectable') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1196,9 +1196,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}}
|
||||
>
|
||||
{this.renderError(isIncoming)}
|
||||
{isRss || isKickedFromGroup
|
||||
? null
|
||||
: this.renderMenu(!isIncoming, triggerId)}
|
||||
{enableContextMenu ? this.renderMenu(!isIncoming, triggerId) : null}
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
'module-message__container',
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
### In group conversation
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<SafetyNumberNotification
|
||||
i18n={util.i18n}
|
||||
isGroup={true}
|
||||
contact={{ phoneNumber: '(202) 500-1000', profileName: 'Mr. Fire' }}
|
||||
onVerify={() => console.log('onVerify')}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
### In one-on-one conversation
|
||||
|
||||
```js
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<SafetyNumberNotification
|
||||
i18n={util.i18n}
|
||||
isGroup={false}
|
||||
contact={{ phoneNumber: '(202) 500-1000', profileName: 'Mr. Fire' }}
|
||||
onVerify={() => console.log('onVerify')}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
|
@ -1,61 +0,0 @@
|
|||
import React from 'react';
|
||||
// import classNames from 'classnames';
|
||||
|
||||
import { ContactName } from './ContactName';
|
||||
import { Intl } from '../Intl';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
interface Contact {
|
||||
phoneNumber: string;
|
||||
profileName?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
isGroup: boolean;
|
||||
contact: Contact;
|
||||
i18n: LocalizerType;
|
||||
onVerify: () => void;
|
||||
}
|
||||
|
||||
export class SafetyNumberNotification extends React.Component<Props> {
|
||||
public render() {
|
||||
const { contact, isGroup, i18n, onVerify } = this.props;
|
||||
const changeKey = isGroup
|
||||
? 'safetyNumberChangedGroup'
|
||||
: 'safetyNumberChanged';
|
||||
|
||||
return (
|
||||
<div className="module-safety-number-notification">
|
||||
<div className="module-safety-number-notification__icon" />
|
||||
<div className="module-safety-number-notification__text">
|
||||
<Intl
|
||||
id={changeKey}
|
||||
components={[
|
||||
<span
|
||||
key="external-1"
|
||||
className="module-safety-number-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
i18n={i18n}
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
module="module-verification-notification__contact"
|
||||
/>
|
||||
</span>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
onClick={onVerify}
|
||||
className="module-verification-notification__button"
|
||||
>
|
||||
{i18n('verifyNewNumber')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import { ConversationModel } from '../../../js/models/conversations';
|
|||
import { SessionSpinner } from './SessionSpinner';
|
||||
import classNames from 'classnames';
|
||||
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
|
||||
import { Constants } from '../../session';
|
||||
|
||||
interface Props {
|
||||
conversation: ConversationModel;
|
||||
|
@ -70,7 +71,9 @@ export class SessionKeyVerification extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
const verificationIconColor = isVerified ? '#00f782' : '#ff453a';
|
||||
const verificationIconColor = isVerified
|
||||
? Constants.UI.COLORS.GREEN
|
||||
: Constants.UI.COLORS.DANGER;
|
||||
const verificationButtonColor = isVerified
|
||||
? SessionButtonColor.Warning
|
||||
: SessionButtonColor.Success;
|
||||
|
@ -112,7 +115,7 @@ export class SessionKeyVerification extends React.Component<Props, State> {
|
|||
<span>
|
||||
<SessionIcon
|
||||
iconType={SessionIconType.Lock}
|
||||
iconSize={SessionIconSize.Large}
|
||||
iconSize={SessionIconSize.Huge}
|
||||
iconColor={verificationIconColor}
|
||||
/>
|
||||
{window.i18n(
|
||||
|
|
|
@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|||
|
||||
import { Avatar } from '../Avatar';
|
||||
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
|
||||
import { Constants } from '../../session';
|
||||
|
||||
export interface ContactType {
|
||||
id: string;
|
||||
|
@ -79,7 +80,7 @@ export class SessionMemberListItem extends React.Component<Props, State> {
|
|||
<SessionIcon
|
||||
iconType={SessionIconType.Check}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
iconColor={'#00f782'}
|
||||
iconColor={Constants.UI.COLORS.GREEN}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
SessionButtonColor,
|
||||
SessionButtonType,
|
||||
} from './SessionButton';
|
||||
import { Constants } from '../../session';
|
||||
|
||||
interface State {
|
||||
error: string;
|
||||
|
@ -82,7 +83,7 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
|
|||
<SessionIcon
|
||||
iconType={SessionIconType.Lock}
|
||||
iconSize={35}
|
||||
iconColor="#00f782"
|
||||
iconColor={Constants.UI.COLORS.GREEN}
|
||||
/>
|
||||
);
|
||||
const errorSection = !this.state.clearDataView && (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Constants } from '../../session';
|
||||
|
||||
interface Props {
|
||||
// Value ranges from 0 to 100
|
||||
|
@ -54,11 +55,8 @@ export class SessionProgress extends React.PureComponent<Props, State> {
|
|||
// 2. Transition duration scales with the
|
||||
// distance it needs to travel
|
||||
|
||||
// FIXME VINCE - globalise all JS color references
|
||||
const sessionBrandColor = '#00f782';
|
||||
const sessionDangerAlt = '#ff4538';
|
||||
const successColor = sessionBrandColor;
|
||||
const failureColor = sessionDangerAlt;
|
||||
const successColor = Constants.UI.COLORS.GREEN;
|
||||
const failureColor = Constants.UI.COLORS.DANGER_ALT;
|
||||
const backgroundColor = sendStatus === -1 ? failureColor : successColor;
|
||||
|
||||
const shiftDurationMs =
|
||||
|
@ -78,7 +76,7 @@ export class SessionProgress extends React.PureComponent<Props, State> {
|
|||
// 'transition-property': 'transform, opacity',
|
||||
'transition-duration': `${shiftDurationMs}ms`,
|
||||
// 'transition-duration': `${shiftDurationMs}ms, ${showDurationMs}ms`,
|
||||
'transition-delay': `0ms`,
|
||||
'transition-delay': '0ms',
|
||||
// 'transition-delay': `0ms, ${showOffsetMs}ms`,
|
||||
'transition-timing-funtion': 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
//'transition-timing-funtion':'cubic-bezier(0.25, 0.46, 0.45, 0.94), linear',
|
||||
|
|
|
@ -205,7 +205,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
|
|||
<SessionIconButton
|
||||
iconType={SessionIconType.Send}
|
||||
iconSize={SessionIconSize.Large}
|
||||
iconColor={'#FFFFFF'}
|
||||
iconColor={Constants.UI.COLORS.WHITE}
|
||||
iconRotation={90}
|
||||
onClick={this.onSendMessage}
|
||||
/>
|
||||
|
|
|
@ -19,6 +19,7 @@ import { ResetSessionNotification } from '../../conversation/ResetSessionNotific
|
|||
import { Constants, getMessageQueue } from '../../../session';
|
||||
import { MessageQueue } from '../../../session/sending';
|
||||
import { SessionKeyVerification } from '../SessionKeyVerification';
|
||||
import _ from 'lodash';
|
||||
|
||||
interface State {
|
||||
conversationKey: string;
|
||||
|
@ -113,6 +114,7 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
this.onMessageSending = this.onMessageSending.bind(this);
|
||||
this.onMessageSuccess = this.onMessageSuccess.bind(this);
|
||||
this.onMessageFailure = this.onMessageFailure.bind(this);
|
||||
this.deleteSelectedMessages = this.deleteSelectedMessages.bind(this);
|
||||
|
||||
this.messagesEndRef = React.createRef();
|
||||
this.messageContainerRef = React.createRef();
|
||||
|
@ -126,7 +128,7 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
public componentDidMount() {
|
||||
this.getMessages()
|
||||
this.loadInitialMessages()
|
||||
.then(() => {
|
||||
// Pause thread to wait for rendering to complete
|
||||
setTimeout(() => {
|
||||
|
@ -158,6 +160,14 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
if (timestamp > this.state.messageFetchTimestamp) {
|
||||
await this.getMessages();
|
||||
}
|
||||
|
||||
// console.log('[vince] this.props.conversations:', this.props.conversations);
|
||||
console.log(`[vince] Conversation changed from redux`);
|
||||
|
||||
const conversationModel = window.ConversationController.get(this.state.conversationKey);
|
||||
const messages = conversationModel.messageCollection;
|
||||
|
||||
console.log('[vince] messages:', messages);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -172,7 +182,7 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
showOptionsPane,
|
||||
showScrollButton,
|
||||
} = this.state;
|
||||
const loading = !doneInitialScroll || messages.length === 0;
|
||||
const loading = !doneInitialScroll;
|
||||
const selectionMode = !!this.state.selectedMessages.length;
|
||||
|
||||
const conversation = this.props.conversations.conversationLookup[
|
||||
|
@ -195,18 +205,9 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'conversation-item__content',
|
||||
selectionMode && 'selection-mode'
|
||||
)}
|
||||
tabIndex={0}
|
||||
onKeyDown={this.onKeyDown}
|
||||
role="navigation"
|
||||
>
|
||||
<div className="conversation-header">{this.renderHeader()}</div>
|
||||
<div className="conversation-header">{this.renderHeader()}</div>
|
||||
|
||||
{/* <SessionProgress
|
||||
{/* <SessionProgress
|
||||
visible={this.state.messageProgressVisible}
|
||||
value={this.state.sendingProgress}
|
||||
prevValue={this.state.prevSendingProgress}
|
||||
|
@ -214,54 +215,59 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
resetProgress={this.resetSendingProgress}
|
||||
/> */}
|
||||
|
||||
<div className="conversation-content">
|
||||
<div
|
||||
className={classNames(
|
||||
'conversation-info-panel',
|
||||
this.state.infoViewState && 'show'
|
||||
)}
|
||||
>
|
||||
{showSafetyNumber && (
|
||||
<SessionKeyVerification conversation={conversationModel} />
|
||||
)}
|
||||
{showMessageDetails && <> </>}
|
||||
</div>
|
||||
|
||||
<div className="conversation-messages">
|
||||
<div className="messages-wrapper">
|
||||
{loading && <div className="messages-container__loading" />}
|
||||
|
||||
<div
|
||||
className="messages-container"
|
||||
onScroll={this.handleScroll}
|
||||
ref={this.messageContainerRef}
|
||||
>
|
||||
{this.renderMessages()}
|
||||
<div ref={this.messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<SessionScrollButton
|
||||
show={showScrollButton}
|
||||
onClick={this.scrollToBottom}
|
||||
/>
|
||||
{showRecordingView && (
|
||||
<div className="messages-wrapper--blocking-overlay" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isRss && (
|
||||
<SessionCompositionBox
|
||||
sendMessage={sendMessageFn}
|
||||
dropZoneFiles={this.state.dropZoneFiles}
|
||||
onMessageSending={this.onMessageSending}
|
||||
onMessageSuccess={this.onMessageSuccess}
|
||||
onMessageFailure={this.onMessageFailure}
|
||||
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
|
||||
onExitVoiceNoteView={this.onExitVoiceNoteView}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'conversation-content',
|
||||
selectionMode && 'selection-mode'
|
||||
)}
|
||||
tabIndex={0}
|
||||
onKeyDown={this.onKeyDown}
|
||||
role="navigation"
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'conversation-info-panel',
|
||||
this.state.infoViewState && 'show'
|
||||
)}
|
||||
>
|
||||
{showSafetyNumber && (
|
||||
<SessionKeyVerification conversation={conversationModel} />
|
||||
)}
|
||||
{showMessageDetails && <> </>}
|
||||
</div>
|
||||
|
||||
<div className="conversation-messages">
|
||||
{loading && <div className="messages-container__loading" />}
|
||||
|
||||
<div
|
||||
className="messages-container"
|
||||
onScroll={this.handleScroll}
|
||||
ref={this.messageContainerRef}
|
||||
>
|
||||
{this.renderMessages()}
|
||||
<div ref={this.messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<SessionScrollButton
|
||||
show={showScrollButton}
|
||||
onClick={this.scrollToBottom}
|
||||
/>
|
||||
{showRecordingView && (
|
||||
<div className="conversation-messages__blocking-overlay" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isRss && (
|
||||
<SessionCompositionBox
|
||||
sendMessage={sendMessageFn}
|
||||
dropZoneFiles={this.state.dropZoneFiles}
|
||||
onMessageSending={this.onMessageSending}
|
||||
onMessageSuccess={this.onMessageSuccess}
|
||||
onMessageFailure={this.onMessageFailure}
|
||||
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
|
||||
onExitVoiceNoteView={this.onExitVoiceNoteView}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{shouldRenderGroupSettings && (
|
||||
|
@ -281,6 +287,7 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
public renderMessages() {
|
||||
const { messages } = this.state;
|
||||
|
||||
const multiSelectMode = Boolean(this.state.selectedMessages.length);
|
||||
// FIXME VINCE: IF MESSAGE IS THE TOP OF UNREAD, THEN INSERT AN UNREAD BANNER
|
||||
|
||||
return (
|
||||
|
@ -293,10 +300,6 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
i18n: window.i18n,
|
||||
...message.propsForTimerNotification,
|
||||
};
|
||||
const friendRequestProps = message.propsForFriendRequest && {
|
||||
i18n: window.i18n,
|
||||
...message.propsForFriendRequest,
|
||||
};
|
||||
const resetSessionProps = message.propsForResetSessionNotification && {
|
||||
i18n: window.i18n,
|
||||
...message.propsForResetSessionNotification,
|
||||
|
@ -309,12 +312,17 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
// firstMessageOfSeries tells us to render the avatar only for the first message
|
||||
// in a series of messages from the same user
|
||||
item = messageProps
|
||||
? this.renderMessage(messageProps, message.firstMessageOfSeries)
|
||||
? this.renderMessage(
|
||||
messageProps,
|
||||
message.firstMessageOfSeries,
|
||||
multiSelectMode
|
||||
)
|
||||
: item;
|
||||
item = quoteProps
|
||||
? this.renderMessage(
|
||||
timerProps,
|
||||
message.firstMessageOfSeries,
|
||||
multiSelectMode,
|
||||
quoteProps
|
||||
)
|
||||
: item;
|
||||
|
@ -335,13 +343,13 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
|
||||
public renderHeader() {
|
||||
const headerProps = this.getHeaderProps();
|
||||
|
||||
return <ConversationHeader {...headerProps} />;
|
||||
}
|
||||
|
||||
public renderMessage(
|
||||
messageProps: any,
|
||||
firstMessageOfSeries: boolean,
|
||||
multiSelectMode: boolean,
|
||||
quoteProps?: any
|
||||
) {
|
||||
const selected =
|
||||
|
@ -351,6 +359,7 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
messageProps.i18n = window.i18n;
|
||||
messageProps.selected = selected;
|
||||
messageProps.firstMessageOfSeries = firstMessageOfSeries;
|
||||
messageProps.multiSelectMode = multiSelectMode;
|
||||
messageProps.onSelectMessage = (messageId: string) => {
|
||||
this.selectMessage(messageId);
|
||||
};
|
||||
|
@ -364,11 +373,38 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
// ~~~~~~~~~~~~~~ GETTER METHODS ~~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
public async loadInitialMessages() {
|
||||
// Grabs the initial set of messages and adds them to our conversation model.
|
||||
// After the inital fetch, all new messages are automatically added from onNewMessage
|
||||
// in the conversation model.
|
||||
// The only time we need to call getMessages() is to grab more messages on scroll.
|
||||
const { conversationKey } = this.state;
|
||||
const conversationModel = window.ConversationController.get(conversationKey);
|
||||
|
||||
const messageSet = await window.Signal.Data.getMessagesByConversation(
|
||||
conversationKey,
|
||||
{ limit: Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT, MessageCollection: window.Whisper.MessageCollection }
|
||||
);
|
||||
|
||||
const messageModels = messageSet.models;
|
||||
const messages = messageModels.map((message: any) => message.id);
|
||||
|
||||
this.setState({ messages }, () => {
|
||||
if (this.state.isScrolledToBottom) {
|
||||
this.updateReadMessages();
|
||||
}
|
||||
});
|
||||
|
||||
// Add new messages to conversation collection
|
||||
conversationModel.messageCollection = messageSet;
|
||||
}
|
||||
|
||||
public async getMessages(
|
||||
numMessages?: number,
|
||||
fetchInterval = Constants.CONVERSATION.MESSAGE_FETCH_INTERVAL
|
||||
) {
|
||||
const { conversationKey, messageFetchTimestamp } = this.state;
|
||||
const conversationModel = window.ConversationController.get(conversationKey);
|
||||
const timestamp = getTimestamp();
|
||||
|
||||
// If we have pulled messages in the last interval, don't bother rescanning
|
||||
|
@ -393,19 +429,19 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
);
|
||||
|
||||
// Set first member of series here.
|
||||
const messageModels = messageSet.models;
|
||||
const messages = [];
|
||||
let previousSender;
|
||||
for (let i = 0; i < messageModels.length; i++) {
|
||||
// Handle firstMessageOfSeries for conditional avatar rendering
|
||||
let firstMessageOfSeries = true;
|
||||
if (i > 0 && previousSender === messageModels[i].authorPhoneNumber) {
|
||||
firstMessageOfSeries = false;
|
||||
}
|
||||
// const messageModels = messageSet.models;
|
||||
// const messages = [];
|
||||
// let previousSender;
|
||||
// for (let i = 0; i < messageModels.length; i++) {
|
||||
// // Handle firstMessageOfSeries for conditional avatar rendering
|
||||
// let firstMessageOfSeries = true;
|
||||
// if (i > 0 && previousSender === messageModels[i].authorPhoneNumber) {
|
||||
// firstMessageOfSeries = false;
|
||||
// }
|
||||
|
||||
messages.push({ ...messageModels[i], firstMessageOfSeries });
|
||||
previousSender = messageModels[i].authorPhoneNumber;
|
||||
}
|
||||
// messages.push({ ...messageModels[i], firstMessageOfSeries });
|
||||
// previousSender = messageModels[i].authorPhoneNumber;
|
||||
// }
|
||||
|
||||
const previousTopMessage = this.state.messages[0]?.id;
|
||||
const newTopMessage = messages[0]?.id;
|
||||
|
@ -416,6 +452,12 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
}
|
||||
});
|
||||
|
||||
// Add new messages to conversation collection
|
||||
// const newMessages = _.xor(messages, previousMessageSet);
|
||||
// newMessages.forEach(message => conversationModel.addSingleMessage(message));
|
||||
|
||||
// console.log('[vince] conversationModel.messageCollection:', conversationModel.messageCollection);
|
||||
|
||||
return { newTopMessage, previousTopMessage };
|
||||
}
|
||||
|
||||
|
@ -463,9 +505,14 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
|
||||
onSetDisappearingMessages: (seconds: any) =>
|
||||
conversation.updateExpirationTimer(seconds),
|
||||
onDeleteMessages: () => conversation.destroyMessages(),
|
||||
onDeleteSelectedMessages: () => conversation.deleteSelectedMessages(),
|
||||
onCloseOverlay: () => conversation.resetMessageSelection(),
|
||||
onDeleteMessages: () => null,
|
||||
onDeleteSelectedMessages: async () => {
|
||||
await this.deleteSelectedMessages();
|
||||
},
|
||||
onCloseOverlay: () => {
|
||||
this.setState({ selectedMessages: [] });
|
||||
conversation.resetMessageSelection();
|
||||
},
|
||||
onDeleteContact: () => conversation.deleteContact(),
|
||||
onResetSession: () => {
|
||||
conversation.endSession();
|
||||
|
@ -715,6 +762,97 @@ export class SessionConversation extends React.Component<any, State> {
|
|||
return null;
|
||||
}
|
||||
|
||||
public async deleteSelectedMessages(onSuccess?: any) {
|
||||
// Get message objects
|
||||
const messageObjects = this.state.messages.filter(message => this.state.selectedMessages.find(
|
||||
selectedMessage => selectedMessage === message.id
|
||||
));
|
||||
// Get message model for each message
|
||||
const messages = messageObjects.map(message => message?.collection?.models[0]);
|
||||
|
||||
const { conversationKey } = this.state;
|
||||
const conversationModel = window.ConversationController.get(
|
||||
conversationKey
|
||||
);
|
||||
|
||||
const multiple = messages.length > 1;
|
||||
const isPublic = conversationModel.isPublic();
|
||||
|
||||
// In future, we may be able to unsend private messages also
|
||||
// isServerDeletable also defined in ConversationHeader.tsx for
|
||||
// future reference
|
||||
const isServerDeletable = isPublic;
|
||||
|
||||
const warningMessage = (() => {
|
||||
if (isPublic) {
|
||||
return multiple
|
||||
? window.i18n('deleteMultiplePublicWarning')
|
||||
: window.i18n('deletePublicWarning');
|
||||
}
|
||||
return multiple ? window.i18n('deleteMultipleWarning') : window.i18n('deleteWarning');
|
||||
})();
|
||||
|
||||
const doDelete = async () => {
|
||||
let toDeleteLocally;
|
||||
|
||||
console.log('[vince] conversationKey:', conversationKey);
|
||||
console.log('[vince] conversationModel:', conversationModel);
|
||||
console.log('[vince] messages:', messages);
|
||||
|
||||
// VINCE TOOD: MARK TO-DELETE MESSAGES AS READ
|
||||
|
||||
if (isPublic) {
|
||||
toDeleteLocally = await conversationModel.deletePublicMessages(messages);
|
||||
if (toDeleteLocally.length === 0) {
|
||||
// Message failed to delete from server, show error?
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
messages.forEach(m => conversationModel.messageCollection.remove(m.id));
|
||||
toDeleteLocally = messages;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
toDeleteLocally.map(async (message: any) => {
|
||||
await window.Signal.Data.removeMessage(message.id, {
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
message.trigger('unload');
|
||||
})
|
||||
);
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
// Only show a warning when at least one messages was successfully
|
||||
// saved in on the server
|
||||
if (!messages.some(m => !m.hasErrors())) {
|
||||
await doDelete();
|
||||
return;
|
||||
}
|
||||
|
||||
// If removable from server, we "Unsend" - otherwise "Delete"
|
||||
const pluralSuffix = multiple ? 's' : '';
|
||||
const title = window.i18n(
|
||||
isPublic
|
||||
? `unsendMessage${pluralSuffix}`
|
||||
: `deleteMessage${pluralSuffix}`
|
||||
);
|
||||
|
||||
const okText = window.i18n(isServerDeletable ? 'unsend' : 'delete');
|
||||
|
||||
window.confirmationDialog({
|
||||
title,
|
||||
message: warningMessage,
|
||||
okText,
|
||||
okTheme: 'danger',
|
||||
resolve: doDelete,
|
||||
centeredText: true,
|
||||
});
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~ SCROLLING METHODS ~~~~~~~~~~~~~
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Picker } from 'emoji-mart';
|
||||
import { Constants } from '../../../session';
|
||||
|
||||
interface Props {
|
||||
onEmojiClicked: (emoji: any) => void;
|
||||
|
@ -31,7 +32,7 @@ export class SessionEmojiPanel extends React.Component<Props, State> {
|
|||
set={'twitter'}
|
||||
sheetSize={32}
|
||||
darkMode={true}
|
||||
color={'#00F782'}
|
||||
color={Constants.UI.COLORS.GREEN}
|
||||
showPreview={true}
|
||||
title={''}
|
||||
onSelect={onEmojiClicked}
|
||||
|
|
|
@ -115,8 +115,8 @@ export class SessionRecording extends React.Component<Props, State> {
|
|||
barRadius: 15,
|
||||
barWidth: 4,
|
||||
barPadding: 3,
|
||||
barColorInit: '#AFAFAF',
|
||||
barColorPlay: '#FFFFFF',
|
||||
barColorInit: Constants.UI.COLORS.WHITE_PALE,
|
||||
barColorPlay: Constants.UI.COLORS.WHITE,
|
||||
maxBarHeight: 30,
|
||||
minBarHeight: 3,
|
||||
},
|
||||
|
@ -194,8 +194,7 @@ export class SessionRecording extends React.Component<Props, State> {
|
|||
<SessionIconButton
|
||||
iconType={SessionIconType.Pause}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
// FIXME VINCE: Globalise constants for JS Session Colors
|
||||
iconColor={'#FF4538'}
|
||||
iconColor={Constants.UI.COLORS.DANGER_ALT}
|
||||
onClick={actionPauseFn}
|
||||
/>
|
||||
)}
|
||||
|
@ -203,8 +202,7 @@ export class SessionRecording extends React.Component<Props, State> {
|
|||
<SessionIconButton
|
||||
iconType={SessionIconType.Pause}
|
||||
iconSize={SessionIconSize.Medium}
|
||||
// FIXME VINCE: Globalise constants for JS Session Colors
|
||||
iconColor={'#FFFFFF'}
|
||||
iconColor={Constants.UI.COLORS.WHITE}
|
||||
onClick={actionPauseFn}
|
||||
/>
|
||||
)}
|
||||
|
@ -249,7 +247,7 @@ export class SessionRecording extends React.Component<Props, State> {
|
|||
<SessionIconButton
|
||||
iconType={SessionIconType.Send}
|
||||
iconSize={SessionIconSize.Large}
|
||||
iconColor={'#FFFFFF'}
|
||||
iconColor={Constants.UI.COLORS.WHITE}
|
||||
iconRotation={90}
|
||||
onClick={this.onSendVoiceMessage}
|
||||
/>
|
||||
|
@ -546,19 +544,21 @@ export class SessionRecording extends React.Component<Props, State> {
|
|||
|
||||
const canvasContext = canvas && canvas.getContext('2d');
|
||||
|
||||
for (var i = 0; i < volumeArray.length; i++) {
|
||||
for (let i = 0; i < volumeArray.length; i++) {
|
||||
const barHeight = Math.ceil(volumeArray[i]);
|
||||
const offset_x = Math.ceil(i * (barWidth + barPadding));
|
||||
const offset_y = Math.ceil(height / 2 - barHeight / 2);
|
||||
const offsetX = Math.ceil(i * (barWidth + barPadding));
|
||||
const offsetY = Math.ceil(height / 2 - barHeight / 2);
|
||||
|
||||
// FIXME VINCE - Globalise JS references to colors
|
||||
canvasContext && (canvasContext.fillStyle = barColorInit);
|
||||
canvasContext &&
|
||||
this.drawRoundedRect(canvasContext, offset_x, offset_y, barHeight);
|
||||
if (canvasContext) {
|
||||
canvasContext.fillStyle = barColorInit;
|
||||
this.drawRoundedRect(canvasContext, offsetX, offsetY, barHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.state.isRecording && requestAnimationFrame(drawRecordingCanvas);
|
||||
if (this.state.isRecording) {
|
||||
requestAnimationFrame(drawRecordingCanvas);
|
||||
}
|
||||
};
|
||||
|
||||
// Init listeners for visualisation
|
||||
|
@ -663,7 +663,6 @@ export class SessionRecording extends React.Component<Props, State> {
|
|||
const offsetX = Math.ceil(i * (barWidth + barPadding));
|
||||
const offsetY = Math.ceil(height / 2 - barHeight / 2);
|
||||
|
||||
// FIXME VINCE - Globalise JS references to colors
|
||||
canvasContext.fillStyle = barColorInit;
|
||||
|
||||
this.drawRoundedRect(canvasContext, offsetX, offsetY, barHeight);
|
||||
|
|
|
@ -31,4 +31,51 @@ export const UI = {
|
|||
// Pixels (scroll) from the top of the top of message container
|
||||
// at which more messages should be loaded
|
||||
MESSAGE_CONTAINER_BUFFER_OFFSET_PX: 30,
|
||||
|
||||
COLORS: {
|
||||
// COMMON
|
||||
WHITE: '#FFFFFF',
|
||||
WHITE_PALE: '#AFAFAF',
|
||||
LIGHT_GREY: '#A0A0A0',
|
||||
DARK_GREY: '#353535',
|
||||
BLACK: '#000000',
|
||||
GREEN: '#00F782',
|
||||
GREEN_ALT_1: '#00F480',
|
||||
GREEN_ALT_2: '#00FD73',
|
||||
GREEN_ALT_3: '#00F782',
|
||||
BACKGROUND: '#121212',
|
||||
|
||||
// SHADES
|
||||
SHADE_1: '#0C0C0C',
|
||||
SHADE_1_ALT: '#0F1011',
|
||||
SHADE_2: '#161616',
|
||||
SHADE_3: '#191818',
|
||||
SHADE_4: '#1B1B1B',
|
||||
SHADE_5: '#222325',
|
||||
SHADE_6: '#232323',
|
||||
SHADE_6_ALT: '#2C2C2C',
|
||||
SHADE_7: '#2E2E2E',
|
||||
SHADE_8: '#2F2F2F',
|
||||
SHADE_9: '#313131',
|
||||
SHADE_10: '#3E3E3E',
|
||||
SHADE_11: '#3F3F3F',
|
||||
SHADE_12: '#3F4146',
|
||||
SHADE_13: '#474646',
|
||||
SHADE_14: '#535865',
|
||||
SHADE_15: '#5B6C72',
|
||||
SHADE_16: '#979797',
|
||||
SHADE_17: '#A0A0A0',
|
||||
SHADE_18: '#141414',
|
||||
|
||||
// SEMANTIC COLORS
|
||||
INFO: '#3F3F3F',
|
||||
SUCCESS: '#35D388',
|
||||
ERROR: '#EDD422',
|
||||
WARNING: '#A0A0A0',
|
||||
WARNING_ALT: '#FF9D00',
|
||||
DANGER: '#FF453A',
|
||||
DANGER_ALT: '#FF4538',
|
||||
PRIMARY: '#474646',
|
||||
SECONDARY: '#232323',
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue