diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 9925acd7..4c564d90 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -448,6 +448,16 @@ "type": "text", "placeholders": {} }, + "verified": "Verified", + "@verified": { + "type": "text", + "placeholders": {} + }, + "blocked": "Blocked", + "@blocked": { + "type": "text", + "placeholders": {} + }, "zoomIn": "Zoom in", "@zoomIn": { "type": "text", diff --git a/lib/views/settings_devices.dart b/lib/views/settings_devices.dart index 0b3fdcfe..cbc949ae 100644 --- a/lib/views/settings_devices.dart +++ b/lib/views/settings_devices.dart @@ -1,5 +1,7 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:famedlysdk/encryption/utils/key_verification.dart'; import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/components/dialogs/key_verification_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -81,6 +83,36 @@ class DevicesSettingsState extends State { } } + void _verifyDeviceAction(BuildContext context, Device device) async { + final req = Matrix.of(context) + .client + .userDeviceKeys[Matrix.of(context).client.userID] + .deviceKeys[device.deviceId] + .startVerification(); + req.onUpdate = () { + if ({KeyVerificationState.error, KeyVerificationState.done} + .contains(req.state)) { + setState(() => null); + } + }; + await KeyVerificationDialog( + request: req, + l10n: L10n.of(context), + ).show(context); + } + + void _blockDeviceAction(BuildContext context, Device device) async { + final key = Matrix.of(context) + .client + .userDeviceKeys[Matrix.of(context).client.userID] + .deviceKeys[device.deviceId]; + if (key.directVerified) { + await key.setVerified(false); + } + await key.setBlocked(true); + setState(() => null); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -118,6 +150,8 @@ class DevicesSettingsState extends State { thisDevice, rename: (d) => _renameDeviceAction(context, d), remove: (d) => _removeDevicesAction(context, [d]), + verify: (d) => _verifyDeviceAction(context, d), + block: (d) => _blockDeviceAction(context, d), ), Divider(height: 1), if (devices.isNotEmpty) @@ -153,6 +187,8 @@ class DevicesSettingsState extends State { devices[i], rename: (d) => _renameDeviceAction(context, d), remove: (d) => _removeDevicesAction(context, [d]), + verify: (d) => _verifyDeviceAction(context, d), + block: (d) => _blockDeviceAction(context, d), ), ), ), @@ -164,62 +200,125 @@ class DevicesSettingsState extends State { } } +enum UserDeviceListItemAction { + rename, + remove, + verify, + block, +} + class UserDeviceListItem extends StatelessWidget { final Device userDevice; - final Function remove; - final Function rename; + final void Function(Device) remove; + final void Function(Device) rename; + final void Function(Device) verify; + final void Function(Device) block; - const UserDeviceListItem(this.userDevice, {this.remove, this.rename, Key key}) - : super(key: key); + const UserDeviceListItem( + this.userDevice, { + @required this.remove, + @required this.rename, + @required this.verify, + @required this.block, + Key key, + }) : super(key: key); @override Widget build(BuildContext context) { - return PopupMenuButton( - onSelected: (String action) { + final keys = Matrix.of(context) + .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 { + final action = await showModalActionSheet( + context: context, + actions: [ + SheetAction( + key: UserDeviceListItemAction.rename, + label: L10n.of(context).changeDeviceName, + ), + SheetAction( + key: UserDeviceListItemAction.verify, + label: L10n.of(context).verify, + ), + if (keys != null) ...{ + SheetAction( + key: UserDeviceListItemAction.block, + label: L10n.of(context).blockDevice, + isDestructiveAction: true, + ), + SheetAction( + key: UserDeviceListItemAction.block, + label: L10n.of(context).delete, + isDestructiveAction: true, + ), + }, + ], + ); switch (action) { - case 'remove': - if (remove != null) remove(userDevice); + case UserDeviceListItemAction.rename: + rename(userDevice); + break; + case UserDeviceListItemAction.remove: + remove(userDevice); + break; + case UserDeviceListItemAction.verify: + verify(userDevice); + break; + case UserDeviceListItemAction.block: + block(userDevice); break; - case 'rename': - if (rename != null) rename(userDevice); } }, - itemBuilder: (BuildContext context) => [ - PopupMenuItem( - value: 'rename', - child: Text(L10n.of(context).changeDeviceName), - ), - PopupMenuItem( - value: 'remove', - child: Text( - L10n.of(context).removeDevice, - style: TextStyle(color: Colors.red), + 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), + ), + title: Row( + children: [ + Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - ), - ], - child: ListTile( - contentPadding: EdgeInsets.all(16.0), - title: Row( - children: [ - Expanded( - child: Text( - (userDevice.displayName?.isNotEmpty ?? false) - ? userDevice.displayName - : L10n.of(context).unknownDevice, - maxLines: 1, - overflow: TextOverflow.ellipsis, + Spacer(), + Text(userDevice.lastSeenTs.localizedTimeShort(context)), + ], + ), + subtitle: Row( + children: [ + Text(userDevice.deviceId), + Spacer(), + if (keys != null) + Text( + keys.blocked + ? L10n.of(context).blocked + : keys.verified + ? L10n.of(context).verified + : L10n.of(context).unknownDevice, + style: TextStyle( + color: keys.blocked + ? Colors.red + : keys.verified + ? Colors.green + : Colors.orange, ), ), - Text(userDevice.lastSeenTs.localizedTimeShort(context)), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('${L10n.of(context).id}: ${userDevice.deviceId}'), - Text('${L10n.of(context).lastSeenIp}: ${userDevice.lastSeenIp}'), - ], - ), + ], ), ); }