Frontend for timer updates and timer indicator
This commit is contained in:
parent
448835e4d5
commit
2b2c6ab040
10 changed files with 242 additions and 26 deletions
|
@ -360,5 +360,77 @@
|
||||||
"example": "10m"
|
"example": "10m"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"timerOption_0_seconds": {
|
||||||
|
"message": "off",
|
||||||
|
"description": "Label for option to turn off message expiration in the timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_5_seconds": {
|
||||||
|
"message": "5 seconds",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_10_seconds": {
|
||||||
|
"message": "10 seconds",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_30_seconds": {
|
||||||
|
"message": "30 seconds",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_1_day": {
|
||||||
|
"message": "1 day",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_1_week": {
|
||||||
|
"message": "1 week",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"disappearingMessages": {
|
||||||
|
"message": "Disappearing messages",
|
||||||
|
"description": "Conversation menu option to enable disappearing messages"
|
||||||
|
},
|
||||||
|
"timerOption_5_seconds_abbreviated": {
|
||||||
|
"message": "5s",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_10_seconds_abbreviated": {
|
||||||
|
"message": "10s",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_30_seconds_abbreviated": {
|
||||||
|
"message": "30s",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_1_minute_abbreviated": {
|
||||||
|
"message": "1m",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_5_minutes_abbreviated": {
|
||||||
|
"message": "5m",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_30_minutes_abbreviated": {
|
||||||
|
"message": "30m",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_1_hour_abbreviated": {
|
||||||
|
"message": "1h",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_6_hours_abbreviated": {
|
||||||
|
"message": "6h",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_12_hours_abbreviated": {
|
||||||
|
"message": "12h",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_1_day_abbreviated": {
|
||||||
|
"message": "1d",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
|
},
|
||||||
|
"timerOption_1_week_abbreviated": {
|
||||||
|
"message": "1w",
|
||||||
|
"description": "Label for a selectable option in the message expiration timer menu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,9 +75,18 @@
|
||||||
<li class='end-session'>{{ end-session }}</li>
|
<li class='end-session'>{{ end-session }}</li>
|
||||||
<li class='verify-identity'>{{ verify-identity }}</li>
|
<li class='verify-identity'>{{ verify-identity }}</li>
|
||||||
{{/group}}
|
{{/group}}
|
||||||
|
<li class='disappearing-messages'>{{ disappearing-messages }}</li>
|
||||||
<li class='destroy'>{{ destroy }}</li>
|
<li class='destroy'>{{ destroy }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class='timer-menu menu'>
|
||||||
|
<button class='clock' alt='timer menu'></button>
|
||||||
|
<ul class='menu-list'>
|
||||||
|
{{ #timer_options }}
|
||||||
|
<li data-seconds={{ attributes.seconds }}>{{ getName }}</li>
|
||||||
|
{{ /timer_options }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class='conversation-title'>
|
<span class='conversation-title'>
|
||||||
|
|
|
@ -11,4 +11,55 @@
|
||||||
this.fetchExpiring();
|
this.fetchExpiring();
|
||||||
}
|
}
|
||||||
}))();
|
}))();
|
||||||
|
|
||||||
|
var TimerOption = Backbone.Model.extend({
|
||||||
|
getName: function() {
|
||||||
|
return i18n([
|
||||||
|
'timerOption', this.get('time'), this.get('unit'),
|
||||||
|
].join('_')) || moment.duration(this.get('time'), this.get('unit')).humanize();
|
||||||
|
},
|
||||||
|
getAbbreviated: function() {
|
||||||
|
return i18n([
|
||||||
|
'timerOption', this.get('time'), this.get('unit'), 'abbreviated'
|
||||||
|
].join('_'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Whisper.ExpirationTimerOptions = new (Backbone.Collection.extend({
|
||||||
|
model: TimerOption,
|
||||||
|
getName: function(seconds) {
|
||||||
|
if (!seconds) {
|
||||||
|
seconds = 0;
|
||||||
|
}
|
||||||
|
var o = this.findWhere({seconds: seconds});
|
||||||
|
if (o) { return o.getName(); }
|
||||||
|
},
|
||||||
|
getAbbreviated: function(seconds) {
|
||||||
|
if (!seconds) {
|
||||||
|
seconds = 0;
|
||||||
|
}
|
||||||
|
var o = this.findWhere({seconds: seconds});
|
||||||
|
if (o) { return o.getAbbreviated(); }
|
||||||
|
}
|
||||||
|
}))([
|
||||||
|
[ 0, 'seconds' ],
|
||||||
|
[ 5, 'seconds' ],
|
||||||
|
[ 10, 'seconds' ],
|
||||||
|
[ 30, 'seconds' ],
|
||||||
|
[ 1, 'minute' ],
|
||||||
|
[ 5, 'minutes' ],
|
||||||
|
[ 30, 'minutes' ],
|
||||||
|
[ 1, 'hour' ],
|
||||||
|
[ 6, 'hours' ],
|
||||||
|
[ 12, 'hours' ],
|
||||||
|
[ 1, 'day' ],
|
||||||
|
[ 1, 'week' ],
|
||||||
|
].map(function(o) {
|
||||||
|
var duration = moment.duration(o[0], o[1]); // 5, 'seconds'
|
||||||
|
return {
|
||||||
|
time: o[0],
|
||||||
|
unit: o[1],
|
||||||
|
seconds: duration.asSeconds()
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -166,20 +166,26 @@
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
addExpirationTimerUpdate: function(source, time) {
|
addExpirationTimerUpdate: function(time, source) {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
|
this.save({ expireTimer: time });
|
||||||
var message = this.messageCollection.add({
|
var message = this.messageCollection.add({
|
||||||
conversationId : this.id,
|
conversationId : this.id,
|
||||||
type : 'expirationTimerUpdate',
|
type : 'outgoing',
|
||||||
sent_at : now,
|
sent_at : now,
|
||||||
received_at : now,
|
received_at : now,
|
||||||
timerUpdate : {
|
flags : textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||||
|
expirationTimerUpdate : {
|
||||||
expireTimer : time,
|
expireTimer : time,
|
||||||
source : source
|
source : source
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
message.save();
|
message.save();
|
||||||
},
|
},
|
||||||
|
sendExpirationTimerUpdate: function(time) {
|
||||||
|
this.addExpirationTimerUpdate(time, textsecure.storage.user.getNumber());
|
||||||
|
// todo: send.
|
||||||
|
},
|
||||||
|
|
||||||
isSearchable: function() {
|
isSearchable: function() {
|
||||||
return !this.get('left') || !!this.get('lastMessage');
|
return !this.get('left') || !!this.get('lastMessage');
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
if (this.isIncoming() && this.hasErrors()) {
|
if (this.isIncoming() && this.hasErrors()) {
|
||||||
return i18n('incomingError');
|
return i18n('incomingError');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get('body');
|
return this.get('body');
|
||||||
},
|
},
|
||||||
getNotificationText: function() {
|
getNotificationText: function() {
|
||||||
|
@ -126,15 +125,6 @@
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
},
|
},
|
||||||
getModelForExpirationTimerUpdate: function() {
|
|
||||||
var id = this.get('timerUpdate').source;
|
|
||||||
var c = ConversationController.get(id);
|
|
||||||
if (!c) {
|
|
||||||
c = ConversationController.create({ id: id, type: 'private' });
|
|
||||||
c.fetch();
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
},
|
|
||||||
isOutgoing: function() {
|
isOutgoing: function() {
|
||||||
return this.get('type') === 'outgoing';
|
return this.get('type') === 'outgoing';
|
||||||
},
|
},
|
||||||
|
@ -364,9 +354,18 @@
|
||||||
flags : dataMessage.flags,
|
flags : dataMessage.flags,
|
||||||
errors : []
|
errors : []
|
||||||
});
|
});
|
||||||
|
if (message.isExpirationTimerUpdate()) {
|
||||||
if (dataMessage.expireTimer) {
|
message.set({
|
||||||
|
expirationTimerUpdate: {
|
||||||
|
source : source,
|
||||||
|
expireTimer : dataMessage.expireTimer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
conversation.set({expireTimer: dataMessage.expireTimer});
|
||||||
|
} else if (dataMessage.expireTimer) {
|
||||||
message.set({expireTimer: dataMessage.expireTimer});
|
message.set({expireTimer: dataMessage.expireTimer});
|
||||||
|
// todo: insert an update if needed
|
||||||
|
conversation.set({expireTimer: dataMessage.expireTimer});
|
||||||
}
|
}
|
||||||
|
|
||||||
var conversation_timestamp = conversation.get('timestamp');
|
var conversation_timestamp = conversation.get('timestamp');
|
||||||
|
|
|
@ -16,6 +16,39 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var MenuView = Whisper.View.extend({
|
||||||
|
toggleMenu: function() {
|
||||||
|
this.$('.menu-list').toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var TimerMenuView = MenuView.extend({
|
||||||
|
initialize: function() {
|
||||||
|
this.render();
|
||||||
|
this.listenTo(this.model, 'change:expireTimer', this.render);
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
'click button': 'toggleMenu',
|
||||||
|
'click li': 'setTimer'
|
||||||
|
},
|
||||||
|
setTimer: function(e) {
|
||||||
|
var seconds = this.$(e.target).data().seconds;
|
||||||
|
if (seconds >= 0) {
|
||||||
|
this.model.sendExpirationTimerUpdate(seconds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var seconds = this.model.get('expireTimer');
|
||||||
|
if (seconds) {
|
||||||
|
var s = Whisper.ExpirationTimerOptions.getAbbreviated(seconds);
|
||||||
|
this.$el.attr('data-time', s);
|
||||||
|
this.$el.show();
|
||||||
|
} else {
|
||||||
|
this.$el.attr('data-time', null);
|
||||||
|
this.$el.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.ConversationView = Whisper.View.extend({
|
Whisper.ConversationView = Whisper.View.extend({
|
||||||
className: function() {
|
className: function() {
|
||||||
return [ 'conversation', this.model.get('type') ].join(' ');
|
return [ 'conversation', this.model.get('type') ].join(' ');
|
||||||
|
@ -30,11 +63,14 @@
|
||||||
name: this.model.getName(),
|
name: this.model.getName(),
|
||||||
number: this.model.getNumber(),
|
number: this.model.getNumber(),
|
||||||
avatar: this.model.getAvatar(),
|
avatar: this.model.getAvatar(),
|
||||||
|
expireTimer: this.model.get('expireTimer'),
|
||||||
'view-members' : i18n('members'),
|
'view-members' : i18n('members'),
|
||||||
'end-session' : i18n('resetSession'),
|
'end-session' : i18n('resetSession'),
|
||||||
'verify-identity' : i18n('verifyIdentity'),
|
'verify-identity' : i18n('verifyIdentity'),
|
||||||
'destroy' : i18n('deleteMessages'),
|
'destroy' : i18n('deleteMessages'),
|
||||||
'send-message' : i18n('sendMessage')
|
'send-message' : i18n('sendMessage'),
|
||||||
|
'disappearing-messages': i18n('disappearingMessages'),
|
||||||
|
timer_options : Whisper.ExpirationTimerOptions.models
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
|
@ -47,6 +83,7 @@
|
||||||
this.listenTo(this.model.messageCollection, 'expired', this.onExpiredCollection);
|
this.listenTo(this.model.messageCollection, 'expired', this.onExpiredCollection);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
new TimerMenuView({ el: this.$('.timer-menu'), model: this.model });
|
||||||
|
|
||||||
emoji_util.parse(this.$('.conversation-name'));
|
emoji_util.parse(this.$('.conversation-name'));
|
||||||
|
|
||||||
|
@ -105,6 +142,7 @@
|
||||||
'click .bottom-bar': 'focusMessageField',
|
'click .bottom-bar': 'focusMessageField',
|
||||||
'click .back': 'resetPanel',
|
'click .back': 'resetPanel',
|
||||||
'click .microphone': 'captureAudio',
|
'click .microphone': 'captureAudio',
|
||||||
|
'click .disappearing-messages': 'enableDisappearingMessages',
|
||||||
'focus .send-message': 'focusBottomBar',
|
'focus .send-message': 'focusBottomBar',
|
||||||
'change .file-input': 'toggleMicrophone',
|
'change .file-input': 'toggleMicrophone',
|
||||||
'blur .send-message': 'unfocusBottomBar',
|
'blur .send-message': 'unfocusBottomBar',
|
||||||
|
@ -113,6 +151,13 @@
|
||||||
'select .message-list .entry': 'messageDetail',
|
'select .message-list .entry': 'messageDetail',
|
||||||
'force-resize': 'forceUpdateMessageFieldSize'
|
'force-resize': 'forceUpdateMessageFieldSize'
|
||||||
},
|
},
|
||||||
|
enableDisappearingMessages: function() {
|
||||||
|
if (!this.model.get('expireTimer')) {
|
||||||
|
this.model.sendExpirationTimerUpdate(
|
||||||
|
moment.duration(1, 'day').asSeconds()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
toggleMicrophone: function() {
|
toggleMicrophone: function() {
|
||||||
if (this.$('.send-message').val().length > 0 || this.fileInput.hasFiles()) {
|
if (this.$('.send-message').val().length > 0 || this.fileInput.hasFiles()) {
|
||||||
this.$('.capture-audio').hide();
|
this.$('.capture-audio').hide();
|
||||||
|
@ -240,7 +285,10 @@
|
||||||
|
|
||||||
closeMenu: function(e) {
|
closeMenu: function(e) {
|
||||||
if (e && !$(e.target).hasClass('hamburger')) {
|
if (e && !$(e.target).hasClass('hamburger')) {
|
||||||
this.$('.menu-list').hide();
|
this.$('.conversation-menu .menu-list').hide();
|
||||||
|
}
|
||||||
|
if (e && !$(e.target).hasClass('clock')) {
|
||||||
|
this.$('.timer-menu .menu-list').hide();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -255,7 +303,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleMenu: function() {
|
toggleMenu: function() {
|
||||||
this.$('.menu-list').toggle();
|
this.$('.conversation-menu .menu-list').toggle();
|
||||||
},
|
},
|
||||||
|
|
||||||
newGroupUpdate: function() {
|
newGroupUpdate: function() {
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
},
|
},
|
||||||
addOne: function(model) {
|
addOne: function(model) {
|
||||||
var view;
|
var view;
|
||||||
if (model.get('type') === 'expirationTimerUpdate') {
|
if (model.isExpirationTimerUpdate()) {
|
||||||
view = new Whisper.ExpirationTimerUpdateView({model: model}).render();
|
view = new Whisper.ExpirationTimerUpdateView({model: model}).render();
|
||||||
} else {
|
} else {
|
||||||
view = new this.itemView({model: model}).render();
|
view = new this.itemView({model: model}).render();
|
||||||
|
|
|
@ -48,14 +48,16 @@
|
||||||
className: 'expirationTimerUpdate advisory',
|
className: 'expirationTimerUpdate advisory',
|
||||||
templateName: 'expirationTimerUpdate',
|
templateName: 'expirationTimerUpdate',
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
this.conversation = this.model.getModelForExpirationTimerUpdate();
|
this.conversation = this.model.getContact();
|
||||||
this.listenTo(this.conversation, 'change', this.render);
|
this.listenTo(this.conversation, 'change', this.render);
|
||||||
},
|
},
|
||||||
render_attributes: function() {
|
render_attributes: function() {
|
||||||
|
var seconds = this.model.get('expirationTimerUpdate').expireTimer;
|
||||||
return {
|
return {
|
||||||
content: i18n('changedTheTimer',
|
content: i18n('changedTheTimer', [
|
||||||
this.conversation.getTitle(),
|
this.conversation.getTitle(),
|
||||||
this.model.get('timerUpdate').time)
|
Whisper.ExpirationTimerOptions.getName(seconds)
|
||||||
|
])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -123,6 +123,22 @@ button.hamburger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation-header .timer-menu {
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: attr(data-time);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
height: 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
|
@ -126,6 +126,19 @@ button.hamburger {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: table-cell; }
|
display: table-cell; }
|
||||||
|
|
||||||
|
.conversation-header .timer-menu {
|
||||||
|
margin-right: 10px; }
|
||||||
|
.conversation-header .timer-menu:before {
|
||||||
|
content: attr(data-time);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
height: 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: bold; }
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
float: right; }
|
float: right; }
|
||||||
|
|
Loading…
Reference in a new issue