Skip to content
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

Add desktop dock icon indicator in Electron #525

Merged
merged 36 commits into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
df2653d
Add desktop dock icon indicator
marcaaron Sep 22, 2020
772ba7a
fix newline
marcaaron Sep 22, 2020
b216c53
save lastReadActionIDs so we can reference them later
marcaaron Sep 22, 2020
99c9b9e
not zero use sequenceNumber
marcaaron Sep 22, 2020
40083b9
remove isUnread from report
marcaaron Sep 22, 2020
3153f2e
Simplify the SidebarLink component
marcaaron Sep 22, 2020
0e904e2
add electron events
marcaaron Sep 22, 2020
50999a7
use throttle to minimize calls to the updater
marcaaron Sep 22, 2020
7717b1b
add comment
marcaaron Sep 22, 2020
d9d1424
Move updateUnread method to its own utility
marcaaron Sep 23, 2020
2ec176e
fix broken imports
marcaaron Sep 23, 2020
5766871
Implement iOS
marcaaron Sep 23, 2020
4499c7e
remove new line
marcaaron Sep 23, 2020
515c1f4
Merge remote-tracking branch 'origin' into marcaaron-desktopbadge
marcaaron Sep 24, 2020
f83cfe7
create a index.desktop.js and index.website.js file
marcaaron Sep 24, 2020
2ddade2
add android and ios
marcaaron Sep 24, 2020
f6b9958
fix comment
marcaaron Sep 24, 2020
a7f2d58
fix conflict
marcaaron Sep 25, 2020
4608b09
eslint likes new lines
marcaaron Sep 25, 2020
21a950b
fix index.website.js bundler for cross platform and fix import
roryabraham Sep 28, 2020
34d66f5
remove unnecessary blacklist code
roryabraham Sep 28, 2020
a1532f5
fix conflicts
marcaaron Oct 1, 2020
dd72182
fix conflicts
marcaaron Oct 1, 2020
26a6ced
Merge branch 'marcaaron-desktopbadge' of https://github.com/Expensify…
marcaaron Oct 1, 2020
a5792ce
use Math.max remove new line
marcaaron Oct 1, 2020
c640d6f
Fix bug where chat reports would not showing up as unread
marcaaron Oct 6, 2020
0dbb4cf
fix conflicts
marcaaron Oct 7, 2020
92bf235
woops
marcaaron Oct 7, 2020
71fd6aa
remove unneeded stuff
marcaaron Oct 7, 2020
2f2fb02
update names of methods and move to sign out area
marcaaron Oct 7, 2020
58793f6
derp
marcaaron Oct 7, 2020
30dfd18
woops snuck in old change
marcaaron Oct 8, 2020
427185a
derp
marcaaron Oct 9, 2020
8e5ead8
add Podfile.lock back
marcaaron Oct 9, 2020
70a5b46
fix podfile
marcaaron Oct 9, 2020
aa1f1bd
make these constants consistent
marcaaron Oct 9, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions desktop/ELECTRON_EVENTS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const ELECTRON_EVENTS = {
REQUEST_UPDATE_BADGE_COUNT: 'request-update-badge-count',
REQUEST_VISIBILITY: 'request_visibility',
};

module.exports = ELECTRON_EVENTS;
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ PODS:
- React-jsi (= 0.63.2)
- RNCAsyncStorage (1.11.0):
- React
- RNCPushNotificationIOS (1.5.0):
- React
- Yoga (1.14.0)
- YogaKit (1.18.1):
- Yoga (~> 1.14)
Expand Down Expand Up @@ -367,6 +369,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)

SPEC REPOS:
Expand Down Expand Up @@ -447,6 +450,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
RNCPushNotificationIOS:
:path: "../node_modules/@react-native-community/push-notification-ios"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"

Expand Down Expand Up @@ -493,6 +498,7 @@ SPEC CHECKSUMS:
React-RCTVibration: 4d2e726957f4087449739b595f107c0d4b6c2d2d
ReactCommon: a0a1edbebcac5e91338371b72ffc66aa822792ce
RNCAsyncStorage: db711e29e5e0500d9bd21aa0c2e397efa45302b1
RNCPushNotificationIOS: 8025ff0b610d7b28d29ddc1b619cd55814362e4c
Yoga: 7740b94929bbacbddda59bf115b5317e9a161598
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

Expand Down
134 changes: 69 additions & 65 deletions ios/ReactNativeChat.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion ios/ReactNativeChat/AppDelegate.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#import <UserNotifications/UNUserNotificationCenter.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>

@property (nonatomic, strong) UIWindow *window;

Expand Down
49 changes: 49 additions & 0 deletions ios/ReactNativeChat/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
Expand Down Expand Up @@ -43,9 +46,55 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];

// Define UNUserNotificationCenter
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;

return YES;
}

//Called when a notification is delivered to a foreground app.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
}

// Required to register for notifications
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings];
}
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// IOS 10+ Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
{
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
completionHandler();
}
// IOS 4-10 Required for the localNotification event.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[RNCPushNotificationIOS didReceiveLocalNotification:notification];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
Expand Down
8 changes: 8 additions & 0 deletions ios/ReactNativeChat/Chat.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
4 changes: 4 additions & 0 deletions ios/ReactNativeChat/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
<string>GTAmericaExp-Regular.otf</string>
<string>GTAmericaExp-Thin.otf</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
Expand Down
9 changes: 8 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const serve = require('electron-serve');
const contextMenu = require('electron-context-menu');
const {autoUpdater} = require('electron-updater');
const log = require('electron-log');
const ELECTRON_EVENTS = require('./desktop/ELECTRON_EVENTS');

