Merge pull request #165 from Mikunj/online-indicator

Online indicator
pull/167/head
Beaudan Campbell-Brown 6 years ago committed by GitHub
commit 5f49c5aafd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,4 @@
/* global _, Whisper, Backbone, storage */ /* global _, Whisper, Backbone, storage, lokiP2pAPI */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
@ -248,6 +248,14 @@
async load() { async load() {
window.log.info('ConversationController: starting initial fetch'); window.log.info('ConversationController: starting initial fetch');
// We setup online and offline listeners here because we want
// to minimize the amount of listeners we have to avoid memory leaks
if (!this.p2pListenersSet) {
lokiP2pAPI.on('online', this._handleOnline.bind(this));
lokiP2pAPI.on('offline', this._handleOffline.bind(this));
this.p2pListenersSet = true;
}
if (conversations.length) { if (conversations.length) {
throw new Error('ConversationController: Already loaded!'); throw new Error('ConversationController: Already loaded!');
} }
@ -268,7 +276,6 @@
conversation.updateProfile(), conversation.updateProfile(),
conversation.updateProfileAvatar(), conversation.updateProfileAvatar(),
conversation.resetPendingSend(), conversation.resetPendingSend(),
conversation.updateProfile(),
]); ]);
}); });
await Promise.all(promises); await Promise.all(promises);
@ -292,5 +299,17 @@
return this._initialPromise; return this._initialPromise;
}, },
_handleOnline(pubKey) {
try {
const conversation = this.get(pubKey);
conversation.set({ isOnline: true });
} catch (e) {} // eslint-disable-line
},
_handleOffline(pubKey) {
try {
const conversation = this.get(pubKey);
conversation.set({ isOnline: false });
} catch (e) {} // eslint-disable-line
},
}; };
})(); })();

@ -7,6 +7,7 @@
/* global storage: false */ /* global storage: false */
/* global textsecure: false */ /* global textsecure: false */
/* global Whisper: false */ /* global Whisper: false */
/* global lokiP2pAPI: false */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
@ -77,6 +78,7 @@
unlockTimestamp: null, // Timestamp used for expiring friend requests. unlockTimestamp: null, // Timestamp used for expiring friend requests.
sessionResetStatus: SessionResetEnum.none, sessionResetStatus: SessionResetEnum.none,
swarmNodes: new Set([]), swarmNodes: new Set([]),
isOnline: false,
}; };
}, },
@ -155,6 +157,9 @@
this.setFriendRequestExpiryTimeout(); this.setFriendRequestExpiryTimeout();
this.typingRefreshTimer = null; this.typingRefreshTimer = null;
this.typingPauseTimer = null; this.typingPauseTimer = null;
// Online status handling
this.set({ isOnline: lokiP2pAPI.isOnline(this.id) });
}, },
isMe() { isMe() {
@ -386,6 +391,7 @@
status: this.lastMessageStatus, status: this.lastMessageStatus,
text: this.lastMessage, text: this.lastMessage,
}, },
isOnline: this.get('isOnline'),
onClick: () => this.trigger('select', this), onClick: () => this.trigger('select', this),
}; };

