link backbone message added to redux

pull/1381/head
Audric Ackermann 5 years ago
parent 2f2eb2ad53
commit f9ab90fb71
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -468,7 +468,12 @@
const model = this.addSingleMessage(message);
MessageController.register(model.id, model);
this.trigger('change');
window.Whisper.events.trigger('messageAdded', {
conversationKey: this.id,
messageModel: model,
});
this.trigger('change', this);
},
addSingleMessage(message, setToExpire = true) {
const model = this.messageCollection.add(message, { merge: true });
@ -1201,6 +1206,11 @@
const id = await message.commit();
message.set({ id });
window.Whisper.events.trigger('messageAdded', {
conversationKey: this.id,
messageModel: message,
});
this.set({
lastMessage: model.getNotificationText(),
lastMessageStatus: 'sending',

@ -107,7 +107,6 @@ const {
// State
const conversationsDuck = require('../../ts/state/ducks/conversations');
const userDuck = require('../../ts/state/ducks/user');
const messagesDuck = require('../../ts/state/ducks/messages');
// Migrations
const {
@ -283,7 +282,6 @@ exports.setup = (options = {}) => {
const Ducks = {
conversations: conversationsDuck,
user: userDuck,
messages: messagesDuck,
};
const State = {
Ducks,

@ -124,13 +124,12 @@
},
onEmpty() {
// const view = this.inboxView;
// this.initialLoadComplete = true;
// if (view) {
// view.onEmpty();
// }
},
onProgress(count) {
onProgress() {
// const view = this.inboxView;
// if (view) {
// view.onProgress(count);

@ -56,7 +56,6 @@ export class SessionInboxView extends React.Component<Props, State> {
void this.setupLeftPane();
// ConversationCollection
conversationModels;
// this.listenTo(inboxCollection, 'messageError', () => {
// if (this.networkStatusView) {
// this.networkStatusView.render();
@ -219,7 +218,7 @@ export class SessionInboxView extends React.Component<Props, State> {
window.inboxStore = this.store;
// Enables our redux store to be updated by backbone events in the outside world
const { messageExpired } = bindActionCreators(
const { messageExpired, messageAdded, messageChanged } = bindActionCreators(
window.Signal.State.Ducks.conversations.actions,
this.store.dispatch
);
@ -228,10 +227,6 @@ export class SessionInboxView extends React.Component<Props, State> {
window.Signal.State.Ducks.user.actions,
this.store.dispatch
);
const { messageChanged } = bindActionCreators(
window.Signal.State.Ducks.messages.actions,
this.store.dispatch
);
this.fetchHandleMessageSentData = this.fetchHandleMessageSentData.bind(
this
@ -248,6 +243,7 @@ export class SessionInboxView extends React.Component<Props, State> {
window.Whisper.events.on('messageExpired', messageExpired);
window.Whisper.events.on('messageChanged', messageChanged);
window.Whisper.events.on('messageAdded', messageAdded);
window.Whisper.events.on('userChanged', userChanged);
this.setState({ isInitialLoadComplete: true });

@ -191,12 +191,12 @@ export class SessionConversation extends React.Component<Props, State> {
} = this.state;
const selectionMode = !!selectedMessages.length;
const { conversation, conversationKey } = this.props;
const { conversation, conversationKey, messages } = this.props;
const conversationModel = window.ConversationController.get(
conversationKey
);
if (!conversationModel) {
if (!conversationModel || !messages) {
// return an empty message view
return <MessageView />;
}

@ -4,13 +4,12 @@ import { actions as search } from './ducks/search';
import { actions as conversations } from './ducks/conversations';
import { actions as user } from './ducks/user';
import { actions as sections } from './ducks/section';
import { actions as messages } from './ducks/messages';
const actions = {
...search,
...conversations,
...user,
...messages,
// ...messages,
...sections,
};

@ -1,6 +1,8 @@
import { omit } from 'lodash';
import _, { omit } from 'lodash';
import { trigger } from '../../shims/events';
import { Constants } from '../../session';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { MessageModel } from '../../../js/models/messages';
// State
@ -30,6 +32,22 @@ export type MessageType = {
isSelected?: boolean;
};
export type MessageTypeInConvo = {
id: string;
conversationId: string;
attributes: any;
propsForMessage: Object;
propsForSearchResult: Object;
propsForGroupInvitation: Object;
propsForTimerNotification: Object;
propsForVerificationNotification: Object;
propsForResetSessionNotification: Object;
propsForGroupNotification: Object;
firstMessageOfSeries: boolean;
receivedAt: number;
};
export type ConversationType = {
id: string;
name?: string;
@ -65,8 +83,82 @@ export type ConversationLookupType = {
export type ConversationsStateType = {
conversationLookup: ConversationLookupType;
selectedConversation?: string;
messages: Array<MessageTypeInConvo>;
};
async function getMessages(
conversationKey: string,
numMessages: number
): Promise<Array<MessageTypeInConvo>> {
const conversation = window.ConversationController.get(conversationKey);
if (!conversation) {
// no valid conversation, early return
window.log.error('Failed to get convo on reducer.');
return [];
}
const unreadCount = await conversation.getUnreadCount();
let msgCount =
numMessages ||
Number(Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) + unreadCount;
msgCount =
msgCount > Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
? Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
: msgCount;
if (msgCount < Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) {
msgCount = Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
}
const messageSet = await window.Signal.Data.getMessagesByConversation(
conversationKey,
{ limit: msgCount, MessageCollection: window.Whisper.MessageCollection }
);
// Set first member of series here.
const messageModels = messageSet.models;
const messages = [];
// no need to do that `firstMessageOfSeries` on a private chat
if (conversation.isPrivate()) {
return messageModels;
}
// messages are got from the more recent to the oldest, so we need to check if
// the next messages in the list is still the same author.
// The message is the first of the series if the next message is not from the same author
for (let i = 0; i < messageModels.length; i++) {
// Handle firstMessageOfSeries for conditional avatar rendering
let firstMessageOfSeries = true;
const currentSender = messageModels[i].propsForMessage?.authorPhoneNumber;
const nextSender =
i < messageModels.length - 1
? messageModels[i + 1].propsForMessage?.authorPhoneNumber
: undefined;
if (i > 0 && currentSender === nextSender) {
firstMessageOfSeries = false;
}
messages.push({ ...messageModels[i], firstMessageOfSeries });
}
return messages;
}
const fetchMessagesForConversation = createAsyncThunk(
'messages/fetchByConversationKey',
async ({
conversationKey,
count,
}: {
conversationKey: string;
count: number;
}) => {
const messages = await getMessages(conversationKey, count);
return {
conversationKey,
messages,
};
}
);
// Actions
type ConversationAddedActionType = {
@ -100,6 +192,17 @@ export type MessageExpiredActionType = {
conversationId: string;
};
};
export type MessageChangedActionType = {
type: 'MESSAGE_CHANGED';
payload: MessageModel;
};
export type MessageAddedActionType = {
type: 'MESSAGE_ADDED';
payload: {
conversationKey: string;
messageModel: MessageModel;
};
};
export type SelectedConversationChangedActionType = {
type: 'SELECTED_CONVERSATION_CHANGED';
payload: {
@ -108,15 +211,25 @@ export type SelectedConversationChangedActionType = {
};
};
export type FetchMessagesForConversationType = {
type: 'messages/fetchByConversationKey/fulfilled';
payload: {
conversationKey: string;
messages: Array<MessageModel>;
};
};
export type ConversationActionType =
| ConversationAddedActionType
| ConversationChangedActionType
| ConversationRemovedActionType
| RemoveAllConversationsActionType
| MessageExpiredActionType
| MessageAddedActionType
| MessageChangedActionType
| SelectedConversationChangedActionType
| MessageExpiredActionType
| SelectedConversationChangedActionType;
| SelectedConversationChangedActionType
| FetchMessagesForConversationType;
// Action Creators
@ -126,6 +239,9 @@ export const actions = {
conversationRemoved,
removeAllConversations,
messageExpired,
messageAdded,
messageChanged,
fetchMessagesForConversation,
openConversationExternal,
};
@ -181,6 +297,29 @@ function messageExpired(
};
}
function messageChanged(messageModel: MessageModel): MessageChangedActionType {
return {
type: 'MESSAGE_CHANGED',
payload: messageModel,
};
}
function messageAdded({
conversationKey,
messageModel,
}: {
conversationKey: string;
messageModel: MessageModel;
}): MessageAddedActionType {
return {
type: 'MESSAGE_ADDED',
payload: {
conversationKey,
messageModel,
},
};
}
function openConversationExternal(
id: string,
messageId?: string
@ -196,12 +335,29 @@ function openConversationExternal(
// Reducer
const toPickFromMessageModel = [
'attributes',
'id',
'propsForSearchResult',
'propsForMessage',
'receivedAt',
'conversationId',
'firstMessageOfSeries',
'propsForGroupInvitation',
'propsForTimerNotification',
'propsForVerificationNotification',
'propsForResetSessionNotification',
'propsForGroupNotification',
];
function getEmptyState(): ConversationsStateType {
return {
conversationLookup: {},
messages: [],
};
}
// tslint:disable-next-line: cyclomatic-complexity
export function reducer(
state: ConversationsStateType = getEmptyState(),
action: ConversationActionType
@ -263,7 +419,8 @@ export function reducer(
return getEmptyState();
}
if (action.type === 'MESSAGE_EXPIRED') {
// noop - for now this is only important for search
// FIXME
console.warn('EXPIRED');
}
if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
const { payload } = action;
@ -273,5 +430,77 @@ export function reducer(
selectedConversation: id,
};
}
if (action.type === fetchMessagesForConversation.fulfilled.type) {
const { messages, conversationKey } = action.payload as any;
// double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) {
const lightMessages = messages.map((m: any) =>
_.pick(m, toPickFromMessageModel)
) as Array<MessageTypeInConvo>;
return {
...state,
messages: lightMessages,
};
}
return state;
}
if (action.type === 'MESSAGE_CHANGED') {
const messageInStoreIndex = state?.messages.findIndex(
m => m.id === action.payload.id
);
if (messageInStoreIndex >= 0) {
const changedMessage = _.pick(
action.payload as any,
toPickFromMessageModel
) as MessageTypeInConvo;
// we cannot edit the array directly, so slice the first part, insert our edited message, and slice the second part
const editedMessages = [
...state.messages.slice(0, messageInStoreIndex),
changedMessage,
...state.messages.slice(messageInStoreIndex + 1),
];
return {
...state,
messages: editedMessages,
};
}
return state;
}
if (action.type === 'MESSAGE_ADDED') {
const { conversationKey, messageModel } = action.payload;
if (conversationKey === state.selectedConversation) {
const { messages } = state;
const addedMessage = _.pick(
messageModel as any,
toPickFromMessageModel
) as MessageTypeInConvo;
const messagesWithNewMessage = [...messages, addedMessage];
const convo = state.conversationLookup[state.selectedConversation];
const isPublic = convo?.isPublic;
if (convo && isPublic) {
return {
...state,
messages: messagesWithNewMessage.sort(
(a: any, b: any) =>
b.attributes.serverTimestamp - a.attributes.serverTimestamp
),
};
}
if (convo) {
return {
...state,
messages: messagesWithNewMessage.sort(
(a, b) => b.attributes.timestamp - a.attributes.timestamp
),
};
}
}
return state;
}
return state;
}

@ -1,129 +0,0 @@
import { Constants } from '../../session';
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import _ from 'lodash';
import { MessageType } from './conversations';
export type MessagesStateType = Array<MessageType>;
export async function getMessages(
conversationKey: string,
numMessages: number
): Promise<MessagesStateType> {
const conversation = window.ConversationController.get(conversationKey);
if (!conversation) {
// no valid conversation, early return
window.log.error('Failed to get convo on reducer.');
return [];
}
const unreadCount = await conversation.getUnreadCount();
let msgCount =
numMessages ||
Number(Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) + unreadCount;
msgCount =
msgCount > Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
? Constants.CONVERSATION.MAX_MESSAGE_FETCH_COUNT
: msgCount;
if (msgCount < Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT) {
msgCount = Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
}
const messageSet = await window.Signal.Data.getMessagesByConversation(
conversationKey,
{ limit: msgCount, MessageCollection: window.Whisper.MessageCollection }
);
// Set first member of series here.
const messageModels = messageSet.models;
const messages = [];
// no need to do that `firstMessageOfSeries` on a private chat
if (conversation.isPrivate()) {
return messageModels;
}
// messages are got from the more recent to the oldest, so we need to check if
// the next messages in the list is still the same author.
// The message is the first of the series if the next message is not from the same authori
for (let i = 0; i < messageModels.length; i++) {
// Handle firstMessageOfSeries for conditional avatar rendering
let firstMessageOfSeries = true;
const currentSender = messageModels[i].propsForMessage?.authorPhoneNumber;
const nextSender =
i < messageModels.length - 1
? messageModels[i + 1].propsForMessage?.authorPhoneNumber
: undefined;
if (i > 0 && currentSender === nextSender) {
firstMessageOfSeries = false;
}
messages.push({ ...messageModels[i], firstMessageOfSeries });
}
return messages;
}
// ACTIONS
const fetchMessagesForConversation = createAsyncThunk(
'messages/fetchByConversationKey',
async ({
conversationKey,
count,
}: {
conversationKey: string;
count: number;
}) => {
return getMessages(conversationKey, count);
}
);
const toPickFromMessageModel = [
'attributes',
'id',
'propsForSearchResult',
'propsForMessage',
'receivedAt',
'conversationId',
'firstMessageOfSeries',
'propsForGroupInvitation',
'propsForTimerNotification',
'propsForVerificationNotification',
'propsForResetSessionNotification',
'propsForGroupNotification',
];
const messageSlice = createSlice({
name: 'messages',
initialState: [] as MessagesStateType,
reducers: {
messageChanged(state: MessagesStateType, action) {
console.log('message changed ', action);
const messageInStoreIndex = state.findIndex(
m => m.id === action.payload.id
);
if (messageInStoreIndex >= 0) {
state[messageInStoreIndex] = _.pick(
action.payload,
toPickFromMessageModel
) as MessageType;
}
return state;
},
},
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[fetchMessagesForConversation.fulfilled.type]: (state, action) => {
// console.log('fetchMessagesForConversatio0 NON LIGHT', action.payload);
const lightMessages = action.payload.map((m: any) =>
_.pick(m, toPickFromMessageModel)
) as MessagesStateType;
// console.log('fetchMessagesForConversation', lightMessages);
return lightMessages;
},
},
});
export const actions = {
...messageSlice.actions,
fetchMessagesForConversation,
};
export const reducer = messageSlice.reducer;

@ -8,11 +8,10 @@ import {
import { reducer as user, UserStateType } from './ducks/user';
import { reducer as theme, ThemeStateType } from './ducks/theme';
import { reducer as section, SectionStateType } from './ducks/section';
import { MessagesStateType, reducer as messages } from './ducks/messages';
export type StateType = {
search: SearchStateType;
messages: MessagesStateType;
// messages: MessagesStateType;
user: UserStateType;
conversations: ConversationsStateType;
theme: ThemeStateType;
@ -22,7 +21,7 @@ export type StateType = {
export const reducers = {
search,
// Temporary until ./ducks/messages is working
messages,
// messages,
// messages: search,
conversations,
user,

@ -14,7 +14,7 @@ const mapStateToProps = (state: StateType) => {
conversation,
conversationKey,
theme: state.theme,
messages: state.messages,
messages: state.conversations.messages,
};
};

Loading…
Cancel
Save