avatar.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /**
  2. * This file is part of Threema Web.
  3. *
  4. * Threema Web is free software: you can redistribute it and/or modify it
  5. * under the terms of the GNU Affero General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or (at
  7. * your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. import {bufferToUrl, logAdapter} from '../helpers';
  18. import {isEchoContact, isGatewayContact} from '../receiver_helpers';
  19. import {WebClientService} from '../services/webclient';
  20. import {isContactReceiver} from '../typeguards';
  21. export default [
  22. '$rootScope',
  23. '$timeout',
  24. '$log',
  25. 'WebClientService',
  26. function($rootScope: ng.IRootScopeService,
  27. $timeout: ng.ITimeoutService,
  28. $log: ng.ILogService,
  29. webClientService: WebClientService) {
  30. return {
  31. restrict: 'E',
  32. scope: {},
  33. bindToController: {
  34. receiver: '=eeeReceiver',
  35. resolution: '=eeeResolution',
  36. },
  37. controllerAs: 'ctrl',
  38. controller: [function() {
  39. this.logTag = '[Directives.Avatar]';
  40. let loadingPromise: ng.IPromise<any> = null;
  41. /**
  42. * Convert avatar bytes to an URI.
  43. */
  44. const avatarUri = {
  45. high: null,
  46. low: null,
  47. };
  48. this.avatarToUri = (data: ArrayBuffer, res: 'high' | 'low') => {
  49. if (data === null || data === undefined) {
  50. return '';
  51. }
  52. if (avatarUri[res] === null) {
  53. // Cache avatar image URI
  54. avatarUri[res] = bufferToUrl(
  55. data,
  56. webClientService.appCapabilities.imageFormat.avatar,
  57. logAdapter($log.warn, this.logTag),
  58. );
  59. }
  60. return avatarUri[res];
  61. };
  62. this.$onInit = function() {
  63. this.highResolution = this.resolution === 'high';
  64. this.isLoading = this.highResolution;
  65. this.backgroundColor = this.receiver.color;
  66. this.avatarClass = () => {
  67. return 'avatar-' + this.resolution + (this.isLoading ? ' is-loading' : '');
  68. };
  69. this.avatarExists = () => {
  70. if (this.receiver.avatar === undefined
  71. || this.receiver.avatar[this.resolution] === undefined
  72. || this.receiver.avatar[this.resolution] === null) {
  73. return false;
  74. }
  75. this.isLoading = false;
  76. // Reset background color
  77. this.backgroundColor = null;
  78. return true;
  79. };
  80. /**
  81. * Return path to the default avatar.
  82. */
  83. this.getDefaultAvatarUri = (type: threema.ReceiverType, highResolution: boolean) => {
  84. switch (type) {
  85. case 'group':
  86. return highResolution
  87. ? 'img/ic_group_picture_big.png'
  88. : 'img/ic_group_t.png';
  89. case 'distributionList':
  90. return highResolution
  91. ? 'img/ic_distribution_list_t.png'
  92. : 'img/ic_distribution_list_t.png';
  93. case 'contact':
  94. case 'me':
  95. default:
  96. return highResolution
  97. ? 'img/ic_contact_picture_big.png'
  98. : 'img/ic_contact_picture_t.png';
  99. }
  100. };
  101. /**
  102. * Return an avatar URI.
  103. *
  104. * This will fall back to a low resolution version or to the
  105. * default avatar if no avatar for the desired resolution could
  106. * be found.
  107. */
  108. this.getAvatarUri = () => {
  109. /// If an avatar for the chosen resolution exists, convert it to an URI and return
  110. if (this.avatarExists()) {
  111. return this.avatarToUri(this.receiver.avatar[this.resolution], this.resolution);
  112. }
  113. // Otherwise, if we requested a high res avatar but
  114. // there is only a low-res version, show that.
  115. if (this.highResolution
  116. && this.receiver.avatar !== undefined
  117. && this.receiver.avatar.low !== undefined
  118. && this.receiver.avatar.low !== null) {
  119. return this.avatarToUri(this.receiver.avatar.low, 'low');
  120. }
  121. // As a fallback, get the default avatar.
  122. return this.getDefaultAvatarUri(this.receiver.type, this.highResolution);
  123. };
  124. this.requestAvatar = (inView: boolean) => {
  125. if (this.avatarExists()) {
  126. // do not request
  127. return;
  128. }
  129. if (inView) {
  130. if (loadingPromise === null) {
  131. // Do not wait on high resolution avatar
  132. const loadingTimeout = this.highResolution ? 0 : 500;
  133. loadingPromise = $timeout(() => {
  134. // show loading only on high res images!
  135. webClientService.requestAvatar({
  136. type: this.receiver.type,
  137. id: this.receiver.id,
  138. } as threema.Receiver, this.highResolution).then((avatar) => {
  139. $rootScope.$apply(() => {
  140. this.isLoading = false;
  141. });
  142. }).catch(() => {
  143. $rootScope.$apply(() => {
  144. this.isLoading = false;
  145. });
  146. });
  147. }, loadingTimeout);
  148. }
  149. } else if (loadingPromise !== null) {
  150. // Cancel pending avatar loading
  151. $timeout.cancel(loadingPromise);
  152. loadingPromise = null;
  153. }
  154. };
  155. const isWork = webClientService.clientInfo.isWork;
  156. this.showWorkIndicator = () => {
  157. if (!isContactReceiver(this.receiver)) { return false; }
  158. const contact: threema.ContactReceiver = this.receiver;
  159. return isWork === false
  160. && !this.highResolution
  161. && contact.identityType === threema.IdentityType.Work;
  162. };
  163. this.showHomeIndicator = () => {
  164. if (!isContactReceiver(this.receiver)) { return false; }
  165. const contact: threema.ContactReceiver = this.receiver;
  166. return isWork === true
  167. && !isGatewayContact(contact)
  168. && !isEchoContact(contact)
  169. && contact.identityType === threema.IdentityType.Regular
  170. && !this.highResolution;
  171. };
  172. this.showBlocked = () => {
  173. if (!isContactReceiver(this.receiver)) { return false; }
  174. const contact: threema.ContactReceiver = this.receiver;
  175. return !this.highResolution && contact.isBlocked;
  176. };
  177. };
  178. }],
  179. template: `
  180. <div class="avatar" ng-class="ctrl.avatarClass()">
  181. <div class="avatar-loading" ng-if="ctrl.isLoading">
  182. <span></span>
  183. </div>
  184. <div class="work-indicator" ng-if="ctrl.showWorkIndicator()"
  185. translate-attr="{'aria-label': 'messenger.THREEMA_WORK_CONTACT',
  186. 'title': 'messenger.THREEMA_WORK_CONTACT'}">
  187. <img src="img/ic_work_round.svg" alt="Threema Work user">
  188. </div>
  189. <div class="home-indicator" ng-if="ctrl.showHomeIndicator()"
  190. translate-attr="{'aria-label': 'messenger.THREEMA_HOME_CONTACT',
  191. 'title': 'messenger.THREEMA_HOME_CONTACT'}">
  192. <img src="img/ic_home_round.svg" alt="Private Threema contact">
  193. </div>
  194. <div class="blocked-indicator" ng-if="ctrl.showBlocked()"
  195. translate-attr="{'aria-label': 'messenger.THREEMA_BLOCKED_RECEIVER',
  196. 'title': 'messenger.THREEMA_BLOCKED_RECEIVER'}">
  197. <img src="img/ic_blocked_24px.svg" alt="blocked icon">
  198. </div>
  199. <img
  200. ng-class="ctrl.avatarClass()"
  201. ng-style="{ 'background-color': ctrl.backgroundColor }"
  202. ng-src="{{ ctrl.getAvatarUri() }}"
  203. in-view="ctrl.requestAvatar($inview)">
  204. </div>
  205. `,
  206. };
  207. },
  208. ];