/** * This file is part of Threema Web. * * Threema Web is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero * General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Threema Web. If not, see . */ import {bufferToUrl, hasValue} from '../helpers'; import {isEchoContact, isGatewayContact} from '../receiver_helpers'; import {LogService} from '../services/log'; import {TimeoutService} from '../services/timeout'; import {WebClientService} from '../services/webclient'; import {isContactReceiver} from '../typeguards'; export default [ '$rootScope', 'LogService', 'TimeoutService', 'WebClientService', function($rootScope: ng.IRootScopeService, logService: LogService, timeoutService: TimeoutService, webClientService: WebClientService) { const log = logService.getLogger('Avatar-C'); return { restrict: 'E', scope: {}, bindToController: { receiver: '=eeeReceiver', resolution: '=eeeResolution', }, link: function(scope, elem, attrs) { scope.$watch( () => scope.ctrl.receiver, (newReceiver: threema.Receiver, oldReceiver: threema.Receiver) => { // Register for receiver changes. When something relevant changes, call the update function. // This prevents processing the avatar more often than necessary. if (!hasValue(newReceiver)) { // New receiver has no value return; } if (!hasValue(oldReceiver)) { // New receiver has value, old receiver doesn't scope.ctrl.update(false); return; } // Check for changes in relevant attributes if (newReceiver.id !== oldReceiver.id || newReceiver.type !== oldReceiver.type || newReceiver.color !== oldReceiver.color || newReceiver.displayName !== oldReceiver.displayName) { scope.ctrl.update(false); return; } // Check for changes in the avatar itself if (hasValue(newReceiver.avatar)) { if (hasValue(oldReceiver.avatar)) { if (newReceiver.avatar.high !== oldReceiver.avatar.high || newReceiver.avatar.low !== oldReceiver.avatar.low) { scope.ctrl.update(false); return; } } else { scope.ctrl.update(false); return; } } }, ); }, controllerAs: 'ctrl', controller: [function() { let loadingPromise: ng.IPromise = null; /** * Convert avatar bytes to an URI. */ const avatarUri = { high: null, low: null, }; this.avatarToUri = (data: ArrayBuffer, res: 'high' | 'low') => { if (!hasValue(data)) { return ''; } if (avatarUri[res] === null) { // Cache avatar image URI avatarUri[res] = bufferToUrl( data, webClientService.appCapabilities.imageFormat.avatar, log); } return avatarUri[res]; }; /** * Update data when the receiver changes. */ this.update = (initial: boolean) => { // Reset avatar cache avatarUri.high = null; avatarUri.low = null; // Get receiver const receiver: threema.Receiver = this.receiver; // Set initial values this.highResolution = this.resolution === 'high'; this.isLoading = this.highResolution; this.receiverName = receiver.displayName; }; this.$onInit = function() { this.update(true); /** * Return the CSS class for the avatar. */ this.avatarClass = () => 'avatar-' + this.resolution + (this.isLoading ? ' is-loading' : ''); /** * Return whether or not an avatar is available. */ this.avatarExists = () => { if (this.receiver.avatar === undefined || this.receiver.avatar[this.resolution] === undefined || this.receiver.avatar[this.resolution] === null) { return false; } this.isLoading = false; return true; }; /** * Return path to the default avatar. */ this.getDefaultAvatarUri = (type: threema.ReceiverType, highResolution: boolean) => { switch (type) { case 'group': return highResolution ? 'img/ic_group_picture_big.png' : 'img/ic_group_t.png'; case 'distributionList': return highResolution ? 'img/ic_distribution_list_t.png' : 'img/ic_distribution_list_t.png'; case 'contact': case 'me': default: return highResolution ? 'img/ic_contact_picture_big.png' : 'img/ic_contact_picture_t.png'; } }; /** * Return an avatar URI. * * This will fall back to a low resolution version or to the * default avatar if no avatar for the desired resolution could * be found. */ this.getAvatarUri = () => { /// If an avatar for the chosen resolution exists, convert it to an URI and return if (this.avatarExists()) { return this.avatarToUri(this.receiver.avatar[this.resolution], this.resolution); } // Otherwise, if we requested a high res avatar but // there is only a low-res version, show that. if (this.highResolution && this.receiver.avatar !== undefined && this.receiver.avatar.low !== undefined && this.receiver.avatar.low !== null) { return this.avatarToUri(this.receiver.avatar.low, 'low'); } // As a fallback, get the default avatar. return this.getDefaultAvatarUri(this.receiver.type, this.highResolution); }; this.requestAvatar = (inView: boolean) => { if (this.avatarExists()) { // do not request return; } if (inView) { if (loadingPromise === null) { // Do not wait on high resolution avatar const loadingTimeout = this.highResolution ? 0 : 500; loadingPromise = timeoutService.register(() => { // show loading only on high res images! webClientService.requestAvatar({ type: this.receiver.type, id: this.receiver.id, } as threema.Receiver, this.highResolution) .then((avatar) => { $rootScope.$apply(() => { this.isLoading = false; }); loadingPromise = null; }) .catch((error) => { // TODO: Handle this properly / show an error message log.error(`Avatar request has been rejected: ${error}`); $rootScope.$apply(() => { this.isLoading = false; }); loadingPromise = null; }); }, loadingTimeout, false, 'avatar'); } } else if (loadingPromise !== null) { // Cancel pending avatar loading timeoutService.cancel(loadingPromise); loadingPromise = null; } }; const isWorkApp = webClientService.clientInfo.isWork; this.showWorkIndicator = () => { if (!isContactReceiver(this.receiver)) { return false; } const contact: threema.ContactReceiver = this.receiver; return isWorkApp === false && !this.highResolution && contact.identityType === threema.IdentityType.Work; }; this.showHomeIndicator = () => { if (!isContactReceiver(this.receiver)) { return false; } const contact: threema.ContactReceiver = this.receiver; return isWorkApp === true && !isGatewayContact(contact) && !isEchoContact(contact) && contact.identityType === threema.IdentityType.Regular && !this.highResolution; }; this.showBlocked = () => { if (!isContactReceiver(this.receiver)) { return false; } const contact: threema.ContactReceiver = this.receiver; return !this.highResolution && contact.isBlocked; }; }; }], template: `
Threema Work user
Private Threema contact
blocked icon
`, }; }, ];