From 2516848e817cfc5d9ff22bed22e2b381ae012e8c Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sun, 22 Nov 2020 11:46:31 +0100 Subject: [PATCH] feat: Improved encryption UI --- lib/components/list_items/message.dart | 49 ----------------- lib/components/message_content.dart | 70 +++++++++++++++++++++++++ lib/components/user_bottom_sheet.dart | 6 +-- lib/l10n/intl_en.arb | 10 ++++ lib/views/chat.dart | 2 + lib/views/chat_encryption_settings.dart | 9 +--- lib/views/key_verification.dart | 59 ++++++++++++++------- 7 files changed, 128 insertions(+), 77 deletions(-) diff --git a/lib/components/list_items/message.dart b/lib/components/list_items/message.dart index 5e99962d..5abb3b44 100644 --- a/lib/components/list_items/message.dart +++ b/lib/components/list_items/message.dart @@ -1,21 +1,16 @@ import 'package:famedlysdk/famedlysdk.dart'; -import 'package:famedlysdk/encryption.dart'; -import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/message_content.dart'; import 'package:fluffychat/components/reply_content.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/event_extension.dart'; import 'package:fluffychat/utils/string_color.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../adaptive_page_layout.dart'; import '../avatar.dart'; import '../matrix.dart'; import '../message_reactions.dart'; import 'state_message.dart'; -import '../../views/key_verification.dart'; -import '../../utils/app_route.dart'; class Message extends StatelessWidget { final Event event; @@ -40,36 +35,6 @@ class Message extends StatelessWidget { /// of touchscreen. static bool useMouse = false; - void _verifyOrRequestKey(BuildContext context) async { - final client = Matrix.of(context).client; - if (client.isUnknownSession && client.encryption.crossSigning.enabled) { - final req = - await client.userDeviceKeys[client.userID].startVerification(); - req.onUpdate = () async { - if (req.state == KeyVerificationState.done) { - for (var i = 0; i < 12; i++) { - if (await client.encryption.keyManager.isCached()) { - break; - } - await Future.delayed(Duration(seconds: 1)); - } - final timeline = await event.room.getTimeline(); - timeline.requestKeys(); - timeline.cancelSubscriptions(); - } - }; - await Navigator.of(context).push( - AppRoute.defaultRoute( - context, - KeyVerificationView(request: req), - ), - ); - } else { - await SimpleDialogs(context).tryRequestWithLoadingDialog( - event.getDisplayEvent(timeline).requestKey()); - } - } - @override Widget build(BuildContext context) { if (![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted] @@ -161,20 +126,6 @@ class Message extends StatelessWidget { displayEvent, textColor: textColor, ), - if (displayEvent.type == EventTypes.Encrypted && - displayEvent.messageType == MessageTypes.BadEncrypted && - displayEvent.content['can_request_session'] == true) - RaisedButton( - color: color.withAlpha(100), - child: Text( - client.isUnknownSession && - client.encryption.crossSigning.enabled - ? L10n.of(context).verify - : L10n.of(context).requestPermission, - style: TextStyle(color: textColor), - ), - onPressed: () => _verifyOrRequestKey(context), - ), SizedBox(height: 4), Opacity( opacity: 0, diff --git a/lib/components/message_content.dart b/lib/components/message_content.dart index a28c15c3..5553cb47 100644 --- a/lib/components/message_content.dart +++ b/lib/components/message_content.dart @@ -1,8 +1,13 @@ +import 'package:famedlysdk/encryption/utils/key_verification.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/audio_player.dart'; +import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; import 'package:fluffychat/components/image_bubble.dart'; +import 'package:fluffychat/utils/app_route.dart'; import 'package:fluffychat/utils/event_extension.dart'; import 'package:fluffychat/utils/matrix_locals.dart'; +import 'package:fluffychat/views/key_verification.dart'; +import 'package:flushbar/flushbar_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix_link_text/link_text.dart'; @@ -20,6 +25,54 @@ class MessageContent extends StatelessWidget { const MessageContent(this.event, {this.textColor}); + void _verifyOrRequestKey(BuildContext context) async { + if (event.content['can_request_session'] != true) { + FlushbarHelper.createError( + message: event.type == EventTypes.Encrypted + ? L10n.of(context).needPantalaimonWarning + : event.getLocalizedBody( + MatrixLocals(L10n.of(context)), + ), + ); + return; + } + final client = Matrix.of(context).client; + if (client.isUnknownSession && client.encryption.crossSigning.enabled) { + final req = + await client.userDeviceKeys[client.userID].startVerification(); + req.onUpdate = () async { + if (req.state == KeyVerificationState.done) { + for (var i = 0; i < 12; i++) { + if (await client.encryption.keyManager.isCached()) { + break; + } + await Future.delayed(Duration(seconds: 1)); + } + final timeline = await event.room.getTimeline(); + timeline.requestKeys(); + timeline.cancelSubscriptions(); + } + }; + await Navigator.of(context).push( + AppRoute.defaultRoute( + context, + KeyVerificationView(request: req), + ), + ); + } else { + final success = await SimpleDialogs(context).tryRequestWithLoadingDialog( + event.requestKey(), + ); + if (success != false) { + await FlushbarHelper.createLoading( + title: L10n.of(context).loadingPleaseWait, + message: L10n.of(context).requestToReadOlderMessages, + linearProgressIndicator: LinearProgressIndicator(), + ).show(context); + } + } + } + @override Widget build(BuildContext context) { switch (event.type) { @@ -68,6 +121,23 @@ class MessageContent extends StatelessWidget { // else we fall through to the normal message rendering continue textmessage; case MessageTypes.BadEncrypted: + case EventTypes.Encrypted: + return RaisedButton( + elevation: 7, + color: Theme.of(context).scaffoldBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.lock_outline), + SizedBox(width: 8), + Text(L10n.of(context).encrypted), + ], + ), + onPressed: () => _verifyOrRequestKey(context), + ); case MessageTypes.Location: case MessageTypes.None: textmessage: diff --git a/lib/components/user_bottom_sheet.dart b/lib/components/user_bottom_sheet.dart index 3a0dece6..dc8e0b63 100644 --- a/lib/components/user_bottom_sheet.dart +++ b/lib/components/user_bottom_sheet.dart @@ -166,8 +166,8 @@ class UserBottomSheet extends StatelessWidget { title: Text(user.calcDisplayname()), actions: [ if (verificationStatus != null) - InkWell( - child: Icon( + IconButton( + icon: Icon( Icons.lock, color: { UserVerifiedStatus.unknownDevice: Colors.red, @@ -175,7 +175,7 @@ class UserBottomSheet extends StatelessWidget { }[verificationStatus] ?? Colors.orange, ), - onTap: () => + onPressed: () => verificationStatus == UserVerifiedStatus.unknown ? _verifyAction(context) : null, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c81bddbd..bd9b58d9 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -489,6 +489,11 @@ "type": "text", "placeholders": {} }, + "deviceId": "Device ID", + "@deviceId": { + "type": "text", + "placeholders": {} + }, "devices": "Devices", "@devices": { "type": "text", @@ -574,6 +579,11 @@ "type": "text", "placeholders": {} }, + "encrypted": "Encrypted", + "@encrypted": { + "type": "text", + "placeholders": {} + }, "end2endEncryptionSettings": "End-to-end encryption settings", "@end2endEncryptionSettings": { "type": "text", diff --git a/lib/views/chat.dart b/lib/views/chat.dart index 277acb78..2618d2fc 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -434,6 +434,8 @@ class _ChatState extends State<_Chat> { // always filter out edit and reaction relationships !{RelationshipTypes.Edit, RelationshipTypes.Reaction} .contains(e.relationshipType) && + // always filter out m.key.* events + !e.type.startsWith('m.key.verification.') && // if a reaction has been redacted we also want it to appear in the timeline e.type != EventTypes.Reaction && // if we enabled to hide all redacted events, don't show those diff --git a/lib/views/chat_encryption_settings.dart b/lib/views/chat_encryption_settings.dart index cad4da96..b2c14ecd 100644 --- a/lib/views/chat_encryption_settings.dart +++ b/lib/views/chat_encryption_settings.dart @@ -222,7 +222,7 @@ class _ChatEncryptionSettingsState extends State { }, child: ListTile( title: Text( - '${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}', + '${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice}', style: TextStyle( color: deviceKeys[i].blocked ? Colors.red @@ -231,12 +231,7 @@ class _ChatEncryptionSettingsState extends State { : Colors.orange), ), subtitle: Text( - deviceKeys[i].ed25519Key.beautified, - style: TextStyle( - color: Theme.of(context) - .textTheme - .bodyText2 - .color), + '${L10n.of(context).deviceId}: ${deviceKeys[i].deviceId}', ), ), ), diff --git a/lib/views/key_verification.dart b/lib/views/key_verification.dart index f3e24621..f41c2d67 100644 --- a/lib/views/key_verification.dart +++ b/lib/views/key_verification.dart @@ -208,8 +208,13 @@ class _KeyVerificationPageState extends State { body = Column( children: [ Container( - child: Text(compareText, style: TextStyle(fontSize: 20)), - margin: EdgeInsets.only(left: 8.0, right: 8.0), + alignment: Alignment.center, + child: Text( + compareText, + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + padding: const EdgeInsets.all(16.0), ), Container(height: 10), Text.rich( @@ -221,14 +226,20 @@ class _KeyVerificationPageState extends State { ); buttons.add(RaisedButton( color: Theme.of(context).primaryColor, - elevation: 5, + elevation: 7, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), textColor: Colors.white, child: Text(L10n.of(context).theyMatch), onPressed: () => widget.request.acceptSas(), )); buttons.add(RaisedButton( textColor: Theme.of(context).primaryColor, - elevation: 5, + elevation: 7, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), color: Colors.white, child: Text(L10n.of(context).theyDontMatch), onPressed: () => widget.request.rejectSas(), @@ -258,7 +269,10 @@ class _KeyVerificationPageState extends State { ); buttons.add(RaisedButton( color: Theme.of(context).primaryColor, - elevation: 5, + elevation: 7, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), textColor: Colors.white, child: Text(L10n.of(context).close), onPressed: () => Navigator.of(context).pop(), @@ -276,7 +290,10 @@ class _KeyVerificationPageState extends State { ); buttons.add(RaisedButton( color: Theme.of(context).primaryColor, - elevation: 5, + elevation: 7, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), textColor: Colors.white, child: Text(L10n.of(context).close), onPressed: () => Navigator.of(context).pop(), @@ -294,10 +311,11 @@ class _KeyVerificationPageState extends State { ?.deviceKeys[widget.request.deviceId] ?.deviceDisplayName ?? ''; - bottom = PreferredSize( + bottom = Container( + alignment: Alignment.center, + padding: EdgeInsets.all(16.0), child: Text('$deviceName (${widget.request.deviceId})', style: TextStyle(color: Theme.of(context).textTheme.caption.color)), - preferredSize: Size(0.0, 20.0), ); } return Scaffold( @@ -305,24 +323,29 @@ class _KeyVerificationPageState extends State { title: ListTile( leading: Avatar(profile?.avatarUrl, otherName), contentPadding: EdgeInsets.zero, - title: Text(L10n.of(context).verifyTitle), - isThreeLine: otherName != widget.request.userId, - subtitle: Column( + title: Text(L10n.of(context).verifyTitle, maxLines: 1), + subtitle: Row( children: [ - Text(otherName), + Text(otherName, maxLines: 1), if (otherName != widget.request.userId) - Text(widget.request.userId), + Text(' -> ' + widget.request.userId, maxLines: 1), ], crossAxisAlignment: CrossAxisAlignment.start, ), ), elevation: 0, - bottom: bottom, ), - extendBody: true, - extendBodyBehindAppBar: true, - body: Center( - child: body, + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Center( + child: body, + ), + ), + bottom, + ], + ), ), persistentFooterButtons: buttons.isEmpty ? null : buttons, );