diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 7aca3154b..5e57b128c 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -8,7 +8,9 @@ import 'package:html/dom.dart' as dom; import 'package:html/parser.dart' as parser; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/utils/event_checkbox_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import '../../../utils/url_launcher.dart'; @@ -19,6 +21,8 @@ class HtmlMessage extends StatelessWidget { final double fontSize; final TextStyle linkStyle; final void Function(LinkableElement) onOpen; + final String? eventId; + final Set? checkboxCheckedEvents; const HtmlMessage({ super.key, @@ -28,6 +32,8 @@ class HtmlMessage extends StatelessWidget { required this.linkStyle, this.textColor = Colors.black, required this.onOpen, + this.eventId, + this.checkboxCheckedEvents, }); /// Keep in sync with: https://spec.matrix.org/latest/client-server-api/#mroommessage-msgtypes @@ -218,6 +224,24 @@ class HtmlMessage extends StatelessWidget { if (!{'ol', 'ul'}.contains(node.parent?.localName)) { continue block; } + final eventId = this.eventId; + + final isCheckbox = node.className == 'task-list-item'; + final checkboxIndex = isCheckbox + ? node.rootElement + .getElementsByClassName('task-list-item') + .indexOf(node) + + 1 + : null; + final checkedByReaction = !isCheckbox + ? null + : checkboxCheckedEvents?.firstWhereOrNull( + (event) => event.checkedCheckboxId == checkboxIndex, + ); + final staticallyChecked = !isCheckbox + ? false + : node.children.first.attributes['checked'] == 'true'; + return WidgetSpan( child: Padding( padding: EdgeInsets.only(left: fontSize), @@ -231,6 +255,42 @@ class HtmlMessage extends StatelessWidget { text: '${(node.parent?.nodes.whereType().toList().indexOf(node) ?? 0) + (int.tryParse(node.parent?.attributes['start'] ?? '1') ?? 1)}. ', ), + if (node.className == 'task-list-item') + WidgetSpan( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: SizedBox.square( + dimension: fontSize, + child: Checkbox.adaptive( + checkColor: textColor, + side: BorderSide(color: textColor), + activeColor: textColor.withAlpha(64), + visualDensity: VisualDensity.compact, + value: + staticallyChecked || checkedByReaction != null, + onChanged: eventId == null || + checkboxIndex == null || + staticallyChecked || + !room.canSendDefaultMessages || + (checkedByReaction != null && + checkedByReaction.senderId != + room.client.userID) + ? null + : (_) => showFutureLoadingDialog( + context: context, + future: () => checkedByReaction != null + ? room.redactEvent( + checkedByReaction.eventId, + ) + : room.checkCheckbox( + eventId, + checkboxIndex, + ), + ), + ), + ), + ), + ), ..._renderWithLineBreaks( node.nodes, context, @@ -446,11 +506,9 @@ class HtmlMessage extends StatelessWidget { @override Widget build(BuildContext context) { + final element = parser.parse(html).body ?? dom.Element.html(''); return Text.rich( - _renderHtml( - parser.parse(html).body ?? dom.Element.html(''), - context, - ), + _renderHtml(element, context), style: TextStyle( fontSize: fontSize, color: textColor, @@ -516,3 +574,7 @@ extension on String { return colorValue == null ? null : Color(colorValue); } } + +extension on dom.Element { + dom.Element get rootElement => parent?.rootElement ?? this; +} diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 116aaf4ef..8c72cae3b 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -13,6 +13,7 @@ import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../../config/app_config.dart'; +import '../../../utils/event_checkbox_extension.dart'; import '../../../utils/platform_infos.dart'; import '../../../utils/url_launcher.dart'; import '../../bootstrap/bootstrap_dialog.dart'; @@ -204,6 +205,11 @@ class MessageContent extends StatelessWidget { decorationColor: linkColor, ), onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), + eventId: event.eventId, + checkboxCheckedEvents: event.aggregatedEvents( + timeline, + EventCheckboxRoomExtension.relationshipType, + ), ), ); } diff --git a/lib/utils/event_checkbox_extension.dart b/lib/utils/event_checkbox_extension.dart new file mode 100644 index 000000000..cf3832ba6 --- /dev/null +++ b/lib/utils/event_checkbox_extension.dart @@ -0,0 +1,27 @@ +import 'package:matrix/matrix.dart'; + +extension EventCheckboxRoomExtension on Room { + static const String relationshipType = 'im.fluffychat.checkboxes'; + Future checkCheckbox( + String eventId, + int checkboxId, { + String? txid, + }) => + sendEvent( + { + 'm.relates_to': { + 'rel_type': relationshipType, + 'event_id': eventId, + 'checkbox_id': checkboxId, + }, + }, + type: EventTypes.Reaction, + txid: txid, + ); +} + +extension EventCheckboxExtension on Event { + int? get checkedCheckboxId => content + .tryGetMap('m.relates_to') + ?.tryGet('checkbox_id'); +} diff --git a/pubspec.lock b/pubspec.lock index f1c892e49..084b31477 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1150,10 +1150,11 @@ packages: matrix: dependency: "direct main" description: - name: matrix - sha256: "7d15fdbc760be7e40c58bb65e03baa8241b1e31db2bc67dab61883aabc083a85" - url: "https://pub.dev" - source: hosted + path: "." + ref: "krille/add-markdown-checkboxes" + resolved-ref: f3bb654ac2cda19bdd8a35fb46846018acd01a89 + url: "https://github.com/famedly/matrix-dart-sdk.git" + source: git version: "0.40.0" meta: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index a3fc68d61..61efd69b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,7 +61,10 @@ dependencies: just_audio: ^0.9.39 latlong2: ^0.9.1 linkify: ^5.0.0 - matrix: ^0.40.0 + matrix: + git: + url: https://github.com/famedly/matrix-dart-sdk.git + ref: krille/add-markdown-checkboxes mime: ^1.0.6 native_imaging: ^0.2.0 opus_caf_converter_dart: ^1.0.1 @@ -140,4 +143,4 @@ dependency_overrides: url: https://github.com/ThexXTURBOXx/flutter_web_auth_2.git ref: 3.x-without-v1 path: flutter_web_auth_2 - win32: 5.5.3 + win32: 5.5.3 \ No newline at end of file