Skip to content

Instantly share code, notes, and snippets.

@bryantwilliam
Last active May 16, 2024 12:23
Show Gist options
  • Save bryantwilliam/c2de6910249c47350dc966a85a37ccfb to your computer and use it in GitHub Desktop.
Save bryantwilliam/c2de6910249c47350dc966a85a37ccfb to your computer and use it in GitHub Desktop.
highlight selected html in flutter
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:html/dom.dart' as dom;
Future<void> main() async {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
@override
ConsumerState<MyApp> createState() => _MyAppState();
}
class _MyAppState extends ConsumerState<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: Scaffold(
body: Center(
child: HtmlWidget(
html,
// specify custom styling for an element
// see supported inline styling below
customStylesBuilder: (element) {
if (element.classes.contains('foo')) {
return {'color': 'red'};
}
return null;
},
customWidgetBuilder: (element) {
final highlightedElement = ref.read(highlightedElementProvider);
print("highlightedElement: $highlightedElement");
// maybe set ids for each element in here?
// or maybe just set the background color here.
if (isSameHtmlElement(element, highlightedElement)) {
// Uncomment and see what happens (it works, but the highlighting from below stops working):
//element.text = "Selected";
}
return null;
},
factoryBuilder: () {
return CustomWidgetFactory(ref);
},
// this callback will be triggered when user taps a link
onTapUrl: (url) {
print('tapped $url');
return false;
},
// select the render mode for HTML body
// by default, a simple `Column` is rendered
// consider using `ListView` or `SliverList` for better performance
renderMode: RenderMode.column,
// set the default styling for text
textStyle: const TextStyle(fontSize: 15),
// triggers a rebuild if this value changes.
rebuildTriggers: [ref.read(highlightedElementProvider)],
),
),
),
);
}
}
bool isSameHtmlElement(dom.Element element, dom.Element? otherElement) {
// Could maybe use id instead of innerHtml to check, because it's unique for every element (first need to make every element have an id).
return element.innerHtml == otherElement?.innerHtml;
}
final highlightedElementProvider = StateProvider<dom.Element?>((ref) => null);
class CustomWidgetFactory extends WidgetFactory {
WidgetRef ref;
CustomWidgetFactory(this.ref);
@override
Widget? buildText(
BuildTree tree, InheritedProperties resolved, InlineSpan text) {
final highlightedElement = ref.watch(highlightedElementProvider);
return SelectableText.rich(
TextSpan(
style: TextStyle(
backgroundColor: isSameHtmlElement(tree.element, highlightedElement)
? Colors.red
: null,
),
children: <InlineSpan>[text],
),
onSelectionChanged:
(TextSelection selection, SelectionChangedCause? cause) {
// Can use the offsets and direction of highlight
print("selection: $selection");
String textInside = selection.textInside(text.toPlainText());
print("plain text selected: $textInside");
ref.read(highlightedElementProvider.notifier).state = tree.element;
},
);
}
}
String html = '''
<html><body><article><h1>Thunder (mascot)</h1><p>Thunder is the stage name for the horse who is the official live animal mascot for the Denver Broncos</p><p>test</p></article></body></html>
''';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment