Bug 1378823 - Implement tooltips for Matrix. r=pmorris
authorPatrick Cloke <clokep@gmail.com>
Thu, 23 Jan 2020 19:26:34 -0800
changeset 38036 fdc16e6a800631497a823feb28ea0e61f1a91262
parent 38035 8d8d787c4fa726866891ac661d0b4319c6dc8730
child 38037 06b59eceaa1140d367b7972f8d1215d051b6eac9
push id398
push userclokep@gmail.com
push dateMon, 09 Mar 2020 19:10:28 +0000
reviewerspmorris
bugs1378823
Bug 1378823 - Implement tooltips for Matrix. r=pmorris
chat/locales/en-US/matrix.properties
chat/protocols/matrix/matrix-sdk.jsm
chat/protocols/matrix/matrix.jsm
--- a/chat/locales/en-US/matrix.properties
+++ b/chat/locales/en-US/matrix.properties
@@ -10,8 +10,17 @@ options.connectPort=Port
 
 # LOCALIZATION NOTE (chatRoomField.*):
 #   These are the name of fields displayed in the 'Join Chat' dialog
 #   for Matrix accounts.
 #   The _ character won't be displayed; it indicates the next
 #   character of the string should be used as the access key for this
 #   field.
 chatRoomField.room=_Room
+
+# LOCALIZATION NOTE (tooltip.*):
+#    These are the descriptions given in a tooltip with information received
+#    from the "User" object.
+# The human readable name of the user.
+tooltip.displayName=Display name
+# %S is the timespan elapsed since the last activity.
+tooltip.timespan=%S ago
+tooltip.lastActive=Last activity
--- a/chat/protocols/matrix/matrix-sdk.jsm
+++ b/chat/protocols/matrix/matrix-sdk.jsm
@@ -12,17 +12,17 @@ const {
 const { scriptError } = ChromeUtils.import(
   "resource:///modules/imXPCOMUtils.jsm"
 );
 
 const { Loader, Require, Module } = ChromeUtils.import(
   "resource://devtools/shared/base-loader.js"
 );
 
-this.EXPORTED_SYMBOLS = ["MatrixSDK"];
+this.EXPORTED_SYMBOLS = ["MatrixSDK", "getHttpUriForMxc"];
 
 // Set-up loading so require works properly in CommonJS modules.
 //
 // These are organized in a somewhat funky way:
 // * First they're ordered by module.
 // * Then they're ordered alphabetically by the destination file (e.g. this
 //   keeps all references to utils.js next to each other).
 // * They're then ordered by source, with the bare name first, then prefixed by
@@ -131,10 +131,15 @@ let loader = Loader({
     // Necessary for interacting with the logging framework.
     scriptError,
     imIDebugMessage: Ci.imIDebugMessage,
   },
 });
 
 let requirer = Module("matrix-module", "");
 let require = Require(loader, requirer);
+
+// The main entry point into the Matrix client.
 let MatrixSDK = require("matrix.js");
 MatrixSDK.request(require("browser-request"));
+
+// Helper functions.
+let getHttpUriForMxc = require("../content-repo").getHttpUriForMxc;
--- a/chat/protocols/matrix/matrix.jsm
+++ b/chat/protocols/matrix/matrix.jsm
@@ -1,34 +1,51 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var EXPORTED_SYMBOLS = ["MatrixProtocol"];
 
-var { XPCOMUtils, nsSimpleEnumerator, l10nHelper } = ChromeUtils.import(
-  "resource:///modules/imXPCOMUtils.jsm"
-);
+var {
+  XPCOMUtils,
+  EmptyEnumerator,
+  nsSimpleEnumerator,
+  l10nHelper,
+} = ChromeUtils.import("resource:///modules/imXPCOMUtils.jsm");
+var { Services } = ChromeUtils.import("resource:///modules/imServices.jsm");
 var {
   GenericAccountPrototype,
   GenericConvChatPrototype,
   GenericConvChatBuddyPrototype,
   GenericProtocolPrototype,
+  TooltipInfo,
 } = ChromeUtils.import("resource:///modules/jsProtoHelper.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "_", () =>
   l10nHelper("chrome://chat/locale/matrix.properties")
 );
 
 ChromeUtils.defineModuleGetter(
   this,
   "MatrixSDK",
   "resource:///modules/matrix-sdk.jsm"
 );
 
