diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 9350bbf5..809f3e68 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -284,5 +284,29 @@
"unregisteredUser": {
"message": "Number is not registered",
"description": "Error message displayed when sending to an unregistered user."
+ },
+ "sync": {
+ "message": "Contacts",
+ "description": "Label for contact and group sync settings"
+ },
+ "syncExplanation": {
+ "message": "Import all Signal groups and contacts from your mobile device.",
+ "description": "Explanatory text for sync settings"
+ },
+ "lastSynced": {
+ "message": "Last import at",
+ "description": "Label for date and time of last sync operation"
+ },
+ "syncNow": {
+ "message": "Import now",
+ "description": "Label for a button that syncs contacts and groups from your phone"
+ },
+ "syncing": {
+ "message": "Importing...",
+ "description": "Label for a disabled sync button while sync is in progress."
+ },
+ "syncFailed": {
+ "message": "Import failed. Make sure your computer and your phone are connected to the internet.",
+ "description": "Informational text displayed if a sync operation times out."
}
}
diff --git a/background.html b/background.html
index 1be35442..889ef41c 100644
--- a/background.html
+++ b/background.html
@@ -382,6 +382,8 @@
+
diff --git a/js/background.js b/js/background.js
index 03c3d3ef..db16bdcc 100644
--- a/js/background.js
+++ b/js/background.js
@@ -83,6 +83,10 @@
}
});
+ window.getSyncRequest = function() {
+ return new textsecure.SyncRequest(textsecure.messaging, messageReceiver);
+ };
+
function init(firstRun) {
window.removeEventListener('online', init);
if (!textsecure.registration.isDone()) { return; }
@@ -112,6 +116,7 @@
var syncRequest = new textsecure.SyncRequest(textsecure.messaging, messageReceiver);
syncRequest.addEventListener('success', function() {
console.log('sync successful');
+ storage.put('synced_at', Date.now());
window.dispatchEvent(new Event('textsecure:contactsync'));
});
syncRequest.addEventListener('timeout', function() {
diff --git a/js/views/settings_view.js b/js/views/settings_view.js
index 0f1be944..17077b42 100644
--- a/js/views/settings_view.js
+++ b/js/views/settings_view.js
@@ -23,6 +23,10 @@
setting = 'message';
}
this.$('#notification-setting-' + setting).attr('checked','checked');
+ if (textsecure.storage.user.getDeviceId() != '1') {
+ var syncView = new SyncView().render();
+ this.$('.content').append(syncView.el);
+ }
},
render_attributes: function() {
return {
@@ -36,4 +40,59 @@
};
}
});
+
+ var SyncView = Whisper.View.extend({
+ templateName: 'syncSettings',
+ className: 'syncSettings',
+ events: {
+ 'click .sync': 'sync'
+ },
+ enable: function() {
+ this.$('.sync').text(i18n('syncNow'));
+ this.$('.sync').removeAttr('disabled');
+ },
+ disable: function() {
+ this.$('.sync').attr('disabled', 'disabled');
+ this.$('.sync').text(i18n('syncing'));
+ },
+ onsuccess: function() {
+ storage.put('synced_at', Date.now());
+ console.log('sync successful');
+ this.enable();
+ this.render();
+ },
+ ontimeout: function() {
+ console.log('sync timed out');
+ this.$('.synced_at').hide();
+ this.$('.sync_failed').show();
+ this.enable();
+ },
+ sync: function() {
+ this.$('.sync_failed').hide();
+ if (textsecure.storage.user.getDeviceId() != '1') {
+ this.disable();
+ var syncRequest = window.getSyncRequest();
+ syncRequest.addEventListener('success', this.onsuccess.bind(this));
+ syncRequest.addEventListener('timeout', this.ontimeout.bind(this));
+ } else {
+ console.log("Tried to sync from device 1");
+ }
+ },
+ render_attributes: function() {
+ var attrs = {
+ sync: i18n('sync'),
+ syncNow: i18n('syncNow'),
+ syncExplanation: i18n('syncExplanation'),
+ syncFailed: i18n('syncFailed')
+ };
+ var date = storage.get('synced_at');
+ if (date) {
+ date = new Date(date);
+ attrs.lastSynced = i18n('lastSynced');
+ attrs.syncDate = date.toLocaleDateString();
+ attrs.syncTime = date.toLocaleTimeString();
+ }
+ return attrs;
+ }
+ });
})();
diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss
index ca02380f..990f3dc6 100644
--- a/stylesheets/_index.scss
+++ b/stylesheets/_index.scss
@@ -253,3 +253,30 @@ input.search {
top: -30px;
}
}
+.syncSettings {
+ button {
+ float: right;
+ border: none;
+ border-radius: $border-radius;
+ color: white;
+ font-weight: bold;
+ line-height: 36px;
+ padding: 0 20px;
+ background: $blue;
+ margin-left: 20px;
+ margin-bottom: 20px;
+
+ &[disabled=disabled] {
+ background: $grey;
+ }
+ }
+ .synced_at {
+ font-size: small;
+ color: $grey;
+ }
+ .sync_failed {
+ display: none;
+ font-size: small;
+ color: red;
+ }
+}
diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css
index 8addd03b..f4f23741 100644
--- a/stylesheets/manifest.css
+++ b/stylesheets/manifest.css
@@ -698,6 +698,27 @@ input.search {
border-color: transparent transparent #2eace0 transparent;
top: -30px; }
+.syncSettings button {
+ float: right;
+ border: none;
+ border-radius: 5px;
+ color: white;
+ font-weight: bold;
+ line-height: 36px;
+ padding: 0 20px;
+ background: #2090ea;
+ margin-left: 20px;
+ margin-bottom: 20px; }
+ .syncSettings button[disabled=disabled] {
+ background: #616161; }
+.syncSettings .synced_at {
+ font-size: small;
+ color: #616161; }
+.syncSettings .sync_failed {
+ display: none;
+ font-size: small;
+ color: red; }
+
.conversation-title {
display: block;
line-height: 36px;