import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:html/dom.dart' as dom; import 'package:surface/widgets/universal_image.dart'; import 'package:url_launcher/url_launcher_string.dart'; List parseHtmlToWidgets( BuildContext context, Iterable? elements) { if (elements == null) return []; final List widgets = []; for (final node in elements) { switch (node.localName) { case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': widgets.add(Text(node.text.trim(), style: Theme.of(context).textTheme.titleMedium)); break; case 'p': if (node.text.trim().isEmpty) continue; widgets.add( Text.rich( TextSpan( text: node.text.trim(), children: [ for (final child in node.children) switch (child.localName) { 'a' => TextSpan( text: child.text.trim(), style: const TextStyle( decoration: TextDecoration.underline), recognizer: TapGestureRecognizer() ..onTap = () { launchUrlString(child.attributes['href']!); }, ), _ => TextSpan(text: child.text.trim()), }, ], ), style: Theme.of(context).textTheme.bodyLarge, ), ); break; case 'a': // drop single link break; case 'div': // ignore div text, normally it is not meaningful widgets.addAll(parseHtmlToWidgets(context, node.children)); break; case 'hr': widgets.add(const Divider()); break; case 'img': var src = node.attributes['src']; if (src == null) break; final width = double.tryParse(node.attributes['width'] ?? 'null'); final height = double.tryParse(node.attributes['height'] ?? 'null'); final ratio = width != null && height != null ? width / height : 1.0; if (src.startsWith('//')) { src = 'https:$src'; } else if (!src.startsWith('http')) { // final baseUri = Uri.parse(_article!.url); // final baseUrl = '${baseUri.scheme}://${baseUri.host}'; src = src; } widgets.add( AspectRatio( aspectRatio: ratio, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(8)), border: Border.all( color: Theme.of(context).dividerColor, width: 1, ), ), height: height ?? double.infinity, child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(8)), child: Container( color: Theme.of(context).colorScheme.surfaceContainer, child: AutoResizeUniversalImage( src, fit: width != null && height != null ? BoxFit.cover : BoxFit.contain, ), ), ), ), ), ); break; default: widgets.addAll(parseHtmlToWidgets(context, node.children)); break; } } return widgets; }