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 3 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
4 changes: 4 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const mainWindow = (() => {
event.returnValue = browserWindow.isFocused();
});

ipcMain.on('request-update-badge-count', (event, totalCount) => {
app.setBadgeCount(totalCount);
});

return browserWindow;
})

Expand Down
16 changes: 0 additions & 16 deletions src/lib/PageTitleUpdater/index.js

This file was deleted.

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

/**
* Web browsers have a tab title and favicon which can be updated to show there are unread comments
*/
import CONFIG from '../../CONFIG';

// 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;

const reportMap = {};

/**
* Updates the title and favicon of the current browser tab
* and Mac OS dock icon with an unread indicator.
*
* @param {boolean} hasUnread
*/
function updatePageTitleAndUnreadCount() {
const totalCount = _.reduce(reportMap, (total, report) => total + ((report && report.unreadActionCount) || 0), 0);
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;

if (ipcRenderer) {
// Ask the main Electron process to update our
// badge count in the Mac OS dock icon
ipcRenderer.send('request-update-badge-count', totalCount);
}
}

Ion.connect({
key: IONKEYS.COLLECTION.REPORT,
callback: (report) => {
if (!report || !report.reportID) {
return;
}

reportMap[report.reportID] = report;
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
updatePageTitleAndUnreadCount();
}
});

export default {
init: () => {},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/**
* Native devices (iOS, Android) don't have page title like web does, so this module doesn't do anything
*/
export default () => {};
export default {
init: () => {},
};
30 changes: 20 additions & 10 deletions src/lib/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Ion.connect({
// Keeps track of the max sequence number for each report
const reportMaxSequenceNumbers = {};

// Keeps track of the last read for each report
const lastReadActionIDs = {};

// List of reportIDs that we define in .env
const configReportIDs = CONFIG.REPORT_IDS.split(',').map(Number);

Expand All @@ -56,29 +59,30 @@ const configReportIDs = CONFIG.REPORT_IDS.split(',').map(Number);
* @param {object} report
* @returns {boolean}
*/
function hasUnreadActions(report) {
function getUnreadActionCount(report) {
const usersLastReadActionID = lodashGet(report, [
'reportNameValuePairs',
`lastReadActionID_${currentUserAccountID}`,
]);

// Save the lastReadActionID locally so we can access this later
lastReadActionIDs[report.reportID] = usersLastReadActionID;

if (report.reportActionList.length === 0) {
return false;
return 0;
}

if (!usersLastReadActionID) {
return true;
return report.reportActionList.length;
}

// Find the most recent sequence number from the report actions
const maxSequenceNumber = reportMaxSequenceNumbers[report.reportID];

if (!maxSequenceNumber) {
return false;
}

// There are unread items if the last one the user has read is less than the highest sequence number we have
return usersLastReadActionID < maxSequenceNumber;
// There are unread items if the last one the user has read is less
// than the highest sequence number we have.
const unreadActionCount = (maxSequenceNumber || report.reportActionList.length) - usersLastReadActionID;
return unreadActionCount > 0 ? unreadActionCount : 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think using Math.max() here makes it more readable.

}

/**
Expand All @@ -92,11 +96,13 @@ function hasUnreadActions(report) {
* @returns {object}
*/
function getSimplifiedReportObject(report) {
const unreadActionCount = getUnreadActionCount(report);
return {
reportID: report.reportID,
reportName: report.reportName,
reportNameValuePairs: report.reportNameValuePairs,
isUnread: hasUnreadActions(report),
unreadActionCount,
isUnread: unreadActionCount > 0,
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
pinnedReport: configReportIDs.includes(report.reportID),
};
}
Expand Down Expand Up @@ -183,6 +189,7 @@ function updateReportWithNewAction(reportID, reportAction) {
// by handleReportChanged
Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, {
reportID,
unreadActionCount: newMaxSequenceNumber - (lastReadActionIDs[reportID] || 0),
isUnread: hasNewSequenceNumber,
maxSequenceNumber: reportAction.sequenceNumber,
});
Expand Down Expand Up @@ -440,9 +447,12 @@ function updateLastReadActionID(reportID, sequenceNumber) {
return;
}

lastReadActionIDs[reportID] = 0;

// Update the lastReadActionID on the report optimistically
Ion.merge(`${IONKEYS.COLLECTION.REPORT}${reportID}`, {
isUnread: false,
unreadActionCount: 0,
reportNameValuePairs: {
[`lastReadActionID_${currentUserAccountID}`]: sequenceNumber,
}
Expand Down
3 changes: 3 additions & 0 deletions src/page/home/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Main from './MainView';
import {subscribeToReportCommentEvents, fetchAll as fetchAllReports} from '../../lib/actions/Report';
import {fetch as fetchPersonalDetails} from '../../lib/actions/PersonalDetails';
import * as Pusher from '../../lib/Pusher/pusher';
import UnreadIndicatorUpdater from '../../lib/UnreadIndicatorUpdater';

const windowSize = Dimensions.get('window');
const widthBreakPoint = 1000;
Expand Down Expand Up @@ -43,6 +44,8 @@ export default class App extends React.Component {
// Fetch all the reports
fetchAllReports();

UnreadIndicatorUpdater.init();
marcaaron marked this conversation as resolved.
Show resolved Hide resolved

Dimensions.addEventListener('change', this.toggleHamburgerBasedOnDimensions);

StatusBar.setBarStyle('dark-content', true);
Expand Down
6 changes: 1 addition & 5 deletions src/page/home/sidebar/SidebarLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Text from '../../../components/Text';
import SidebarLink from './SidebarLink';
import withIon from '../../../components/withIon';
import IONKEYS from '../../../IONKEYS';
import PageTitleUpdater from '../../../lib/PageTitleUpdater';
import ChatSwitcherView from './ChatSwitcherView';
import SafeAreaInsetPropTypes from '../../SafeAreaInsetPropTypes';
import compose from '../../../lib/compose';
Expand Down Expand Up @@ -54,17 +53,14 @@ class SidebarLinks extends React.Component {
}

render() {
const {reports, onLinkClick} = this.props;
const {onLinkClick} = this.props;
const reportIDInUrl = parseInt(this.props.match.params.reportID, 10);
const sortedReports = lodashOrderby(this.props.reports, ['pinnedReport', 'reportName'], ['desc', 'asc']);

// Filter the reports so that the only reports shown are pinned, unread, and the one matching the URL
// eslint-disable-next-line max-len
const reportsToDisplay = _.filter(sortedReports, report => (report.pinnedReport || report.isUnread || report.reportID === reportIDInUrl));

// Updates the page title to indicate there are unread reports
PageTitleUpdater(_.any(reports, report => report.isUnread));

return (
<View style={[styles.flex1, {marginTop: this.props.insets.top}]}>
<View style={[styles.sidebarHeader]}>
Expand Down