diff --git a/README.md b/README.md index 6fefed9..c9a98b3 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ __Additional Settings__ * [historyEnabled]{boolean}: Defines whether the component should call the "getMessageHistory" from the chat-adapter. Default is true. * [emojisEnabled]{boolean}: Enables emoji parsing on the messages. Default is true. * [linkfyEnabled]{boolean}: Transforms links within the messages to valid HTML links. Default is true. +* [audioEnabled]{boolean}: Enables audio notifications on received messages. Default is true. +* [audioSource]{string}: WAV source of the audio notification. Default is a RAW github WAV content from ng-chat repository. #### Implement your ChatAdapter: diff --git a/demo/aspnetcore_signalr/package.json b/demo/aspnetcore_signalr/package.json index 5ab5dd7..2435c44 100644 --- a/demo/aspnetcore_signalr/package.json +++ b/demo/aspnetcore_signalr/package.json @@ -29,7 +29,7 @@ "bootstrap": "^3.3.7", "core-js": "^2.4.1", "jquery": "3.2.1", - "ng-chat": "1.0.3", + "ng-chat": "1.0.4", "ng2-loading-bar": "0.0.6", "reflect-metadata": "^0.1.10", "rxjs": "5.4.2", diff --git a/demo/offline_bot/package.json b/demo/offline_bot/package.json index 25ed924..c6dd7d2 100644 --- a/demo/offline_bot/package.json +++ b/demo/offline_bot/package.json @@ -24,7 +24,7 @@ "core-js": "^2.5.3", "rxjs": "^5.5.6", "zone.js": "^0.8.20", - "ng-chat": "1.0.3" + "ng-chat": "1.0.4" }, "devDependencies": { "@angular/cli": "^1.6.4", diff --git a/src/ng-chat/assets/notification.wav b/src/ng-chat/assets/notification.wav new file mode 100644 index 0000000..abceafd Binary files /dev/null and b/src/ng-chat/assets/notification.wav differ diff --git a/src/ng-chat/ng-chat.component.ts b/src/ng-chat/ng-chat.component.ts index 887a137..c1b9a57 100644 --- a/src/ng-chat/ng-chat.component.ts +++ b/src/ng-chat/ng-chat.component.ts @@ -52,7 +52,15 @@ export class NgChat implements OnInit { @Input() public linkfyEnabled: boolean = true; - public searchInput: string = ""; + @Input() + public audioEnabled: boolean = true; + + @Input() // TODO: This might need a better content strategy + public audioSource: string = 'https://raw.githubusercontent.com/rpaschoal/ng-chat/master/src/ng-chat/assets/notification.wav'; + + private audioFile: HTMLAudioElement; + + public searchInput: string = ''; private users: User[]; @@ -126,6 +134,8 @@ export class NgChat implements OnInit { this.fetchFriendsList(); } + this.bufferAudioFile(); + this.isBootsrapped = true; } @@ -170,6 +180,8 @@ export class NgChat implements OnInit { this.scrollChatWindowToBottom(chatWindow[0]); } + + this.emitMessageSound(chatWindow[0]); } } @@ -240,6 +252,24 @@ export class NgChat implements OnInit { }); } + // Buffers audio file (For component's bootstrapping) + private bufferAudioFile(): void { + if (this.audioSource && this.audioSource.length > 0) + { + this.audioFile = new Audio(); + this.audioFile.src = this.audioSource; + this.audioFile.load(); + } + } + + // Emits a message notification audio if enabled after every message received + private emitMessageSound(window: Window): void + { + if (this.audioEnabled && !window.hasFocus && this.audioFile) { + this.audioFile.play(); + } + } + // Returns the total unread messages from a chat window. TODO: Could use some Angular pipes in the future unreadMessagesTotal(window: Window): string { diff --git a/src/package.json b/src/package.json index 6e42128..1c79934 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "ng-chat", - "version": "1.0.3", + "version": "1.0.4", "peerDependencies": { "@angular/common": "*", "@angular/core": "*", diff --git a/src/spec/ng-chat.component.spec.ts b/src/spec/ng-chat.component.spec.ts index 9140ab6..ac0ea4e 100644 --- a/src/spec/ng-chat.component.spec.ts +++ b/src/spec/ng-chat.component.spec.ts @@ -18,10 +18,17 @@ class MockableAdapter extends ChatAdapter { } } +class MockableHTMLAudioElement { + public play(): void {} + public load(): void {} +} + describe('NgChat', () => { beforeEach(() => { this.subject = new NgChat(); + this.subject.userId = 123; this.subject.adapter = new MockableAdapter(); + this.subject.audioFile = new MockableHTMLAudioElement(); }); it('Should have default title', () => { @@ -44,6 +51,14 @@ describe('NgChat', () => { expect(this.subject.emojisEnabled).not.toBeFalsy(); }); + it('Audio notification must be enabled by default', () => { + expect(this.subject.audioEnabled).not.toBeFalsy(); + }); + + it('Audio notification must have a default source', () => { + expect(this.subject.audioSource).not.toBeUndefined(); + }); + it('Exercise users filter', () => { this.subject.users = [{ id: 1, @@ -200,4 +215,51 @@ describe('NgChat', () => { expect(messages[1].seenOn).not.toBeUndefined(); expect(messages[1].seenOn.getTime()).toBeGreaterThan(new Date().getTime() - 60000); }); + + it('Should play HTMLAudioElement when emitting a message sound on an unfocused window', () => { + let window = new Window(); + + spyOn(MockableHTMLAudioElement.prototype, 'play'); + + this.subject.emitMessageSound(window); + + expect(MockableHTMLAudioElement.prototype.play).toHaveBeenCalledTimes(1); + }); + + it('Should not play HTMLAudioElement when emitting a message sound on a focused window', () => { + let window = new Window(); + + window.hasFocus = true; + + spyOn(MockableHTMLAudioElement.prototype, 'play'); + + this.subject.emitMessageSound(window); + + expect(MockableHTMLAudioElement.prototype.play).not.toHaveBeenCalled(); + }); + + it('Should not play HTMLAudioElement when audio notification is disabled', () => { + let window = new Window(); + + this.subject.audioEnabled = false; + + spyOn(MockableHTMLAudioElement.prototype, 'play'); + + this.subject.emitMessageSound(window); + + expect(MockableHTMLAudioElement.prototype.play).not.toHaveBeenCalled(); + }); + + it('Must invoke message notification method on new messages', () => { + let message = new Message(); + let user = new User(); + + spyOn(this.subject, 'emitMessageSound'); + spyOn(this.subject, 'openChatWindow').and.returnValue([null, true]); + spyOn(this.subject, 'scrollChatWindowToBottom'); // Masking this call as we're not testing this part on this spec + + this.subject.onMessageReceived(user, message); + + expect(this.subject.emitMessageSound).toHaveBeenCalledTimes(1); + }); });