Skip to content

Commit

Permalink
Desktop Notifications (#97)
Browse files Browse the repository at this point in the history
* desktop notification - initial commit

* notification open on app hidden

* notification click action: nav to local node page regardless of tab selected when app is closed

* launch notification on app blurred

* quit app on quit selection on close main window

* code review changes

* code review changes and merged with develop

* code review changes

* code review changes
  • Loading branch information
yhaspel authored and Ilya Vilensky committed May 30, 2019
1 parent 7729937 commit 566e9b3
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 15 deletions.
Binary file added app/assets/icons/app_icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion app/components/localNode/LeftPane.js
Expand Up @@ -52,9 +52,10 @@ class LeftPane extends Component<Props> {
componentDidMount() {
const { getLocalNodeSetupProgress } = this.props;
this.checkInitStatus();
getLocalNodeSetupProgress();
this.timer = setInterval(() => {
getLocalNodeSetupProgress();
}, 10000);
}, 30000);
}

componentDidUpdate() {
Expand Down
9 changes: 5 additions & 4 deletions app/components/localNode/LocalNodeLog.js
Expand Up @@ -2,7 +2,7 @@ import React, { Component } from 'react';
import styled from 'styled-components';
import { smColors } from '/vars';

// TODO: remove stab
// TODO: remove stub
const getRandomNumber = (maxValue: number) => Math.floor(Math.random() * Math.floor(maxValue));
const getTimestamp = () => new Date().toUTCString();
const generateLogEntry = (i: number) => {
Expand Down Expand Up @@ -56,6 +56,7 @@ const LogRow = styled.div`
align-items: center;
`;

// $FlowStyledIssue
const LogEntry = styled.div`
font-size: 16px;
line-height: 30px;
Expand Down Expand Up @@ -87,12 +88,12 @@ class LocalNodeLog extends Component<{}, { log: LogRecord[] }> {
);
}

// TODO: remove stab
// TODO: remove stub
componentDidMount() {
this.generateLog();
}

// TODO: remove stab
// TODO: remove stub
componentWillUnmount() {
this.timer && clearInterval(this.timer);
}
Expand All @@ -104,7 +105,7 @@ class LocalNodeLog extends Component<{}, { log: LogRecord[] }> {
</LogRow>
));

// TODO: remove stab
// TODO: remove stub
generateLog = () => {
let i = 0;
this.timer = setInterval(() => {
Expand Down
5 changes: 4 additions & 1 deletion app/infra/httpService/httpService.js
Expand Up @@ -29,7 +29,10 @@ class HttpService {
ipcRenderer.send(ipcConsts.GET_INIT_PROGRESS);
return new Promise<string, Error>((resolve: Function, reject: Function) => {
ipcRenderer.once(ipcConsts.GET_INIT_PROGRESS_SUCCESS, (event, response) => {
resolve(response);
const timer = setTimeout(() => {
resolve(response);
clearTimeout(timer);
}, 10000);
});
ipcRenderer.once(ipcConsts.GET_INIT_PROGRESS_FAILURE, (event, args) => {
reject(args);
Expand Down
1 change: 1 addition & 0 deletions app/infra/notificationsService/index.js
@@ -0,0 +1 @@
export { default as notificationsService } from './notificationsService'; // eslint-disable-line import/prefer-default-export
34 changes: 34 additions & 0 deletions app/infra/notificationsService/notificationsService.js
@@ -0,0 +1,34 @@
import path from 'path';
import { ipcRenderer } from 'electron';
import { ipcConsts } from '/vars';

// @flow
class NotificationsService {
static notify = ({ title, notification, callback }: { title: string, notification: string, callback: () => void }) => {
NotificationsService.getNotificationAllowedStatus().then(({ isNotificationAllowed }: { isNotificationAllowed: boolean }) => {
if (isNotificationAllowed) {
const notificationOptions: any = {
body: notification,
icon: path.join(__dirname, '..', 'app', 'assets', 'icons', 'app_icon.png')
};
const desktopNotification = new Notification(title || 'Alert', notificationOptions);
desktopNotification.onclick = () => {
ipcRenderer.send(ipcConsts.NOTIFICATION_CLICK);
callback && callback();
};
}
});
};

static getNotificationAllowedStatus = async () => {
const isPermitted = await Notification.requestPermission();
ipcRenderer.send(ipcConsts.CAN_NOTIFY);
return new Promise((resolve) => {
ipcRenderer.once(ipcConsts.CAN_NOTIFY_SUCCESS, (event, isInFocus) => {
resolve({ isNotificationAllowed: isPermitted && !isInFocus });
});
});
};
}

export default NotificationsService;
3 changes: 2 additions & 1 deletion app/redux/localNode/reducer.js
Expand Up @@ -21,7 +21,8 @@ const initialState = {
progress: null,
totalEarnings: null,
upcomingEarnings: null,
awardsAddress: null
awardsAddress: null,
pathToLocalNode: null
};

const reducer = (state: any = initialState, action: Action) => {
Expand Down
21 changes: 19 additions & 2 deletions app/screens/main/Main.js
Expand Up @@ -10,6 +10,9 @@ import type { SideMenuItem } from '/basicComponents';
import { menu1, menu2, menu3, menu4, menu5, menu6, menu7 } from '/assets/images';
import routes from '/routes';
import type { Account, Action } from '/types';
import { notificationsService } from '/infra/notificationsService';

const completeValue = 80; // TODO: change to actual complete value

const sideMenuItems: SideMenuItem[] = [
{
Expand Down Expand Up @@ -73,7 +76,9 @@ type Props = {
location: { pathname: string, hash: string },
accounts: Account[],
resetNodeSettings: Action,
logout: Action
logout: Action,
// pathToLocalNode: string,
progress: number
};

type State = {
Expand Down Expand Up @@ -109,6 +114,17 @@ class Main extends Component<Props, State> {
);
}

componentDidUpdate(prevProps: Props) {
const { progress, history } = this.props;
if (prevProps.progress !== progress && progress === completeValue) {
notificationsService.notify({
title: 'Local Node',
notification: 'Your full node setup is complete! You are now participating in the Spacemesh network!',
callback: () => this.handleSideMenuPress({ index: 0 })
});
}
}

handleSideMenuPress = ({ index }: { index: number }) => {
const { history, accounts, location } = this.props;
const newPath: ?string = sideMenuItems[index].path;
Expand All @@ -133,7 +149,8 @@ class Main extends Component<Props, State> {
}

const mapStateToProps = (state) => ({
accounts: state.wallet.accounts
accounts: state.wallet.accounts,
progress: state.localNode.progress
});

const mapDispatchToProps = {
Expand Down
3 changes: 3 additions & 0 deletions app/vars/ipcConsts.js
Expand Up @@ -28,6 +28,9 @@ const ipcConsts = {
SET_NODE_IP: 'SET_NODE_IP',
SET_NODE_IP_SUCCESS: 'SET_NODE_IP_SUCCESS',
SET_NODE_IP_FAILURE: 'SET_NODE_IP_FAILURE',
CAN_NOTIFY: 'CAN_NOTIFY',
CAN_NOTIFY_SUCCESS: 'CAN_NOTIFY_SUCCESS',
NOTIFICATION_CLICK: 'NOTIFICATION_CLICK',
// gRPC calls
GET_BALANCE: 'GET_BALANCE',
GET_BALANCE_SUCCESS: 'GET_BALANCE_SUCCESS',
Expand Down
10 changes: 10 additions & 0 deletions desktop/eventListners.js
Expand Up @@ -84,6 +84,16 @@ const subscribeToEventListeners = ({ mainWindow }) => {
ipcMain.on(ipcConsts.SET_NODE_IP, async (event, request) => {
netService.setNodeIpAddress({ event, ...request });
});

ipcMain.on(ipcConsts.NOTIFICATION_CLICK, () => {
mainWindow.show();
mainWindow.focus();
});

ipcMain.on(ipcConsts.CAN_NOTIFY, (event) => {
const isInFocus = mainWindow.isFocused();
event.sender.send(ipcConsts.CAN_NOTIFY_SUCCESS, isInFocus);
});
};

export { subscribeToEventListeners }; // eslint-disable-line import/prefer-default-export
32 changes: 26 additions & 6 deletions desktop/main.dev.js
Expand Up @@ -8,7 +8,7 @@
* When running `npm run build` or `npm run build-main`, this file is compiled to
* `./desktop/main.prod.js` using webpack. This gives us some performance wins.
*/
import { app, BrowserWindow } from 'electron';
import { app, BrowserWindow, dialog } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import MenuBuilder from './menu';
Expand Down Expand Up @@ -41,9 +41,6 @@ const installExtensions = async () => {
return Promise.all(extensions.map((name) => installer.default(installer[name], forceDownload))).catch(console.error); // eslint-disable-line no-console
};

// Add event listeners.
subscribeToEventListeners({ mainWindow });

app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
Expand All @@ -64,6 +61,8 @@ const createWindow = () => {
nodeIntegration: true
}
});
// Add event listeners.
subscribeToEventListeners({ mainWindow });
};

app.on('ready', async () => {
Expand All @@ -83,8 +82,26 @@ app.on('ready', async () => {
mainWindow.focus();
});

mainWindow.on('closed', () => {
mainWindow = null;
mainWindow.on('close', (event) => {
event.preventDefault();
const options = {
title: 'Spacemesh',
message: 'Quit app or keep in background?',
buttons: ['Keep running in background', 'Quit']
};
dialog.showMessageBox(mainWindow, options, (response) => {
if (response === 0) {
mainWindow.hide();
}
if (response === 1) {
mainWindow.destroy();
mainWindow = null;
app.quit();
}
if (process.platform !== 'darwin') {
app.dock.hide();
}
});
});

const menuBuilder = new MenuBuilder(mainWindow);
Expand All @@ -98,5 +115,8 @@ app.on('ready', async () => {
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
} else if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});

0 comments on commit 566e9b3

Please sign in to comment.