Skip to content

Refactor content widgets toward touchable links #209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 30, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 70 additions & 58 deletions lib/widgets/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,61 +58,34 @@ class BlockContentList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
...nodes.map((node) => BlockContentNodeWidget(node: node)),
// Text(nodes.map((n) => n.debugHtmlText ?? "").join())
...nodes.map((node) {
if (node is LineBreakNode) {
// This goes in a Column. So to get the effect of a newline,
// just use an empty Text.
return const Text('');
} else if (node is ParagraphNode) {
return Paragraph(node: node);
} else if (node is HeadingNode) {
return Heading(node: node);
} else if (node is QuotationNode) {
return Quotation(node: node);
} else if (node is ListNode) {
return ListNodeWidget(node: node);
} else if (node is CodeBlockNode) {
return CodeBlock(node: node);
} else if (node is ImageNode) {
return MessageImage(node: node);
} else if (node is UnimplementedBlockContentNode) {
return Text.rich(_errorUnimplemented(node));
} else {
// TODO(dart-3): Use a sealed class / pattern-matching to exclude this.
throw Exception("impossible BlockContentNode: ${node.debugHtmlText}");
}
}),
]);
}
}

/// A single DOM node to display in block layout.
class BlockContentNodeWidget extends StatelessWidget {
const BlockContentNodeWidget({super.key, required this.node});

final BlockContentNode node;

@override
Widget build(BuildContext context) {
final node = this.node;
if (node is LineBreakNode) {
// In block context, the widget we return is going into a Column.
// So to get the effect of a newline, just use an empty Text.
return const Text('');
} else if (node is ParagraphNode) {
return Paragraph(node: node);
} else if (node is HeadingNode) {
// TODO(#192) h1, h2, h3, h4, h5 -- same as h6 except font size
assert(node.level == HeadingLevel.h6);
return Padding(
padding: const EdgeInsets.only(top: 15, bottom: 5),
child: Text.rich(TextSpan(
style: const TextStyle(fontWeight: FontWeight.w600, height: 1.4),
children: _buildInlineList(node.nodes))));
} else if (node is ListNode) {
return ListNodeWidget(node: node);
} else if (node is QuotationNode) {
return Padding(
padding: const EdgeInsets.only(left: 10),
child: Container(
padding: const EdgeInsets.only(left: 5),
decoration: BoxDecoration(
border: Border(
left: BorderSide(
width: 5,
color: const HSLColor.fromAHSL(1, 0, 0, 0.87).toColor()))),
child: BlockContentList(nodes: node.nodes)));
} else if (node is CodeBlockNode) {
return CodeBlock(node: node);
} else if (node is ImageNode) {
return MessageImage(node: node);
} else if (node is UnimplementedBlockContentNode) {
return Text.rich(_errorUnimplemented(node));
} else {
// TODO(dart-3): Use a sealed class / pattern-matching to exclude this.
throw Exception("impossible BlockContentNode: ${node.debugHtmlText}");
}
}
}

class Paragraph extends StatelessWidget {
const Paragraph({super.key, required this.node});

Expand All @@ -124,7 +97,7 @@ class Paragraph extends StatelessWidget {
// The paragraph has vertical CSS margins, but those have no effect.
if (node.nodes.isEmpty) return const SizedBox();

final text = Text.rich(TextSpan(children: _buildInlineList(node.nodes)));
final text = Text.rich(_buildInlineSpan(node.nodes, style: null));

// If the paragraph didn't actually have a `p` element in the HTML,
// then apply no margins. (For example, these are seen in list items.)
Expand All @@ -138,6 +111,43 @@ class Paragraph extends StatelessWidget {
}
}

class Heading extends StatelessWidget {
const Heading({super.key, required this.node});

final HeadingNode node;

@override
Widget build(BuildContext context) {
// TODO(#192) h1, h2, h3, h4, h5 -- same as h6 except font size
assert(node.level == HeadingLevel.h6);
return Padding(
padding: const EdgeInsets.only(top: 15, bottom: 5),
child: Text.rich(_buildInlineSpan(
style: const TextStyle(fontWeight: FontWeight.w600, height: 1.4),
node.nodes)));
}
}

class Quotation extends StatelessWidget {
const Quotation({super.key, required this.node});

final QuotationNode node;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 10),
child: Container(
padding: const EdgeInsets.only(left: 5),
decoration: BoxDecoration(
border: Border(
left: BorderSide(
width: 5,
color: const HSLColor.fromAHSL(1, 0, 0, 0.87).toColor()))),
child: BlockContentList(nodes: node.nodes)));
}
}

class ListNodeWidget extends StatelessWidget {
const ListNodeWidget({super.key, required this.node});

Expand Down Expand Up @@ -287,12 +297,15 @@ class _SingleChildScrollViewWithScrollbarState
// Inline layout.
//

List<InlineSpan> _buildInlineList(List<InlineContentNode> nodes) =>
List.of(nodes.map(_buildInlineNode));
InlineSpan _buildInlineSpan(List<InlineContentNode> nodes, {required TextStyle? style}) {
return TextSpan(
style: style,
children: nodes.map(_buildInlineNode).toList(growable: false));
}

InlineSpan _buildInlineNode(InlineContentNode node) {
InlineSpan styled(List<InlineContentNode> nodes, TextStyle style) =>
TextSpan(children: _buildInlineList(nodes), style: style);
_buildInlineSpan(nodes, style: style);

if (node is TextNode) {
return TextSpan(text: node.text);
Expand Down Expand Up @@ -352,8 +365,7 @@ InlineSpan inlineCode(InlineCodeNode node) {
// TODO `code`: find equivalent of web's `unicode-bidi: embed; direction: ltr`

// Use a light gray background, instead of a border.
return TextSpan(style: _kInlineCodeStyle,
children: _buildInlineList(node.nodes));
return _buildInlineSpan(style: _kInlineCodeStyle, node.nodes);

// Another fun solution -- we can in fact have a border! Like so:
// TextStyle(
Expand Down Expand Up @@ -418,7 +430,7 @@ class UserMention extends StatelessWidget {
return Container(
decoration: _kDecoration,
padding: const EdgeInsets.symmetric(horizontal: 0.2 * kBaseFontSize),
child: Text.rich(TextSpan(children: _buildInlineList(node.nodes))));
child: Text.rich(_buildInlineSpan(node.nodes, style: null)));
}

static get _kDecoration => BoxDecoration(
Expand Down