Skip to content

Commit ec15749

Browse files
Merge pull request #57 from ekonstantinidis/native-notifications
Native Notifications
2 parents 5e78c37 + eca42dd commit ec15749

File tree

11 files changed

+196
-39
lines changed

11 files changed

+196
-39
lines changed

Diff for: package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
"watch": "grunt build && npm build && npm run watch-js | grunt watch",
1111
"start": "electron .",
1212
"dist": "rm -rf Gitify.app/ && electron-packager . Gitify --platform=darwin --arch=x64 --version=0.27.2 --icon=images/app-icon.icns --prune --ignore=src",
13-
"test": "jsxhint --reporter node_modules/jshint-stylish/stylish.js 'src/**/*.js', 'index.js' --exclude 'Gruntfile.js' && jscs 'src/js/' && jest",
14-
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
13+
"test": "jsxhint --reporter node_modules/jshint-stylish/stylish.js 'src/**/*.js', 'index.js' --exclude 'Gruntfile.js' && jscs 'src/js/' && jest"
1514
},
1615
"jshintConfig": {
1716
"browserify": true,
@@ -76,7 +75,8 @@
7675
"src/js/stores/auth.js": true,
7776
"src/js/stores/notifications.js": true,
7877
"src/js/stores/search.js": true,
79-
"src/js/stores/settings.js": true
78+
"src/js/stores/settings.js": true,
79+
"src/js/stores/sound-notification.js": true
8080
},
8181
"unmockedModulePathPatterns": [
8282
"node_modules/react",

Diff for: src/js/__tests__/components/notification.js

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ describe('Test for Notification Component', function () {
3434
item: false,
3535
getItem: function () {
3636
return this.item;
37+
},
38+
setItem: function (item) {
39+
this.item = item;
3740
}
3841
};
3942

Diff for: src/js/__tests__/components/notifications.js

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ describe('Test for Notifications Component', function () {
3434
item: false,
3535
getItem: function () {
3636
return this.item;
37+
},
38+
setItem: function (item) {
39+
this.item = item;
3740
}
3841
};
3942

Diff for: src/js/__tests__/components/settings.js

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ describe('Test for Settings Component', function () {
2828
item: false,
2929
getItem: function () {
3030
return this.item;
31+
},
32+
setItem: function (item) {
33+
this.item = item;
3134
}
3235
};
3336

Diff for: src/js/__tests__/stores/notifications.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,26 @@ describe('Tests for NotificationsStore', function () {
2727
item: false,
2828
getItem: function () {
2929
return this.item;
30+
},
31+
setItem: function (item) {
32+
this.item = item;
3033
}
3134
};
3235

3336
// Mock Audio
34-
window.Audio = function (src) {
35-
console.log('Loading Audio: ' + src);
37+
window.Audio = function () {
3638
return {
3739
play: function () {}
3840
};
3941
};
4042

43+
// Mock Notifications
44+
window.Notification = function () {
45+
return {
46+
onClick: function () {}
47+
};
48+
};
49+
4150
Actions = require('../../actions/actions.js');
4251
apiRequests = require('../../utils/api-requests.js');
4352
NotificationsStore = require('../../stores/notifications.js');

Diff for: src/js/__tests__/stores/sound-notification.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*global jest, describe, it, expect, spyOn, beforeEach */
2+
3+
'use strict';
4+
5+
jest.dontMock('reflux');
6+
jest.dontMock('../../stores/sound-notification.js');
7+
jest.dontMock('../../utils/api-requests.js');
8+
jest.dontMock('../../actions/actions.js');
9+
10+
describe('Tests for SoundNotificationStore', function () {
11+
12+
var SoundNotificationStore, Actions;
13+
14+
beforeEach(function () {
15+
16+
// Mock Electron's window.require
17+
window.require = function () {
18+
return {
19+
sendChannel: function () {
20+
return;
21+
}
22+
};
23+
};
24+
25+
// Mock localStorage
26+
window.localStorage = {
27+
item: false,
28+
getItem: function () {
29+
return this.item;
30+
},
31+
setItem: function (item) {
32+
this.item = item;
33+
}
34+
};
35+
36+
// Mock Audio
37+
window.Audio = function () {
38+
return {
39+
play: function () {}
40+
};
41+
};
42+
43+
// Mock Notifications
44+
window.Notification = function () {
45+
return {
46+
onClick: function () {}
47+
};
48+
};
49+
50+
Actions = require('../../actions/actions.js');
51+
SoundNotificationStore = require('../../stores/sound-notification.js');
52+
});
53+
54+
it('should get a payload and check if it should play sound & show notification.', function () {
55+
56+
spyOn(SoundNotificationStore, 'showNotification');
57+
58+
var payload = [{
59+
'id': '1',
60+
'repository': {
61+
'id': 1296269,
62+
'full_name': 'octocat/Hello-World',
63+
'description': 'This your first repo!'
64+
},
65+
'subject': {
66+
'title': 'Greetings',
67+
'url': 'https://api.github.com/repos/octokit/octokit.rb/issues/123'
68+
}
69+
}];
70+
71+
SoundNotificationStore.onIsNewNotification(payload);
72+
73+
expect(SoundNotificationStore.showNotification).toHaveBeenCalled();
74+
75+
});
76+
77+
});

Diff for: src/js/actions/actions.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var Actions = Reflux.createActions({
55
'login': {},
66
'logout': {},
77
'getNotifications': {asyncResult: true},
8+
'isNewNotification': {},
89
'updateSearchTerm': {},
910
'clearSearchTerm': {},
1011
'setSetting': {}

Diff for: src/js/components/settings.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ var SettingsPage = React.createClass({
1111
var settings = SettingsStore.getSettings();
1212
return {
1313
participating: settings.participating,
14-
playSound: settings.playSound
14+
playSound: settings.playSound,
15+
showNotifications: settings.showNotifications
1516
};
1617
},
1718

@@ -42,6 +43,14 @@ var SettingsPage = React.createClass({
4243
onChange={this.toggleSetting.bind(this, 'playSound')} />
4344
</div>
4445
</div>
46+
<div className='row'>
47+
<div className='col-xs-8'>Show notifications</div>
48+
<div className='col-xs-4'>
49+
<Toggle
50+
defaultChecked={this.state.showNotifications}
51+
onChange={this.toggleSetting.bind(this, 'showNotifications')} />
52+
</div>
53+
</div>
4554
<div className='row'>
4655
<button
4756
className='btn btn-block btn-danger btn-close'

Diff for: src/js/stores/notifications.js

+3-31
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ var Actions = require('../actions/actions');
66
var apiRequests = require('../utils/api-requests');
77
var SettingsStore = require('../stores/settings');
88

9+
require('../stores/sound-notification');
10+
911
var NotificationsStore = Reflux.createStore({
1012
listenables: Actions,
1113

1214
init: function () {
1315
this._notifications = [];
14-
this._previousNotifications = [];
1516
},
1617

1718
updateTrayIcon: function (notifications) {
@@ -22,35 +23,6 @@ var NotificationsStore = Reflux.createStore({
2223
}
2324
},
2425

25-
isNewNotification: function (response) {
26-
var self = this;
27-
var playSound = SettingsStore.getSettings().playSound;
28-
29-
if (!playSound) { return; }
30-
31-
// Check if notification is already in the store.
32-
var isNew = false;
33-
_.map(response, function (obj) {
34-
if (!_.contains(self._previousNotifications, obj.id)) {
35-
isNew = true;
36-
}
37-
});
38-
39-
// Play Sound.
40-
if (isNew) {
41-
if (playSound) {
42-
var audio = new Audio('sounds/digi.wav');
43-
audio.play();
44-
}
45-
}
46-
47-
// Now Reset the previousNotifications array.
48-
self._previousNotifications = [];
49-
_.map(response, function (obj) {
50-
self._previousNotifications.push(obj.id);
51-
});
52-
},
53-
5426
onGetNotifications: function () {
5527
var self = this;
5628
var participating = SettingsStore.getSettings().participating;
@@ -63,7 +35,7 @@ var NotificationsStore = Reflux.createStore({
6335
// Success - Do Something.
6436
Actions.getNotifications.completed(response.body);
6537
self.updateTrayIcon(response.body);
66-
self.isNewNotification(response.body);
38+
Actions.isNewNotification(response.body);
6739
} else {
6840
// Error - Show messages.
6941
Actions.getNotifications.failed(err);

Diff for: src/js/stores/settings.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,28 @@ var SettingsStore = Reflux.createStore({
99

1010
if (!settings) {
1111
settings = {
12-
'participating': false,
13-
'playSound': true
12+
participating: false,
13+
playSound: true,
14+
showNotifications: true
1415
};
1516
}
1617

1718
if (settings[0] === '{') {
1819
settings = JSON.parse(settings);
1920
}
2021

22+
if (!settings.participating) {
23+
settings.participating = false;
24+
}
25+
if (!settings.playSound) {
26+
settings.playSound = true;
27+
}
28+
if (!settings.showNotifications) {
29+
settings.showNotifications = true;
30+
}
31+
2132
this._settings = settings;
33+
window.localStorage.setItem('settings', JSON.stringify(this._settings));
2234
},
2335

2436
getSettings: function () {

Diff for: src/js/stores/sound-notification.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
var ipc = window.require('ipc');
2+
var Reflux = require('reflux');
3+
var _ = require('underscore');
4+
5+
var Actions = require('../actions/actions');
6+
var SettingsStore = require('../stores/settings');
7+
8+
var SoundNotificationStore = Reflux.createStore({
9+
listenables: Actions,
10+
11+
init: function () {
12+
this._previousNotifications = [];
13+
},
14+
15+
playSound: function () {
16+
var audio = new Audio('sounds/digi.wav');
17+
audio.play();
18+
},
19+
20+
showNotification: function (countNew, response, latestNotification) {
21+
var title = (countNew == 1 ?
22+
'Gitify - ' + latestNotification.full_name :
23+
'Gitify');
24+
var body = (countNew == 1 ?
25+
latestNotification.subject :
26+
'You\'ve got ' + countNew + ' notifications.');
27+
var nativeNotification = new Notification(title, {
28+
body: body
29+
});
30+
nativeNotification.onclick = function () {
31+
ipc.sendChannel('reopen-window');
32+
};
33+
},
34+
35+
onIsNewNotification: function (response) {
36+
var self = this;
37+
var playSound = SettingsStore.getSettings().playSound;
38+
var showNotifications = SettingsStore.getSettings().showNotifications;
39+
40+
if (!playSound && !showNotifications) { return; }
41+
42+
// Check if notification is already in the store.
43+
var newNotifications = _.filter(response, function (obj) {
44+
return !_.contains(self._previousNotifications, obj.id);
45+
});
46+
47+
// Play Sound / Show Notification.
48+
if (newNotifications && newNotifications.length) {
49+
if (playSound) {
50+
self.playSound();
51+
}
52+
if (showNotifications) {
53+
this.showNotification(newNotifications.length, response, {
54+
full_name: newNotifications[0].repository.full_name,
55+
subject: newNotifications[0].subject.title
56+
});
57+
}
58+
}
59+
60+
// Now Reset the previousNotifications array.
61+
self._previousNotifications = _.map(response, function (obj) {
62+
return obj.id;
63+
});
64+
}
65+
66+
});
67+
68+
module.exports = SoundNotificationStore;

0 commit comments

Comments
 (0)