+ChromeUtils.defineModuleGetter(
+  this,
+  "getHttpUriForMxc",
+  "resource:///modules/matrix-sdk.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "DownloadUtils",
+  "resource://gre/modules/DownloadUtils.jsm"
+);
+
 function MatrixParticipant(aRoomMember) {
   this._id = aRoomMember.userId;
   this._roomMember = aRoomMember;
 }
 MatrixParticipant.prototype = {
   __proto__: GenericConvChatBuddyPrototype,
   get alias() {
     return this._roomMember.name;
@@ -315,16 +332,103 @@ MatrixAccount.prototype = {
         // TODO Perhaps we should call createRoom if the room doesn't exist.
       });
     return conv;
   },
   createConversation(aName) {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
 
+  requestBuddyInfo(aUserId) {
+    let user = this._client.getUser(aUserId);
+    if (!user) {
+      Services.obs.notifyObservers(
+        EmptyEnumerator,
+        "user-info-received",
+        aUserId
+      );
+      return;
+    }
+
+    // Convert timespan in milli-seconds into a human-readable form.
+    let getNormalizedTime = function(aTime) {
+      let valuesAndUnits = DownloadUtils.convertTimeUnits(aTime / 1000);
+      // If the time is exact to the first set of units, trim off
+      // the subsequent zeroes.
+      if (!valuesAndUnits[2]) {
+        valuesAndUnits.splice(2, 2);
+      }
+      return _("tooltip.timespan", valuesAndUnits.join(" "));
+    };
+
+    let tooltipInfo = [];
+
+    if (user.displayName) {
+      tooltipInfo.push(
+        new TooltipInfo(_("tooltip.displayName"), user.displayName)
+      );
+    }
+
+    // Add the user's current status.
+    const kSetIdleStatusAfterSeconds = 3600;
+    const kPresentToStatusEnum = {
+      online: Ci.imIStatusInfo.STATUS_AVAILABLE,
+      offline: Ci.imIStatusInfo.STATUS_AWAY,
+      unavailable: Ci.imIStatusInfo.STATUS_OFFLINE,
+    };
+    let status = kPresentToStatusEnum[user.presence];
+    // If the user hasn't been seen in a long time, consider them idle.
+    if (
+      !user.currentlyActive &&
+      user.lastActiveAgo > kSetIdleStatusAfterSeconds
+    ) {
+      status = Ci.imIStatusInfo.STATUS_IDLE;
+
+      tooltipInfo.push(
+        new TooltipInfo(
+          _("tooltip.lastActive"),
+          getNormalizedTime(user.lastActiveAgo)
+        )
+      );
+    }
+    tooltipInfo.push(
+      new TooltipInfo(
+        status,
+        user.presenceStatusMsg,
+        Ci.prplITooltipInfo.status
+      )
+    );
+
+    if (user.avatarUrl) {
+      // This matches the configuration of the .userIcon class in chat.css.
+      const width = 48;
+      const height = 48;
+
+      // Convert the MXC URL to an HTTP URL.
+      let realUrl = getHttpUriForMxc(
+        this._client.getHomeserverUrl(),
+        user.avatarUrl,
+        width,
+        height,
+        "scale",
+        false
+      );
+      // TODO Cache the photo URI for this participant.
+      tooltipInfo.push(
+        new TooltipInfo(null, realUrl, Ci.prplITooltipInfo.icon)
+      );
+    }
+
+    Services.obs.notifyObservers(
+      new nsSimpleEnumerator(tooltipInfo),
+      "user-info-received",
+      aUserId
+    );
+  },
+
   get userId() {
     return this._client.credentials.userId;
   },
   _client: null,
   _roomList: new Map(),
 };
 
 function MatrixProtocol() {}