diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 18c30414..88c46a17 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -360,5 +360,77 @@
"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"
}
}
diff --git a/background.html b/background.html
index 2d172491..142f6941 100644
--- a/background.html
+++ b/background.html
@@ -75,9 +75,18 @@
{{ end-session }}
{{ verify-identity }}
{{/group}}
+ {{ disappearing-messages }}
{{ destroy }}
+
diff --git a/js/expiring_messages.js b/js/expiring_messages.js
index 40fa5565..ab7b0ce4 100644
--- a/js/expiring_messages.js
+++ b/js/expiring_messages.js
@@ -11,4 +11,55 @@
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()
+ };
+ }));
+
})();
diff --git a/js/models/conversations.js b/js/models/conversations.js
index 54d81f2f..49f8c6be 100644
--- a/js/models/conversations.js
+++ b/js/models/conversations.js
@@ -166,20 +166,26 @@
}.bind(this));
},
- addExpirationTimerUpdate: function(source, time) {
+ addExpirationTimerUpdate: function(time, source) {
var now = Date.now();
+ this.save({ expireTimer: time });
var message = this.messageCollection.add({
- conversationId : this.id,
- type : 'expirationTimerUpdate',
- sent_at : now,
- received_at : now,
- timerUpdate : {
+ conversationId : this.id,
+ type : 'outgoing',
+ sent_at : now,
+ received_at : now,
+ flags : textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
+ expirationTimerUpdate : {
expireTimer : time,
source : source
}
});
message.save();
},
+ sendExpirationTimerUpdate: function(time) {
+ this.addExpirationTimerUpdate(time, textsecure.storage.user.getNumber());
+ // todo: send.
+ },
isSearchable: function() {
return !this.get('left') || !!this.get('lastMessage');
diff --git a/js/models/messages.js b/js/models/messages.js
index 503c2146..9aea937b 100644
--- a/js/models/messages.js
+++ b/js/models/messages.js
@@ -71,7 +71,6 @@
if (this.isIncoming() && this.hasErrors()) {
return i18n('incomingError');
}
-
return this.get('body');
},
getNotificationText: function() {
@@ -126,15 +125,6 @@
}
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() {
return this.get('type') === 'outgoing';
},
@@ -364,9 +354,18 @@
flags : dataMessage.flags,
errors : []
});
-
- if (dataMessage.expireTimer) {
+ if (message.isExpirationTimerUpdate()) {
+ message.set({
+ expirationTimerUpdate: {
+ source : source,
+ expireTimer : dataMessage.expireTimer
+ }
+ });
+ conversation.set({expireTimer: dataMessage.expireTimer});
+ } else if (dataMessage.expireTimer) {
message.set({expireTimer: dataMessage.expireTimer});
+ // todo: insert an update if needed
+ conversation.set({expireTimer: dataMessage.expireTimer});
}
var conversation_timestamp = conversation.get('timestamp');
diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js
index 0d211783..e3dfa685 100644
--- a/js/views/conversation_view.js
+++ b/js/views/conversation_view.js
@@ -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({
className: function() {
return [ 'conversation', this.model.get('type') ].join(' ');
@@ -30,11 +63,14 @@
name: this.model.getName(),
number: this.model.getNumber(),
avatar: this.model.getAvatar(),
+ expireTimer: this.model.get('expireTimer'),
'view-members' : i18n('members'),
'end-session' : i18n('resetSession'),
'verify-identity' : i18n('verifyIdentity'),
'destroy' : i18n('deleteMessages'),
- 'send-message' : i18n('sendMessage')
+ 'send-message' : i18n('sendMessage'),
+ 'disappearing-messages': i18n('disappearingMessages'),
+ timer_options : Whisper.ExpirationTimerOptions.models
};
},
initialize: function(options) {
@@ -47,6 +83,7 @@
this.listenTo(this.model.messageCollection, 'expired', this.onExpiredCollection);
this.render();
+ new TimerMenuView({ el: this.$('.timer-menu'), model: this.model });
emoji_util.parse(this.$('.conversation-name'));
@@ -105,6 +142,7 @@
'click .bottom-bar': 'focusMessageField',
'click .back': 'resetPanel',
'click .microphone': 'captureAudio',
+ 'click .disappearing-messages': 'enableDisappearingMessages',
'focus .send-message': 'focusBottomBar',
'change .file-input': 'toggleMicrophone',
'blur .send-message': 'unfocusBottomBar',
@@ -113,6 +151,13 @@
'select .message-list .entry': 'messageDetail',
'force-resize': 'forceUpdateMessageFieldSize'
},
+ enableDisappearingMessages: function() {
+ if (!this.model.get('expireTimer')) {
+ this.model.sendExpirationTimerUpdate(
+ moment.duration(1, 'day').asSeconds()
+ );
+ }
+ },
toggleMicrophone: function() {
if (this.$('.send-message').val().length > 0 || this.fileInput.hasFiles()) {
this.$('.capture-audio').hide();
@@ -240,7 +285,10 @@
closeMenu: function(e) {
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() {
- this.$('.menu-list').toggle();
+ this.$('.conversation-menu .menu-list').toggle();
},
newGroupUpdate: function() {
diff --git a/js/views/message_list_view.js b/js/views/message_list_view.js
index 4ed79c47..e7d2cde6 100644
--- a/js/views/message_list_view.js
+++ b/js/views/message_list_view.js
@@ -44,7 +44,7 @@
},
addOne: function(model) {
var view;
- if (model.get('type') === 'expirationTimerUpdate') {
+ if (model.isExpirationTimerUpdate()) {
view = new Whisper.ExpirationTimerUpdateView({model: model}).render();
} else {
view = new this.itemView({model: model}).render();
diff --git a/js/views/message_view.js b/js/views/message_view.js
index 65414e3c..5e430fd4 100644
--- a/js/views/message_view.js
+++ b/js/views/message_view.js
@@ -48,14 +48,16 @@
className: 'expirationTimerUpdate advisory',
templateName: 'expirationTimerUpdate',
initialize: function() {
- this.conversation = this.model.getModelForExpirationTimerUpdate();
+ this.conversation = this.model.getContact();
this.listenTo(this.conversation, 'change', this.render);
},
render_attributes: function() {
+ var seconds = this.model.get('expirationTimerUpdate').expireTimer;
return {
- content: i18n('changedTheTimer',
- this.conversation.getTitle(),
- this.model.get('timerUpdate').time)
+ content: i18n('changedTheTimer', [
+ this.conversation.getTitle(),
+ Whisper.ExpirationTimerOptions.getName(seconds)
+ ])
};
}
});
diff --git a/stylesheets/_global.scss b/stylesheets/_global.scss
index 5c51c709..3c920172 100644
--- a/stylesheets/_global.scss
+++ b/stylesheets/_global.scss
@@ -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 {
position: relative;
float: right;
diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css
index 31450071..986310ac 100644
--- a/stylesheets/manifest.css
+++ b/stylesheets/manifest.css
@@ -126,6 +126,19 @@ button.hamburger {
vertical-align: middle;
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 {
position: relative;
float: right; }