Calling support

This commit is contained in:
Peter Thatcher 2020-06-04 11:16:19 -07:00 committed by Scott Nonnenberg
parent 83574eb067
commit d3a27a6442
72 changed files with 3864 additions and 191 deletions

View file

@ -367,12 +367,27 @@
storage.put('notification-setting', value),
getAudioNotification: () => storage.get('audio-notification'),
setAudioNotification: value => storage.put('audio-notification', value),
getCallRingtoneNotification: () =>
storage.get('call-ringtone-notification', true),
setCallRingtoneNotification: value =>
storage.put('call-ringtone-notification', value),
getCallSystemNotification: () =>
storage.get('call-system-notification', true),
setCallSystemNotification: value =>
storage.put('call-system-notification', value),
getIncomingCallNotification: () =>
storage.get('incoming-call-notification', true),
setIncomingCallNotification: value =>
storage.put('incoming-call-notification', value),
getSpellCheck: () => storage.get('spell-check', true),
setSpellCheck: value => {
storage.put('spell-check', value);
},
getAlwaysRelayCalls: () => storage.get('always-relay-calls'),
setAlwaysRelayCalls: value => storage.put('always-relay-calls', value),
// eslint-disable-next-line eqeqeq
isPrimary: () => textsecure.storage.user.getDeviceId() == '1',
getSyncRequest: () =>
@ -586,6 +601,7 @@
window.reduxActions.updates,
window.Whisper.events
);
window.Signal.Services.calling.initialize(window.reduxActions.calling);
window.reduxActions.expiration.hydrateExpirationStatus(
window.Signal.Util.hasExpired()
);
@ -638,6 +654,10 @@
// Binding these actions to our redux store and exposing them allows us to update
// redux when things change in the backbone world.
actions.calling = Signal.State.bindActionCreators(
Signal.State.Ducks.calling.actions,
store.dispatch
);
actions.conversations = Signal.State.bindActionCreators(
Signal.State.Ducks.conversations.actions,
store.dispatch

View file

@ -1033,6 +1033,30 @@
}
},
async addCallHistory(callHistoryDetails) {
const { acceptedTime, endedTime, wasDeclined } = callHistoryDetails;
const message = {
conversationId: this.id,
type: 'call-history',
sent_at: endedTime,
received_at: endedTime,
unread: !wasDeclined && !acceptedTime,
callHistoryDetails,
};
const id = await window.Signal.Data.saveMessage(message, {
Message: Whisper.Message,
});
const model = MessageController.register(
id,
new Whisper.Message({
...message,
id,
})
);
this.trigger('newmessage', model);
},
async onReadMessage(message, readAt) {
// We mark as read everything older than this message - to clean up old stuff
// still marked unread in the database. If the user generally doesn't read in

View file

@ -178,6 +178,11 @@
type: 'resetSessionNotification',
data: this.getPropsForResetSessionNotification(),
};
} else if (this.isCallHistory()) {
return {
type: 'callHistory',
data: this.getPropsForCallHistory(),
};
}
return {
@ -366,6 +371,9 @@
// eslint-disable-next-line no-bitwise
return !!(this.get('flags') & flag);
},
isCallHistory() {
return this.get('type') === 'call-history';
},
// Props for each message type
getPropsForUnsupportedMessage() {
@ -500,6 +508,11 @@
// It doesn't need anything right now!
return {};
},
getPropsForCallHistory() {
return {
callHistoryDetails: this.get('callHistoryDetails'),
};
},
getAttachmentsForMessage() {
const sticker = this.get('sticker');
if (sticker && sticker.data) {

View file

@ -49,6 +49,7 @@ const { createTimeline } = require('../../ts/state/roots/createTimeline');
const {
createCompositionArea,
} = require('../../ts/state/roots/createCompositionArea');
const { createCallManager } = require('../../ts/state/roots/createCallManager');
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
const {
createStickerManager,
@ -61,6 +62,7 @@ const {
} = require('../../ts/state/roots/createShortcutGuideModal');
const { createStore } = require('../../ts/state/createStore');
const callingDuck = require('../../ts/state/ducks/calling');
const conversationsDuck = require('../../ts/state/ducks/conversations');
const emojisDuck = require('../../ts/state/ducks/emojis');
const expirationDuck = require('../../ts/state/ducks/expiration');
@ -100,6 +102,8 @@ const {
const {
initializeUpdateListener,
} = require('../../ts/services/updateListener');
const { notify } = require('../../ts/services/notify');
const { calling } = require('../../ts/services/calling');
function initializeMigrations({
userDataPath,
@ -277,6 +281,7 @@ exports.setup = (options = {}) => {
};
const Roots = {
createCallManager,
createCompositionArea,
createLeftPane,
createShortcutGuideModal,
@ -286,6 +291,7 @@ exports.setup = (options = {}) => {
};
const Ducks = {
calling: callingDuck,
conversations: conversationsDuck,
emojis: emojisDuck,
expiration: expirationDuck,
@ -305,6 +311,8 @@ exports.setup = (options = {}) => {
const Services = {
initializeNetworkObserver,
initializeUpdateListener,
notify,
calling,
};
const State = {

View file

@ -21,15 +21,6 @@
MESSAGE: 'message',
};
function filter(text) {
return (text || '')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
Whisper.Notifications = new (Backbone.Collection.extend({
initialize() {
this.isEnabled = false;
@ -164,13 +155,16 @@
drawAttention();
this.lastNotification = new Notification(title, {
body: window.platform === 'linux' ? filter(message) : message,
this.lastNotification = window.Signal.Services.notify({
platform: window.platform,
title,
icon: iconUrl,
message,
silent: !status.shouldPlayNotificationSound,
onNotificationClick: () => {
this.trigger('click', last.conversationId, last.messageId);
},
});
this.lastNotification.onclick = () =>
this.trigger('click', last.conversationId, last.messageId);
// 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

View file

@ -27,13 +27,28 @@ window.subscribeToSystemThemeChange(() => {
applyTheme();
});
let message;
if (window.forCalling) {
if (window.forCamera) {
message = i18n('videoCallingPermissionNeeded');
} else {
message = i18n('audioCallingPermissionNeeded');
}
} else {
message = i18n('audioPermissionNeeded');
}
window.view = new Whisper.ConfirmationDialogView({
message: i18n('audioPermissionNeeded'),
message,
okText: i18n('allowAccess'),
resolve: () => {
'use strict';
window.setMediaPermissions(true);
if (!window.forCamera) {
window.setMediaPermissions(true);
} else {
window.setMediaCameraPermissions(true);
}
window.closePermissionsPopup();
},
reject: window.closePermissionsPopup,

View file

@ -39,7 +39,13 @@ const getInitialData = async () => ({
spellCheck: await window.getSpellCheck(),
incomingCallNotification: await window.getIncomingCallNotification(),
callRingtoneNotification: await window.getCallRingtoneNotification(),
callSystemNotification: await window.getCallSystemNotification(),
alwaysRelayCalls: await window.getAlwaysRelayCalls(),
mediaPermissions: await window.getMediaPermissions(),
mediaCameraPermissions: await window.getMediaCameraPermissions(),
isPrimary: await window.isPrimary(),
lastSyncTime: await window.getLastSyncTime(),

View file

@ -395,6 +395,22 @@
// These are view only and don't update the Conversation model, so they
// need a manual update call.
onOutgoingAudioCallInConversation: async () => {
const conversation = this.model;
const isVideoCall = false;
await window.Signal.Services.calling.startOutgoingCall(
conversation,
isVideoCall
);
},
onOutgoingVideoCallInConversation: async () => {
const conversation = this.model;
const isVideoCall = true;
await window.Signal.Services.calling.startOutgoingCall(
conversation,
isVideoCall
);
},
onShowSafetyNumber: () => {
this.showSafetyNumber();
},

View file

@ -91,6 +91,7 @@
this.startConnectionListener();
} else {
this.setupLeftPane();
this.setupCallManagerUI();
}
Whisper.events.on('pack-install-failed', () => {
@ -106,6 +107,19 @@
events: {
click: 'onClick',
},
setupCallManagerUI() {
if (!window.CALLING) {
return;
}
if (this.callManagerView) {
return;
}
this.callManagerView = new Whisper.ReactWrapperView({
className: 'call-manager-wrapper',
JSX: Signal.State.Roots.createCallManager(window.reduxStore),
});
this.$('.call-manager-placeholder').append(this.callManagerView.el);
},
setupLeftPane() {
if (this.leftPaneView) {
return;
@ -144,6 +158,7 @@
},
onEmpty() {
this.setupLeftPane();
this.setupCallManagerUI();
const view = this.appLoadingScreen;
if (view) {

View file

@ -50,6 +50,25 @@
},
});
const MediaCameraPermissionsSettingView = Whisper.View.extend({
initialize(options) {
this.value = options.value;
this.setFn = options.setFn;
this.populate();
},
events: {
change: 'change',
},
change(e) {
this.value = e.target.checked;
this.setFn(this.value);
window.log.info('media-camera-permissions changed to', this.value);
},
populate() {
this.$('input').prop('checked', Boolean(this.value));
},
});
const RadioButtonGroupView = Whisper.View.extend({
initialize(options) {
this.name = options.name;
@ -126,11 +145,40 @@
setFn: window.setHideMenuBar,
});
}
new CheckboxView({
el: this.$('.always-relay-calls-setting'),
name: 'always-relay-calls-setting',
value: window.initialData.alwaysRelayCalls,
setFn: window.setAlwaysRelayCalls,
});
new CheckboxView({
el: this.$('.call-ringtone-notification-setting'),
name: 'call-ringtone-notification-setting',
value: window.initialData.callRingtoneNotification,
setFn: window.setCallRingtoneNotification,
});
new CheckboxView({
el: this.$('.call-system-notification-setting'),
name: 'call-system-notification-setting',
value: window.initialData.callSystemNotification,
setFn: window.setCallSystemNotification,
});
new CheckboxView({
el: this.$('.incoming-call-notification-setting'),
name: 'incoming-call-notification-setting',
value: window.initialData.incomingCallNotification,
setFn: window.setIncomingCallNotification,
});
new MediaPermissionsSettingView({
el: this.$('.media-permissions'),
value: window.initialData.mediaPermissions,
setFn: window.setMediaPermissions,
});
new MediaCameraPermissionsSettingView({
el: this.$('.media-camera-permissions'),
value: window.initialData.mediaCameraPermissions,
setFn: window.setMediaCameraPermissions,
});
if (!window.initialData.isPrimary) {
const syncView = new SyncView().render();
this.$('.sync-setting').append(syncView.el);
@ -167,8 +215,23 @@
clearDataHeader: i18n('clearDataHeader'),
clearDataButton: i18n('clearDataButton'),
clearDataExplanation: i18n('clearDataExplanation'),
calling: i18n('calling'),
alwaysRelayCallsDescription: i18n('alwaysRelayCallsDescription'),
alwaysRelayCallsDetail: i18n('alwaysRelayCallsDetail'),
callRingtoneNotificationDescription: i18n(
'callRingtoneNotificationDescription'
),
callSystemNotificationDescription: i18n(
'callSystemNotificationDescription'
),
incomingCallNotificationDescription: i18n(
'incomingCallNotificationDescription'
),
permissions: i18n('permissions'),
mediaPermissionsDescription: i18n('mediaPermissionsDescription'),
mediaCameraPermissionsDescription: i18n(
'mediaCameraPermissionsDescription'
),
generalHeader: i18n('general'),
spellCheckDescription: i18n('spellCheckDescription'),
spellCheckHidden: spellCheckDirty ? 'false' : 'true',