mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-12-18 02:42:34 +01:00
feat: Improved encryption UI
This commit is contained in:
parent
887322cd6e
commit
2516848e81
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user