Support for group-member verifications via second-level panel
Also: - All the necessary wire-up to update things in real time. If you have a safety number page up via a group member view as well as via a 1:1 conversation with that contact, they'll both be updated as the underlying model changes. Similarly, the overall group will update in real-time as members change. - A bit of special-casing for yourself in a group conversation - you're shown as 'me' and are not clickable, where normally that would take you to the Safety Number screen for that contact. You are also not included in the trust calculations for a given group. FREEBIE
This commit is contained in:
parent
ae3587f05e
commit
bedf10056b
12 changed files with 281 additions and 46 deletions
|
@ -37,6 +37,8 @@
|
|||
},
|
||||
|
||||
initialize: function() {
|
||||
this.ourNumber = textsecure.storage.user.getNumber();
|
||||
|
||||
this.contactCollection = new Backbone.Collection();
|
||||
this.messageCollection = new Whisper.MessageCollection([], {
|
||||
conversation: this
|
||||
|
@ -48,6 +50,84 @@
|
|||
this.on('destroy', this.revokeAvatarUrl);
|
||||
},
|
||||
|
||||
updateVerified: function() {
|
||||
// TODO: replace this with the real call
|
||||
function checkTrustStore() {
|
||||
return Promise.resolve('default');
|
||||
}
|
||||
|
||||
if (this.isPrivate()) {
|
||||
return Promise.all([
|
||||
checkTrustStore(this.id),
|
||||
this.fetch()
|
||||
]).then(function(results) {
|
||||
var trust = results[0];
|
||||
return this.save({verified: trust});
|
||||
});
|
||||
} else {
|
||||
return this.fetchContacts().then(function() {
|
||||
return Promise.all(this.contactCollection.map(function(contact) {
|
||||
if (contact.id !== this.myNumber) {
|
||||
return contact.updateVerified();
|
||||
}
|
||||
}.bind(this)));
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
isVerified: function() {
|
||||
if (this.isPrivate()) {
|
||||
return this.get('verified') === 'verified';
|
||||
} else {
|
||||
return this.contactCollection.every(function(contact) {
|
||||
if (contact.id === this.myNumber) {
|
||||
return true;
|
||||
} else {
|
||||
return contact.isVerified();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
isConflict: function() {
|
||||
if (this.isPrivate()) {
|
||||
var verified = this.get('verified');
|
||||
return verified !== 'verified' && verified !== 'default';
|
||||
} else {
|
||||
return Boolean(this.getConflicts().length);
|
||||
}
|
||||
},
|
||||
getConflicts: function() {
|
||||
if (this.isPrivate()) {
|
||||
return this.isConflict() ? [this] : [];
|
||||
} else {
|
||||
return this.contactCollection.filter(function(contact) {
|
||||
if (contact.id === this.myNumber) {
|
||||
return false;
|
||||
} else {
|
||||
return contact.isConflict();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
onMemberVerifiedChange: function() {
|
||||
// If the verified state of a member changes, our aggregate state changes.
|
||||
// We trigger both events to replicate the behavior of Backbone.Model.set()
|
||||
this.trigger('change:verified');
|
||||
this.trigger('change');
|
||||
},
|
||||
toggleVerified: function() {
|
||||
if (!this.isPrivate()) {
|
||||
throw new Error('You cannot verify a group conversation. ' +
|
||||
'You must verify individual contacts.');
|
||||
}
|
||||
|
||||
if (this.isVerified()) {
|
||||
this.save({verified: 'default'});
|
||||
} else {
|
||||
this.save({verified: 'verified'});
|
||||
}
|
||||
},
|
||||
|
||||
addKeyChange: function(id) {
|
||||
console.log('adding key change advisory for', this.id, this.get('timestamp'));
|
||||
var timestamp = Date.now();
|
||||
|
@ -373,12 +453,14 @@
|
|||
} else {
|
||||
var promises = [];
|
||||
var members = this.get('members') || [];
|
||||
|
||||
this.contactCollection.reset(
|
||||
members.map(function(number) {
|
||||
var c = ConversationController.create({
|
||||
id : number,
|
||||
type : 'private'
|
||||
});
|
||||
this.listenTo(c, 'change:verified', this.onMemberVerifiedChange);
|
||||
promises.push(new Promise(function(resolve) {
|
||||
c.fetch().always(resolve);
|
||||
}));
|
||||
|
|
|
@ -12,12 +12,41 @@
|
|||
tagName: 'div',
|
||||
className: 'contact',
|
||||
templateName: 'contact',
|
||||
events: {
|
||||
'click': 'showIdentity'
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.ourNumber = textsecure.storage.user.getNumber();
|
||||
this.listenBack = options.listenBack;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
render_attributes: function() {
|
||||
if (this.model.id === this.ourNumber) {
|
||||
return {
|
||||
class: 'not-clickable',
|
||||
title: i18n('me'),
|
||||
number: this.model.getNumber(),
|
||||
avatar: this.model.getAvatar()
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
class: '',
|
||||
title: this.model.getTitle(),
|
||||
number: this.model.getNumber(),
|
||||
avatar: this.model.getAvatar()
|
||||
avatar: this.model.getAvatar(),
|
||||
verified: this.model.isVerified()
|
||||
};
|
||||
},
|
||||
showIdentity: function() {
|
||||
if (this.model.id === this.ourNumber) {
|
||||
return;
|
||||
}
|
||||
var view = new Whisper.KeyVerificationPanelView({
|
||||
model: this.model
|
||||
});
|
||||
this.listenBack(view);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -57,6 +57,17 @@
|
|||
}
|
||||
});
|
||||
|
||||
Whisper.ConversationTitleView = Whisper.View.extend({
|
||||
templateName: 'conversation-title',
|
||||
render_attributes: function() {
|
||||
return {
|
||||
verified: this.model.isVerified(),
|
||||
name: this.model.getName(),
|
||||
number: this.model.getNumber(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Whisper.ConversationView = Whisper.View.extend({
|
||||
className: function() {
|
||||
return [ 'conversation', this.model.get('type') ].join(' ');
|
||||
|
@ -68,13 +79,11 @@
|
|||
render_attributes: function() {
|
||||
return {
|
||||
group: this.model.get('type') === 'group',
|
||||
name: this.model.getName(),
|
||||
number: this.model.getNumber(),
|
||||
avatar: this.model.getAvatar(),
|
||||
expireTimer: this.model.get('expireTimer'),
|
||||
'view-members' : i18n('members'),
|
||||
'show-members' : i18n('showMembers'),
|
||||
'end-session' : i18n('resetSession'),
|
||||
'show-identity' : i18n('showSafetyNumber'),
|
||||
'show-identity' : i18n('showSafetyNumber'),
|
||||
'destroy' : i18n('deleteMessages'),
|
||||
'send-message' : i18n('sendMessage'),
|
||||
'disappearing-messages': i18n('disappearingMessages'),
|
||||
|
@ -84,6 +93,7 @@
|
|||
},
|
||||
initialize: function(options) {
|
||||
this.listenTo(this.model, 'destroy', this.stopListening);
|
||||
this.listenTo(this.model, 'change', this.updateTitle);
|
||||
this.listenTo(this.model, 'change:color', this.updateColor);
|
||||
this.listenTo(this.model, 'change:name', this.updateTitle);
|
||||
this.listenTo(this.model, 'newmessage', this.addMessage);
|
||||
|
@ -103,6 +113,13 @@
|
|||
window: this.window
|
||||
});
|
||||
|
||||
this.titleView = new Whisper.ConversationTitleView({
|
||||
el: this.$('.conversation-title'),
|
||||
model: this.model
|
||||
});
|
||||
this.titleView.render();
|
||||
this.titleView.render();
|
||||
|
||||
this.view = new Whisper.MessageListView({
|
||||
collection: this.model.messageCollection,
|
||||
window: this.window
|
||||
|
@ -145,7 +162,7 @@
|
|||
'click .leave-group': 'leaveGroup',
|
||||
'click .update-group': 'newGroupUpdate',
|
||||
'click .show-identity': 'showIdentity',
|
||||
'click .view-members': 'viewMembers',
|
||||
'click .show-members': 'showMembers',
|
||||
'click .conversation-menu .hamburger': 'toggleMenu',
|
||||
'click .openInbox' : 'openInbox',
|
||||
'click' : 'onClick',
|
||||
|
@ -167,6 +184,10 @@
|
|||
'force-resize': 'forceUpdateMessageFieldSize',
|
||||
'show-identity': 'showIdentity'
|
||||
},
|
||||
|
||||
updateTitle: function() {
|
||||
this.titleView.render();
|
||||
},
|
||||
enableDisappearingMessages: function() {
|
||||
if (!this.model.get('expireTimer')) {
|
||||
this.model.updateExpirationTimer(
|
||||
|
@ -416,9 +437,12 @@
|
|||
this.model.messageCollection.add(message, {merge: true});
|
||||
},
|
||||
|
||||
viewMembers: function() {
|
||||
showMembers: function() {
|
||||
return this.model.fetchContacts().then(function() {
|
||||
var view = new Whisper.GroupMemberList({ model: this.model });
|
||||
var view = new Whisper.GroupMemberList({
|
||||
model: this.model,
|
||||
listenBack: this.listenBack.bind(this)
|
||||
});
|
||||
this.listenBack(view);
|
||||
}.bind(this));
|
||||
},
|
||||
|
@ -515,16 +539,25 @@
|
|||
},
|
||||
|
||||
listenBack: function(view) {
|
||||
this.panel = view;
|
||||
this.$('.main.panel, .header-buttons.right').hide();
|
||||
this.$('.back').show();
|
||||
view.$el.insertBefore(this.$('.panel'));
|
||||
this.panels = this.panels || [];
|
||||
this.panels.unshift(view);
|
||||
|
||||
if (this.panels.length === 1) {
|
||||
this.$('.main.panel, .header-buttons.right').hide();
|
||||
this.$('.back').show();
|
||||
}
|
||||
|
||||
view.$el.insertBefore(this.$('.panel').first());
|
||||
},
|
||||
resetPanel: function() {
|
||||
this.panel.remove();
|
||||
this.$('.main.panel, .header-buttons.right').show();
|
||||
this.$('.back').hide();
|
||||
this.$el.trigger('force-resize');
|
||||
var view = this.panels.shift();
|
||||
view.remove();
|
||||
|
||||
if (this.panels.length === 0) {
|
||||
this.$('.main.panel, .header-buttons.right').show();
|
||||
this.$('.back').hide();
|
||||
this.$el.trigger('force-resize');
|
||||
}
|
||||
},
|
||||
|
||||
closeMenu: function(e) {
|
||||
|
@ -614,10 +647,6 @@
|
|||
});
|
||||
},
|
||||
|
||||
updateTitle: function() {
|
||||
this.$('.conversation-title').text(this.model.getTitle());
|
||||
},
|
||||
|
||||
updateColor: function(model, color) {
|
||||
var header = this.$('.conversation-header');
|
||||
header.removeClass(Whisper.Conversation.COLORS);
|
||||
|
|
|
@ -13,17 +13,22 @@
|
|||
Whisper.GroupMemberList = Whisper.View.extend({
|
||||
className: 'group-member-list panel',
|
||||
templateName: 'group-member-list',
|
||||
initialize: function() {
|
||||
initialize: function(options) {
|
||||
this.render();
|
||||
console.log('GroupMemberList', options);
|
||||
|
||||
this.member_list_view = new Whisper.ContactListView({
|
||||
collection: this.model.contactCollection,
|
||||
className: 'members'
|
||||
className: 'members',
|
||||
toInclude: {
|
||||
listenBack: options.listenBack
|
||||
}
|
||||
});
|
||||
this.member_list_view.render();
|
||||
this.$('.container').append(this.member_list_view.el);
|
||||
},
|
||||
render_attributes: {
|
||||
members: i18n('members')
|
||||
members: i18n('groupMembers')
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -5,25 +5,30 @@
|
|||
'use strict';
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
// TODO; find all uses of that removed panel
|
||||
// Add the Verify functionality to this view
|
||||
Whisper.KeyVerificationPanelView = Whisper.View.extend({
|
||||
className: 'key-verification panel',
|
||||
templateName: 'key-verification',
|
||||
events: {
|
||||
'click button.verify': 'toggleVerified',
|
||||
},
|
||||
initialize: function(options) {
|
||||
this.our_number = textsecure.storage.user.getNumber();
|
||||
if (options.newKey) {
|
||||
this.their_key = options.newKey;
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
this.loadTheirKey(),
|
||||
this.loadOurKey(),
|
||||
]).then(this.generateSecurityNumber.bind(this))
|
||||
.then(function() {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}.bind(this))
|
||||
.then(this.render.bind(this));
|
||||
//.then(this.makeQRCode.bind(this));
|
||||
},
|
||||
makeQRCode: function() {
|
||||
// Per Lilia: We can't turn this on until it geneates a Latin1 string, as is
|
||||
// Per Lilia: We can't turn this on until it generates a Latin1 string, as is
|
||||
// required by the mobile clients.
|
||||
new QRCode(this.$('.qr')[0]).makeCode(
|
||||
dcodeIO.ByteBuffer.wrap(this.our_key).toString('base64')
|
||||
|
@ -58,6 +63,9 @@
|
|||
this.securityNumber = securityNumber;
|
||||
}.bind(this));
|
||||
},
|
||||
toggleVerified: function() {
|
||||
this.model.toggleVerified();
|
||||
},
|
||||
render_attributes: function() {
|
||||
var s = this.securityNumber;
|
||||
var chunks = [];
|
||||
|
@ -67,10 +75,15 @@
|
|||
var yourSafetyNumberWith = i18n(
|
||||
'yourSafetyNumberWith', this.model.getTitle()
|
||||
);
|
||||
console.log('this.model',this.model);
|
||||
var verifyButton = this.model.isVerified() ? i18n('markAsNotVerified') : i18n('verify');
|
||||
|
||||
return {
|
||||
learnMore : i18n('learnMore'),
|
||||
their_key_unknown : i18n('theirIdentityUnknown'),
|
||||
yourSafetyNumberWith : i18n('yourSafetyNumberWith', this.model.getTitle()),
|
||||
verifyHelp : i18n('verifyHelp', this.model.getTitle()),
|
||||
verifyButton : verifyButton,
|
||||
has_their_key : this.their_key !== undefined,
|
||||
chunks : chunks,
|
||||
};
|
||||
|
|
|
@ -13,13 +13,15 @@
|
|||
tagName: 'ul',
|
||||
itemView: Backbone.View,
|
||||
initialize: function(options) {
|
||||
this.options = options || {};
|
||||
this.listenTo(this.collection, 'add', this.addOne);
|
||||
this.listenTo(this.collection, 'reset', this.addAll);
|
||||
},
|
||||
|
||||
addOne: function(model) {
|
||||
if (this.itemView) {
|
||||
var view = new this.itemView({model: model});
|
||||
var options = Object.assign({}, this.options.toInclude, {model: model});
|
||||
var view = new this.itemView(options);
|
||||
this.$el.append(view.render().el);
|
||||
this.$el.trigger('add');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue