Skip to content

Commit 2ddabdb

Browse files
committed
content: revise "unimplemented content" UX
1 parent 092405c commit 2ddabdb

File tree

1 file changed

+128
-82
lines changed

1 file changed

+128
-82
lines changed

lib/widgets/content.dart

Lines changed: 128 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -378,10 +378,8 @@ class BlockContentList extends StatelessWidget {
378378
return const SizedBox.shrink();
379379
}(),
380380
WebsitePreviewNode() => WebsitePreview(node: node),
381-
UnimplementedBlockContentNode() =>
382-
Text.rich(_errorUnimplemented(node, context: context)),
381+
UnimplementedBlockContentNode() => ErrorUnimplemented(node: node),
383382
};
384-
385383
}),
386384
]);
387385
}
@@ -528,16 +526,29 @@ class ListNodeWidget extends StatelessWidget {
528526
}
529527
}
530528

531-
class Spoiler extends StatefulWidget {
532-
const Spoiler({super.key, required this.node});
529+
class Modal extends StatefulWidget {
530+
const Modal({
531+
super.key,
532+
required this.header,
533+
required this.content,
534+
required this.borderColor,
535+
required this.expandIconColor,
536+
this.bgColor,
537+
this.textColor,
538+
});
533539

534-
final SpoilerNode node;
540+
final List<BlockContentNode> header;
541+
final List<BlockContentNode> content;
542+
final Color borderColor;
543+
final Color expandIconColor;
544+
final Color? bgColor;
545+
final Color? textColor;
535546

536547
@override
537-
State<Spoiler> createState() => _SpoilerState();
548+
State<Modal> createState() => _ModalState();
538549
}
539550

540-
class _SpoilerState extends State<Spoiler> with TickerProviderStateMixin {
551+
class _ModalState extends State<Modal> with TickerProviderStateMixin {
541552
bool expanded = false;
542553

543554
late final AnimationController _controller = AnimationController(
@@ -565,56 +576,73 @@ class _SpoilerState extends State<Spoiler> with TickerProviderStateMixin {
565576

566577
@override
567578
Widget build(BuildContext context) {
568-
final zulipLocalizations = ZulipLocalizations.of(context);
569-
final header = widget.node.header;
570-
final effectiveHeader = header.isNotEmpty
571-
? header
572-
: [ParagraphNode(links: null,
573-
nodes: [TextNode(zulipLocalizations.spoilerDefaultHeaderText)])];
574579
return Padding(
575580
padding: const EdgeInsets.fromLTRB(0, 5, 0, 15),
576581
child: DecoratedBox(
577582
decoration: BoxDecoration(
578-
// Web has the same color in light and dark mode.
579-
border: Border.all(color: const Color(0xff808080)),
583+
border: Border.all(color: widget.borderColor),
584+
color: widget.bgColor,
580585
borderRadius: BorderRadius.circular(10),
581586
),
582-
child: Padding(padding: const EdgeInsetsDirectional.fromSTEB(10, 2, 8, 2),
583-
child: Column(
584-
children: [
585-
GestureDetector(
586-
behavior: HitTestBehavior.translucent,
587-
onTap: _handleTap,
588-
child: Padding(
589-
padding: const EdgeInsets.all(5),
590-
child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [
591-
Expanded(
592-
child: DefaultTextStyle.merge(
593-
style: weightVariableTextStyle(context, wght: 700),
594-
child: BlockContentList(
595-
nodes: effectiveHeader))),
596-
RotationTransition(
597-
turns: _animation.drive(Tween(begin: 0, end: 0.5)),
598-
// Web has the same color in light and dark mode.
599-
child: const Icon(color: Color(0xffd4d4d4), size: 25,
600-
Icons.expand_more)),
601-
]))),
602-
FadeTransition(
603-
opacity: _animation,
604-
child: const SizedBox(height: 0, width: double.infinity,
605-
child: DecoratedBox(
606-
decoration: BoxDecoration(
607-
border: Border(
608-
// Web has the same color in light and dark mode.
609-
bottom: BorderSide(width: 1, color: Color(0xff808080))))))),
610-
SizeTransition(
611-
sizeFactor: _animation,
612-
axis: Axis.vertical,
613-
axisAlignment: -1,
614-
child: Padding(
615-
padding: const EdgeInsets.all(5),
616-
child: BlockContentList(nodes: widget.node.content))),
617-
]))));
587+
child: DefaultTextStyle.merge(
588+
style: TextStyle(color: widget.textColor),
589+
child: Padding(padding: const EdgeInsetsDirectional.fromSTEB(10, 2, 8, 2),
590+
child: Column(
591+
children: [
592+
GestureDetector(
593+
behavior: HitTestBehavior.translucent,
594+
onTap: _handleTap,
595+
child: Padding(
596+
padding: const EdgeInsets.all(5),
597+
child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [
598+
Expanded(
599+
child: DefaultTextStyle.merge(
600+
style: weightVariableTextStyle(context, wght: 700),
601+
child: BlockContentList(
602+
nodes: widget.header))),
603+
RotationTransition(
604+
turns: _animation.drive(Tween(begin: 0, end: 0.5)),
605+
child: Icon(color: widget.expandIconColor, size: 25,
606+
Icons.expand_more)),
607+
]))),
608+
FadeTransition(
609+
opacity: _animation,
610+
child: SizedBox(height: 0, width: double.infinity,
611+
child: DecoratedBox(
612+
decoration: BoxDecoration(
613+
border: Border(
614+
bottom: BorderSide(width: 1, color: widget.borderColor)))))),
615+
SizeTransition(
616+
sizeFactor: _animation,
617+
axis: Axis.vertical,
618+
axisAlignment: -1,
619+
child: Padding(
620+
padding: const EdgeInsets.all(5),
621+
child: BlockContentList(nodes: widget.content))),
622+
])),
623+
)));
624+
}
625+
}
626+
627+
class Spoiler extends StatelessWidget {
628+
const Spoiler({super.key, required this.node});
629+
630+
final SpoilerNode node;
631+
632+
@override
633+
Widget build(BuildContext context) {
634+
final zulipLocalizations = ZulipLocalizations.of(context);
635+
final header = node.header;
636+
final effectiveHeader = header.isNotEmpty
637+
? header
638+
: [ParagraphNode(links: null,
639+
nodes: [TextNode(zulipLocalizations.spoilerDefaultHeaderText)])];
640+
return Modal(
641+
header: effectiveHeader,
642+
content: node.content,
643+
borderColor: const Color(0xff808080), // Web has the same color in light and dark mode.
644+
expandIconColor: const Color(0xffd4d4d4), // Web has the same color in light and dark mode.
645+
);
618646
}
619647
}
620648

@@ -1272,7 +1300,8 @@ class _InlineContentBuilder {
12721300
child: GlobalTime(node: node, ambientTextStyle: widget.style));
12731301

12741302
case UnimplementedInlineContentNode():
1275-
return _errorUnimplemented(node, context: _context!);
1303+
return WidgetSpan(alignment: PlaceholderAlignment.middle,
1304+
child: ErrorUnimplemented(node: node));
12761305
}
12771306
}
12781307

@@ -1899,35 +1928,52 @@ class _PresenceCircleState extends State<PresenceCircle> with PerAccountStoreAwa
18991928
}
19001929
}
19011930

1902-
//
1903-
// Small helpers.
1904-
//
1931+
class ErrorUnimplemented extends StatelessWidget {
1932+
const ErrorUnimplemented({
1933+
super.key,
1934+
required this.node,
1935+
});
19051936

1906-
InlineSpan _errorUnimplemented(UnimplementedNode node, {required BuildContext context}) {
1907-
final contentTheme = ContentTheme.of(context);
1908-
final errorStyle = contentTheme.textStyleError;
1909-
final errorCodeStyle = contentTheme.textStyleErrorCode;
1910-
// For now this shows error-styled HTML code even in release mode,
1911-
// because release mode isn't yet about general users but developer demos,
1912-
// and we want to keep the demos honest.
1913-
// TODO(#194) think through UX for general release
1914-
// TODO(#1285) translate this
1915-
final htmlNode = node.htmlNode;
1916-
if (htmlNode is dom.Element) {
1917-
return TextSpan(children: [
1918-
TextSpan(text: "(unimplemented:", style: errorStyle),
1919-
TextSpan(text: htmlNode.outerHtml, style: errorCodeStyle),
1920-
TextSpan(text: ")", style: errorStyle),
1921-
]);
1922-
} else if (htmlNode is dom.Text) {
1923-
return TextSpan(children: [
1924-
TextSpan(text: "(unimplemented: text «", style: errorStyle),
1925-
TextSpan(text: htmlNode.text, style: errorCodeStyle),
1926-
TextSpan(text: "»)", style: errorStyle),
1927-
]);
1928-
} else {
1929-
return TextSpan(
1930-
text: "(unimplemented: DOM node type ${htmlNode.nodeType})",
1931-
style: errorStyle);
1937+
final UnimplementedNode node;
1938+
1939+
@override
1940+
Widget build(BuildContext context) {
1941+
final zulipLocalizations = ZulipLocalizations.of(context);
1942+
final htmlNode = node.htmlNode;
1943+
final text = htmlNode is dom.Element ? htmlNode.outerHtml : htmlNode.text ?? '';
1944+
final header = [
1945+
ParagraphNode(
1946+
links: null,
1947+
nodes: [TextNode(zulipLocalizations.errorUnimplementedHeader)],
1948+
),
1949+
];
1950+
final content = [
1951+
HeadingNode(
1952+
links: null,
1953+
nodes: [TextNode(zulipLocalizations.errorUnimplementedWhatHappened)],
1954+
level: HeadingLevel.h3,
1955+
),
1956+
ParagraphNode(
1957+
links: null,
1958+
nodes: [TextNode(zulipLocalizations.errorUnimplementedDescription)],
1959+
),
1960+
HeadingNode(
1961+
links: null,
1962+
nodes: [TextNode(zulipLocalizations.errorUnimplementedHtmlHeading)],
1963+
level: HeadingLevel.h3,
1964+
),
1965+
ParagraphNode(
1966+
links: null,
1967+
nodes: [InlineCodeNode(nodes: [TextNode(text)])],
1968+
),
1969+
];
1970+
return Modal(
1971+
borderColor: const Color(0xffbb0000),
1972+
expandIconColor: const Color(0xffffff00),
1973+
textColor: const Color(0xffffff00),
1974+
bgColor: const Color(0xffff0000),
1975+
header: header,
1976+
content: content,
1977+
);
19321978
}
19331979
}

0 commit comments

Comments
 (0)