Przeglądaj źródła

Update profile and avatar handling

- New profile request and respose
- Store avatars in binary
- Don't send along me contact in receivers response
Danilo Bargen 7 lat temu
rodzic
commit
f438ef8dc1

+ 8 - 4
src/controller_model/avatar.ts

@@ -18,9 +18,11 @@
 import {WebClientService} from '../services/webclient';
 
 export class AvatarControllerModel {
+    private logTag: string = '[AvatarControllerModel]';
+
     private $log: ng.ILogService;
     private avatar: ArrayBuffer = null;
-    private loadAvatar: Promise<string>;
+    private loadAvatar: Promise<ArrayBuffer | null>;
     private onChangeAvatar: (image: ArrayBuffer) => void;
     private _avatarChanged: boolean = false;
 
@@ -30,14 +32,16 @@ export class AvatarControllerModel {
         this.$log = $log;
         this.loadAvatar = new Promise((resolve, reject) => {
             if (receiver === null) {
+                $log.debug(this.logTag, 'loadAvatar: No receiver defined, no avatar');
                 resolve(null);
                 return;
-            }
-            if (receiver.avatar.high === undefined) {
+            } else if (receiver.avatar.high === undefined) {
+                $log.debug(this.logTag, 'loadAvatar: Requesting high res avatar from app');
                 webClientService.requestAvatar(receiver, true)
-                    .then((image: string) => resolve(image))
+                    .then((data: ArrayBuffer) => resolve(data))
                     .catch(() => reject());
             } else {
+                $log.debug(this.logTag, 'loadAvatar: Returning cached version');
                 resolve(receiver.avatar.high);
             }
         });

+ 2 - 2
src/controller_model/distributionList.ts

@@ -82,8 +82,8 @@ export class DistributionListControllerModel implements threema.ControllerModel<
 
     public isValid(): boolean {
         return this.members.filter((identity: string) => {
-                return identity !== this.webClientService.getProfile().identity;
-            }).length > 0;
+            return identity !== this.webClientService.me.id;
+        }).length > 0;
     }
 
     public canChat(): boolean {

+ 2 - 2
src/controller_model/group.ts

@@ -94,7 +94,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
 
     public isValid(): boolean {
         return this.members.filter((identity: string) => {
-                return identity !== this.webClientService.getProfile().identity;
+                return identity !== this.webClientService.me.id;
             }).length > 0;
     }
 
@@ -153,7 +153,7 @@ export class GroupControllerModel implements threema.ControllerModel<threema.Gro
         const confirm = this.$mdDialog.confirm()
             .title(this.$translate.instant('messenger.GROUP_LEAVE'))
             .textContent(this.$translate.instant(
-                this.group.administrator === this.webClientService.getProfile().identity
+                this.group.administrator === this.webClientService.me.id
                     ? 'messenger.GROUP_REALLY_LEAVE_ADMIN'
                     : 'messenger.GROUP_REALLY_LEAVE'))
             .targetEvent(ev)

+ 5 - 6
src/controller_model/me.ts

@@ -60,18 +60,16 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
         this.webClientService = webClientService;
         this.mode = mode;
 
-        const profile = webClientService.getProfile();
+        this.nickname = webClientService.me.publicNickname;
         switch (mode) {
             case ControllerModelMode.EDIT:
                 this.subject = $translate.instant('messenger.EDIT_RECEIVER');
-                this.nickname = profile.publicNickname;
                 this.avatarController = new AvatarControllerModel(
                     this.$log, this.webClientService, this.me,
                 );
                 break;
             case ControllerModelMode.VIEW:
                 this.subject = $translate.instant('messenger.MY_THREEMA_ID');
-                this.nickname = profile.publicNickname;
                 break;
             default:
                 $log.error(this.logTag, 'Invalid controller model mode: ', this.getMode());
@@ -159,10 +157,11 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
                     this.nickname,
                     this.avatarController.getAvatar(),
                 ).then((val) => {
-                    const profile = this.webClientService.getProfile();
-                    profile.publicNickname = this.nickname;
+                    // Profile was successfully updated. Update local data.
+                    this.webClientService.me.publicNickname = this.nickname;
+                    this.webClientService.me.displayName = this.nickname;
                     if (this.avatarController.avatarChanged) {
-                        profile.avatar = this.avatarController.getAvatar();
+                        this.webClientService.me.avatar.high = this.avatarController.getAvatar();
                     }
                     return this.me;
                 });

+ 30 - 5
src/directives/avatar.ts

@@ -20,9 +20,11 @@ import {WebClientService} from '../services/webclient';
 export default [
     '$rootScope',
     '$timeout',
+    '$filter',
     'WebClientService',
     function($rootScope: ng.IRootScopeService,
              $timeout: ng.ITimeoutService,
+             $filter: ng.IFilterService,
              webClientService: WebClientService) {
         return {
             restrict: 'E',
@@ -53,15 +55,38 @@ export default [
                     return true;
                 };
 
-                this.getAvatar = () => {
+                /**
+                 * 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 'contact':
+                        case 'me':
+                            return highResolution ? 'img/ic_contact_picture_big.png' : 'img/ic_contact_picture_t.png';
+                        case 'distributionList':
+                            return highResolution ? 'img/ic_distribution_list_t.png' : 'img/ic_distribution_list_t.png';
+                    }
+                    return null;
+                };
+
+                this.avatarToUri = (data: ArrayBuffer) => {
+                    if (data === null || data === undefined) {
+                        return '';
+                    }
+                    return ($filter('bufferToUrl') as (data: ArrayBuffer, mime: string) => string)(data, 'image/png');
+                };
+
+                this.getAvatarUri = () => {
                     if (this.avatarExists()) {
-                        return this.receiver.avatar[this.resolution];
+                        return this.avatarToUri(this.receiver.avatar[this.resolution]);
                     } else if (this.highResolution
                         && this.receiver.avatar !== undefined
                         && this.receiver.avatar.low !== undefined) {
-                        return this.receiver.avatar.low;
+                        return this.avatarToUri(this.receiver.avatar.low);
                     }
-                    return webClientService.defaults.getAvatar(this.type, this.highResolution);
+                    return this.getDefaultAvatarUri(this.type, this.highResolution);
                 };
 
                 this.requestAvatar = (inView: boolean) => {
@@ -126,7 +151,7 @@ export default [
                     <img
                          ng-class="ctrl.avatarClass()"
                          ng-style="{ 'background-color': ctrl.backgroundColor }"
-                         ng-src="{{ ctrl.getAvatar() }}"
+                         ng-src="{{ ctrl.getAvatarUri() }}"
                          in-view="ctrl.requestAvatar($inview)"/>
                </div>
             `,

+ 1 - 1
src/directives/avatar_area.ts

@@ -150,7 +150,7 @@ export default [
                                     md-diameter="96"></md-progress-circular>
 
                         </div>
-                        <img ng-src="{{ctrl.avatar}}" ng-show="ctrl.avatar !== null" />
+                        <img ng-src="{{ ctrl.avatar | bufferToUrl:'image/png' }}" ng-show="ctrl.avatar !== null" />
                     </div>
                     <div class="avatar-area-navigation"  layout="row" layout-wrap layout-margin layout-align="center">
 

+ 1 - 1
src/directives/member_list_editor.ts

@@ -73,7 +73,7 @@ export default [
                 };
 
                 this.onRemoveMember = (contact: threema.ContactReceiver): boolean => {
-                    if (contact.id === webClientService.getProfile().identity) {
+                    if (contact.id === webClientService.me.id) {
                         return false;
                     }
 

+ 1 - 1
src/partials/messenger.navigation.html

@@ -2,7 +2,7 @@
 <div id="navigation-topheader">
     <div class="my-identity">
         <span ng-click="ctrl.showProfile()" ng-cloak translate-attr="{'title': 'messenger.MY_PUBLIC_NICKNAME'}">
-            {{ ctrl.getProfile().publicNickname || ctrl.getProfile().identity }}
+            {{ ctrl.getMe().displayName }}
         </span>
     </div>
 

+ 11 - 13
src/partials/messenger.receiver/me.edit.html

@@ -1,20 +1,18 @@
 <div layout="column" layout-wrap layout-margin layout-align="center center">
-
-       <h3 class="md-headline" translate>messenger.RECEIVER_AVATAR</h3>
-
+    <h3 class="md-headline" translate>messenger.RECEIVER_AVATAR</h3>
     <avatar-area
-            load-avatar="ctrl.controllerModel.avatarController.loadAvatar"
-            on-change="ctrl.controllerModel.avatarController.onChangeAvatar"
-            color="ctrl.controllerModel.me.color"
-            enable-clear="true">
+        load-avatar="ctrl.controllerModel.avatarController.loadAvatar"
+        on-change="ctrl.controllerModel.avatarController.onChangeAvatar"
+        color="ctrl.controllerModel.me.color"
+        enable-clear="true">
     </avatar-area>
 </div>
 
 <md-card>
-       <md-card-content>
-               <md-input-container class="md-block">
-                       <label translate>messenger.MY_PUBLIC_NICKNAME</label>
-                       <input ng-disabled="ctrl.isSaving()" ng-model="ctrl.controllerModel.nickname" ng-keypress="ctrl.keypress($event)">
-               </md-input-container>
-       </md-card-content>
+    <md-card-content>
+        <md-input-container class="md-block">
+            <label translate>messenger.MY_PUBLIC_NICKNAME</label>
+            <input ng-disabled="ctrl.isSaving()" ng-model="ctrl.controllerModel.nickname" ng-keypress="ctrl.keypress($event)">
+        </md-input-container>
+    </md-card-content>
 </md-card>

+ 1 - 1
src/partials/messenger.receiver/me.html

@@ -2,7 +2,7 @@
 	<!-- information list card -->
 	<md-card class="two-row">
 		<div class="avatar">
-			<eee-avatar eee-type="'contact'"
+			<eee-avatar eee-type="'me'"
 						eee-receiver="ctrl.receiver"
 						eee-resolution="'high'"></eee-avatar>
 		</div>

+ 4 - 4
src/partials/messenger.ts

@@ -983,8 +983,8 @@ class NavigationController {
     /**
      * Return the user profile.
      */
-    public getProfile(): threema.Profile {
-        return this.webClientService.getProfile();
+    public getMe(): threema.MeReceiver {
+        return this.webClientService.me;
     }
 
 }
@@ -1200,7 +1200,7 @@ class ReceiverDetailController {
      * Show the QR code of the public key.
      */
     public showQr(): void {
-        const profile = this.webClientService.getProfile();
+        const profile = this.webClientService.me;
         const $mdDialog = this.$mdDialog;
         $mdDialog.show({
             controllerAs: 'ctrl',
@@ -1213,7 +1213,7 @@ class ReceiverDetailController {
                     errorCorrectionLevel: 'L',
                     size: '400px',
                     data: '3mid:'
-                    + profile.identity
+                    + profile.id
                     + ','
                     + u8aToHex(new Uint8Array(profile.publicKey)),
                 };

+ 32 - 65
src/services/webclient.ts

@@ -38,36 +38,6 @@ import {VersionService} from './version';
 // Aliases
 import InitializationStep = threema.InitializationStep;
 
-class WebClientDefault {
-    private avatar: threema.AvatarRegistry = {
-        group: {
-            low: 'img/ic_group_t.png',
-            high: 'img/ic_group_picture_big.png',
-        },
-        contact: {
-            low: 'img/ic_contact_picture_t.png',
-            high: 'img/ic_contact_picture_big.png',
-        },
-        distributionList: {
-            low: 'img/ic_distribution_list_t.png',
-            high: 'img/ic_distribution_list_t.png',
-        },
-    };
-
-    /**
-     * Return path to avatar.
-     *
-     * If the avatar type is invalid, return null.
-     */
-    public getAvatar(type: string, highResolution: boolean): string {
-        const field: string = highResolution ? 'high' : 'low';
-        if (typeof this.avatar[type] === 'undefined') {
-            return null;
-        }
-        return this.avatar[type][field];
-    }
-}
-
 /**
  * This service handles everything related to the communication with the peer.
  */
@@ -184,8 +154,6 @@ export class WebClientService {
     public conversations: threema.Container.Conversations;
     public receivers: threema.Container.Receivers;
     public alerts: threema.Alert[] = [];
-    public defaults: WebClientDefault;
-    private profile: threema.Profile;
     private pushToken: string = null;
 
     // Other
@@ -277,9 +245,6 @@ export class WebClientService {
         this.container = container;
         this.trustedKeyStore = trustedKeyStore;
 
-        // Get default class
-        this.defaults = new WebClientDefault();
-
         // Initialize drafts
         this.drafts = this.container.createDrafts();
 
@@ -837,16 +802,14 @@ export class WebClientService {
     /**
      * Send an avatar request for the specified receiver.
      */
-    public requestAvatar(receiver: threema.Receiver, highResolution: boolean): Promise<any> {
+    public requestAvatar(receiver: threema.Receiver, highResolution: boolean): Promise<ArrayBuffer> {
         // Check if the receiver has an avatar or the avatar already exists
         const resolution = highResolution ? 'high' : 'low';
         const receiverInfo = this.receivers.getData(receiver);
         if (receiverInfo && receiverInfo.avatar && receiverInfo.avatar[resolution]) {
             // Avatar already exists
             // TODO: Do we get avatar changes via update?
-            return new Promise<any>((e) => {
-                e(receiverInfo.avatar[resolution]);
-            });
+            return Promise.resolve(receiverInfo.avatar[resolution]);
         }
 
         // Create arguments and send request
@@ -1363,13 +1326,6 @@ export class WebClientService {
         return this.typing.isTyping(contact);
     }
 
-    /**
-     * Return own profile.
-     */
-    public getProfile(): threema.Profile {
-        return this.profile;
-    }
-
     /**
      * Return the curring quoted message model
      */
@@ -1690,21 +1646,19 @@ export class WebClientService {
         if (data === undefined) {
             this.$log.warn('Invalid conversation response, data missing');
         } else {
-            // if a avatar was set on a conversation
-            // convert and copy to the receiver
+            // If a avatar was set on a conversation, convert and copy to the receiver
             for (const conversation of data) {
                 if (conversation.avatar !== undefined && conversation.avatar !== null) {
-                    const receiver = this.receivers.getData({
+                    const receiver: threema.Receiver = this.receivers.getData({
                         id: conversation.id,
                         type: conversation.type,
-                    } as threema.Receiver);
-                    if (receiver !== undefined
-                            && receiver.avatar === undefined) {
+                    });
+                    if (receiver !== undefined && receiver.avatar === undefined) {
                         receiver.avatar = {
-                            low: this.$filter('bufferToUrl')(conversation.avatar, 'image/png'),
+                            low: conversation.avatar,
                         };
                     }
-                    // reset avatar from object
+                    // Remove avatar from conversation
                     delete conversation.avatar;
                 }
 
@@ -1841,8 +1795,8 @@ export class WebClientService {
             return this.promiseRequestError('invalidResponse');
         }
 
-        const data = message.data;
-        if (data === undefined) {
+        const avatar = message.data;
+        if (avatar === undefined) {
             // It's ok, a receiver without a avatar
             return { success: true, data: null };
         }
@@ -1863,7 +1817,6 @@ export class WebClientService {
             receiverData.avatar = {};
         }
 
-        const avatar = this.$filter('bufferToUrl')(data, 'image/png');
         receiverData.avatar[field] = avatar;
 
         return { success: true, data: avatar };
@@ -2059,9 +2012,6 @@ export class WebClientService {
 
         // Refresh lists of receivers
         switch (type) {
-            case 'me':
-                this.receivers.setMe(data);
-                break;
             case 'contact':
                 this.receivers.setContacts(data);
                 break;
@@ -2196,12 +2146,27 @@ export class WebClientService {
             return;
         }
 
-        this.profile = {
-            identity: data.identity,
+        // Create 'me' receiver with profile + dummy data
+        // TODO: Send both high-res and low-res avatars
+        this.receivers.setMe({
+            type: 'me',
+            id: data.identity,
             publicNickname: data.publicNickname,
+            displayName: data.publicNickname || data.identity,
             publicKey: data.publicKey,
-            avatar: data.avatar,
-        };
+            avatar: {
+                high: data.avatar,
+            },
+            featureLevel: 3,
+            verificationLevel: 3,
+            state: 'ACTIVE',
+            access: {
+                canChangeAvatar: true,
+                canChangeFirstName: true,
+                canChangeLastName: true,
+            },
+            color: '#000000',
+        });
 
         this.registerInitializationStep(InitializationStep.Profile);
     }
@@ -2336,7 +2301,9 @@ export class WebClientService {
                     body = partnerName + ': ' + body;
                 }
                 const tag = conversation.type + '-' + conversation.id;
-                const avatar = (sender.avatar && sender.avatar.low) ? sender.avatar.low : null;
+                const avatar = (sender.avatar && sender.avatar.low)
+                    ? this.$filter('bufferToUrl')(sender.avatar.low, 'image/png')
+                    : null;
                 this.notificationService.showNotification(tag, title, body, avatar, () => {
                     this.$state.go('messenger.home.conversation', {
                         type: conversation.type,

+ 4 - 12
src/threema.d.ts

@@ -20,10 +20,10 @@ declare const angular: ng.IAngularStatic;
 declare namespace threema {
 
     interface Avatar {
-        // Low resolution avatar path
-        low?: string;
-        // High resolution avatar path
-        high?: string;
+        // Low resolution avatar URI
+        low?: ArrayBuffer;
+        // High resolution avatar URI
+        high?: ArrayBuffer;
     }
 
     interface AvatarRegistry {
@@ -392,13 +392,6 @@ declare namespace threema {
         text: string;
     }
 
-    interface Profile {
-        identity: string;
-        publicNickname: string;
-        publicKey: ArrayBuffer;
-        avatar: ArrayBuffer;
-    }
-
     interface TrustedKeyStoreData {
         ownPublicKey: Uint8Array;
         ownSecretKey: Uint8Array;
@@ -611,7 +604,6 @@ declare namespace threema {
 
     namespace Container {
         interface ReceiverData {
-            me: MeReceiver;
             contacts: ContactReceiver[];
             groups: GroupReceiver[];
             distributionLists: DistributionListReceiver[];

+ 0 - 1
src/threema/container.ts

@@ -106,7 +106,6 @@ angular.module('3ema.container', [])
          * Set receiver data.
          */
         public set(data: threema.Container.ReceiverData) {
-            this.setMe(data['me' as threema.ReceiverType]);
             this.setContacts(data['contact' as threema.ReceiverType]);
             this.setGroups(data['group' as threema.ReceiverType]);
             this.setDistributionLists(data['distributionList' as threema.ReceiverType]);