/**
* Electron main process that handles wrapping the web application.
Expand Down Expand Up @@ -76,12 +77,18 @@ const mainWindow = (() => {
app.on('before-quit', () => quitting = true);
app.on('activate', () => browserWindow.show());

ipcMain.on('request-visibility', (event) => {
ipcMain.on(ELECTRON_EVENTS.REQUEST_VISIBILITY, (event) => {
// This is how synchronous messages work in Electron
// eslint-disable-next-line no-param-reassign
event.returnValue = browserWindow.isFocused();
});

// Listen to badge updater event emitted by the render process
// and update the app badge count (MacOS only)
ipcMain.on(ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, (event, totalCount) => {
app.setBadgeCount(totalCount);
});

return browserWindow;
})

Expand Down
1 change: 1 addition & 0 deletions metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @format
*/


Copy link
Contributor

Choose a reason for hiding this comment

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

Unnecessary extra space

module.exports = {
transformer: {
getTransformOptions: async () => ({
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dependencies": {
"@react-native-community/async-storage": "^1.11.0",
"@react-native-community/netinfo": "^5.9.6",
"@react-native-community/push-notification-ios": "^1.5.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"dotenv": "^8.2.0",
"electron-context-menu": "^2.3.0",
Expand Down
4 changes: 0 additions & 4 deletions src/lib/PageTitleUpdater/index.native.js

This file was deleted.

48 changes: 48 additions & 0 deletions src/lib/UnreadIndicatorUpdater/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import _ from 'underscore';
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
import Ion from '../Ion';
import IONKEYS from '../../IONKEYS';
import updateUnread from './updateUnread';

// Stash the unread action counts for each report
const unreadActionCounts = {};

/**
* Updates the title and favicon of the current browser tab
* and Mac OS or iOS dock icon with an unread indicator.
*/
const throttledUpdatePageTitleAndUnreadCount = _.throttle(() => {
const totalCount = _.reduce(unreadActionCounts, (total, reportCount) => total + reportCount, 0);
updateUnread(totalCount);
}, 1000, {leading: false});

let connectionID;

/**
* Bind to the report collection key and update
* the title and unread count indicators
*/
function init() {
connectionID = Ion.connect({
key: IONKEYS.COLLECTION.REPORT,
callback: (report) => {
if (!report || !report.reportID) {
return;
}

unreadActionCounts[report.reportID] = report.unreadActionCount || 0;
throttledUpdatePageTitleAndUnreadCount();
}
});
}

/**
* Remove the subscription callback when we no longer need it.
*/
function destroy() {
Ion.disconnect(connectionID);
}

export default {
init,
destroy,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Android does not yet implement this
export default () => {};
16 changes: 16 additions & 0 deletions src/lib/UnreadIndicatorUpdater/updateUnread/index.desktop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS';

const ipcRenderer = window.require('electron').ipcRenderer;

/**
* Set the badge on desktop
*
* @param {Number} totalCount
*/
function updateUnread(totalCount) {
// Ask the main Electron process to update our
// badge count in the Mac OS dock icon
ipcRenderer.send(ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT, totalCount);
}

export default updateUnread;
13 changes: 13 additions & 0 deletions src/lib/UnreadIndicatorUpdater/updateUnread/index.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import PushNotificationIOS from '@react-native-community/push-notification-ios';

/**
* Set the App Icon badge with the number of
* unread messages on iOS
*
* @param {Number} totalCount
*/
function updateUnread(totalCount) {
PushNotificationIOS.setApplicationIconBadgeNumber(totalCount);
}

export default updateUnread;
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/**
* Web browsers have a tab title and favicon which can be updated to show there are unread comments
*/
import CONFIG from '../../CONFIG';
import CONFIG from '../../../CONFIG';

/**
* Updates the title and favicon of the current browser tab with an unread indicator
* Set the page title on web
*
* @param {boolean} hasUnread
* @param {Number} totalCount
*/
const PageTitleUpdater = (hasUnread) => {
function updateUnread(totalCount) {
const hasUnread = totalCount > 0;
document.title = hasUnread ? `(NEW!) ${CONFIG.SITE_TITLE}` : CONFIG.SITE_TITLE;
document.getElementById('favicon').href = hasUnread ? CONFIG.FAVICON.UNREAD : CONFIG.FAVICON.DEFAULT;
};
}

export default PageTitleUpdater;
export default updateUnread;
4 changes: 3 additions & 1 deletion src/lib/Visibility/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import ELECTRON_EVENTS from '../../../desktop/ELECTRON_EVENTS';

// We conditionally import the ipcRenderer here so that we can
// communicate with the main Electron process in main.js
const ipcRenderer = window.require ? window.require('electron').ipcRenderer : null;
Expand All @@ -13,7 +15,7 @@ const ipcRenderer = window.require ? window.require('electron').ipcRenderer : nu
*/
function isVisible() {
return ipcRenderer
? ipcRenderer.sendSync('request-visibility')
? ipcRenderer.sendSync(ELECTRON_EVENTS.REQUEST_VISIBILITY)
: document.visibilityState === 'visible';
}

Expand Down
Loading