>>[ routeObserver ],
+runApp(
+ RobotDetector(
+ debug: true, // you can set true to enable robot mode
+ child: MaterialApp(
+ home: MyApp(),
+ navigatorObservers: [seoRouteObserver],
+ ),
+ ),
+);
```
-ps : routeObserver is an object, which can be found in utils.dart file.
-
-There are 3 Widgets, `TextRenderer`, `LinkRenderer` & `ImageRenderer`
-
### TextRenderer
-**TextRenderer**
-Just pass the element `new ParagraphElement()`, `new HeadingElement()` or one of other HtmlElement and your `Text`/`RichText` Widget.
-
-#### Paragraph
+To render html text element above a child you pass `Text`, `RichText` as the child or simply set the `text`.
```dart
TextRenderer(
- element: new ParagraphElement(), // This is ParagraphElement by default
- text: Text(
- 'Paragraph: Lorem Ipsum is simply dummy text of the printing and typesetting industry.'),
-),
+ child: Text(
+ 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
+ ),
+)
+
+TextRenderer(
+ text: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
+ child: CustomWidget(),
+)
```
-#### Heading
+Optionally you can change the html element between `` to `` and `
` by setting `style`. Default value is `TextRendererStyle.paragraph`.
```dart
TextRenderer(
- element: new HeadingElement.h1(),
- text: Text(
- 'Heading H1: Lorem Ipsum is simply dummy text of the printing and typesetting industry.'),
-),
+ style: TextRendererStyle.paragraph,
+ child: Text(
+ 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
+ ),
+)
+
+TextRenderer(
+ style: TextRendererStyle.header1,
+ child: Text(
+ 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
+ ),
+)
```
### LinkRenderer
-Need to pass `child : Widget`, `anchorText : String`, `link : String`
-
-Example :
+To render html link element above a child set `text` and `href`.
```dart
LinkRenderer(
- anchorText: 'Try Flutter',
- link: 'https://www.flutter.dev',
- child: OutlinedButton(
- onPressed: () {
- launch('https://www.flutter.dev');
- },
- child: Text('Flutter.dev'),
- ),
-),
+ text: 'Try Flutter',
+ href: 'https://www.flutter.dev',
+ child: ...,
+)
```
### ImageRenderer
-Need to pass `child : Widget`, `link : String`, `alt : String`
-
-Example :
+To render html image element above a child set `alt` and pass `Image.network(...)`, `Image.asset(...)`, `Image.memory(...)` as the child or simply set the `src`.
```dart
ImageRenderer(
- alt: 'Flutter logo',
- link:
- 'https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png',
- child: Image.network(
- "https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png"
- ),
-),
-```
-
-### RendererScrollListener
+ alt: 'Network Image',
+ child: Image.network('https://fakeimg.pl/300x300/?text=Network'),
+)
-In case any of your renderer widgets are inside scrollable widgets like `SingleChildScrollView`, `ListView` you should wrap it within `RendererScrollListener` just so renderer widgets can subscribe to scroll changes and reposition themselves if needed.
-
-Example :
-
-```dart
-RendererScrollListener(
- child: ListView.builder(
- ...
- ),
-);
+ImageRenderer(
+ alt: 'Network Image',
+ src: 'https://fakeimg.pl/300x300/?text=Network',
+ child: CustomWidget(),
+)
```
## ScreenShot & Example
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 6ca2344..86d5ab4 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 30
+ compileSdkVersion 31
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -36,7 +36,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
- targetSdkVersion 30
+ targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
diff --git a/example/android/build.gradle b/example/android/build.gradle
index 9b6ed06..9170929 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,5 +1,5 @@
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.5.32'
repositories {
google()
jcenter()
diff --git a/example/assets/asset_image.png b/example/assets/asset_image.png
new file mode 100644
index 0000000..a21a45d
Binary files /dev/null and b/example/assets/asset_image.png differ
diff --git a/example/lib/examples/image_renderer_example.dart b/example/lib/examples/image_renderer_example.dart
index 72db407..09c985d 100644
--- a/example/lib/examples/image_renderer_example.dart
+++ b/example/lib/examples/image_renderer_example.dart
@@ -1,5 +1,7 @@
+import 'dart:convert';
+
import 'package:flutter/material.dart';
-import 'package:seo_renderer/renderers/image_renderer/image_renderer.dart';
+import 'package:seo_renderer/seo_renderer.dart';
class ImageRendererExample extends StatelessWidget {
const ImageRendererExample({Key? key}) : super(key: key);
@@ -8,12 +10,27 @@ class ImageRendererExample extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
- child: ImageRenderer(
- alt: 'Flutter logo',
- link:
- 'https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png',
- child: Image.network(
- "https://flutter.dev/assets/images/shared/brand/flutter/logo/flutter-lockup.png")),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ImageRenderer(
+ alt: 'Network Image',
+ child: Image.network('https://fakeimg.pl/300x300/?text=Network'),
+ ),
+ ImageRenderer(
+ alt: 'Asset Image',
+ child: Image.asset('assets/asset_image.png'),
+ ),
+ ImageRenderer(
+ alt: 'Memory Image',
+ child: Image.memory(
+ base64Decode(
+ 'iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAASIklEQVR4nO3d2U8bVxuA8XfG+4ZtIBBSSNKobZSlDTe96P8v9aJCitpGaquUACUpJQFv8e6x/V1E5jMwy5nxQt7w/C6DbYzjeXzOmcXW3t7eWABAAfumnwAAmCJYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1Ijf9BP4HLx9+1aOj49lOBy6/vz58+dSKpUiPfbx8bEcHx/LaDRy/fnOzo48ePAg0mMDtw0jLBGpVquesRIROT8/j/S4w+HQN1YAwiFYIjIej31/HjVYtVqNWAFzRLAM9Pt9+fjxY+j7VSqVBTwb4PYiWIaijLIIFjBfBMvQ2dlZqNs3m00ZDAYLejbA7USwDHW7XWm1Wsa3Z3QFzB/BCiHMtJBgAfNHsEIwDVa/35dms7ngZwPcPgQrhFarJd1uN/B2jK6AxSBYIZmMsqrV6hKeCXD7EKyQgoI1Go2kVqst58kAtwzBCqnRaEi/3/f8eb1e9z3NB0B0BCsCvzUq1q+AxSFYEfgdRMr6FbA4XF7Gh2VZridG1+t1cRxH4vHLL1+73fbci+j1WGH1+32pVCpSr9el1WrJYDAQx3HEtm1JJBKSy+WkXC7LnTt3JBaLhXrs8XgszWZTms2mtNvti8cfDofiOI6IiMTjcclms1IsFmVra+vaa9DtdqVSqVw8Rr/fv3TfRCIhhUJBSqWSrK+vR3oNxuOxVKtVOTs7u/gdg8FAYrGYxONxSaVSUiqVpFwuSz6fj/Q7+v2+1Ot1qdVq0m63ZTAYXCwFxONxyefzcu/evYvLDg0GA/n777+l1WqJ4zgyGo1kPB5f+j9/8eKFFAoF19/X7Xbl6OhIms2mOI4jw+Hw0v1t25affvpJLMuK9Pd8KQiWj3K57DrFG4/HUqlUZGNj49K/e00HLcuSUqk00+jLcRw5Pj6Wk5MT1ytADIdDGQ6H0u125fz8XI6OjuTBgwdy9+5d38cdj8dyfn4uZ2dnUqvVLuLipd/vS7/fl1qtJu/evZOdnR1ZXV29eAy/488m9221WvLff/9JLpeTx48fSzabNXsRROT9+/fy5s0b1+fpOI44jiPdblfq9bocHR1JuVyWR48eSSaTMXr8SqUi//77r++Ok8mHRqVSkadPn8rq6qr0er3AHTLtdtszWMfHx/LhwwfP++ZyuVsfKxGC5evq6GHa+fm5cbDy+bzvQn2QTqcjr169kl6vZ3yfySd+t9uVhw8fut7m7OxM9vf3I5/z6DiOHBwcyMHBQaT7t1otefXqlezu7koymfS97XA4lL/++iv0GmG1WpWXL1/Ks2fPpFgset6uXq/LmzdvQp1+JSLy7t07WV1dNQpiu932/FnQ1UC8QnfbsIblw7Isz2hVq9VLIx3HcTzfdMViUTqdTqTn0G635bfffgsVq2lv3771XHP7HE7Q7vf7cnx87Hub8XgcKVYTo9FI/vjjD88YnZycyO+//x46ViJysQQQi8Ukl8v53tbv8YMOSCZYnxAsH51Ox/NT+erxVrVazXONKpPJRLqQ32RDnTUq+/v7n/WFBE9PT33X9w4ODmbe++o4juzv77v+LOqHwVVBUfEaYfX7/cD/n6hrcV8aguWj2+36TiOmNyKv9SnLsiSRSET6/ScnJ5E+9a8aDAahL4+zTKPRyHPtq9PpyMnJyVx+T6PRmPtBvdP/tysrK763nd4BMS1odBWPx43X4L50BMtHv9/3/WSbjpRXsAqFQqQRkuM4cnR0FPp+Xt6/fz+3x1oErynz4eHhXPauTrx7925ujyVyeeRjMm1z+zuZDppj0T3AZFe52ydjr9e7eAN6LaqXSiWjE6avOj09DTxivlwuy+bmpjiOI//884/vwn6USzyLfNogNzc3JZvNynA4lMPDQ9/FYxGRZDIp6+vrkslkZDwey+npaeBI0e25T/Z4+rFtW+7fvy+5XE7Ozs7k9PTU9/b1el1Go5HY9nw+q6dHVZlMRhKJhO8HVKvVuhYggmWOYAXo9XpSLBY9N5xareb75i8Wi4EbkRu/Xdwin97ET58+vdjVncvl5Ndff/W8/XA4lE6nE2pqkU6nZXd399K/jUYj+fPPP33v9+TJk0sb2fr6uvzyyy++93GLs8mJ5js7O7K9vS0inwLebrd94zwajaTRaIT+2rZ4PC7b29tSLpcllUrJYDCQarV67XEKhYLveptb7AmWOaaEAXq9nu+bu16ve24gsVhMVlZWQi/qmlxPa3t7+9JxOYVCwfcwDJHgDcNEKpUKfZ9kMhl42ILbtM8kWFcPLVlbWwu8T5R1rN3dXdne3pZcLnexpnTv3r1rf1fQOlaUYLHg/n8EK0BQsBqNhu/hDJZlhQ5W0AY1ORD1qqCYBB0UaiLqVCooplf3kjmOI41Gw/c+qVTq2t9schBqlENMTM8aiLKn0C9Y6XQ68k6bLxHBCtDv9yWTyXiOECZHbruZRCXsQaNBo6tsNuu6AQVtVJquIhG0TiYirtNbkxHgPEaaXvL5vO8R6Vf3FI5GI9/3B9PBywhWgMmbqVwuh75vuVyW4XAY+hiooGB5rUMFjX7mubdt0UxGQel0+tq/mYxGFhkskwNIp2PM+lU4BCvAZI9P2GCl02nJZDKRTskJehN7jfaCwhj2ZOibZDLCcouTyd84fTL3IgRFZjrGBCscghVgeoQVZv1msvgb9hisoCmCiHewgqZ889qVvwwmoyC3dTHTvzHs9DjMicdBC++mwbIsK3C0dtvoeQffkMkncSwWC7Ur/M6dOyISPlgmIzKvUUTQNErTCMskKG5/j2VZRnFZ5KlKYUZYfjtkcrmcqg+ZZeA4rADj8fjiQMP19XWjc9oymczFruiwUw+T29u2fbE2NrmcSq1W+6KmhCbBmuVyK4sM1mTPnteHlekIi+ngdQTLgOM4kkwmZW1tTWzbDnyzTx8btIhgvX79Wl6/fh3qcUVuT7BMLpa46JPBV1ZWPI8j63Q6Mh6PAw95IVjXMd40EHZaOJkOTt/X1CIPPYhy0OdNMQnK5zrCEvGPzXg8vpj6E6xwCJaB6YgEXdZ3ZWXl0u72sAFa1KEHk3MitVj0IRiLvnpn0MJ7r9eT8XjsOW3kCg3uCJaB6U/jybTQy9VTRT6XYIW5DPHnwCQoXq+VyWu46Olx0AGk3W7XdwcLp+O4I1gGpjcAv2mhZVnXRmBhpx6L+uTXtgHM8jqYBCvs3rewz8e2bd9DEnq9Hke4R0CwDFyNjte0cHV19dq0K2yw5vnJH4/HpVAoyMbGxrWR3+fOJChuYTIdoS5jB4TftLDX67F+FYGeRY0bdDU6XnsLpxfbJ8JOCU02pEKhIPl8XmKxmNi2fbE+FYvFJJFIXFwdQdNewatMnrvbh4HpTo5lvDZ+0en1er7H6BEsdwTLwNVP7cm0cPqYrHg8Lqurq9fuG3aEZTKyKBaLnt+E86Uw2UHg9mFg8gGRSCRufIQ1+cozN1yhwRvBMuA2zXj48OGlEVU2m3WNTdhFdJMNdV5fmvA5M9lg3UZTJmcWuJ00vQipVEqSyaRrmPxGWIyuvBEsA27RyWazRnvewo6wUqlU4MGp8/hiis9d0AX/RNzjZBLzKIcLRN0JsLKy4voFII7jeD5XbTtIlolFdwOzHGoQ9r6WZQVuUO12O/DidtqZjILcNniTy9Is8xAPv9GS1wcPIyxvBMvALMGKckS1yQZ1eHgYuMDc6/Xk7du38vLly0jXlb9JJq+B2wZv8mUbYa/nPgu/+LhNFS3LYoTlgymhgWWOsEQ+vcmDvoSi0WjIy5cvZWtrS7LZrCSTSRkOhxdXQG00GpdGYR8/fpTNzc3Qz+WmTK5U4Bf8wWAgzWbz0onmQZeXTiQSSw3C5ABS0/cBV2jwR7AMzHLeWZT7rq2tyZs3bwJv1+v15PDw0OgxF3mVzUWwbVuKxaLn9z1OHBwcyLfffiuxWEwODg4CX2+3PbmLZNu25PN5469ZYzroj2AtWJQRViqVkpWVlbmuUy3yCpuLUiqVAoNVr9dlb2/P+DG3trZmfVqhraysEKw5YexpYNlrWCIid+/ejfw73WgM1vr6+lxPVVpdXY08HZzleYSJEMHyR7AMLHtKKPLpJOqgM/7D0PQFFBOpVGpuIyLbtuXBgwdzeaywTCPEFRqCESwDUTf2WSPxzTffzO2I7Cjf+vM52NnZmctR3999992NXR/d7fsT3bB3MBjBWqBZLxKXzWbl+fPnM1/HqlAoqD2VJ5FIzPQaWJYljx49CryO2aKZjLKYDgYjWAtkMsIKWhspFAry4sULo69gvyqdTsvXX38t33//faQNPuru9SjrPX6/K5fLyQ8//BB6lJjP52V3d1fu3bsX+vlMM/1iCz8m03uCFYy9hC4sy7q4CkImk4n86ew2wpo8tm3bkk6njXazZzIZefLkiTSbTalWq1Kr1aTb7YrjODIajSQej0sikZB4PC7JZFJKpZIUi8XQ6yGTDdO2bUkmk8YL/9OvVy6XM/q9k98Vi8UknU4HBjmbzcqzZ8+k2WxKpVKRjx8/SqfTEcdxZDgcim3bF1eqKBaLsra2NtMUa/I3JRKJuUynGWHNh7W3t6dvNRZQZjwey88//+w56k6lUvLjjz8u+Vnpw5QQWILRaOS7RDDPPcJfMoIFLEHQSdkEywzBApYg6KyFYrG4pGeiG8EClsDrS1VFPh26oe1bjW4KwQLmwO8bcGq1mtTrdc+fL/uEbM04rAGY0WAwkL29PSmXy1IulyWdTksqlZJYLCa1Wk329/d97x/lGLvbimABM/rw4YOMRiM5Pz/3nfq5icfjS72goHZMCYEZNZvNyPfd2trign0h8EoBM4r6LUaxWGzm04ZuG4IFzCjqVTkePnzI9w+GRLCAGUU5B/DOnTs3cvVT7QgWMKP79+8bn2htWZZ89dVX8vjx4wU/qy8TewmBGcViMXnx4oV8+PBBKpWKtNtt6ff7MhwOxbIsicfjkkqlpFQqyebm5tK+efpLRLCAObAsSzY2NmRjY+Omn8oXjSkhADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1CBYANQgWADUIFgA1/geV1rSWGtJnBgAAAABJRU5ErkJggg==',
+ ),
+ ),
+ ),
+ ],
+ ),
),
);
}
diff --git a/example/lib/examples/link_singletext_example.dart b/example/lib/examples/link_singletext_example.dart
index 0681d43..4dc215a 100644
--- a/example/lib/examples/link_singletext_example.dart
+++ b/example/lib/examples/link_singletext_example.dart
@@ -10,8 +10,8 @@ class SingleTextLinkExample extends StatelessWidget {
return Scaffold(
body: Center(
child: LinkRenderer(
- anchorText: 'Try Flutter',
- link: 'https://www.flutter.dev',
+ text: 'Try Flutter',
+ href: 'https://www.flutter.dev',
child: OutlinedButton(
onPressed: () {
launch('https://www.flutter.dev');
diff --git a/example/lib/examples/scrollable_content.dart b/example/lib/examples/scrollable_content.dart
index 47bb6fe..502b0b4 100644
--- a/example/lib/examples/scrollable_content.dart
+++ b/example/lib/examples/scrollable_content.dart
@@ -1,5 +1,3 @@
-import 'dart:html';
-
import 'package:flutter/material.dart';
import 'package:seo_renderer/seo_renderer.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -14,20 +12,18 @@ class ScrollableContent extends StatelessWidget {
title: const Text('SEO HTML Tag Creator'),
),
body: Center(
- child: RendererScrollListener(
- child: SingleChildScrollView(
- child: Column(
- children: [
- for (var i = 0; i < 10; i++) ...[
- TextWidget(),
- TextWidget(),
- LinkWidget(),
- TextWidget(),
- TextWidget(),
- ImageWidget(),
- ]
- ],
- ),
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ for (var i = 0; i < 10; i++) ...[
+ TextWidget(),
+ TextWidget(),
+ LinkWidget(),
+ TextWidget(),
+ TextWidget(),
+ ImageWidget(),
+ ]
+ ],
),
),
),
@@ -41,8 +37,8 @@ class TextWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TextRenderer(
- element: ParagraphElement(),
- text: Text(
+ style: TextRendererStyle.paragraph,
+ child: Text(
'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.',
),
);
@@ -56,8 +52,7 @@ class ImageWidget extends StatelessWidget {
Widget build(BuildContext context) {
return ImageRenderer(
alt: 'Fake Image',
- link: 'https://fakeimg.pl/300x300/?text=Image',
- child: Image.network("https://fakeimg.pl/300x300/?text=Image"),
+ child: Image.network('https://fakeimg.pl/300x300/?text=Image'),
);
}
}
@@ -68,8 +63,8 @@ class LinkWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LinkRenderer(
- anchorText: 'Try Flutter',
- link: 'https://www.flutter.dev',
+ text: 'Try Flutter',
+ href: 'https://www.flutter.dev',
child: OutlinedButton(
onPressed: () => launch('https://www.flutter.dev'),
child: Text('Flutter.dev'),
diff --git a/example/lib/examples/single_text_item.dart b/example/lib/examples/single_text_item.dart
index d50c0eb..414b4b1 100644
--- a/example/lib/examples/single_text_item.dart
+++ b/example/lib/examples/single_text_item.dart
@@ -1,6 +1,3 @@
-import 'dart:html';
-
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:seo_renderer/seo_renderer.dart';
@@ -14,19 +11,17 @@ class SingleTextItem extends StatelessWidget {
child: Column(
children: [
TextRenderer(
- element: new HeadingElement.h1(),
- text: Text('''Heading element
- '''),
+ style: TextRendererStyle.header1,
+ child: Text('Heading element '),
),
TextRenderer(
- element: new HeadingElement.h2(),
- text: Text('''Heading element and etc to h6
- '''),
+ style: TextRendererStyle.header2,
+ child: Text('Heading element and etc to h6'),
),
TextRenderer(
- element: new ParagraphElement(),
- text: Text(
- '''Paragraph
: Lorem Ipsum is simply dummy text of the printing and typesetting industry.''',
+ style: TextRendererStyle.paragraph,
+ child: Text(
+ 'Paragraph
: Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
),
),
],
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 6e1c72d..7d3de85 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -6,10 +6,14 @@ import 'package:seo_renderer_example/examples/scrollable_content.dart';
import 'package:seo_renderer_example/examples/single_text_item.dart';
void main() {
- runApp(MaterialApp(
- navigatorObservers: [routeObserver],
- home: MyApp(),
- ));
+ runApp(
+ RobotDetector(
+ child: MaterialApp(
+ home: MyApp(),
+ navigatorObservers: [seoRouteObserver],
+ ),
+ ),
+ );
}
class MyApp extends StatelessWidget {
@@ -22,29 +26,41 @@ class MyApp extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(builder: (_) => SingleTextItem()));
- },
- child: TextRenderer(text: Text('Single Text Item'))),
+ onPressed: () {
+ Navigator.of(context)
+ .push(MaterialPageRoute(builder: (_) => SingleTextItem()));
+ },
+ child: TextRenderer(
+ child: Text('Single Text Item'),
+ ),
+ ),
OutlinedButton(
- onPressed: () {
- Navigator.of(context).push(
- MaterialPageRoute(builder: (_) => ScrollableContent()));
- },
- child: TextRenderer(text: Text('Scrollable Text Content'))),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(builder: (_) => ScrollableContent()));
+ },
+ child: TextRenderer(
+ child: Text('Scrollable Text Content'),
+ ),
+ ),
OutlinedButton(
- onPressed: () {
- Navigator.of(context).push(MaterialPageRoute(
- builder: (_) => SingleTextLinkExample()));
- },
- child: TextRenderer(text: Text('Single Link Text Item'))),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(builder: (_) => SingleTextLinkExample()));
+ },
+ child: TextRenderer(
+ child: Text('Single Link Text Item'),
+ ),
+ ),
OutlinedButton(
- onPressed: () {
- Navigator.of(context).push(MaterialPageRoute(
- builder: (_) => ImageRendererExample()));
- },
- child: TextRenderer(text: Text('Image renderer'))),
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(builder: (_) => ImageRendererExample()));
+ },
+ child: TextRenderer(
+ child: Text('Image renderer'),
+ ),
+ ),
],
),
),
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 2310e2d..7eb7464 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -7,7 +7,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.8.1"
+ version: "2.8.2"
boolean_selector:
dependency: transitive
description:
@@ -21,7 +21,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
charcode:
dependency: transitive
description:
@@ -85,7 +85,14 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.10"
+ version: "0.12.11"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.3"
meta:
dependency: transitive
description:
@@ -113,7 +120,7 @@ packages:
path: ".."
relative: true
source: path
- version: "0.2.0"
+ version: "0.4.0"
sky_engine:
dependency: transitive
description: flutter
@@ -160,7 +167,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.2"
+ version: "0.4.8"
typed_data:
dependency: transitive
description:
@@ -216,7 +223,7 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0"
+ version: "2.1.1"
sdks:
- dart: ">=2.12.0 <3.0.0"
+ dart: ">=2.14.0 <3.0.0"
flutter: ">=2.0.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index b2488c4..4941640 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -39,6 +39,8 @@ flutter:
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
+ assets:
+ - assets/
# To add assets to your application, add an assets section, like this:
# assets:
diff --git a/lib/helpers/robot_detector_vm.dart b/lib/helpers/robot_detector_vm.dart
new file mode 100644
index 0000000..699480b
--- /dev/null
+++ b/lib/helpers/robot_detector_vm.dart
@@ -0,0 +1,14 @@
+import 'package:flutter/material.dart';
+
+class RobotDetector extends StatelessWidget {
+ final Widget child;
+
+ const RobotDetector({
+ Key? key,
+ bool debug = false,
+ required this.child,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) => child;
+}
diff --git a/lib/helpers/robot_detector_web.dart b/lib/helpers/robot_detector_web.dart
new file mode 100644
index 0000000..36d6b92
--- /dev/null
+++ b/lib/helpers/robot_detector_web.dart
@@ -0,0 +1,38 @@
+// ignore: avoid_web_libraries_in_flutter
+import 'dart:html';
+
+import 'package:flutter/material.dart';
+
+class RobotDetector extends StatefulWidget {
+ final bool debug;
+ final Widget child;
+
+ const RobotDetector({
+ Key? key,
+ this.debug = false,
+ required this.child,
+ }) : super(key: key);
+
+ @override
+ _RobotDetectorState createState() => _RobotDetectorState();
+
+ static bool detected(BuildContext context) {
+ return context.findAncestorStateOfType<_RobotDetectorState>()!._detected;
+ }
+}
+
+class _RobotDetectorState extends State {
+ /// Regex to detect Crawler for Search Engines
+ final _regExp = RegExp(r'/bot|google|baidu|bing|msn|teoma|slurp|yandex/i');
+ late bool _detected;
+
+ @override
+ void initState() {
+ super.initState();
+ _detected =
+ widget.debug || _regExp.hasMatch(window.navigator.userAgent.toString());
+ }
+
+ @override
+ Widget build(BuildContext context) => widget.child;
+}
diff --git a/lib/helpers/route_aware_state.dart b/lib/helpers/route_aware_state.dart
new file mode 100644
index 0000000..6cf9227
--- /dev/null
+++ b/lib/helpers/route_aware_state.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+
+final seoRouteObserver = RouteObserver>();
+
+abstract class RouteAwareState extends State
+ implements RouteAware {
+ var visible = true;
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ final route = ModalRoute.of(context);
+ if (route == null) {
+ setState(() => visible = true);
+ return;
+ }
+
+ seoRouteObserver.subscribe(this, route);
+ }
+
+ @override
+ void didPop() {
+ // do nothing as the route will be disposed
+ }
+
+ @override
+ void didPopNext() {
+ setState(() => visible = true);
+ }
+
+ @override
+ void didPush() {
+ setState(() => visible = true);
+ }
+
+ @override
+ void didPushNext() {
+ setState(() => visible = false);
+ }
+
+ @override
+ void dispose() {
+ seoRouteObserver.unsubscribe(this);
+ super.dispose();
+ }
+}
diff --git a/lib/helpers/scroll_aware.dart b/lib/helpers/scroll_aware.dart
deleted file mode 100644
index 11ab8a1..0000000
--- a/lib/helpers/scroll_aware.dart
+++ /dev/null
@@ -1,18 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:seo_renderer/helpers/scroll_listener/renderer_scroll_listener.dart';
-
-abstract class ScrollAware {
- Listenable? _listenable;
-
- void subscribe(BuildContext context) {
- unsubscribe();
- _listenable = RendererScrollListener.of(context);
- _listenable?.addListener(didScroll);
- }
-
- void unsubscribe() {
- _listenable?.removeListener(didScroll);
- }
-
- void didScroll();
-}
diff --git a/lib/helpers/scroll_listener/renderer_scroll_listener.dart b/lib/helpers/scroll_listener/renderer_scroll_listener.dart
deleted file mode 100644
index 82f225b..0000000
--- a/lib/helpers/scroll_listener/renderer_scroll_listener.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-/// Conditional imports based on if 'dart.io' is supported.
-///
-/// We export lib 'renderer_scroll_listener_web.dart', but if dart.io is supported
-/// then we export 'renderer_scroll_listener_vm.dart' instead.
-export 'renderer_scroll_listener_web.dart'
- if (dart.library.io) 'renderer_scroll_listener_vm.dart';
diff --git a/lib/helpers/scroll_listener/renderer_scroll_listener_vm.dart b/lib/helpers/scroll_listener/renderer_scroll_listener_vm.dart
deleted file mode 100644
index 957feb4..0000000
--- a/lib/helpers/scroll_listener/renderer_scroll_listener_vm.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-import 'package:flutter/material.dart';
-
-class RendererScrollListener extends StatelessWidget {
- final Widget child;
-
- const RendererScrollListener({
- Key? key,
- required this.child,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return child;
- }
-}
diff --git a/lib/helpers/scroll_listener/renderer_scroll_listener_web.dart b/lib/helpers/scroll_listener/renderer_scroll_listener_web.dart
deleted file mode 100644
index 88a7ad0..0000000
--- a/lib/helpers/scroll_listener/renderer_scroll_listener_web.dart
+++ /dev/null
@@ -1,38 +0,0 @@
-import 'package:flutter/material.dart';
-
-class RendererScrollListener extends StatefulWidget {
- final Widget child;
-
- const RendererScrollListener({
- Key? key,
- required this.child,
- }) : super(key: key);
-
- @override
- _RendererScrollListenerState createState() => _RendererScrollListenerState();
-
- static Listenable? of(BuildContext context) {
- return context
- .findAncestorStateOfType<_RendererScrollListenerState>()
- ?._notifier;
- }
-}
-
-class _RendererScrollListenerState extends State {
- final _notifier = _ScrollNotifier();
-
- @override
- Widget build(BuildContext context) {
- return NotificationListener(
- child: widget.child,
- onNotification: (_) {
- _notifier.didScroll();
- return false;
- },
- );
- }
-}
-
-class _ScrollNotifier extends ChangeNotifier {
- void didScroll() => notifyListeners();
-}
diff --git a/lib/helpers/size_widget.dart b/lib/helpers/size_widget.dart
new file mode 100644
index 0000000..e056d4f
--- /dev/null
+++ b/lib/helpers/size_widget.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/scheduler.dart';
+
+class SizeWidget extends SingleChildRenderObjectWidget {
+ final Function(Size) onSize;
+
+ const SizeWidget({
+ Key? key,
+ required this.onSize,
+ required Widget child,
+ }) : super(key: key, child: child);
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return _SizeWidgetRenderObject(onSize);
+ }
+}
+
+class _SizeWidgetRenderObject extends RenderProxyBox {
+ final Function(Size) onSize;
+
+ _SizeWidgetRenderObject(this.onSize);
+
+ @override
+ void performLayout() {
+ super.performLayout();
+
+ final size = child?.size;
+ if (size == null) return;
+
+ if (SchedulerBinding.instance?.schedulerPhase !=
+ SchedulerPhase.persistentCallbacks) {
+ onSize(size);
+ } else {
+ SchedulerBinding.instance?.addPostFrameCallback((_) => onSize(size));
+ }
+ }
+}
diff --git a/lib/helpers/utils.dart b/lib/helpers/utils.dart
deleted file mode 100644
index d2eadd5..0000000
--- a/lib/helpers/utils.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-import 'package:flutter/material.dart';
-
-/// [RouteObserver] created to remove Element in case pop in [RouteAware]
-final RouteObserver> routeObserver =
- RouteObserver>();
-
-///Regex to detect Crawler for Search Engines
-RegExp regExpBots = RegExp(r'/bot|google|baidu|bing|msn|teoma|slurp|yandex/i');
-
-/// A [GlobalKey] Extension to get Rect from the RenderObject from a GlobalKey
-extension GlobalKeyExtension on GlobalKey {
- Rect? get globalPaintBounds {
- final renderObject = currentContext?.findRenderObject();
- final translation = renderObject?.getTransformTo(null).getTranslation();
- if (translation != null && renderObject?.paintBounds != null) {
- return renderObject!.paintBounds
- .shift(Offset(translation.x, translation.y));
- } else {
- return null;
- }
- }
-}
diff --git a/lib/renderers/image_renderer/image_renderer.dart b/lib/renderers/image_renderer/image_renderer.dart
deleted file mode 100644
index 24142e3..0000000
--- a/lib/renderers/image_renderer/image_renderer.dart
+++ /dev/null
@@ -1,5 +0,0 @@
-/// Conditional imports based on if 'dart.io' is supported.
-///
-/// We export lib 'image_renderer_web.dart', but if dart.io is supported
-/// then we export 'image_renderer_vm.dart' instead.
-export 'image_renderer_web.dart' if (dart.library.io) 'image_renderer_vm.dart';
diff --git a/lib/renderers/image_renderer/image_renderer_vm.dart b/lib/renderers/image_renderer/image_renderer_vm.dart
index c90d54e..d942972 100644
--- a/lib/renderers/image_renderer/image_renderer_vm.dart
+++ b/lib/renderers/image_renderer/image_renderer_vm.dart
@@ -6,21 +6,19 @@ class ImageRenderer extends StatelessWidget {
const ImageRenderer({
Key? key,
required this.child,
- required this.link,
+ this.src,
required this.alt,
}) : super(key: key);
- ///Any Widget with image in it
+ /// Any Widget with image in it
final Widget child;
- ///Image source
- final String link;
+ /// Image source
+ final String? src;
- ///Alternative to image
+ /// Alternative to image
final String alt;
@override
- Widget build(BuildContext context) {
- return child;
- }
+ Widget build(BuildContext context) => child;
}
diff --git a/lib/renderers/image_renderer/image_renderer_web.dart b/lib/renderers/image_renderer/image_renderer_web.dart
index 990fc04..f9bba14 100644
--- a/lib/renderers/image_renderer/image_renderer_web.dart
+++ b/lib/renderers/image_renderer/image_renderer_web.dart
@@ -1,8 +1,12 @@
+// ignore: avoid_web_libraries_in_flutter
+import 'dart:convert';
import 'dart:html';
+import 'dart:ui' as ui;
import 'package:flutter/material.dart';
-import 'package:seo_renderer/helpers/scroll_aware.dart';
-import 'package:seo_renderer/helpers/utils.dart';
+import 'package:seo_renderer/helpers/robot_detector_web.dart';
+import 'package:seo_renderer/helpers/route_aware_state.dart';
+import 'package:seo_renderer/helpers/size_widget.dart';
/// This VM import stub does nothing and only returns the child.
class ImageRenderer extends StatefulWidget {
@@ -10,112 +14,100 @@ class ImageRenderer extends StatefulWidget {
const ImageRenderer({
Key? key,
required this.child,
- required this.link,
+ this.src,
required this.alt,
}) : super(key: key);
- ///Any Widget with image in it
+ /// Any Widget with image in it
final Widget child;
- ///Image source
- final String link;
+ /// Image source
+ final String? src;
- ///Alternative to image
+ /// Alternative to image
final String alt;
@override
_ImageRendererState createState() => _ImageRendererState();
}
-class _ImageRendererState extends State
- with RouteAware, ScrollAware {
- final DivElement div = DivElement();
- final key = GlobalKey();
+class _ImageRendererState extends RouteAwareState {
+ Size? _size;
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- routeObserver.subscribe(this, ModalRoute.of(context)!);
- subscribe(context);
- }
-
- @override
- void dispose() {
- clear();
- routeObserver.unsubscribe(this);
- unsubscribe();
- super.dispose();
- }
-
- @override
- void didPop() {
- clear();
- super.didPop();
- }
-
- @override
- void didPush() {
- addDivElement();
- super.didPush();
- }
+ void _onSize(Size size) {
+ if (_size == size) return;
+ if (size.isEmpty) return;
+ _size = size;
- @override
- void didPopNext() {
- addDivElement();
- super.didPopNext();
+ if (!mounted) return;
+ setState(() {});
}
- @override
- void didPushNext() {
- clear();
- super.didPushNext();
- }
+ String get _src {
+ final src = widget.src;
+ if (src != null) {
+ return src;
+ }
+
+ final child = widget.child;
+ if (child is Image) {
+ final image = (child.image is ResizeImage)
+ ? (child.image as ResizeImage).imageProvider
+ : child.image;
+
+ if (image is NetworkImage) {
+ return image.url;
+ } else if (image is AssetImage) {
+ return image.assetName;
+ } else if (image is ExactAssetImage) {
+ return image.assetName;
+ } else if (image is MemoryImage) {
+ return 'data:image/png;base64,${base64Encode(image.bytes)}';
+ }
- @override
- void didScroll() {
- refresh();
- }
+ throw FlutterError(
+ 'ImageRenderer child is ${widget.child.runtimeType}, image is ${image.runtimeType} not supported',
+ );
+ }
- void refresh() {
- div.style.position = 'absolute';
- div.style.top = '${key.globalPaintBounds?.top ?? 0}px';
- div.style.left = '${key.globalPaintBounds?.left ?? 0}px';
- var imageElement = new ImageElement()
- ..src = widget.link
- ..alt = widget.alt
- ..width = (key.globalPaintBounds?.width ?? 100).toInt()
- ..height = (key.globalPaintBounds?.height ?? 100).toInt();
- div.children.removeWhere((element) => true);
- div.append(imageElement);
+ throw FlutterError(
+ 'ImageRenderer child is ${widget.child.runtimeType} and src is null',
+ );
}
@override
Widget build(BuildContext context) {
- return LayoutBuilder(
- key: key,
- builder: (_, __) {
- return NotificationListener(
- onNotification: (SizeChangedLayoutNotification notification) {
- WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
- refresh();
- });
- return true;
- },
- child: SizeChangedLayoutNotifier(child: widget.child));
- });
- }
-
- addDivElement() {
- WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
- if (!regExpBots.hasMatch(window.navigator.userAgent.toString())) {
- return;
- }
- refresh();
- if (!document.body!.contains(div)) document.body?.append(div);
- });
- }
-
- void clear() {
- div.remove();
+ if (!RobotDetector.detected(context)) {
+ return widget.child;
+ }
+
+ final viewType = 'html-image-$_src';
+ // ignore: undefined_prefixed_name
+ ui.platformViewRegistry.registerViewFactory(
+ viewType,
+ (int viewId) => ImageElement(src: _src)
+ ..alt = widget.alt
+ ..style.margin = '0px'
+ ..style.padding = '0px'
+ ..style.width = '${_size?.width ?? 0}px'
+ ..style.height = '${_size?.height ?? 0}px',
+ );
+
+ return SizedBox(
+ width: _size?.width,
+ height: _size?.height,
+ child: Stack(
+ children: [
+ SizeWidget(
+ onSize: _onSize,
+ child: widget.child,
+ ),
+ if (_size != null && visible)
+ IgnorePointer(
+ child: HtmlElementView(viewType: viewType),
+ ),
+ ],
+ ),
+ );
}
}
diff --git a/lib/renderers/link_renderer/link_renderer.dart b/lib/renderers/link_renderer/link_renderer.dart
deleted file mode 100644
index 5102f72..0000000
--- a/lib/renderers/link_renderer/link_renderer.dart
+++ /dev/null
@@ -1,5 +0,0 @@
-/// Conditional imports based on if 'dart.io' is supported.
-///
-/// We export lib 'link_renderer_web.dart', but if dart.io is supported
-/// then we export 'link_renderer_vm.dart' instead.
-export 'link_renderer_web.dart' if (dart.library.io) 'link_renderer_vm.dart';
diff --git a/lib/renderers/link_renderer/link_renderer_vm.dart b/lib/renderers/link_renderer/link_renderer_vm.dart
index d10400e..fe2eeeb 100644
--- a/lib/renderers/link_renderer/link_renderer_vm.dart
+++ b/lib/renderers/link_renderer/link_renderer_vm.dart
@@ -8,22 +8,20 @@ class LinkRenderer extends StatelessWidget {
const LinkRenderer({
Key? key,
required this.child,
- required this.anchorText,
- required this.link,
+ required this.text,
+ required this.href,
}) : super(key: key);
///Any Widget with link in it
final Widget child;
///Anchor Text just like html, will work like a replacement to
- ///provided [child] with [link] to it.
- final String anchorText;
+ ///provided [child] with [href] to it.
+ final String text;
///link to put in href
- final String link;
+ final String href;
@override
- Widget build(BuildContext context) {
- return child;
- }
+ Widget build(BuildContext context) => child;
}
diff --git a/lib/renderers/link_renderer/link_renderer_web.dart b/lib/renderers/link_renderer/link_renderer_web.dart
index 0b2a6d2..24d4d74 100644
--- a/lib/renderers/link_renderer/link_renderer_web.dart
+++ b/lib/renderers/link_renderer/link_renderer_web.dart
@@ -1,8 +1,11 @@
+// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
+import 'dart:ui' as ui;
import 'package:flutter/material.dart';
-import 'package:seo_renderer/helpers/scroll_aware.dart';
-import 'package:seo_renderer/helpers/utils.dart';
+import 'package:seo_renderer/helpers/robot_detector_web.dart';
+import 'package:seo_renderer/helpers/route_aware_state.dart';
+import 'package:seo_renderer/helpers/size_widget.dart';
/// A Widget to create the HTML Tags but with Link (href) from any [Widget].
class LinkRenderer extends StatefulWidget {
@@ -10,105 +13,69 @@ class LinkRenderer extends StatefulWidget {
const LinkRenderer({
Key? key,
required this.child,
- required this.anchorText,
- required this.link,
+ required this.text,
+ required this.href,
}) : super(key: key);
///Any Widget with link in it
final Widget child;
- ///Anchor Text just like html, will work like a replacement to provided [child] with [link] to it.
- final String anchorText;
+ ///Anchor Text just like html, will work like a replacement to provided [child] with [href] to it.
+ final String text;
///link to put in href
- final String link;
+ final String href;
@override
_LinkRendererState createState() => _LinkRendererState();
}
-class _LinkRendererState extends State
- with RouteAware, ScrollAware {
- final DivElement div = DivElement();
- final key = GlobalKey();
+class _LinkRendererState extends RouteAwareState {
+ Size? _size;
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- routeObserver.subscribe(this, ModalRoute.of(context)!);
- subscribe(context);
- }
-
- @override
- void dispose() {
- clear();
- routeObserver.unsubscribe(this);
- unsubscribe();
- super.dispose();
- }
-
- @override
- void didPop() {
- clear();
- super.didPop();
- }
-
- @override
- void didPush() {
- addDivElement();
- super.didPush();
- }
-
- @override
- void didPopNext() {
- addDivElement();
- super.didPopNext();
- }
-
- @override
- void didPushNext() {
- clear();
- super.didPushNext();
- }
-
- @override
- void didScroll() {
- refresh();
- }
+ void _onSize(Size size) {
+ if (_size == size) return;
+ _size = size;
- void refresh() {
- div.style.position = 'absolute';
- div.style.top = '${key.globalPaintBounds?.top ?? 0}px';
- div.style.left = '${key.globalPaintBounds?.left ?? 0}px';
- div.style.width = '${key.globalPaintBounds?.width ?? 100}px';
- div.style.color = '#ff0000';
- var anchorElement = new AnchorElement()
- ..href = widget.link
- ..text = widget.anchorText;
- div.children.removeWhere((element) => true);
- div.append(anchorElement);
+ if (!mounted) return;
+ setState(() {});
}
@override
Widget build(BuildContext context) {
- return LayoutBuilder(
- key: key,
- builder: (_, __) {
- return widget.child;
- });
- }
-
- addDivElement() {
- WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
- if (!regExpBots.hasMatch(window.navigator.userAgent.toString())) {
- return;
- }
- refresh();
- if (!document.body!.contains(div)) document.body?.append(div);
- });
- }
-
- void clear() {
- div.remove();
+ if (!RobotDetector.detected(context)) {
+ return widget.child;
+ }
+
+ final viewType = 'html-link-${widget.href}';
+ // ignore: undefined_prefixed_name
+ ui.platformViewRegistry.registerViewFactory(
+ viewType,
+ (_) => AnchorElement(href: widget.href)
+ ..text = widget.text
+ ..style.fontSize = '14px'
+ ..style.color = '#ff0000'
+ ..style.margin = '0px'
+ ..style.padding = '0px'
+ ..style.width = '${_size?.width ?? 0}px'
+ ..style.height = '${_size?.height ?? 0}px',
+ );
+
+ return SizedBox(
+ width: _size?.width,
+ height: _size?.height,
+ child: Stack(
+ children: [
+ SizeWidget(
+ onSize: _onSize,
+ child: widget.child,
+ ),
+ if (_size != null && visible)
+ IgnorePointer(
+ child: HtmlElementView(viewType: viewType),
+ ),
+ ],
+ ),
+ );
}
}
diff --git a/lib/renderers/text_renderer/text_renderer.dart b/lib/renderers/text_renderer/text_renderer.dart
deleted file mode 100644
index 1c24b05..0000000
--- a/lib/renderers/text_renderer/text_renderer.dart
+++ /dev/null
@@ -1,5 +0,0 @@
-/// Conditional imports based on if 'dart.io' is supported.
-///
-/// We export lib 'text_renderer_web.dart', but if dart.io is supported
-/// then we export 'text_renderer_vm.dart' instead.
-export 'text_renderer_web.dart' if (dart.library.io) 'text_renderer_vm.dart';
diff --git a/lib/renderers/text_renderer/text_renderer_style.dart b/lib/renderers/text_renderer/text_renderer_style.dart
new file mode 100644
index 0000000..741a9d5
--- /dev/null
+++ b/lib/renderers/text_renderer/text_renderer_style.dart
@@ -0,0 +1,9 @@
+enum TextRendererStyle {
+ paragraph,
+ header1,
+ header2,
+ header3,
+ header4,
+ header5,
+ header6,
+}
diff --git a/lib/renderers/text_renderer/text_renderer_vm.dart b/lib/renderers/text_renderer/text_renderer_vm.dart
index cff10e4..312166d 100644
--- a/lib/renderers/text_renderer/text_renderer_vm.dart
+++ b/lib/renderers/text_renderer/text_renderer_vm.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:seo_renderer/renderers/text_renderer/text_renderer_style.dart';
/// A Widget to create the HTML Tags from the TEXT widget.
///
@@ -7,14 +8,19 @@ class TextRenderer extends StatelessWidget {
/// Default [TextRenderer] const constructor.
const TextRenderer({
Key? key,
- required this.text,
+ required this.child,
+ this.text,
+ this.style,
}) : super(key: key);
- /// Provide with [Text] widget to get data from it.
- final Widget text;
+ ///Any Widget with text in it
+ final Widget child;
+
+ ///Text that the child contains
+ final String? text;
+
+ final TextRendererStyle? style;
@override
- Widget build(BuildContext context) {
- return text;
- }
+ Widget build(BuildContext context) => child;
}
diff --git a/lib/renderers/text_renderer/text_renderer_web.dart b/lib/renderers/text_renderer/text_renderer_web.dart
index 494b8bb..cb41cb1 100644
--- a/lib/renderers/text_renderer/text_renderer_web.dart
+++ b/lib/renderers/text_renderer/text_renderer_web.dart
@@ -1,138 +1,129 @@
+// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
+import 'dart:ui' as ui;
import 'package:flutter/material.dart';
-import 'package:seo_renderer/helpers/scroll_aware.dart';
-import 'package:seo_renderer/helpers/utils.dart';
+import 'package:seo_renderer/helpers/route_aware_state.dart';
+import 'package:seo_renderer/helpers/robot_detector_web.dart';
+import 'package:seo_renderer/helpers/size_widget.dart';
+import 'package:seo_renderer/renderers/text_renderer/text_renderer_style.dart';
/// A Widget to create the HtmlElement Tags from the TEXT widget.
class TextRenderer extends StatefulWidget {
/// Default [TextRenderer] const constructor.
const TextRenderer({
Key? key,
- required this.text,
- this.element,
+ required this.child,
+ this.text,
+ this.style,
}) : super(key: key);
- /// Provide with [Widget] widget to get data from it.
- final Widget text;
+ ///Any Widget with text in it
+ final Widget child;
- /// HtmlElement freqently use for text:
- /// - Default: new ParagraphElement()
- /// - new ParagraphElement()
- /// - new HeadingElement.h1() tp h6()
- final HtmlElement? element;
+ ///Text that the child contains
+ final String? text;
+
+ final TextRendererStyle? style;
@override
- _TextRendererState createState() =>
- _TextRendererState(element: element ?? new ParagraphElement());
+ _TextRendererState createState() => _TextRendererState();
}
-class _TextRendererState extends State
- with RouteAware, ScrollAware {
- _TextRendererState({required this.element});
+class _TextRendererState extends RouteAwareState {
+ Size? _size;
- final HtmlElement element;
- final key = GlobalKey();
+ void _onSize(Size size) {
+ if (_size == size) return;
+ _size = size;
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- routeObserver.subscribe(this, ModalRoute.of(context)!);
- subscribe(context);
+ if (!mounted) return;
+ setState(() {});
}
- @override
- void dispose() {
- clear();
- routeObserver.unsubscribe(this);
- unsubscribe();
- super.dispose();
+ HtmlElement get _htmlElement {
+ switch (widget.style) {
+ case TextRendererStyle.header1:
+ return HeadingElement.h1();
+ case TextRendererStyle.header2:
+ return HeadingElement.h2();
+ case TextRendererStyle.header3:
+ return HeadingElement.h3();
+ case TextRendererStyle.header4:
+ return HeadingElement.h4();
+ case TextRendererStyle.header5:
+ return HeadingElement.h5();
+ case TextRendererStyle.header6:
+ return HeadingElement.h6();
+ case TextRendererStyle.paragraph:
+ default:
+ return ParagraphElement();
+ }
}
- @override
- void didPop() {
- clear();
- super.didPop();
- }
+ String get _text {
+ final text = widget.text;
+ if (text != null) {
+ return text;
+ }
- @override
- void didPush() {
- addElement();
- super.didPush();
- }
+ final child = widget.child;
+ if (child is Text) {
+ final text = child.data ?? child.textSpan?.toPlainText();
- @override
- void didPopNext() {
- addElement();
- super.didPopNext();
- }
+ if (text == null) {
+ throw FlutterError(
+ 'TextRenderer child is ${widget.child.runtimeType} and data, textSpan are null',
+ );
+ }
- @override
- void didPushNext() {
- clear();
- super.didPushNext();
- }
+ return text;
+ }
- @override
- void didScroll() {
- refresh();
- }
+ if (child is RichText) {
+ return child.text.toPlainText();
+ }
- void refresh() {
- element.style.position = 'absolute';
- element.style.fontSize = '14px';
- element.style.top = '${key.globalPaintBounds?.top ?? 0}px';
- element.style.left = '${key.globalPaintBounds?.left ?? 0}px';
- element.style.width = '${key.globalPaintBounds?.width ?? 100}px';
- element.style.margin = '0px';
- element.style.padding = '0px';
- element.text = _getTextFromWidget().toString();
- element.style.color = '#ff0000';
+ throw FlutterError(
+ 'TextRenderer child is ${widget.child.runtimeType} and text is null',
+ );
}
@override
Widget build(BuildContext context) {
- return LayoutBuilder(
- key: key,
- builder: (_, __) {
- return widget.text;
- });
- }
-
- addElement() {
- WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
- if (!regExpBots.hasMatch(window.navigator.userAgent.toString())) {
- return;
- }
- refresh();
- if (!document.body!.contains(element)) document.body?.append(element);
- });
- }
-
- void clear() {
- element.remove();
- }
-
- String? _getTextFromWidget() {
- if (widget.text is Text) {
- Text wid = (widget.text as Text);
- String? data;
- data = wid.data;
- if (data != null) {
- return data;
- }
- if (wid.textSpan != null) {
- data = wid.textSpan!.toPlainText();
- }
- if (data != null) {
- return data;
- }
- }
- if (widget.text is RichText) {
- return (widget.text as RichText).text.toPlainText();
+ if (!RobotDetector.detected(context)) {
+ return widget.child;
}
- throw FlutterError(
- 'Provided Widget is of Type ${widget.text.runtimeType}. Only supported widget is Text & RichText.');
+ final viewType = 'html-text-$_text';
+ // ignore: undefined_prefixed_name
+ ui.platformViewRegistry.registerViewFactory(
+ viewType,
+ (_) => _htmlElement
+ ..text = _text
+ ..style.fontSize = '14px'
+ ..style.color = '#ff0000'
+ ..style.margin = '0px'
+ ..style.padding = '0px'
+ ..style.width = '${_size?.width ?? 0}px'
+ ..style.height = '${_size?.height ?? 0}px',
+ );
+
+ return SizedBox(
+ width: _size?.width,
+ height: _size?.height,
+ child: Stack(
+ children: [
+ SizeWidget(
+ onSize: _onSize,
+ child: widget.child,
+ ),
+ if (_size != null && visible)
+ IgnorePointer(
+ child: HtmlElementView(viewType: viewType),
+ ),
+ ],
+ ),
+ );
}
}
diff --git a/lib/seo_renderer.dart b/lib/seo_renderer.dart
index 5acda79..ca76274 100644
--- a/lib/seo_renderer.dart
+++ b/lib/seo_renderer.dart
@@ -1,5 +1,10 @@
-export 'helpers/scroll_listener/renderer_scroll_listener.dart';
-export 'helpers/utils.dart';
-export 'renderers/image_renderer/image_renderer.dart';
-export 'renderers/link_renderer/link_renderer.dart';
-export 'renderers/text_renderer/text_renderer.dart';
+export 'helpers/robot_detector_web.dart'
+ if (dart.library.io) 'helpers/robot_detector_vm.dart';
+export 'helpers/route_aware_state.dart';
+export 'renderers/image_renderer/image_renderer_web.dart'
+ if (dart.library.io) 'renderers/image_renderer/image_renderer_vm.dart';
+export 'renderers/link_renderer/link_renderer_web.dart'
+ if (dart.library.io) 'renderers/link_renderer/link_renderer_vm.dart';
+export 'renderers/text_renderer/text_renderer_style.dart';
+export 'renderers/text_renderer/text_renderer_web.dart'
+ if (dart.library.io) 'renderers/text_renderer/text_renderer_vm.dart';
diff --git a/pubspec.lock b/pubspec.lock
index 4935c14..b482404 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,7 +7,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.8.1"
+ version: "2.8.2"
boolean_selector:
dependency: transitive
description:
@@ -21,7 +21,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0"
+ version: "1.2.0"
charcode:
dependency: transitive
description:
@@ -66,7 +66,14 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.10"
+ version: "0.12.11"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.3"
meta:
dependency: transitive
description:
@@ -127,7 +134,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.2"
+ version: "0.4.8"
typed_data:
dependency: transitive
description:
@@ -141,7 +148,7 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0"
+ version: "2.1.1"
sdks:
- dart: ">=2.12.0 <3.0.0"
+ dart: ">=2.14.0 <3.0.0"
flutter: ">=1.20.0"