Merge branch 'krille/better-encryption-ui' into 'main'

feat: Improved encryption UI

See merge request ChristianPauly/fluffychat-flutter!289
This commit is contained in:
Christian Pauly 2020-11-22 11:14:43 +00:00
commit aa8b67c8b9
7 changed files with 128 additions and 77 deletions

View File

@ -1,21 +1,16 @@
import 'package:famedlysdk/famedlysdk.dart'; 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/message_content.dart';
import 'package:fluffychat/components/reply_content.dart'; import 'package:fluffychat/components/reply_content.dart';
import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/event_extension.dart'; import 'package:fluffychat/utils/event_extension.dart';
import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/utils/string_color.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import '../adaptive_page_layout.dart'; import '../adaptive_page_layout.dart';
import '../avatar.dart'; import '../avatar.dart';
import '../matrix.dart'; import '../matrix.dart';
import '../message_reactions.dart'; import '../message_reactions.dart';
import 'state_message.dart'; import 'state_message.dart';
import '../../views/key_verification.dart';
import '../../utils/app_route.dart';
class Message extends StatelessWidget { class Message extends StatelessWidget {
final Event event; final Event event;
@ -40,36 +35,6 @@ class Message extends StatelessWidget {
/// of touchscreen. /// of touchscreen.
static bool useMouse = false; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted] if (![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted]
@ -161,20 +126,6 @@ class Message extends StatelessWidget {
displayEvent, displayEvent,
textColor: textColor, 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), SizedBox(height: 4),
Opacity( Opacity(
opacity: 0, opacity: 0,

View File

@ -1,8 +1,13 @@
import 'package:famedlysdk/encryption/utils/key_verification.dart';
import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/components/audio_player.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/components/image_bubble.dart';
import 'package:fluffychat/utils/app_route.dart';
import 'package:fluffychat/utils/event_extension.dart'; import 'package:fluffychat/utils/event_extension.dart';
import 'package:fluffychat/utils/matrix_locals.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/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix_link_text/link_text.dart'; import 'package:matrix_link_text/link_text.dart';
@ -20,6 +25,54 @@ class MessageContent extends StatelessWidget {
const MessageContent(this.event, {this.textColor}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
switch (event.type) { switch (event.type) {
@ -68,6 +121,23 @@ class MessageContent extends StatelessWidget {
// else we fall through to the normal message rendering // else we fall through to the normal message rendering
continue textmessage; continue textmessage;
case MessageTypes.BadEncrypted: 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.Location:
case MessageTypes.None: case MessageTypes.None:
textmessage: textmessage:

View File

@ -166,8 +166,8 @@ class UserBottomSheet extends StatelessWidget {
title: Text(user.calcDisplayname()), title: Text(user.calcDisplayname()),
actions: [ actions: [
if (verificationStatus != null) if (verificationStatus != null)
InkWell( IconButton(
child: Icon( icon: Icon(
Icons.lock, Icons.lock,
color: { color: {
UserVerifiedStatus.unknownDevice: Colors.red, UserVerifiedStatus.unknownDevice: Colors.red,
@ -175,7 +175,7 @@ class UserBottomSheet extends StatelessWidget {
}[verificationStatus] ?? }[verificationStatus] ??
Colors.orange, Colors.orange,
), ),
onTap: () => onPressed: () =>
verificationStatus == UserVerifiedStatus.unknown verificationStatus == UserVerifiedStatus.unknown
? _verifyAction(context) ? _verifyAction(context)
: null, : null,

View File

@ -489,6 +489,11 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"deviceId": "Device ID",
"@deviceId": {
"type": "text",
"placeholders": {}
},
"devices": "Devices", "devices": "Devices",
"@devices": { "@devices": {
"type": "text", "type": "text",
@ -574,6 +579,11 @@
"type": "text", "type": "text",
"placeholders": {} "placeholders": {}
}, },
"encrypted": "Encrypted",
"@encrypted": {
"type": "text",
"placeholders": {}
},
"end2endEncryptionSettings": "End-to-end encryption settings", "end2endEncryptionSettings": "End-to-end encryption settings",
"@end2endEncryptionSettings": { "@end2endEncryptionSettings": {
"type": "text", "type": "text",

View File

@ -434,6 +434,8 @@ class _ChatState extends State<_Chat> {
// always filter out edit and reaction relationships // always filter out edit and reaction relationships
!{RelationshipTypes.Edit, RelationshipTypes.Reaction} !{RelationshipTypes.Edit, RelationshipTypes.Reaction}
.contains(e.relationshipType) && .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 // if a reaction has been redacted we also want it to appear in the timeline
e.type != EventTypes.Reaction && e.type != EventTypes.Reaction &&
// if we enabled to hide all redacted events, don't show those // if we enabled to hide all redacted events, don't show those

View File

@ -222,7 +222,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
}, },
child: ListTile( child: ListTile(
title: Text( title: Text(
'${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}', '${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice}',
style: TextStyle( style: TextStyle(
color: deviceKeys[i].blocked color: deviceKeys[i].blocked
? Colors.red ? Colors.red
@ -231,12 +231,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
: Colors.orange), : Colors.orange),
), ),
subtitle: Text( subtitle: Text(
deviceKeys[i].ed25519Key.beautified, '${L10n.of(context).deviceId}: ${deviceKeys[i].deviceId}',
style: TextStyle(
color: Theme.of(context)
.textTheme
.bodyText2
.color),
), ),
), ),
), ),

View File

@ -208,8 +208,13 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
body = Column( body = Column(
children: <Widget>[ children: <Widget>[
Container( Container(
child: Text(compareText, style: TextStyle(fontSize: 20)), alignment: Alignment.center,
margin: EdgeInsets.only(left: 8.0, right: 8.0), child: Text(
compareText,
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
padding: const EdgeInsets.all(16.0),
), ),
Container(height: 10), Container(height: 10),
Text.rich( Text.rich(
@ -221,14 +226,20 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
); );
buttons.add(RaisedButton( buttons.add(RaisedButton(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
elevation: 5, elevation: 7,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
textColor: Colors.white, textColor: Colors.white,
child: Text(L10n.of(context).theyMatch), child: Text(L10n.of(context).theyMatch),
onPressed: () => widget.request.acceptSas(), onPressed: () => widget.request.acceptSas(),
)); ));
buttons.add(RaisedButton( buttons.add(RaisedButton(
textColor: Theme.of(context).primaryColor, textColor: Theme.of(context).primaryColor,
elevation: 5, elevation: 7,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
color: Colors.white, color: Colors.white,
child: Text(L10n.of(context).theyDontMatch), child: Text(L10n.of(context).theyDontMatch),
onPressed: () => widget.request.rejectSas(), onPressed: () => widget.request.rejectSas(),
@ -258,7 +269,10 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
); );
buttons.add(RaisedButton( buttons.add(RaisedButton(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
elevation: 5, elevation: 7,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
textColor: Colors.white, textColor: Colors.white,
child: Text(L10n.of(context).close), child: Text(L10n.of(context).close),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
@ -276,7 +290,10 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
); );
buttons.add(RaisedButton( buttons.add(RaisedButton(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
elevation: 5, elevation: 7,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
textColor: Colors.white, textColor: Colors.white,
child: Text(L10n.of(context).close), child: Text(L10n.of(context).close),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
@ -294,10 +311,11 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
?.deviceKeys[widget.request.deviceId] ?.deviceKeys[widget.request.deviceId]
?.deviceDisplayName ?? ?.deviceDisplayName ??
''; '';
bottom = PreferredSize( bottom = Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text('$deviceName (${widget.request.deviceId})', child: Text('$deviceName (${widget.request.deviceId})',
style: TextStyle(color: Theme.of(context).textTheme.caption.color)), style: TextStyle(color: Theme.of(context).textTheme.caption.color)),
preferredSize: Size(0.0, 20.0),
); );
} }
return Scaffold( return Scaffold(
@ -305,25 +323,30 @@ class _KeyVerificationPageState extends State<KeyVerificationPage> {
title: ListTile( title: ListTile(
leading: Avatar(profile?.avatarUrl, otherName), leading: Avatar(profile?.avatarUrl, otherName),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text(L10n.of(context).verifyTitle), title: Text(L10n.of(context).verifyTitle, maxLines: 1),
isThreeLine: otherName != widget.request.userId, subtitle: Row(
subtitle: Column(
children: <Widget>[ children: <Widget>[
Text(otherName), Text(otherName, maxLines: 1),
if (otherName != widget.request.userId) if (otherName != widget.request.userId)
Text(widget.request.userId), Text(' -> ' + widget.request.userId, maxLines: 1),
], ],
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
), ),
), ),
elevation: 0, elevation: 0,
bottom: bottom,
), ),
extendBody: true, body: SafeArea(
extendBodyBehindAppBar: true, child: Column(
body: Center( children: [
Expanded(
child: Center(
child: body, child: body,
), ),
),
bottom,
],
),
),
persistentFooterButtons: buttons.isEmpty ? null : buttons, persistentFooterButtons: buttons.isEmpty ? null : buttons,
); );
} }