@ -11,6 +11,7 @@ const {
map, map,
merge, merge,
set, set,
omit,
} = require('lodash'); } = require('lodash');
const { base64ToArrayBuffer, arrayBufferToBase64 } = require('./crypto'); const { base64ToArrayBuffer, arrayBufferToBase64 } = require('./crypto');
@ -688,11 +689,13 @@ async function getConversationCount() {
} }
async function saveConversation(data) { async function saveConversation(data) {
await channels.saveConversation(data); const cleaned = omit(data, 'isOnline');
await channels.saveConversation(cleaned);
} }
async function saveConversations(data) { async function saveConversations(data) {
await channels.saveConversations(data); const cleaned = data.map(d => omit(d, 'isOnline'));
await channels.saveConversations(cleaned);
} }
async function getConversationById(id, { Conversation }) { async function getConversationById(id, { Conversation }) {
@ -712,7 +715,10 @@ async function updateConversation(id, data, { Conversation }) {
if (merged.swarmNodes instanceof Set) { if (merged.swarmNodes instanceof Set) {
merged.swarmNodes = Array.from(merged.swarmNodes); merged.swarmNodes = Array.from(merged.swarmNodes);
} }
await channels.updateConversation(merged);
// Don't save the online status of the object
const cleaned = omit(merged, 'isOnline');
await channels.updateConversation(cleaned);
} }
async function removeConversation(id, { Conversation }) { async function removeConversation(id, { Conversation }) {

@ -16,7 +16,10 @@ class LokiP2pAPI extends EventEmitter {
? 60 * 1000 // 1 minute ? 60 * 1000 // 1 minute
: 2 * 60 * 1000; // 2 minutes : 2 * 60 * 1000; // 2 minutes
if (this.contactP2pDetails[pubKey] && this.contactP2pDetails[pubKey].pingTimer) { if (
this.contactP2pDetails[pubKey] &&
this.contactP2pDetails[pubKey].pingTimer
) {
clearTimeout(this.contactP2pDetails[pubKey].pingTimer); clearTimeout(this.contactP2pDetails[pubKey].pingTimer);
} }
this.contactP2pDetails[pubKey] = { this.contactP2pDetails[pubKey] = {
@ -66,6 +69,12 @@ class LokiP2pAPI extends EventEmitter {
); );
} }
isOnline(pubKey) {
return !!(
this.contactP2pDetails[pubKey] && this.contactP2pDetails[pubKey].isOnline
);
}
pingContact(pubKey) { pingContact(pubKey) {
if (!this.contactP2pDetails[pubKey]) { if (!this.contactP2pDetails[pubKey]) {
return; return;

@ -60,6 +60,7 @@
const props = this.model.getPropsForListItem(); const props = this.model.getPropsForListItem();
delete props.lastMessage; delete props.lastMessage;
delete props.lastUpdated; delete props.lastUpdated;
delete props.isSelected;
return props; return props;
}, },

@ -1767,6 +1767,10 @@
.module-avatar { .module-avatar {
background-color: $color-dark-85; background-color: $color-dark-85;
} }
.module-contact-name {
margin-right: 0px;
}
} }
.module-conversation-list-item--has-unread { .module-conversation-list-item--has-unread {
@ -1822,12 +1826,10 @@
.module-conversation-list-item__content { .module-conversation-list-item__content {
flex-grow: 1; flex-grow: 1;
margin-left: 12px; margin-left: 12px;
// parent - 48px (for avatar) - 16px (our right margin)
max-width: calc(100% - 64px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
overflow: hidden;
} }
.module-conversation-list-item__header { .module-conversation-list-item__header {

@ -555,6 +555,7 @@ describe('Backup', () => {
'friendRequestStatus', 'friendRequestStatus',
'unlockTimestamp', 'unlockTimestamp',
'sessionResetStatus', 'sessionResetStatus',
'isOnline',
]; ];
const conversationFromDB = conversationCollection.at(0).attributes; const conversationFromDB = conversationCollection.at(0).attributes;
console.log({ conversationFromDB, conversation }); console.log({ conversationFromDB, conversation });

@ -13,6 +13,7 @@ interface Props {
phoneNumber?: string; phoneNumber?: string;
profileName?: string; profileName?: string;
size: number; size: number;
borderColor?: string;
} }
interface State { interface State {
@ -41,7 +42,14 @@ export class Avatar extends React.Component<Props, State> {
} }
public renderImage() { public renderImage() {
const { avatarPath, i18n, name, phoneNumber, profileName } = this.props; const {
avatarPath,
i18n,
name,
phoneNumber,
profileName,
borderColor,
} = this.props;
const { imageBroken } = this.state; const { imageBroken } = this.state;
const hasImage = avatarPath && !imageBroken; const hasImage = avatarPath && !imageBroken;
@ -53,8 +61,16 @@ export class Avatar extends React.Component<Props, State> {
!name && profileName ? ` ~${profileName}` : '' !name && profileName ? ` ~${profileName}` : ''
}`; }`;
const borderStyle = borderColor
? {
borderColor: borderColor,
borderStyle: 'solid',
}
: undefined;
return ( return (
<img <img
style={borderStyle}
onError={this.handleImageErrorBound} onError={this.handleImageErrorBound}
alt={i18n('contactAvatarAlt', [title])} alt={i18n('contactAvatarAlt', [title])}
src={avatarPath} src={avatarPath}
@ -63,11 +79,18 @@ export class Avatar extends React.Component<Props, State> {
} }
public renderNoImage() { public renderNoImage() {
const { conversationType, name, size } = this.props; const { conversationType, name, size, borderColor } = this.props;
const initials = getInitials(name); const initials = getInitials(name);
const isGroup = conversationType === 'group'; const isGroup = conversationType === 'group';
const borderStyle = borderColor
? {
borderColor: borderColor,
borderStyle: 'solid',
}
: undefined;
if (!isGroup && initials) { if (!isGroup && initials) {
return ( return (
<div <div
@ -75,6 +98,7 @@ export class Avatar extends React.Component<Props, State> {
'module-avatar__label', 'module-avatar__label',
`module-avatar__label--${size}` `module-avatar__label--${size}`
)} )}
style={borderStyle}
> >
{initials} {initials}
</div> </div>
@ -88,6 +112,7 @@ export class Avatar extends React.Component<Props, State> {
`module-avatar__icon--${conversationType}`, `module-avatar__icon--${conversationType}`,
`module-avatar__icon--${size}` `module-avatar__icon--${size}`
)} )}
style={borderStyle}
/> />
); );
} }

@ -28,6 +28,7 @@ interface Props {
}; };
showFriendRequestIndicator?: boolean; showFriendRequestIndicator?: boolean;
isBlocked: boolean; isBlocked: boolean;
isOnline: boolean;
i18n: Localizer; i18n: Localizer;
onClick?: () => void; onClick?: () => void;
@ -43,6 +44,7 @@ export class ConversationListItem extends React.Component<Props> {
name, name,
phoneNumber, phoneNumber,
profileName, profileName,
isOnline,
} = this.props; } = this.props;
return ( return (
@ -56,6 +58,7 @@ export class ConversationListItem extends React.Component<Props> {
phoneNumber={phoneNumber} phoneNumber={phoneNumber}
profileName={profileName} profileName={profileName}
size={48} size={48}
borderColor={isOnline ? '#1c8260' : '#3d3e44'}
/> />
{this.renderUnread()} {this.renderUnread()}
</div> </div>

Loading…
Cancel
Save