Skip to content

Native Notifications #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 14, 2015
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"watch": "grunt build && npm build && npm run watch-js | grunt watch",
"start": "electron .",
"dist": "rm -rf Gitify.app/ && electron-packager . Gitify --platform=darwin --arch=x64 --version=0.27.2 --icon=images/app-icon.icns --prune --ignore=src",
"test": "jsxhint --reporter node_modules/jshint-stylish/stylish.js 'src/**/*.js', 'index.js' --exclude 'Gruntfile.js' && jscs 'src/js/' && jest",
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
"test": "jsxhint --reporter node_modules/jshint-stylish/stylish.js 'src/**/*.js', 'index.js' --exclude 'Gruntfile.js' && jscs 'src/js/' && jest"
},
"jshintConfig": {
"browserify": true,
Expand Down Expand Up @@ -76,7 +75,8 @@
"src/js/stores/auth.js": true,
"src/js/stores/notifications.js": true,
"src/js/stores/search.js": true,
"src/js/stores/settings.js": true
"src/js/stores/settings.js": true,
"src/js/stores/sound-notification.js": true
},
"unmockedModulePathPatterns": [
"node_modules/react",
Expand Down
3 changes: 3 additions & 0 deletions src/js/__tests__/components/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ describe('Test for Notification Component', function () {
item: false,
getItem: function () {
return this.item;
},
setItem: function (item) {
this.item = item;
}
};

Expand Down
3 changes: 3 additions & 0 deletions src/js/__tests__/components/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ describe('Test for Notifications Component', function () {
item: false,
getItem: function () {
return this.item;
},
setItem: function (item) {
this.item = item;
}
};

Expand Down
3 changes: 3 additions & 0 deletions src/js/__tests__/components/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ describe('Test for Settings Component', function () {
item: false,
getItem: function () {
return this.item;
},
setItem: function (item) {
this.item = item;
}
};

Expand Down
13 changes: 11 additions & 2 deletions src/js/__tests__/stores/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,26 @@ describe('Tests for NotificationsStore', function () {
item: false,
getItem: function () {
return this.item;
},
setItem: function (item) {
this.item = item;
}
};

// Mock Audio
window.Audio = function (src) {
console.log('Loading Audio: ' + src);
window.Audio = function () {
return {
play: function () {}
};
};

// Mock Notifications
window.Notification = function () {
return {
onClick: function () {}
};
};

Actions = require('../../actions/actions.js');
apiRequests = require('../../utils/api-requests.js');
NotificationsStore = require('../../stores/notifications.js');
Expand Down
77 changes: 77 additions & 0 deletions src/js/__tests__/stores/sound-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*global jest, describe, it, expect, spyOn, beforeEach */

'use strict';

jest.dontMock('reflux');
jest.dontMock('../../stores/sound-notification.js');
jest.dontMock('../../utils/api-requests.js');
jest.dontMock('../../actions/actions.js');

describe('Tests for SoundNotificationStore', function () {

var SoundNotificationStore, Actions;

beforeEach(function () {

// Mock Electron's window.require
window.require = function () {
return {
sendChannel: function () {
return;
}
};
};

// Mock localStorage
window.localStorage = {
item: false,
getItem: function () {
return this.item;
},
setItem: function (item) {
this.item = item;
}
};

// Mock Audio
window.Audio = function () {
return {
play: function () {}
};
};

// Mock Notifications
window.Notification = function () {
return {
onClick: function () {}
};
};

Actions = require('../../actions/actions.js');
SoundNotificationStore = require('../../stores/sound-notification.js');
});

it('should get a payload and check if it should play sound & show notification.', function () {

spyOn(SoundNotificationStore, 'showNotification');

var payload = [{
'id': '1',
'repository': {
'id': 1296269,
'full_name': 'octocat/Hello-World',
'description': 'This your first repo!'
},
'subject': {
'title': 'Greetings',
'url': 'https://api.github.com/repos/octokit/octokit.rb/issues/123'
}
}];

SoundNotificationStore.onIsNewNotification(payload);

expect(SoundNotificationStore.showNotification).toHaveBeenCalled();

});

});
1 change: 1 addition & 0 deletions src/js/actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var Actions = Reflux.createActions({
'login': {},
'logout': {},
'getNotifications': {asyncResult: true},
'isNewNotification': {},
'updateSearchTerm': {},
'clearSearchTerm': {},
'setSetting': {}
Expand Down
11 changes: 10 additions & 1 deletion src/js/components/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ var SettingsPage = React.createClass({
var settings = SettingsStore.getSettings();
return {
participating: settings.participating,
playSound: settings.playSound
playSound: settings.playSound,
showNotifications: settings.showNotifications
};
},

Expand Down Expand Up @@ -42,6 +43,14 @@ var SettingsPage = React.createClass({
onChange={this.toggleSetting.bind(this, 'playSound')} />
</div>
</div>
<div className='row'>
<div className='col-xs-8'>Show notifications</div>
<div className='col-xs-4'>
<Toggle
defaultChecked={this.state.showNotifications}
onChange={this.toggleSetting.bind(this, 'showNotifications')} />
</div>
</div>
<div className='row'>
<button
className='btn btn-block btn-danger btn-close'
Expand Down
34 changes: 3 additions & 31 deletions src/js/stores/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ var Actions = require('../actions/actions');
var apiRequests = require('../utils/api-requests');
var SettingsStore = require('../stores/settings');

require('../stores/sound-notification');

var NotificationsStore = Reflux.createStore({
listenables: Actions,

init: function () {
this._notifications = [];
this._previousNotifications = [];
},

updateTrayIcon: function (notifications) {
Expand All @@ -22,35 +23,6 @@ var NotificationsStore = Reflux.createStore({
}
},

isNewNotification: function (response) {
var self = this;
var playSound = SettingsStore.getSettings().playSound;

if (!playSound) { return; }

// Check if notification is already in the store.
var isNew = false;
_.map(response, function (obj) {
if (!_.contains(self._previousNotifications, obj.id)) {
isNew = true;
}
});

// Play Sound.
if (isNew) {
if (playSound) {
var audio = new Audio('sounds/digi.wav');
audio.play();
}
}

// Now Reset the previousNotifications array.
self._previousNotifications = [];
_.map(response, function (obj) {
self._previousNotifications.push(obj.id);
});
},

onGetNotifications: function () {
var self = this;
var participating = SettingsStore.getSettings().participating;
Expand All @@ -63,7 +35,7 @@ var NotificationsStore = Reflux.createStore({
// Success - Do Something.
Actions.getNotifications.completed(response.body);
self.updateTrayIcon(response.body);
self.isNewNotification(response.body);
Actions.isNewNotification(response.body);
} else {
// Error - Show messages.
Actions.getNotifications.failed(err);
Expand Down
16 changes: 14 additions & 2 deletions src/js/stores/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,28 @@ var SettingsStore = Reflux.createStore({

if (!settings) {
settings = {
'participating': false,
'playSound': true
participating: false,
playSound: true,
showNotifications: true
};
}

if (settings[0] === '{') {
settings = JSON.parse(settings);
}

if (!settings.participating) {
settings.participating = false;
}
if (!settings.playSound) {
settings.playSound = true;
}
if (!settings.showNotifications) {
settings.showNotifications = true;
}

this._settings = settings;
window.localStorage.setItem('settings', JSON.stringify(this._settings));
},

getSettings: function () {
Expand Down
68 changes: 68 additions & 0 deletions src/js/stores/sound-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
var ipc = window.require('ipc');
var Reflux = require('reflux');
var _ = require('underscore');

var Actions = require('../actions/actions');
var SettingsStore = require('../stores/settings');

var SoundNotificationStore = Reflux.createStore({
listenables: Actions,

init: function () {
this._previousNotifications = [];
},

playSound: function () {
var audio = new Audio('sounds/digi.wav');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd load this into the store on init this._audio.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also store a variable to tell whether the audio has been loaded.

this._audio.onloadeddata = function () {
  this._audioLoaded = true;
};

audio.play();
},

showNotification: function (countNew, response, latestNotification) {
var title = (countNew == 1 ?
'Gitify - ' + latestNotification.full_name :
'Gitify');
var body = (countNew == 1 ?
latestNotification.subject :
'You\'ve got ' + countNew + ' notifications.');
var nativeNotification = new Notification(title, {
body: body
});
nativeNotification.onclick = function () {
ipc.sendChannel('reopen-window');
};
},

onIsNewNotification: function (response) {
var self = this;
var playSound = SettingsStore.getSettings().playSound;
var showNotifications = SettingsStore.getSettings().showNotifications;

if (!playSound && !showNotifications) { return; }

// Check if notification is already in the store.
var newNotifications = _.filter(response, function (obj) {
return !_.contains(self._previousNotifications, obj.id);
});

// Play Sound / Show Notification.
if (newNotifications && newNotifications.length) {
if (playSound) {
self.playSound();
}
if (showNotifications) {
this.showNotification(newNotifications.length, response, {
full_name: newNotifications[0].repository.full_name,
subject: newNotifications[0].subject.title
});
}
}

// Now Reset the previousNotifications array.
self._previousNotifications = _.map(response, function (obj) {
return obj.id;
});
}

});

module.exports = SoundNotificationStore;