From 9b382de6da369b70126c8ec0db773e5823a35312 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Thu, 17 Jan 2019 15:10:07 +1100
Subject: [PATCH] Added online indicator. Disable selection in contacts.

---
 js/models/conversations.js              |  9 ++++++++
 js/views/conversation_list_item_view.js |  1 +
 stylesheets/_modules.scss               |  8 ++++---
 ts/components/Avatar.tsx                | 29 +++++++++++++++++++++++--
 ts/components/ConversationListItem.tsx  |  3 +++
 5 files changed, 45 insertions(+), 5 deletions(-)

diff --git a/js/models/conversations.js b/js/models/conversations.js
index f5e70fc22..198e75de8 100644
--- a/js/models/conversations.js
+++ b/js/models/conversations.js
@@ -77,6 +77,7 @@
         unlockTimestamp: null, // Timestamp used for expiring friend requests.
         sessionResetStatus: SessionResetEnum.none,
         swarmNodes: new Set([]),
+        isOnline: false,
       };
     },
 
@@ -252,6 +253,13 @@
       );
     },
 
+    async setIsOnline(online) {
+      this.set({ isOnline: online });
+      await window.Signal.Data.updateConversation(this.id, this.attributes, {
+        Conversation: Whisper.Conversation,
+      });
+    },
+
     async cleanup() {
       await window.Signal.Types.Conversation.deleteExternalFiles(
         this.attributes,
@@ -386,6 +394,7 @@
           status: this.lastMessageStatus,
           text: this.lastMessage,
         },
+        isOnline: this.get('isOnline'),
 
         onClick: () => this.trigger('select', this),
       };
diff --git a/js/views/conversation_list_item_view.js b/js/views/conversation_list_item_view.js
index ff6eb7bf1..06459a19e 100644
--- a/js/views/conversation_list_item_view.js
+++ b/js/views/conversation_list_item_view.js
@@ -60,6 +60,7 @@
         const props = this.model.getPropsForListItem();
         delete props.lastMessage;
         delete props.lastUpdated;
+        delete props.isSelected;
 
         return props;
       },
diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index b1a9f4e69..92c0c4de7 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -1767,6 +1767,10 @@
   .module-avatar {
     background-color: $color-dark-85;
   }
+
+  .module-contact-name {
+    margin-right: 0px;
+  }
 }
 
 .module-conversation-list-item--has-unread {
@@ -1822,12 +1826,10 @@
 .module-conversation-list-item__content {
   flex-grow: 1;
   margin-left: 12px;
-  // parent - 48px (for avatar) - 16px (our right margin)
-  max-width: calc(100% - 64px);
-
   display: flex;
   flex-direction: column;
   align-items: stretch;
+  overflow: hidden;
 }
 
 .module-conversation-list-item__header {
diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx
index 9a921ac1d..7b34b123e 100644
--- a/ts/components/Avatar.tsx
+++ b/ts/components/Avatar.tsx
@@ -13,6 +13,7 @@ interface Props {
   phoneNumber?: string;
   profileName?: string;
   size: number;
+  borderColor?: string;
 }
 
 interface State {
@@ -41,7 +42,14 @@ export class Avatar extends React.Component<Props, State> {
   }
 
   public renderImage() {
-    const { avatarPath, i18n, name, phoneNumber, profileName } = this.props;
+    const {
+      avatarPath,
+      i18n,
+      name,
+      phoneNumber,
+      profileName,
+      borderColor,
+    } = this.props;
     const { imageBroken } = this.state;
     const hasImage = avatarPath && !imageBroken;
 
@@ -53,8 +61,16 @@ export class Avatar extends React.Component<Props, State> {
       !name && profileName ? ` ~${profileName}` : ''
     }`;
 
+    const borderStyle = borderColor
+      ? {
+          borderColor: borderColor,
+          borderStyle: 'solid',
+        }
+      : undefined;
+
     return (
       <img
+        style={borderStyle}
         onError={this.handleImageErrorBound}
         alt={i18n('contactAvatarAlt', [title])}
         src={avatarPath}
@@ -63,11 +79,18 @@ export class Avatar extends React.Component<Props, State> {
   }
 
   public renderNoImage() {
-    const { conversationType, name, size } = this.props;
+    const { conversationType, name, size, borderColor } = this.props;
 
     const initials = getInitials(name);
     const isGroup = conversationType === 'group';
 
+    const borderStyle = borderColor
+      ? {
+          borderColor: borderColor,
+          borderStyle: 'solid',
+        }
+      : undefined;
+
     if (!isGroup && initials) {
       return (
         <div
@@ -75,6 +98,7 @@ export class Avatar extends React.Component<Props, State> {
             'module-avatar__label',
             `module-avatar__label--${size}`
           )}
+          style={borderStyle}
         >
           {initials}
         </div>
@@ -88,6 +112,7 @@ export class Avatar extends React.Component<Props, State> {
           `module-avatar__icon--${conversationType}`,
           `module-avatar__icon--${size}`
         )}
+        style={borderStyle}
       />
     );
   }
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index a30359479..bacf476f0 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -28,6 +28,7 @@ interface Props {
   };
   showFriendRequestIndicator?: boolean;
   isBlocked: boolean;
+  isOnline: boolean;
 
   i18n: Localizer;
   onClick?: () => void;
@@ -43,6 +44,7 @@ export class ConversationListItem extends React.Component<Props> {
       name,
       phoneNumber,
       profileName,
+      isOnline,
     } = this.props;
 
     return (
@@ -56,6 +58,7 @@ export class ConversationListItem extends React.Component<Props> {
           phoneNumber={phoneNumber}
           profileName={profileName}
           size={48}
+          borderColor={isOnline ? '#1c8260' : '#3d3e44'}
         />
         {this.renderUnread()}
       </div>