diff --git a/lib/components/dialogs/key_verification_dialog.dart b/lib/components/dialogs/key_verification_dialog.dart index 85fbe661..a1b42dff 100644 --- a/lib/components/dialogs/key_verification_dialog.dart +++ b/lib/components/dialogs/key_verification_dialog.dart @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'adaptive_flat_button.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import '../../utils/string_color.dart'; +import '../../utils/beautify_string_extension.dart'; class KeyVerificationDialog extends StatefulWidget { Future show(BuildContext context) => PlatformInfos.isCupertinoStyle @@ -168,6 +169,26 @@ class _KeyVerificationPageState extends State { ], mainAxisSize: MainAxisSize.min, ); + final key = widget.request.client.userDeviceKeys[widget.request.userId] + .deviceKeys[widget.request.deviceId]; + if (key != null) { + buttons.add(AdaptiveFlatButton( + child: Text(widget.l10n.verifyManual), + onPressed: () async { + final result = await showOkCancelAlertDialog( + context: context, + title: widget.l10n.verifyManual, + message: key.ed25519Key.beautified, + ); + if (result == OkCancelResult.ok) { + await key.setVerified(true); + } + await widget.request.cancel(); + Navigator.of(context).pop(); + }, + )); + } + break; case KeyVerificationState.askSas: TextSpan compareWidget; diff --git a/lib/utils/device_extension.dart b/lib/utils/device_extension.dart new file mode 100644 index 00000000..fcfb04db --- /dev/null +++ b/lib/utils/device_extension.dart @@ -0,0 +1,33 @@ +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:flutter/material.dart'; + +extension DeviceExtension on Device { + String get displayname => + (displayName?.isNotEmpty ?? false) ? displayName : 'Unknown device'; + + IconData get icon => displayname.toLowerCase().contains('android') + ? Icons.phone_android_outlined + : displayname.toLowerCase().contains('ios') + ? Icons.phone_iphone_outlined + : displayname.toLowerCase().contains('web') + ? Icons.web_outlined + : displayname.toLowerCase().contains('desktop') + ? Icons.desktop_mac_outlined + : Icons.device_unknown_outlined; +} + +extension DeviceKeysExtension on DeviceKeys { + String get displayname => (deviceDisplayName?.isNotEmpty ?? false) + ? deviceDisplayName + : 'Unknown device'; + + IconData get icon => displayname.toLowerCase().contains('android') + ? Icons.phone_android_outlined + : displayname.toLowerCase().contains('ios') + ? Icons.phone_iphone_outlined + : displayname.toLowerCase().contains('web') + ? Icons.web_outlined + : displayname.toLowerCase().contains('desktop') + ? Icons.desktop_mac_outlined + : Icons.device_unknown_outlined; +} diff --git a/lib/views/chat_encryption_settings.dart b/lib/views/chat_encryption_settings.dart index f59bd742..c3d8176c 100644 --- a/lib/views/chat_encryption_settings.dart +++ b/lib/views/chat_encryption_settings.dart @@ -1,13 +1,12 @@ -import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:famedlysdk/encryption.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/components/avatar.dart'; import 'package:fluffychat/components/matrix.dart'; -import 'package:fluffychat/utils/beautify_string_extension.dart'; import 'package:flushbar/flushbar_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../components/dialogs/key_verification_dialog.dart'; +import '../utils/device_extension.dart'; class ChatEncryptionSettings extends StatefulWidget { final String id; @@ -41,20 +40,6 @@ class _ChatEncryptionSettingsState extends State { l10n: L10n.of(context), ).show(context); break; - case 'verify_manual': - if (await showOkCancelAlertDialog( - context: context, - title: L10n.of(context).isDeviceKeyCorrect, - message: key.ed25519Key.beautified, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - ) == - OkCancelResult.ok) { - await unblock(); - await key.setVerified(true); - setState(() => null); - } - break; case 'verify_user': await unblock(); final req = @@ -170,20 +155,11 @@ class _ChatEncryptionSettingsState extends State { var items = >[]; if (deviceKeys[i].blocked || !deviceKeys[i].verified) { - if (deviceKeys[i].userId == room.client.userID) { - items.add(PopupMenuItem( - child: Text(L10n.of(context).verifyStart), - value: 'verify', - )); - } else { - items.add(PopupMenuItem( - child: Text(L10n.of(context).verifyUser), - value: 'verify_user', - )); - } items.add(PopupMenuItem( - child: Text(L10n.of(context).verifyManual), - value: 'verify_manual', + child: Text(L10n.of(context).verifyStart), + value: deviceKeys[i].userId == room.client.userID + ? 'verify' + : 'verify_user', )); } if (deviceKeys[i].blocked) { @@ -201,8 +177,15 @@ class _ChatEncryptionSettingsState extends State { return items; }, child: ListTile( + leading: CircleAvatar( + foregroundColor: + Theme.of(context).textTheme.bodyText1.color, + backgroundColor: + Theme.of(context).secondaryHeaderColor, + child: Icon(deviceKeys[i].icon), + ), title: Text( - '${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice}', + deviceKeys[i].displayname, style: TextStyle( color: deviceKeys[i].blocked ? Colors.red diff --git a/lib/views/settings_devices.dart b/lib/views/settings_devices.dart index cbc949ae..a3d43a8a 100644 --- a/lib/views/settings_devices.dart +++ b/lib/views/settings_devices.dart @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../components/matrix.dart'; import '../utils/date_time_extension.dart'; +import '../utils/device_extension.dart'; class DevicesSettings extends StatefulWidget { @override @@ -113,6 +114,15 @@ class DevicesSettingsState extends State { setState(() => null); } + void _unblockDeviceAction(BuildContext context, Device device) async { + final key = Matrix.of(context) + .client + .userDeviceKeys[Matrix.of(context).client.userID] + .deviceKeys[device.deviceId]; + await key.setBlocked(false); + setState(() => null); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -152,6 +162,7 @@ class DevicesSettingsState extends State { remove: (d) => _removeDevicesAction(context, [d]), verify: (d) => _verifyDeviceAction(context, d), block: (d) => _blockDeviceAction(context, d), + unblock: (d) => _unblockDeviceAction(context, d), ), Divider(height: 1), if (devices.isNotEmpty) @@ -189,6 +200,7 @@ class DevicesSettingsState extends State { remove: (d) => _removeDevicesAction(context, [d]), verify: (d) => _verifyDeviceAction(context, d), block: (d) => _blockDeviceAction(context, d), + unblock: (d) => _unblockDeviceAction(context, d), ), ), ), @@ -205,6 +217,7 @@ enum UserDeviceListItemAction { remove, verify, block, + unblock, } class UserDeviceListItem extends StatelessWidget { @@ -213,6 +226,7 @@ class UserDeviceListItem extends StatelessWidget { final void Function(Device) rename; final void Function(Device) verify; final void Function(Device) block; + final void Function(Device) unblock; const UserDeviceListItem( this.userDevice, { @@ -220,6 +234,7 @@ class UserDeviceListItem extends StatelessWidget { @required this.rename, @required this.verify, @required this.block, + @required this.unblock, Key key, }) : super(key: key); @@ -229,9 +244,6 @@ class UserDeviceListItem extends StatelessWidget { .client .userDeviceKeys[Matrix.of(context).client.userID] ?.deviceKeys[userDevice.deviceId]; - final displayname = (userDevice.displayName?.isNotEmpty ?? false) - ? userDevice.displayName - : L10n.of(context).unknownDevice; return ListTile( onTap: () async { @@ -244,16 +256,23 @@ class UserDeviceListItem extends StatelessWidget { ), SheetAction( key: UserDeviceListItemAction.verify, - label: L10n.of(context).verify, + label: L10n.of(context).verifyStart, ), if (keys != null) ...{ + if (!keys.blocked) + SheetAction( + key: UserDeviceListItemAction.block, + label: L10n.of(context).blockDevice, + isDestructiveAction: true, + ), + if (keys.blocked) + SheetAction( + key: UserDeviceListItemAction.unblock, + label: L10n.of(context).unblockDevice, + isDestructiveAction: true, + ), SheetAction( - key: UserDeviceListItemAction.block, - label: L10n.of(context).blockDevice, - isDestructiveAction: true, - ), - SheetAction( - key: UserDeviceListItemAction.block, + key: UserDeviceListItemAction.remove, label: L10n.of(context).delete, isDestructiveAction: true, ), @@ -273,25 +292,20 @@ class UserDeviceListItem extends StatelessWidget { case UserDeviceListItemAction.block: block(userDevice); break; + case UserDeviceListItemAction.unblock: + unblock(userDevice); + break; } }, leading: CircleAvatar( foregroundColor: Theme.of(context).textTheme.bodyText1.color, backgroundColor: Theme.of(context).secondaryHeaderColor, - child: Icon(displayname.toLowerCase().contains('android') - ? Icons.phone_android_outlined - : displayname.toLowerCase().contains('ios') - ? Icons.phone_iphone_outlined - : displayname.toLowerCase().contains('web') - ? Icons.web_outlined - : displayname.toLowerCase().contains('desktop') - ? Icons.desktop_mac_outlined - : Icons.device_unknown_outlined), + child: Icon(userDevice.icon), ), title: Row( children: [ Text( - displayname, + userDevice.displayname, maxLines: 1, overflow: TextOverflow.ellipsis, ),