diff --git a/res/css/_components.scss b/res/css/_components.scss
index 083071ef6c..579856f880 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -101,6 +101,7 @@
@import "./views/rooms/_RoomHeader.scss";
@import "./views/rooms/_RoomList.scss";
@import "./views/rooms/_RoomPreviewBar.scss";
+@import "./views/rooms/_RoomRecoveryReminder.scss";
@import "./views/rooms/_RoomSettings.scss";
@import "./views/rooms/_RoomTile.scss";
@import "./views/rooms/_RoomTooltip.scss";
diff --git a/res/css/views/rooms/_RoomRecoveryReminder.scss b/res/css/views/rooms/_RoomRecoveryReminder.scss
new file mode 100644
index 0000000000..4bb42ff114
--- /dev/null
+++ b/res/css/views/rooms/_RoomRecoveryReminder.scss
@@ -0,0 +1,43 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_RoomRecoveryReminder {
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ background-color: $room-warning-bg-color;
+ padding: 20px;
+ border: 1px solid $primary-hairline-color;
+ border-bottom: unset;
+}
+
+.mx_RoomRecoveryReminder_header {
+ font-weight: bold;
+ margin-bottom: 1em;
+}
+
+.mx_RoomRecoveryReminder_body {
+ margin-bottom: 1em;
+}
+
+.mx_RoomRecoveryReminder_button {
+ @mixin mx_DialogButton;
+ margin: 0 10px;
+}
+
+.mx_RoomRecoveryReminder_button.mx_RoomRecoveryReminder_secondary {
+ @mixin mx_DialogButton_secondary;
+}
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index b773d7c720..5dbc00af4e 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -100,6 +100,8 @@ $voip-accept-color: #80f480;
$rte-bg-color: #353535;
$rte-code-bg-color: #000;
+$room-warning-bg-color: #2d2d2d;
+
// ********************
$roomtile-name-color: rgba(186, 186, 186, 0.8);
@@ -169,6 +171,14 @@ $progressbar-color: #000;
outline: none;
}
+@define-mixin mx_DialogButton_secondary {
+ // flip colours for the secondary ones
+ font-weight: 600;
+ border: 1px solid $accent-color ! important;
+ color: $accent-color;
+ background-color: $accent-fg-color;
+}
+
// Nasty hacks to apply a filter to arbitrary monochrome artwork to make it
// better match the theme. Typically applied to dark grey 'off' buttons or
// light grey 'on' buttons.
diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss
index 49347492ff..c275b94fb5 100644
--- a/res/themes/light/css/_base.scss
+++ b/res/themes/light/css/_base.scss
@@ -155,6 +155,8 @@ $imagebody-giflabel-border: rgba(0, 0, 0, 0.2);
// unused?
$progressbar-color: #000;
+$room-warning-bg-color: #fff8e3;
+
// ***** Mixins! *****
@define-mixin mx_DialogButton {
@@ -187,3 +189,11 @@ $progressbar-color: #000;
font-size: 15px;
padding: 0px 1.5em 0px 1.5em;
}
+
+@define-mixin mx_DialogButton_secondary {
+ // flip colours for the secondary ones
+ font-weight: 600;
+ border: 1px solid $accent-color ! important;
+ color: $accent-color;
+ background-color: $accent-fg-color;
+}
diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
index 8547add256..6b115b890f 100644
--- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
+++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
@@ -251,7 +251,7 @@ export default React.createClass({
/>
{_t(
- "If you don't want encrypted message history to be availble on other devices, "+
+ "If you don't want encrypted message history to be available on other devices, "+
".",
{},
{
diff --git a/src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js b/src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js
new file mode 100644
index 0000000000..a9df3cca6e
--- /dev/null
+++ b/src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js
@@ -0,0 +1,70 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import PropTypes from "prop-types";
+import sdk from "../../../../index";
+import { _t } from "../../../../languageHandler";
+
+export default class IgnoreRecoveryReminderDialog extends React.PureComponent {
+ static propTypes = {
+ onDontAskAgain: PropTypes.func.isRequired,
+ onFinished: PropTypes.func.isRequired,
+ onSetup: PropTypes.func.isRequired,
+ }
+
+ onDontAskAgainClick = () => {
+ this.props.onFinished();
+ this.props.onDontAskAgain();
+ }
+
+ onSetupClick = () => {
+ this.props.onFinished();
+ this.props.onSetup();
+ }
+
+ render() {
+ const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
+ const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
+
+ return (
+
+
+
{_t(
+ "Without setting up Secure Message Recovery, " +
+ "you'll lose your secure message history when you " +
+ "log out.",
+ )}
+
{_t(
+ "If you don't want to set this up now, you can later " +
+ "in Settings.",
+ )}
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 934031e98d..0e0d56647d 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -607,6 +607,20 @@ module.exports = React.createClass({
}
},
+ async onRoomRecoveryReminderFinished(backupCreated) {
+ // If the user cancelled the key backup dialog, it suggests they don't
+ // want to be reminded anymore.
+ if (!backupCreated) {
+ await SettingsStore.setValue(
+ "showRoomRecoveryReminder",
+ null,
+ SettingLevel.ACCOUNT,
+ false,
+ );
+ }
+ this.forceUpdate();
+ },
+
canResetTimeline: function() {
if (!this.refs.messagePanel) {
return true;
@@ -1521,6 +1535,7 @@ module.exports = React.createClass({
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar");
+ const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder");
if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) {
@@ -1655,6 +1670,13 @@ module.exports = React.createClass({
this.state.room.userMayUpgradeRoom(MatrixClientPeg.get().credentials.userId)
);
+ const showRoomRecoveryReminder = (
+ SettingsStore.isFeatureEnabled("feature_keybackup") &&
+ SettingsStore.getValue("showRoomRecoveryReminder") &&
+ MatrixClientPeg.get().isRoomEncrypted(this.state.room.roomId) &&
+ !MatrixClientPeg.get().getKeyBackupEnabled()
+ );
+
let aux = null;
let hideCancel = false;
if (this.state.editingRoomSettings) {
@@ -1669,6 +1691,9 @@ module.exports = React.createClass({
} else if (showRoomUpgradeBar) {
aux = ;
hideCancel = true;
+ } else if (showRoomRecoveryReminder) {
+ aux = ;
+ hideCancel = true;
} else if (this.state.showingPinned) {
hideCancel = true; // has own cancel
aux = ;
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 4c15b4ec27..6f932d71e1 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -64,6 +64,7 @@ const SIMPLE_SETTINGS = [
{ id: "urlPreviewsEnabled" },
{ id: "autoplayGifsAndVideos" },
{ id: "alwaysShowEncryptionIcons" },
+ { id: "showRoomRecoveryReminder" },
{ id: "hideReadReceipts" },
{ id: "dontSendTypingNotifications" },
{ id: "alwaysShowTimestamps" },
diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js
new file mode 100644
index 0000000000..265bfd3ee3
--- /dev/null
+++ b/src/components/views/rooms/RoomRecoveryReminder.js
@@ -0,0 +1,85 @@
+/*
+Copyright 2018 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import PropTypes from "prop-types";
+import sdk from "../../../index";
+import { _t } from "../../../languageHandler";
+import Modal from "../../../Modal";
+
+export default class RoomRecoveryReminder extends React.PureComponent {
+ static propTypes = {
+ onFinished: PropTypes.func.isRequired,
+ }
+
+ showKeyBackupDialog = () => {
+ Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
+ import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
+ {
+ onFinished: this.props.onFinished,
+ },
+ );
+ }
+
+ onDontAskAgainClick = () => {
+ // When you choose "Don't ask again" from the room reminder, we show a
+ // dialog to confirm the choice.
+ Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder",
+ import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"),
+ {
+ onDontAskAgain: () => {
+ // Report false to the caller, who should prevent the
+ // reminder from appearing in the future.
+ this.props.onFinished(false);
+ },
+ onSetup: () => {
+ this.showKeyBackupDialog();
+ },
+ },
+ );
+ }
+
+ onSetupClick = () => {
+ this.showKeyBackupDialog();
+ }
+
+ render() {
+ const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
+
+ return (
+
+
{_t(
+ "Secure Message Recovery",
+ )}
+
{_t(
+ "If you log out or use another device, you'll lose your " +
+ "secure message history. To prevent this, set up Secure " +
+ "Message Recovery.",
+ )}
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 7d263f6a4c..a4ce5143d7 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -268,6 +268,7 @@
"Always show message timestamps": "Always show message timestamps",
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
"Always show encryption icons": "Always show encryption icons",
+ "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms",
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
"Hide avatars in user and room mentions": "Hide avatars in user and room mentions",
"Disable big emoji in chat": "Disable big emoji in chat",
@@ -562,6 +563,10 @@
"You are trying to access a room.": "You are trying to access a room.",
"Click here to join the discussion!": "Click here to join the discussion!",
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
+ "Secure Message Recovery": "Secure Message Recovery",
+ "If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.": "If you log out or use another device, you'll lose your secure message history. To prevent this, set up Secure Message Recovery.",
+ "Don't ask again": "Don't ask again",
+ "Set up": "Set up",
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
"To change the room's name, you must be a": "To change the room's name, you must be a",
"To change the room's main address, you must be a": "To change the room's main address, you must be a",
@@ -1352,7 +1357,7 @@
"Secure your encrypted message history with a Recovery Passphrase.": "Secure your encrypted message history with a Recovery Passphrase.",
"You'll need it if you log out or lose access to this device.": "You'll need it if you log out or lose access to this device.",
"Enter a passphrase...": "Enter a passphrase...",
- "If you don't want encrypted message history to be availble on other devices, .": "If you don't want encrypted message history to be availble on other devices, .",
+ "If you don't want encrypted message history to be available on other devices, .": "If you don't want encrypted message history to be available on other devices, .",
"Or, if you don't want to create a Recovery Passphrase, skip this step and .": "Or, if you don't want to create a Recovery Passphrase, skip this step and .",
"That matches!": "That matches!",
"That doesn't match.": "That doesn't match.",
@@ -1384,6 +1389,8 @@
"Create Key Backup": "Create Key Backup",
"Unable to create key backup": "Unable to create key backup",
"Retry": "Retry",
+ "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.",
+ "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.",
"Failed to set direct chat tag": "Failed to set direct chat tag",
"Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
"Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index eb702a729c..c9a4ecdebe 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -151,6 +151,11 @@ export const SETTINGS = {
displayName: _td('Always show encryption icons'),
default: true,
},
+ "showRoomRecoveryReminder": {
+ supportedLevels: LEVELS_ACCOUNT_SETTINGS,
+ displayName: _td('Show a reminder to enable Secure Message Recovery in encrypted rooms'),
+ default: true,
+ },
"enableSyntaxHighlightLanguageDetection": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Enable automatic language detection for syntax highlighting'),