Browse Source

Allow adding all contacts to distribution lists (#837)

Also split up the `ControllerModel` interface, extract all member-related logic into a `ControllerModelWithMembers` interface.

To ensure that a generic controller model implements this interface, there's a new type guard called `controllerModelHasMembers`.
Danilo Bargen 6 years ago
parent
commit
5873d55490

+ 9 - 12
src/controller_model/contact.ts

@@ -28,17 +28,22 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
 
+    // Custom services
+    private readonly log: Logger;
+    private readonly webClientService: WebClientService;
+
+    // Fields required by interface
+    public readonly receiverType = 'contact';
+    public subject: string;
+    public isLoading = false;
+
     private onRemovedCallback: threema.OnRemovedCallback;
     public firstName?: string;
     public lastName?: string;
     public identity: string;
-    public subject: string;
     public access: threema.ContactReceiverAccess;
-    public isLoading = false;
 
-    private readonly log: Logger;
     private contact: threema.ContactReceiver | null;
-    private webClientService: WebClientService;
     private firstNameLabel: string;
     private avatarController: AvatarControllerModel;
     private mode = ControllerModelMode.NEW;
@@ -180,12 +185,4 @@ export class ContactControllerModel implements threema.ControllerModel<threema.C
                 return Promise.reject('Cannot save contact, invalid mode');
         }
     }
-
-    public onChangeMembers(identities: string[]): void {
-        // Do nothing
-    }
-
-    public getMembers(): string[] {
-        return [this.identity];
-    }
 }

+ 14 - 4
src/controller_model/distributionList.ts

@@ -22,18 +22,28 @@ import {WebClientService} from '../services/webclient';
 // Type aliases
 import ControllerModelMode = threema.ControllerModelMode;
 
