Notification improvements

- Remove on read, on focus, and on exit.
- Show multi-message notifications like '5 new messages'.
pull/1/head
Daniel Gasienica 7 years ago
parent f693c00778
commit 3bf8a8966a

@ -729,6 +729,18 @@
"message": "New Messages", "message": "New Messages",
"description": "Displayed in notifications for multiple messages" "description": "Displayed in notifications for multiple messages"
}, },
"notificationMostRecentFrom": {
"message": "Most recent from:",
"description": "Displayed in notifications when setting is 'name only' and more than one message is waiting"
},
"notificationFrom": {
"message": "From:",
"description": "Displayed in notifications when setting is 'name only' and one message is waiting"
},
"notificationMostRecent": {
"message": "Most recent:",
"description": "Displayed in notifications when setting is 'name and message' and "
},
"messageNotSent": { "messageNotSent": {
"message": "Message not sent.", "message": "Message not sent.",
"description": "Informational label, appears on messages that failed to send" "description": "Informational label, appears on messages that failed to send"

@ -238,6 +238,10 @@
appView.openInbox(); appView.openInbox();
} }
}); });
window.addEventListener('focus', () => Whisper.Notifications.clear());
window.addEventListener('unload', () => Whisper.Notifications.clear());
Whisper.events.on('showConversation', function(conversation) { Whisper.events.on('showConversation', function(conversation) {
if (appView) { if (appView) {
appView.openConversation(conversation); appView.openConversation(conversation);

@ -1642,14 +1642,27 @@
'private' 'private'
).then(sender => ).then(sender =>
sender.getNotificationIcon().then(iconUrl => { sender.getNotificationIcon().then(iconUrl => {
console.log('adding notification'); const messageJSON = message.toJSON();
const messageSentAt = messageJSON.sent_at;
const messageId = message.id;
const isExpiringMessage = Signal.Types.Message.hasExpiration(
messageJSON
);
console.log('Add notification', {
conversationId: this.idForLogging(),
isExpiringMessage,
messageSentAt,
});
Whisper.Notifications.add({ Whisper.Notifications.add({
title: sender.getTitle(), conversationId,
message: message.getNotificationText(),
iconUrl, iconUrl,
imageUrl: message.getImageUrl(), imageUrl: message.getImageUrl(),
conversationId, isExpiringMessage,
messageId: message.id, message: message.getNotificationText(),
messageId,
messageSentAt,
title: sender.getTitle(),
}); });
}) })
); );

@ -7,6 +7,7 @@ const SchemaVersion = require('./schema_version');
const { const {
initializeAttachmentMetadata, initializeAttachmentMetadata,
} = require('../../../ts/types/message/initializeAttachmentMetadata'); } = require('../../../ts/types/message/initializeAttachmentMetadata');
const MessageTS = require('../../../ts/types/Message');
const GROUP = 'group'; const GROUP = 'group';
const PRIVATE = 'private'; const PRIVATE = 'private';
@ -381,3 +382,5 @@ exports.createAttachmentDataWriter = writeExistingAttachmentData => {
return messageWithoutAttachmentData; return messageWithoutAttachmentData;
}; };
}; };
exports.hasExpiration = MessageTS.hasExpiration;

@ -1,3 +1,4 @@
/* global Signal:false */
/* global Backbone: false */ /* global Backbone: false */
/* global ConversationController: false */ /* global ConversationController: false */
@ -16,7 +17,6 @@
const { Settings } = Signal.Types; const { Settings } = Signal.Types;
const SettingNames = { const SettingNames = {
OFF: 'off',
COUNT: 'count', COUNT: 'count',
NAME: 'name', NAME: 'name',
MESSAGE: 'message', MESSAGE: 'message',
@ -27,6 +27,8 @@
this.isEnabled = false; this.isEnabled = false;
this.on('add', this.update); this.on('add', this.update);
this.on('remove', this.onRemove); this.on('remove', this.onRemove);
this.lastNotification = null;
}, },
onClick(conversationId) { onClick(conversationId) {
const conversation = ConversationController.get(conversationId); const conversation = ConversationController.get(conversationId);
@ -74,36 +76,56 @@
// distinguishing between zero (0) and other (non-zero), // distinguishing between zero (0) and other (non-zero),
// e.g. Russian: // e.g. Russian:
// http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html // http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
const newMessageCount = [ const newMessageCountLabel = `${numNotifications} ${
numNotifications, numNotifications === 1 ? i18n('newMessage') : i18n('newMessages')
numNotifications === 1 ? i18n('newMessage') : i18n('newMessages'), }`;
].join(' ');
const last = this.last(); const last = this.last().toJSON();
switch (userSetting) { switch (userSetting) {
case SettingNames.COUNT: case SettingNames.COUNT:
title = 'Signal'; title = 'Signal';
message = newMessageCount; message = newMessageCountLabel;
break; break;
case SettingNames.NAME: case SettingNames.NAME: {
title = newMessageCount; const lastMessageTitle = last.title;
message = `Most recent from ${last.get('title')}`; title = newMessageCountLabel;
iconUrl = last.get('iconUrl'); // eslint-disable-next-line prefer-destructuring
iconUrl = last.iconUrl;
if (numNotifications === 1) {
message = `${i18n('notificationFrom')} ${lastMessageTitle}`;
} else {
message = `${i18n(
'notificationMostRecentFrom'
)} ${lastMessageTitle}`;
}
break; break;
}
case SettingNames.MESSAGE: case SettingNames.MESSAGE:
if (numNotifications === 1) { if (numNotifications === 1) {
title = last.get('title'); // eslint-disable-next-line prefer-destructuring
title = last.title;
// eslint-disable-next-line prefer-destructuring
message = last.message;
} else { } else {
title = newMessageCount; title = newMessageCountLabel;
message = `${i18n('notificationMostRecent')} ${last.message}`;
} }
message = last.get('message'); // eslint-disable-next-line prefer-destructuring
iconUrl = last.get('iconUrl'); iconUrl = last.iconUrl;
break; break;
default: default:
console.log(`Error: Unknown user setting: '${userSetting}'`); console.log(
`Error: Unknown user notification setting: '${userSetting}'`
);
break; break;
} }
const shouldHideExpiringMessageBody =
last.isExpiringMessage && Signal.OS.isMacOS();
if (shouldHideExpiringMessageBody) {
message = i18n('newMessage');
}
drawAttention(); drawAttention();
const notification = new Notification(title, { const notification = new Notification(title, {
@ -112,18 +134,31 @@
tag: isNotificationGroupingSupported ? 'signal' : undefined, tag: isNotificationGroupingSupported ? 'signal' : undefined,
silent: !status.shouldPlayNotificationSound, silent: !status.shouldPlayNotificationSound,
}); });
notification.onclick = () => this.onClick(last.get('conversationId')); notification.onclick = () => this.onClick(last.conversationId);
this.lastNotification = notification;
this.clear(); // We continue to build up more and more messages for our notifications
// until the user comes back to our app or closes the app. Then well
// clear everything out. The good news is that we'll have a maximum of
// 1 notification in the Notification area (something like
// 10 new messages) assuming that `Notification::close` does its job.
}, },
getUserSetting() { getUserSetting() {
return storage.get('notification-setting') || SettingNames.MESSAGE; return storage.get('notification-setting') || SettingNames.MESSAGE;
}, },
onRemove() { onRemove() {
console.log('Remove notification'); console.log('Remove notification');
if (this.length === 0) {
this.clear();
} else {
this.update();
}
}, },
clear() { clear() {
console.log('Remove all notifications'); console.log('Remove all notifications');
if (this.lastNotification) {
this.lastNotification.close();
}
this.reset([]); this.reset([]);
}, },
enable() { enable() {

@ -25,6 +25,7 @@
); );
}); });
if (message) { if (message) {
Whisper.Notifications.remove(message);
return message.markRead(receipt.get('read_at')).then( return message.markRead(receipt.get('read_at')).then(
function() { function() {
this.notifyConversation(message); this.notifyConversation(message);

@ -17,6 +17,7 @@ export type IncomingMessage = Readonly<
body?: string; body?: string;
decrypted_at?: number; decrypted_at?: number;
errors?: Array<any>; errors?: Array<any>;
expireTimer?: number;
flags?: number; flags?: number;
source?: string; source?: string;
sourceDevice?: number; sourceDevice?: number;
@ -89,3 +90,15 @@ type MessageSchemaVersion6 = Partial<
contact: Array<Contact>; contact: Array<Contact>;
}> }>
>; >;
export const isUserMessage = (message: Message): message is UserMessage =>
message.type === 'incoming' || message.type === 'outgoing';
export const hasExpiration = (message: Message): boolean => {
if (!isUserMessage(message)) {
return false;
}
const { expireTimer } = message;
return typeof expireTimer === 'number' && expireTimer > 0;
};

Loading…
Cancel
Save