From a92d2902c4c3db3193598723cb0e2646a70b2528 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 6 Dec 2018 15:39:59 -0600 Subject: [PATCH] Add an in-room reminder to set up key recovery This adds an in-room reminder above the message timeline to set up Secure Message Recovery so that your keys will be backed up. If you try to ignore it, an additional dialog is shown to confirm. Fixes vector-im/riot-web#7783. Signed-off-by: J. Ryan Stinnett --- res/css/_components.scss | 1 + .../views/rooms/_RoomRecoveryReminder.scss | 43 ++++++++++ res/themes/dark/css/_dark.scss | 10 +++ res/themes/light/css/_base.scss | 10 +++ .../keybackup/CreateKeyBackupDialog.js | 2 +- .../keybackup/IgnoreRecoveryReminderDialog.js | 70 +++++++++++++++ src/components/structures/RoomView.js | 25 ++++++ src/components/structures/UserSettings.js | 1 + .../views/rooms/RoomRecoveryReminder.js | 85 +++++++++++++++++++ src/i18n/strings/en_EN.json | 9 +- src/settings/Settings.js | 5 ++ 11 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 res/css/views/rooms/_RoomRecoveryReminder.scss create mode 100644 src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js create mode 100644 src/components/views/rooms/RoomRecoveryReminder.js 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.", + )}
+
+ + { _t("Don't ask again") } + + + { _t("Set up") } + +
+
+ ); + } +} 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'),