-export class DistributionListControllerModel implements threema.ControllerModel<threema.DistributionListReceiver> {
+export class DistributionListControllerModel
+        implements threema.ControllerModel<threema.DistributionListReceiver>,
+                   threema.ControllerModelWithMembers {
+    // Angular services
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
+
+    // Custom services
     private readonly log: Logger;
-    public members: string[];
-    public name: string;
+    private readonly webClientService: WebClientService;
+
+    // Fields required by interface
+    public readonly receiverType = 'distributionList';
     public subject: string;
     public isLoading = false;
+    public readonly requiredMemberFeatureMask = threema.ContactReceiverFeature.NONE;
+
+    public members: string[];
+    public name: string;
 
     private addContactPlaceholder: string;
     private distributionList: threema.DistributionListReceiver | null;
-    private webClientService: WebClientService;
     private mode: ControllerModelMode;
     private onRemovedCallback: threema.OnRemovedCallback;
 

+ 15 - 6
src/controller_model/group.ts

@@ -23,20 +23,29 @@ import {AvatarControllerModel} from './avatar';
 // Type aliases
 import ControllerModelMode = threema.ControllerModelMode;
 
-export class GroupControllerModel implements threema.ControllerModel<threema.GroupReceiver> {
-    private log: Logger;
+export class GroupControllerModel
+        implements threema.ControllerModel<threema.GroupReceiver>,
+                   threema.ControllerModelWithMembers {
+    // Angular services
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
+
+    // Custom services
+    private readonly log: Logger;
+    private readonly webClientService: WebClientService;
+
+    // Fields required by interface
+    public readonly receiverType = 'group';
+    public subject: string;
+    public isLoading = false; // TODO: Show loading indicator
+    public readonly requiredMemberFeatureMask = threema.ContactReceiverFeature.GROUP_CHAT;
+
     public members: string[];
     public name: string;
     public access: threema.GroupReceiverAccess;
-    public subject: string;
-
-    public isLoading = false; // TODO: Show loading indicator
 
     private addContactPlaceholder: string;
     private group: threema.GroupReceiver | null;
-    private webClientService: WebClientService;
     private avatarController: AvatarControllerModel;
     private mode: ControllerModelMode;
     private onRemovedCallback: threema.OnRemovedCallback;

+ 6 - 21
src/controller_model/me.ts

@@ -28,22 +28,21 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
     private $translate: ng.translate.ITranslateService;
     private $mdDialog: ng.material.IDialogService;
 
-    // Logging
+    // Custom services services
     private readonly log: Logger;
-
-    // Own services
     private webClientService: WebClientService;
 
+    // Fields required by interface
+    public readonly receiverType = 'me';
+    public subject: string;
+    public isLoading = false;
+
     // Own receiver instance
     private me: threema.MeReceiver;
 
     // Avatar controller
     private avatarController: AvatarControllerModel;
 
-    // Controller model fields
-    public subject: string;
-    public isLoading = false;
-
     // Profile data
     public nickname: string;
 
@@ -86,20 +85,6 @@ export class MeControllerModel implements threema.ControllerModel<threema.MeRece
         // Not applicable
     }
 
-    /**
-     * Callback called when the members change.
-     */
-    public onChangeMembers(identities: string[]): void {
-        // Not possible
-    }
-
-    /**
-     * Return the members of this receiver.
-     */
-    public getMembers(): string[] {
-        return [this.me.id];
-    }
-
     /**
      * The editing mode, e.g. view or edit this receiver.
      */

+ 15 - 12
src/directives/member_list_editor.ts

@@ -29,21 +29,13 @@ export default [
             restrict: 'EA',
             scope: {},
             bindToController: {
-                members: '=eeeMembers',
-                onChange: '=eeeOnChange',
-                placeholder: '=eeePlaceholder',
+                requiredMemberFeatureMask: '<',
+                members: '=activeMembers',
+                onChange: '=onChange',
+                placeholder: '=placeholder',
             },
             controllerAs: 'ctrl',
             controller: [function() {
-                // Cache all contacts with group chat support
-                this.allContacts = Array
-                    .from(webClientService.contacts.values())
-                    .filter((contactReceiver: threema.ContactReceiver) => hasFeature(
-                        contactReceiver,
-                        threema.ContactReceiverFeature.GROUP_CHAT,
-                        log,
-                    )) as threema.ContactReceiver[];
-
                 this.selectedItemChange = (contactReceiver: threema.ContactReceiver) => {
                     if (contactReceiver !== undefined) {
                         this.members.push(contactReceiver.id);
@@ -100,6 +92,17 @@ export default [
                     );
                     return true;
                 };
+
+                this.$onInit = function() {
+                    // Cache all contacts
+                    this.allContacts = Array.from(webClientService.contacts.values())
+                        .filter((contactReceiver: threema.ContactReceiver) => hasFeature(
+                            contactReceiver,
+                            this.requiredMemberFeatureMask,
+                            log,
+                        )) as threema.ContactReceiver[];
+                };
+
             }],
             template: `
                 <ul class="member-list">

+ 4 - 1
src/helpers.ts

@@ -290,11 +290,14 @@ export function hasFeature(contactReceiver: threema.ContactReceiver,
             log.warn(`Contact receiver with id ${contactReceiver.id} has featureMask 0`);
             return false;
         }
+        if (feature === threema.ContactReceiverFeature.NONE) {
+            return true;
+        }
         // tslint:disable:no-bitwise
         return (contactReceiver.featureMask & feature) !== 0;
         // tslint:enable:no-bitwise
     }
-    log.warn('Cannot check featureMask of a undefined contact receiver');
+    log.warn('Cannot check featureMask of an undefined contact receiver');
     return false;
 }
 

+ 4 - 3
src/partials/messenger.receiver/distributionList.create.html

@@ -25,9 +25,10 @@
     <md-card-content>
         <ul class="member-list">
             <member-list-editor
-                    eee-members="ctrl.controllerModel.members",
-                    eee-placeholder="ctrl.controllerModel.addContactPlaceholder"
-                    eee-on-change="ctrl.controllerModel.onChangeMembers" />
+                required-member-feature-mask="ctrl.controllerModel.requiredMemberFeatureMask"
+                active-members="ctrl.controllerModel.members"
+                placeholder="ctrl.controllerModel.addContactPlaceholder"
+                on-change="ctrl.controllerModel.onChangeMembers" />
         </ul>
     </md-card-content>
 </md-card>

+ 5 - 4
src/partials/messenger.receiver/distributionList.edit.html

@@ -24,9 +24,10 @@
     <md-card-content>
         <ul class="member-list">
             <member-list-editor
-                eee-members="ctrl.controllerModel.members",
-                eee-placeholder="ctrl.controllerModel.addContactPlaceholder"
-                eee-on-change="ctrl.controllerModel.onChangeMembers" />
+                required-member-feature-mask="ctrl.controllerModel.requiredMemberFeatureMask"
+                active-members="ctrl.controllerModel.members"
+                placeholder="ctrl.controllerModel.addContactPlaceholder"
+                on-change="ctrl.controllerModel.onChangeMembers" />
         </ul>
     </md-card-content>
-</md-card>
+</md-card>

+ 4 - 3
src/partials/messenger.receiver/group.create.html

@@ -32,9 +32,10 @@
     <md-card-content>
         <ul class="member-list">
             <member-list-editor
-                    eee-members="ctrl.controllerModel.members",
-                    eee-placeholder="ctrl.controllerModel.addContactPlaceholder"
-                    eee-on-change="ctrl.controllerModel.onChangeMembers" />
+                required-member-feature-mask="ctrl.controllerModel.requiredMemberFeatureMask"
+                active-members="ctrl.controllerModel.members",
+                placeholder="ctrl.controllerModel.addContactPlaceholder"
+                on-change="ctrl.controllerModel.onChangeMembers" />
         </ul>
     </md-card-content>
 </md-card>

+ 5 - 4
src/partials/messenger.receiver/group.edit.html

@@ -34,9 +34,10 @@
     <md-card-content>
         <ul class="member-list">
             <member-list-editor
-                eee-members="ctrl.controllerModel.members",
-                eee-placeholder="ctrl.controllerModel.addContactPlaceholder"
-                eee-on-change="ctrl.controllerModel.onChangeMembers" />
+                required-member-feature-mask="ctrl.controllerModel.requiredMemberFeatureMask"
+                active-members="ctrl.controllerModel.members"
+                placeholder="ctrl.controllerModel.addContactPlaceholder"
+                on-change="ctrl.controllerModel.onChangeMembers" />
         </ul>
     </md-card-content>
-</md-card>
+</md-card>

+ 2 - 2
src/partials/messenger.ts

@@ -41,7 +41,7 @@ import {StateService} from '../services/state';
 import {TimeoutService} from '../services/timeout';
 import {VersionService} from '../services/version';
 import {WebClientService} from '../services/webclient';
-import {isContactReceiver} from '../typeguards';
+import {controllerModelHasMembers, isContactReceiver} from '../typeguards';
 
 // Type aliases
 import ControllerModelMode = threema.ControllerModelMode;
@@ -500,7 +500,7 @@ class ConversationController {
                 this.webClientService.messages.updateFirstUnreadMessage(this.receiver);
 
                 // Enable mentions only in group chats
-                if (this.type === 'group') {
+                if (this.type === 'group' && controllerModelHasMembers(this.controllerModel)) {
                     this.allMentions.push({
                         identity: null,
                         query: this.$translate.instant('messenger.ALL').toLowerCase(),

+ 13 - 0
src/threema.d.ts

@@ -552,6 +552,7 @@ declare namespace threema {
     }
 
     const enum ContactReceiverFeature {
+        NONE = 0x00,
         AUDIO = 0x01,
         GROUP_CHAT = 0x02,
         BALLOT = 0x04,
@@ -560,6 +561,11 @@ declare namespace threema {
     }
 
     interface ControllerModel<T extends BaseReceiver> {
+        /**
+         * The receiver type that is handled by this controller model.
+         */
+        receiverType: threema.ReceiverType;
+
         /**
          * The title shown in the header.
          */
@@ -614,6 +620,13 @@ declare namespace threema {
          * Set the on removed callback.
          */
         setOnRemoved(callback: OnRemovedCallback): void;
+    }
+
+    interface ControllerModelWithMembers {
+        /**
+         * A required feature flag that all members must have.
+         */
+        requiredMemberFeatureMask: threema.ContactReceiverFeature;
 
         /**
          * Callback called when the members change.

+ 11 - 0
src/typeguards.ts

@@ -101,3 +101,14 @@ export function isEmojiInfo(val: string | threema.EmojiInfo): val is threema.Emo
         && val.imgPath !== undefined
         && val.codepoint !== undefined;
 }
+
+/**
+ * Controller model has members.
+ *
+ * This returns true if the `ControllerModel` also implements the `ControllerModelWithMembers` interface.
+ */
+export function controllerModelHasMembers<T extends threema.BaseReceiver>(
+    cm: threema.ControllerModel<T>,
+): cm is threema.ControllerModel<T> & threema.ControllerModelWithMembers {
+    return cm.receiverType === 'group' || cm.receiverType === 'distributionList';
+}