From 7e60c74112d4b97128d7ee682dce21a2e812f62a Mon Sep 17 00:00:00 2001 From: Komyyy Date: Tue, 1 Jul 2025 17:43:49 +0900 Subject: [PATCH 1/3] l10n: add transcriptions for unimplemented content error --- assets/l10n/app_en.arb | 16 +++++++++++++ assets/l10n/app_ja.arb | 10 +++++++- lib/generated/l10n/zulip_localizations.dart | 24 +++++++++++++++++++ .../l10n/zulip_localizations_ar.dart | 13 ++++++++++ .../l10n/zulip_localizations_de.dart | 13 ++++++++++ .../l10n/zulip_localizations_en.dart | 13 ++++++++++ .../l10n/zulip_localizations_it.dart | 13 ++++++++++ .../l10n/zulip_localizations_ja.dart | 13 ++++++++++ .../l10n/zulip_localizations_nb.dart | 13 ++++++++++ .../l10n/zulip_localizations_pl.dart | 13 ++++++++++ .../l10n/zulip_localizations_ru.dart | 13 ++++++++++ .../l10n/zulip_localizations_sk.dart | 13 ++++++++++ .../l10n/zulip_localizations_sl.dart | 13 ++++++++++ .../l10n/zulip_localizations_uk.dart | 13 ++++++++++ .../l10n/zulip_localizations_zh.dart | 13 ++++++++++ 15 files changed, 205 insertions(+), 1 deletion(-) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index a5bb75779a..8cbdc0fbab 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -1070,5 +1070,21 @@ "zulipAppTitle": "Zulip", "@zulipAppTitle": { "description": "The name of Zulip. This should be either 'Zulip' or a transliteration." + }, + "errorUnimplementedHeader": "Error displaying content", + "errorUnimplementedWhatHappened": "What Happened?", + "errorUnimplementedDescription": "Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.", + "errorUnimplementedHtmlHeading": "HTML", + "@errorUnimplementedHeader": { + "description": "Header for an error screen explaining that content could not be displayed." + }, + "@errorUnimplementedWhatHappened": { + "description": "Label for explaining what happened in an error screen." + }, + "@errorUnimplementedDescription": { + "description": "Detailed description of an error that explains why content could not be displayed." + }, + "@errorUnimplementedHtmlHeading": { + "description": "Heading for HTML section in an error screen." } } diff --git a/assets/l10n/app_ja.arb b/assets/l10n/app_ja.arb index a66aede69e..66088f100d 100644 --- a/assets/l10n/app_ja.arb +++ b/assets/l10n/app_ja.arb @@ -16,5 +16,13 @@ "userRoleGuest": "ゲスト", "@userRoleGuest": {}, "userRoleUnknown": "不明", - "@userRoleUnknown": {} + "@userRoleUnknown": {}, + "errorUnimplementedHeader": "コンテンツ表示エラー", + "errorUnimplementedWhatHappened": "何が起こったのですか?", + "errorUnimplementedDescription": "Zulipは本来デスクトップ用のアプリで、メッセージはHTML形式で受信されそのまま表示されています。しかし、モバイルアプリでは、メッセージの表示の為にHTMLをContentNodeという形式に変換する必要があります。このエラーは受信したHTMLがContentNodeに変換されなかった為に起こったエラーです。", + "errorUnimplementedHtmlHeading": "HTML", + "@errorUnimplementedHeader": {}, + "@errorUnimplementedWhatHappened": {}, + "@errorUnimplementedDescription": {}, + "@errorUnimplementedHtmlHeading": {} } diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 241a3bbd16..9ee08ab141 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -1598,6 +1598,30 @@ abstract class ZulipLocalizations { /// In en, this message translates to: /// **'Zulip'** String get zulipAppTitle; + + /// Header for an error screen explaining that content could not be displayed. + /// + /// In en, this message translates to: + /// **'Error displaying content'** + String get errorUnimplementedHeader; + + /// Label for explaining what happened in an error screen. + /// + /// In en, this message translates to: + /// **'What Happened?'** + String get errorUnimplementedWhatHappened; + + /// Detailed description of an error that explains why content could not be displayed. + /// + /// In en, this message translates to: + /// **'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'** + String get errorUnimplementedDescription; + + /// Heading for HTML section in an error screen. + /// + /// In en, this message translates to: + /// **'HTML'** + String get errorUnimplementedHtmlHeading; } class _ZulipLocalizationsDelegate diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index e62354d420..efb126d361 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -871,4 +871,17 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index a7965d81ad..b2ad61e563 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -899,4 +899,17 @@ class ZulipLocalizationsDe extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 0178fe9406..c076a16d92 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -871,6 +871,19 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } /// The translations for English, as used in the United Kingdom (`en_GB`). diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index 847cf68981..efddafa684 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -893,4 +893,17 @@ class ZulipLocalizationsIt extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index d7c84a08cb..e7708d5d35 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -871,4 +871,17 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'コンテンツ表示エラー'; + + @override + String get errorUnimplementedWhatHappened => '何が起こったのですか?'; + + @override + String get errorUnimplementedDescription => + 'Zulipは本来デスクトップ用のアプリで、メッセージはHTML形式で受信されそのまま表示されています。しかし、モバイルアプリでは、メッセージの表示の為にHTMLをContentNodeという形式に変換する必要があります。このエラーは受信したHTMLがContentNodeに変換されなかった為に起こったエラーです。'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index 98bad7d7b8..6cbcd70bed 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -871,4 +871,17 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index 1a9bd161e0..ccaeca9885 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -884,4 +884,17 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index fced1a4980..f7c5bbb67e 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -888,4 +888,17 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 0742cfb143..102ee4ce9c 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -873,4 +873,17 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index 885a18c31a..6f8ec30d81 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -900,4 +900,17 @@ class ZulipLocalizationsSl extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index 92bd6b9185..4831218a0b 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -889,4 +889,17 @@ class ZulipLocalizationsUk extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index 5befa99eea..0ae5f23a92 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -871,6 +871,19 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get zulipAppTitle => 'Zulip'; + + @override + String get errorUnimplementedHeader => 'Error displaying content'; + + @override + String get errorUnimplementedWhatHappened => 'What Happened?'; + + @override + String get errorUnimplementedDescription => + 'Zulip is primarily a desktop-oriented app, where messages are received and displayed in HTML format as-is. However, on the mobile app, HTML must be converted into a format called ContentNode in order to display messages properly. This error occurred because the received HTML could not be converted into a ContentNode.'; + + @override + String get errorUnimplementedHtmlHeading => 'HTML'; } /// The translations for Chinese, as used in China, using the Han script (`zh_Hans_CN`). From 96a793cf2c91d776f4c5e29aa11a5df2638623a7 Mon Sep 17 00:00:00 2001 From: Komyyy Date: Tue, 1 Jul 2025 17:51:45 +0900 Subject: [PATCH 2/3] spoiler: abstract spoiler widget as modal --- lib/widgets/content.dart | 128 ++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index eee41d785e..2e214ce66c 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -528,16 +528,29 @@ class ListNodeWidget extends StatelessWidget { } } -class Spoiler extends StatefulWidget { - const Spoiler({super.key, required this.node}); +class Modal extends StatefulWidget { + const Modal({ + super.key, + required this.header, + required this.content, + required this.borderColor, + required this.expandIconColor, + this.bgColor, + this.textColor, + }); - final SpoilerNode node; + final List header; + final List content; + final Color borderColor; + final Color expandIconColor; + final Color? bgColor; + final Color? textColor; @override - State createState() => _SpoilerState(); + State createState() => _ModalState(); } -class _SpoilerState extends State with TickerProviderStateMixin { +class _ModalState extends State with TickerProviderStateMixin { bool expanded = false; late final AnimationController _controller = AnimationController( @@ -565,56 +578,73 @@ class _SpoilerState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - final zulipLocalizations = ZulipLocalizations.of(context); - final header = widget.node.header; - final effectiveHeader = header.isNotEmpty - ? header - : [ParagraphNode(links: null, - nodes: [TextNode(zulipLocalizations.spoilerDefaultHeaderText)])]; return Padding( padding: const EdgeInsets.fromLTRB(0, 5, 0, 15), child: DecoratedBox( decoration: BoxDecoration( - // Web has the same color in light and dark mode. - border: Border.all(color: const Color(0xff808080)), + border: Border.all(color: widget.borderColor), + color: widget.bgColor, borderRadius: BorderRadius.circular(10), ), - child: Padding(padding: const EdgeInsetsDirectional.fromSTEB(10, 2, 8, 2), - child: Column( - children: [ - GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: _handleTap, - child: Padding( - padding: const EdgeInsets.all(5), - child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [ - Expanded( - child: DefaultTextStyle.merge( - style: weightVariableTextStyle(context, wght: 700), - child: BlockContentList( - nodes: effectiveHeader))), - RotationTransition( - turns: _animation.drive(Tween(begin: 0, end: 0.5)), - // Web has the same color in light and dark mode. - child: const Icon(color: Color(0xffd4d4d4), size: 25, - Icons.expand_more)), - ]))), - FadeTransition( - opacity: _animation, - child: const SizedBox(height: 0, width: double.infinity, - child: DecoratedBox( - decoration: BoxDecoration( - border: Border( - // Web has the same color in light and dark mode. - bottom: BorderSide(width: 1, color: Color(0xff808080))))))), - SizeTransition( - sizeFactor: _animation, - axis: Axis.vertical, - axisAlignment: -1, - child: Padding( - padding: const EdgeInsets.all(5), - child: BlockContentList(nodes: widget.node.content))), - ])))); + child: DefaultTextStyle.merge( + style: TextStyle(color: widget.textColor), + child: Padding(padding: const EdgeInsetsDirectional.fromSTEB(10, 2, 8, 2), + child: Column( + children: [ + GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: _handleTap, + child: Padding( + padding: const EdgeInsets.all(5), + child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [ + Expanded( + child: DefaultTextStyle.merge( + style: weightVariableTextStyle(context, wght: 700), + child: BlockContentList( + nodes: widget.header))), + RotationTransition( + turns: _animation.drive(Tween(begin: 0, end: 0.5)), + child: Icon(color: widget.expandIconColor, size: 25, + Icons.expand_more)), + ]))), + FadeTransition( + opacity: _animation, + child: SizedBox(height: 0, width: double.infinity, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: widget.borderColor)))))), + SizeTransition( + sizeFactor: _animation, + axis: Axis.vertical, + axisAlignment: -1, + child: Padding( + padding: const EdgeInsets.all(5), + child: BlockContentList(nodes: widget.content))), + ])), + ))); + } +} + +class Spoiler extends StatelessWidget { + const Spoiler({super.key, required this.node}); + + final SpoilerNode node; + + @override + Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); + final header = node.header; + final effectiveHeader = header.isNotEmpty + ? header + : [ParagraphNode(links: null, + nodes: [TextNode(zulipLocalizations.spoilerDefaultHeaderText)])]; + return Modal( + header: effectiveHeader, + content: node.content, + borderColor: const Color(0xff808080), // Web has the same color in light and dark mode. + expandIconColor: const Color(0xffd4d4d4), // Web has the same color in light and dark mode. + ); } } From c765966ab48177c9c878fb085dc54ed974c30e03 Mon Sep 17 00:00:00 2001 From: Komyyy Date: Tue, 1 Jul 2025 17:51:57 +0900 Subject: [PATCH 3/3] content: revise "unimplemented content" UX --- lib/widgets/content.dart | 82 ++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index 2e214ce66c..79dc695269 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -378,10 +378,8 @@ class BlockContentList extends StatelessWidget { return const SizedBox.shrink(); }(), WebsitePreviewNode() => WebsitePreview(node: node), - UnimplementedBlockContentNode() => - Text.rich(_errorUnimplemented(node, context: context)), + UnimplementedBlockContentNode() => ErrorUnimplemented(node: node), }; - }), ]); } @@ -1302,7 +1300,8 @@ class _InlineContentBuilder { child: GlobalTime(node: node, ambientTextStyle: widget.style)); case UnimplementedInlineContentNode(): - return _errorUnimplemented(node, context: _context!); + return WidgetSpan(alignment: PlaceholderAlignment.middle, + child: ErrorUnimplemented(node: node)); } } @@ -1929,35 +1928,52 @@ class _PresenceCircleState extends State with PerAccountStoreAwa } } -// -// Small helpers. -// +class ErrorUnimplemented extends StatelessWidget { + const ErrorUnimplemented({ + super.key, + required this.node, + }); -InlineSpan _errorUnimplemented(UnimplementedNode node, {required BuildContext context}) { - final contentTheme = ContentTheme.of(context); - final errorStyle = contentTheme.textStyleError; - final errorCodeStyle = contentTheme.textStyleErrorCode; - // For now this shows error-styled HTML code even in release mode, - // because release mode isn't yet about general users but developer demos, - // and we want to keep the demos honest. - // TODO(#194) think through UX for general release - // TODO(#1285) translate this - final htmlNode = node.htmlNode; - if (htmlNode is dom.Element) { - return TextSpan(children: [ - TextSpan(text: "(unimplemented:", style: errorStyle), - TextSpan(text: htmlNode.outerHtml, style: errorCodeStyle), - TextSpan(text: ")", style: errorStyle), - ]); - } else if (htmlNode is dom.Text) { - return TextSpan(children: [ - TextSpan(text: "(unimplemented: text «", style: errorStyle), - TextSpan(text: htmlNode.text, style: errorCodeStyle), - TextSpan(text: "»)", style: errorStyle), - ]); - } else { - return TextSpan( - text: "(unimplemented: DOM node type ${htmlNode.nodeType})", - style: errorStyle); + final UnimplementedNode node; + + @override + Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); + final htmlNode = node.htmlNode; + final text = htmlNode is dom.Element ? htmlNode.outerHtml : htmlNode.text ?? ''; + final header = [ + ParagraphNode( + links: null, + nodes: [TextNode(zulipLocalizations.errorUnimplementedHeader)], + ), + ]; + final content = [ + HeadingNode( + links: null, + nodes: [TextNode(zulipLocalizations.errorUnimplementedWhatHappened)], + level: HeadingLevel.h3, + ), + ParagraphNode( + links: null, + nodes: [TextNode(zulipLocalizations.errorUnimplementedDescription)], + ), + HeadingNode( + links: null, + nodes: [TextNode(zulipLocalizations.errorUnimplementedHtmlHeading)], + level: HeadingLevel.h3, + ), + ParagraphNode( + links: null, + nodes: [InlineCodeNode(nodes: [TextNode(text)])], + ), + ]; + return Modal( + borderColor: const Color(0xffbb0000), + expandIconColor: const Color(0xffffff00), + textColor: const Color(0xffffff00), + bgColor: const Color(0xffff0000), + header: header, + content: content, + ); } }