diff --git a/chat-SDK-JS b/chat-SDK-JS
index e4f28b4..f0c68a6 160000
--- a/chat-SDK-JS
+++ b/chat-SDK-JS
@@ -1 +1 @@
-Subproject commit e4f28b437a76d04e44f745760a07a68bd65da906
+Subproject commit f0c68a68c7b53cb8abf98cb64c6f15cbd7e7fdd2
diff --git a/src/components/App.jsx b/src/components/App.jsx
index 2443d32..1832d31 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import skygearChat from 'skygear-chat';
-import ManagedConversationList from '../utils/ManagedConversationList.jsx';
+import ManagedUserConversationList from '../utils/ManagedConversationList.jsx';
import Conversation from './Conversation.jsx';
import ConversationPreview from './ConversationPreview.jsx';
@@ -30,38 +30,45 @@ export default class App extends React.Component {
this.state = {
unreadCount: 0, // user's total unread message count (int)
currentModal: null, // currently displayed modal dialog name (or null)
- currentConversation: null // currently selected Conversion Record (or null)
+ activeID: null // currently selected UserConversion ID (or null)
};
- this.conversationList = new ManagedConversationList();
+ this.userConversationList = new ManagedUserConversationList();
}
componentDidMount() {
// subscribe conversation change
- this.conversationList.subscribe(() => {
- const {currentConversation} = this.state;
- const {conversationList} = this;
- if (currentConversation) {
- this.setState({
- currentConversation: conversationList.get(currentConversation._id)
- });
- } else {
- this.forceUpdate();
- }
+ this.userConversationList.subscribe(() => {
+ this.forceUpdate();
+ this.updateUnreadCount();
});
+ this.updateUnreadCount();
+ }
+
+ updateUnreadCount() {
skygearChat.getUnreadCount().then(result => {
this.setState({unreadCount: result.message});
});
}
+ selectConversation(userConversation) {
+ this.setState({
+ activeID: userConversation._id,
+ unreadCount: this.state.unreadCount - userConversation.unread_count
+ });
+ userConversation.unread_count = 0;
+ this.updateUnreadCount();
+ }
+
render() {
const {
state: {
unreadCount,
currentModal,
- currentConversation
+ activeID
},
- conversationList
+ userConversationList
} = this;
+ const activeUC = userConversationList.get(activeID);
return (
{
- conversationList
- .map((c) => {
+ userConversationList
+ .map((uc) => {
return this.setState({currentConversation: c})}/>
+ uc.id === activeID}
+ userConversation={uc}
+ conversation={uc.$transient.conversation}
+ onClick={() => this.selectConversation(uc)}/>
})
}
- {currentConversation &&
+ {activeID &&
this.setState({currentModal: 'details'})}/>
}
{(modal => {
@@ -111,13 +119,13 @@ export default class App extends React.Component {
case 'createGroup':
return (
conversationList.add(c)}
+ addConversationDelegate={c => userConversationList.addConversation(c)}
onClose={() => this.setState({currentModal: null})}/>
);
case 'createChat':
return (
conversationList.add(c)}
+ addConversationDelegate={c => userConversationList.addConversation(c)}
onClose={() => this.setState({currentModal: null})}/>
);
case 'settings':
@@ -128,11 +136,10 @@ export default class App extends React.Component {
case 'details':
return (
conversationList.update(c)}
- removeConversationDelegate={id => conversationList.remove(id)}
+ key={'DetailsModal-' + activeID.id}
+ conversation={activeUC.$transient.conversation}
+ updateConversationDelegate={c => userConversationList.updateConversation(c)}
+ removeConversationDelegate={c => userConversationList.removeConversation(c)}
onClose={() => this.setState({currentModal: null})}/>
);
default:
diff --git a/src/components/Conversation.jsx b/src/components/Conversation.jsx
index fcfd824..cc99d9e 100644
--- a/src/components/Conversation.jsx
+++ b/src/components/Conversation.jsx
@@ -108,9 +108,10 @@ export default class Conversation extends React.Component {
createdBy: skygear.currentUser.id
});
this.messageList.add(message);
- skygear.privateDB.save(message);
- // force update the conversation on new message to trigger pubsub event
- skygearChat.updateConversation(conversation);
+ skygear.privateDB.save(message).then(() => {
+ // force update the conversation on new message to trigger pubsub event
+ skygearChat.updateConversation(conversation);
+ });
}
}
render() {
diff --git a/src/components/ConversationPreview.jsx b/src/components/ConversationPreview.jsx
index fe02f1e..f05a887 100644
--- a/src/components/ConversationPreview.jsx
+++ b/src/components/ConversationPreview.jsx
@@ -8,7 +8,7 @@ export default class ConversationPreview extends React.Component {
super(props);
const {title} = props.conversation;
this.state = {
- title: title || 'loading...', // conversation title (either group name or participant names)
+ title: title || 'loading...', // conversation title (either group name or participant names)
imageURL: 'img/loading.svg' // conversation image URL
};
}
@@ -43,23 +43,17 @@ export default class ConversationPreview extends React.Component {
}
render() {
const {
- props: {
- selected,
- onClick,
- conversation: {
- unread_count,
- $transient: {
- last_message: {
- body: lastMessage
- } = {}
- }
- }
- },
- state: {
- title,
- imageURL
- }
- } = this;
+ selected,
+ onClick,
+ } = this.props;
+ const {
+ unread_count
+ } = this.props.userConversation;
+ const {
+ title,
+ imageURL
+ } = this.state;
+ const lastMessage = this.props.conversation.$transient.last_message;
return (
- { lastMessage.length > 23 ?
- lastMessage.substring(0, 20) + '...' : lastMessage }
+ { lastMessage.body.length > 23 ?
+ lastMessage.body.substring(0, 20) + '...' : lastMessage.body }
}
diff --git a/src/components/DetailsModal.jsx b/src/components/DetailsModal.jsx
index c754522..e13d51a 100644
--- a/src/components/DetailsModal.jsx
+++ b/src/components/DetailsModal.jsx
@@ -77,7 +77,7 @@ export default class DetailsModal extends React.Component {
).then(() => {
// close modal after leaving
onClose();
- removeConversationDelegate(conversation._id);
+ removeConversationDelegate(conversation);
});
}
discoverAndAddUser(username) {
diff --git a/src/utils/ManagedConversationList.jsx b/src/utils/ManagedConversationList.jsx
index 4e979b9..f439b40 100644
--- a/src/utils/ManagedConversationList.jsx
+++ b/src/utils/ManagedConversationList.jsx
@@ -50,10 +50,10 @@ export class ConversationSorting {
}
/**
- * A managed list of conversations for the current user.
- * Note: You must be logged in to use this.
+ * A managed list of user conversations for the current user.
+ * Note: You must be logged in at skygear to use this.
*/
-export default class ManagedConversationList {
+export default class ManagedUserConversationList {
/**
* @param {object} [options={}]
* @param {boolean} [options.initialFetch=true]
@@ -73,8 +73,10 @@ export default class ManagedConversationList {
this._compare = sortBy;
// conversation IDs in order (without type prefix)
this._orderedIDs = [];
- // map of conversaton ID => conversation object
- this._conversations = {};
+ // map of user conversaton ID => user conversation object
+ this._userConversations = {};
+ // map of conversaton ID => user conversation object
+ this._ucByCId = {};
// map of subscription ID => event handler
this._updateHandlers = {};
@@ -100,13 +102,13 @@ export default class ManagedConversationList {
console.log('[conversation event]', event);
switch (event.event_type) {
case 'create':
- this.add(event.record);
+ this.addConversation(event.record);
break;
case 'update':
- this.update(event.record);
+ this.updateConversation(event.record);
break;
case 'delete':
- this.remove(event.record._id);
+ this.removeConversation(event.record);
break;
}
}
@@ -114,10 +116,10 @@ export default class ManagedConversationList {
/**
* @private
*/
- _conversationsUpdated() {
- const {_conversations, _compare, _updateHandlers} = this;
- this._orderedIDs = Object.keys(_conversations)
- .map(id => _conversations[id])
+ _userConversationsUpdated() {
+ const {_userConversations, _compare, _updateHandlers} = this;
+ this._orderedIDs = Object.keys(_userConversations)
+ .map(id => _userConversations[id])
.sort(_compare)
.map(conversation => conversation._id);
Object.keys(_updateHandlers)
@@ -125,27 +127,42 @@ export default class ManagedConversationList {
.forEach(handler => handler(this));
}
/**
- * Fetches list of conversations from the server.
- * @return {Promise}
+ * Fetches list of user conversations from the server.
+ * @return {Promise}
* Promise of this object, resolves if the fetch is successful.
*/
fetch() {
- // FIXME: use the getConversations() API when it is fixed
return skygearChat
.getUserConversations()
- .then(results => results.map(uc => {
- uc.$transient.conversation.unread_count = uc.unread_count;
- return uc.$transient.conversation;
- }))
.then(results => {
- console.log('[fetched conversations]', results);
- results.forEach(conversation => {
- this._conversations[conversation._id] = conversation;
+ console.log('[fetched user conversations]', results);
+ results.forEach((uc) => {
+ this._userConversations[uc._id] = uc;
+ this._ucByCId[uc.$transient.conversation._id] = uc;
});
- this._conversationsUpdated();
+ this._userConversationsUpdated();
return this;
});
}
+ /**
+ * Update user conversations from the server by providing a conversation
+ * @param {Conversation} conversation
+ * @return {ManagedUserConversationList}
+ */
+ updateOne(conversation) {
+ const {
+ _userConversations,
+ _ucByCId
+ } = this;
+ skygearChat
+ .getUserConversation(conversation)
+ .then((uc) => {
+ _userConversations[uc._id] = uc;
+ _ucByCId[conversation._id] = uc;
+ this._userConversationsUpdated();
+ });
+ return this;
+ }
/**
* List length (like the array length property)
* @type {number}
@@ -154,17 +171,17 @@ export default class ManagedConversationList {
return this._orderedIDs.length;
}
/**
- * Get a Conversation
+ * Get a User Conversation
* @param {number|string} indexOrID
* Either the list index (number) or conversation ID (string) without type prefix.
* @return {Conversation}
*/
get(indexOrID) {
- const {_conversations, _orderedIDs} = this;
+ const {_userConversations, _orderedIDs} = this;
if (typeof indexOrID === 'number') {
- return _conversations[_orderedIDs[indexOrID]];
+ return _userConversations[_orderedIDs[indexOrID]];
} else {
- return _conversations[indexOrID];
+ return _userConversations[indexOrID];
}
}
/**
@@ -174,7 +191,7 @@ export default class ManagedConversationList {
*/
map(mappingFunction) {
return this._orderedIDs
- .map(id => this._conversations[id])
+ .map(id => this._userConversations[id])
.map(mappingFunction);
}
/**
@@ -184,51 +201,59 @@ export default class ManagedConversationList {
*/
filter(predicate) {
return this._orderedIDs
- .map(id => this._conversations[id])
+ .map(id => this._userConversations[id])
.filter(predicate);
}
/**
* Add a conversation, no-op if the conversation already exists.
* @param {Conversation} conversation
- * @return {ManagedConversationList}
+ * @return {ManagedUserConversationList}
*/
- add(conversation) {
- const {_conversations} = this;
- if (!_conversations.hasOwnProperty(conversation._id)) {
- _conversations[conversation._id] = conversation;
- this._conversationsUpdated();
+ addConversation(conversation) {
+ const {
+ _ucByCId
+ } = this;
+ if (_ucByCId.hasOwnProperty(conversation._id)) {
+ return this;
}
- return this;
+ return this.updateOne(conversation);
}
/**
* Update a conversation, no-op if the conversation updatedAt date is older than existing.
* Conversation will be added if it's not in the list.
* @param {Conversation} conversation
- * @return {ManagedConversationList}
- */
- update(conversation) {
- const {_conversations} = this;
- if (
- _conversations.hasOwnProperty(conversation._id) &&
- conversation.updatedAt >= _conversations[conversation._id].updatedAt
- ) {
- _conversations[conversation._id] = conversation;
- this._conversationsUpdated();
+ * @return {ManagedUserConversationList}
+ */
+ updateConversation(conversation) {
+ const {_ucByCId} = this;
+ if (_ucByCId.hasOwnProperty(conversation._id)) {
+ const uc = _ucByCId[conversation._id];
+ const c = uc.$transient.conversation;
+ if (conversation.updatedAt <= c.updatedAt) {
+ return this
+ }
+ return this.updateOne(conversation);
} else {
- this.add(conversation);
+ this.addConversation(conversation);
}
return this;
}
/**
* Remove a conversation, no-op if the conversation doesn't exist.
- * @param {string} conversationID Conversation ID without type prefix.
- * @return {ManagedConversationList}
- */
- remove(conversationID) {
- const {_conversations} = this;
- if (_conversations.hasOwnProperty(conversationID)) {
- delete _conversations[conversationID];
- this._conversationsUpdated();
+ * @param {conversation} conversation Conversation without type prefix.
+ * @return {ManagedUserConversationList}
+ */
+ removeConversation(conversation) {
+ const {
+ _userConversations,
+ _ucByCId
+ } = this;
+ const conversationID = conversation._id;
+ if (_ucById.hasOwnProperty(conversationID)) {
+ const uc = _ucByCId[conversationID]
+ delete _userConversations[uc._id];
+ delete _ucById[conversationID];
+ this._userConversationsUpdated();
}
return this;
}
diff --git a/src/utils/ManagedMessageList.jsx b/src/utils/ManagedMessageList.jsx
index cf5987f..693d41a 100644
--- a/src/utils/ManagedMessageList.jsx
+++ b/src/utils/ManagedMessageList.jsx
@@ -94,8 +94,9 @@ export default class ManagedMessageList {
results.forEach(message => {
this._messages[message._id] = message;
});
- if (this._autoRead) {
- skygearChat.markAsRead(results);
+ if (this._autoRead && results) {
+ skygearChat.markAsLastMessageRead(
+ this._conversation, results[0]);
}
this._messagesUpdated();
return this;
@@ -154,7 +155,7 @@ export default class ManagedMessageList {
_messages[message._id] = message;
this._messagesUpdated();
if (this._autoRead) {
- skygearChat.markAsRead([message]);
+ skygearChat.markAsLastMessageRead(this._conversation, message);
}
}
return this;