Calling support
This commit is contained in:
parent
83574eb067
commit
d3a27a6442
72 changed files with 3864 additions and 191 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -21,15 +21,6 @@
|
|||
MESSAGE: 'message',
|
||||
};
|
||||
|
||||
function filter(text) {
|
||||
return (text || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
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 we’ll
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue