Notification improvements
- Remove on read, on focus, and on exit. - Show multi-message notifications like '5 new messages'.
This commit is contained in:
parent
f693c00778
commit
3bf8a8966a
7 changed files with 104 additions and 23 deletions
|
@ -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 we’ll
|
||||||
|
// 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…
Add table
Add a link
Reference in a new issue