A Flutter widget for rendering static html as Flutter widgets (Will render over 80 different html tags!)

Overview

flutter_html

pub package codecov CircleCI MIT License

A Flutter widget for rendering HTML and CSS as Flutter widgets.

Screenshot 1 Screenshot 2 Screenshot 3
A Screenshot of flutter_html Another Screenshot of flutter_html Yet another Screenshot of flutter_html

Table of Contents:

Installing:

Add the following to your pubspec.yaml file:

dependencies:
  flutter_html: ^2.1.5

Currently Supported HTML Tags:

a abbr acronym address article aside audio b bdi bdo big
blockquote body br caption cite code data dd del details dfn
div dl dt em figcaption figure footer font h1 h2 h3
h4 h5 h6 header hr i iframe img ins kbd li
main mark nav noscript ol p pre q rp rt ruby
s samp section small span strike strong sub sup summary svg
table tbody td template tfoot th thead time tr tt u
ul var video math: mrow msup msub mover munder msubsup moverunder
mfrac mlongdiv msqrt mroot mi mn mo

Currently Supported CSS Attributes:

background-color color direction display font-family font-feature-settings font-size
font-style font-weight height letter-spacing line-height list-style-type list-style-position
padding margin text-align text-decoration text-decoration-color text-decoration-style text-decoration-thickness
text-shadow vertical-align white-space width word-spacing

Currently Supported Inline CSS Attributes:

background-color border (including specific directions) color direction display font-family font-feature-settings
font-size font-style font-weight line-height list-style-type list-style-position padding (including specific directions)
margin (including specific directions) text-align text-decoration text-decoration-color text-decoration-style text-shadow

Don't see a tag or attribute you need? File a feature request or contribute to the project!

Why this package?

This package is designed with simplicity in mind. Originally created to allow basic rendering of HTML content into the Flutter widget tree, this project has expanded to include support for basic styling as well! If you need something more robust and customizable, the package also provides a number of optional custom APIs for extremely granular control over widget rendering!

API Reference:

For the full API reference, see here.

For a full example, see here.

Below, you will find brief descriptions of the parameters theHtml widget accepts and some code snippets to help you use this package.

Constructors:

The package currently has two different constructors - Html() and Html.fromDom().

The Html() constructor is for those who would like to directly pass HTML from the source to the package to be rendered.

If you would like to modify or sanitize the HTML before rendering it, then Html.fromDom() is for you - you can convert the HTML string to a Document and use its methods to modify the HTML as you wish. Then, you can directly pass the modified Document to the package. This eliminates the need to parse the modified Document back to a string, pass to Html(), and convert back to a Document, thus cutting down on load times.

Selectable Text

The package also has two constructors for selectable text support - SelectableHtml() and SelectableHtml.fromDom().

The difference between the two is the same as noted above.

Please note: Due to Flutter #38474, selectable text support is significantly watered down compared to the standard non-selectable version of the widget. The changes are as follows:

  1. The list of tags that can be rendered is significantly reduced. Key omissions include no support for images/video/audio, table, and ul/ol.

  2. No support for customRender, customImageRender, onImageError, onImageTap, onMathError, and navigationDelegateForIframe. (Support for customRender may be added in the future).

  3. Styling support is significantly reduced. Only text-related styling works (e.g. bold or italic), while container related styling (e.g. borders or padding/margin) do not work.

Once the above issue is resolved, the aforementioned compromises will go away. Currently the SelectableText.rich() constructor does not support WidgetSpans, resulting in the feature losses above.

Parameters:

Parameters Description
data The HTML data passed to the Html widget. This is required and cannot be null when using Html().
document The DOM document passed to the Html widget. This is required and cannot be null when using Html.fromDom().
onLinkTap A function that defines what the widget should do when a link is tapped. The function exposes the src of the link as a String to use in your implementation.
customRender A powerful API that allows you to customize everything when rendering a specific HTML tag.
onImageError A function that defines what the widget should do when an image fails to load. The function exposes the exception Object and StackTrace to use in your implementation.
onMathError A function that defines what the widget should do when a math fails to render. The function exposes the parsed Tex String, as well as the error and error with type from flutter_math as a String.
shrinkWrap A bool used while rendering different widgets to specify whether they should be shrink-wrapped or not, like ContainerSpan
onImageTap A function that defines what the widget should do when an image is tapped. The function exposes the src of the image as a String to use in your implementation.
tagsList A list of elements the Html widget should render. The list should contain the tags of the HTML elements you wish to include.
style A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag.
navigationDelegateForIframe Allows you to set the NavigationDelegate for the WebViews of all the iframes rendered by the Html widget.
customImageRender A powerful API that allows you to fully customize how images are loaded.
selectionControls A custom text selection controls that allow you to override default toolbar and build toolbar with custom text selection options. See an example.

Getters:

  1. Html.tags. This provides a list of all the tags the package renders. The main use case is to assist in excluding elements using tagsList. See an example below.

  2. SelectableHtml.tags. This provides a list of all the tags that can be rendered in selectable mode.

Data:

The HTML data passed to the Html widget as a String. This is required and cannot be null when using Html. Any HTML tags in the String that are not supported by the package will not be rendered.

Example Usage - Data:

Widget html = Html(
  data: """<div>
        <h1>Demo Page</h1>
        <p>This is a fantastic product that you should buy!</p>
        <h3>Features</h3>
        <ul>
          <li>It actually works</li>
          <li>It exists</li>
          <li>It doesn't cost much!</li>
        </ul>
        <!--You can pretty much put any html in here!-->
      </div>""",
);

Document:

The DOM document passed to the Html widget as a Document. This is required and cannot be null when using Html.fromDom(). Any HTML tags in the document that are not supported by the package will not be rendered. Using the Html.fromDom() constructor can be useful when you would like to sanitize the HTML string yourself before passing it to the package.

Example Usage - Document:

import 'package:html/parser.dart' as htmlparser;
import 'package:html/dom.dart' as dom;
...
String htmlData = """<div>
  <h1>Demo Page</h1>
  <p>This is a fantastic product that you should buy!</p>
  <h3>Features</h3>
  <ul>
    <li>It actually works</li>
    <li>It exists</li>
    <li>It doesn't cost much!</li>
  </ul>
  <!--You can pretty much put any html in here!-->
</div>""";
dom.Document document = htmlparser.parse(htmlData);
/// sanitize or query document here
Widget html = Html(
  document: document,
);

onLinkTap:

A function that defines what the widget should do when a link is tapped.

Example Usage - onLinkTap:

Widget html = Html(
  data: """<p>
   Linking to <a href='https://github.com'>websites</a> has never been easier.
  </p>""",
  onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
    //open URL in webview, or launch URL in browser, or any other logic here
  }
);

Inner links (such as <a href="#top">Back to the top</a> will work out of the box by scrolling the viewport, as long as your Html widget is wrapped in a scroll container such as a SingleChildScrollView.

customRender:

A powerful API that allows you to customize everything when rendering a specific HTML tag. This means you can change the default behaviour or add support for HTML elements that aren't supported natively. You can also make up your own custom tags in your HTML!

customRender accepts a Map<String, CustomRender>. The CustomRender type is a function that requires a Widget or InlineSpan to be returned. It exposes RenderContext and the Widget that would have been rendered by Html without a customRender defined. The RenderContext contains the build context, styling and the HTML element, with attrributes and its subtree,.

To use this API, set the key as the tag of the HTML element you wish to provide a custom implementation for, and create a function with the above parameters that returns a Widget or InlineSpan.

Note: If you add any custom tags, you must add these tags to the tagsList parameter, otherwise they will not be rendered. See below for an example.

Example Usages - customRender:

  1. Simple example - rendering custom HTML tags
Widget html = Html(
  data: """
  <h3>Display bird element and flutter element <bird></bird></h3>
  <flutter></flutter>
  <flutter horizontal></flutter>
  """,
  customRender: {
      "bird": (RenderContext context, Widget child) {
        return TextSpan(text: "🐦");
      },
      "flutter": (RenderContext context, Widget child) {
        return FlutterLogo(
          style: (context.tree.element!.attributes['horizontal'] != null)
              ? FlutterLogoStyle.horizontal
              : FlutterLogoStyle.markOnly,
          textColor: context.style.color!,
          size: context.style.fontSize!.size! * 5,
        );
      },
    },
  tagsList: Html.tags..addAll(["bird", "flutter"]),
);
  1. Complex example - wrapping the default widget with your own, in this case placing a horizontal scroll around a (potentially too wide) table.
View code
Widget html = Html(
  data: """
  <table style="width:100%">
    <caption>Monthly savings</caption>
    <tr> <th>January</th> <th>February</th> <th>March</th> <th>April</th> <th>May</th> <th>June</th> <th>July</th> <th>August</th> <th>September</th> <th>October</th> <th>November</th> <th>December</th> </tr>
    <tr> <td>\$100</td> <td>\$50</td> <td>\$80</td> <td>\$60</td> <td>\$90</td> <td>\$140</td> <td>\$110</td> <td>\$80</td> <td>\$90</td> <td>\$60</td> <td>\$40</td> <td>\$70</td> </tr>
    <tr> <td>\90</td> <td>\$60</td> <td>\$80</td> <td>\$80</td> <td>\$100</td> <td>\$160</td> <td>\$150</td> <td>\$110</td> <td>\$100</td> <td>\$60</td> <td>\$30</td> <td>\$80</td> </tr>
  </table>
  """,
  customRender: {
    "table": (context, child) {
      return SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: (context.tree as TableLayoutElement).toWidget(context),
      );
    }
  },
);
  1. Complex example - rendering an iframe differently based on whether it is an embedded youtube video or some other embedded content.
View code
Widget html = Html(
   data: """
   <h3>Google iframe:</h3>
   <iframe src="https://google.com"></iframe>
   <h3>YouTube iframe:</h3>
   <iframe src="https://www.youtube.com/embed/tgbNymZ7vqY"></iframe>
   """,
   customRender: {
      "iframe": (RenderContext context, Widget child) {
         final attrs = context.tree.element?.attributes;
         if (attrs != null) {
           double? width = double.tryParse(attrs['width'] ?? "");
           double? height = double.tryParse(attrs['height'] ?? "");
           return Container(
             width: width ?? (height ?? 150) * 2,
             height: height ?? (width ?? 300) / 2,
             child: WebView(
                initialUrl: attrs['src'] ?? "about:blank",
                javascriptMode: JavascriptMode.unrestricted,
                //no need for scrolling gesture recognizers on embedded youtube, so set gestureRecognizers null
                //on other iframe content scrolling might be necessary, so use VerticalDragGestureRecognizer
                gestureRecognizers: attrs['src'] != null && attrs['src']!.contains("youtube.com/embed") ? null : [
                  Factory(() => VerticalDragGestureRecognizer())
                ].toSet(),
                navigationDelegate: (NavigationRequest request) async {
                //no need to load any url besides the embedded youtube url when displaying embedded youtube, so prevent url loading
                //on other iframe content allow all url loading
                  if (attrs['src'] != null && attrs['src']!.contains("youtube.com/embed")) {
                    if (!request.url.contains("youtube.com/embed")) {
                      return NavigationDecision.prevent;
                    } else {
                      return NavigationDecision.navigate;
                    }
                  } else {
                    return NavigationDecision.navigate;
                  }
                },
              ),
            );
         } else {
           return Container(height: 0);
         }
       }
     }
 );

More example usages and in-depth details available here.

onImageError:

A function that defines what the widget should do when an image fails to load. The function exposes the exception Object and StackTrace to use in your implementation.

Example Usage - onImageError:

Widget html = Html(
  data: """<img alt='Alt Text of an intentionally broken image' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30d'/>""",
  onImageError: (Exception exception, StackTrace stackTrace) {
    FirebaseCrashlytics.instance.recordError(exception, stackTrace);
  },
);

onMathError:

A function that defines what the widget should do when a math fails to render. The function exposes the parsed Tex String, as well as the error and error with type from flutter_math as a String.

Example Usage - onMathError:

Widget html = Html(
  data: """<!-- Some MathML string that fails to parse -->""",
  onMathError: (String parsedTex, String error, String errorWithType) {
    //your logic here. A Widget must be returned from this function:
    return Text(error);
    //you can also try and fix the parsing yourself:
    return Math.tex(correctedParsedTex);
  },
);

onImageTap:

A function that defines what the widget should do when an image is tapped.

Example Usage - onImageTap:

Widget html = Html(
  data: """<img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />""",
  onImageTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
    //open image in webview, or launch image in browser, or any other logic here
  }
);

tagsList:

A list of elements the Html widget should render. The list should contain the tags of the HTML elements you wish to whitelist.

Example Usage - tagsList - Excluding Tags:

You may have instances where you can choose between two different types of HTML tags to display the same content. In the example below, the <video> and <iframe> elements are going to display the same content.

The tagsList parameter allows you to change which element is rendered. Iframes can be advantageous because they allow parallel loading - Flutter just has to wait for the webview to be initialized before rendering the page, possibly cutting down on load time. Video can be advantageous because it provides a 100% native experience with Flutter widgets, but it may take more time to render the page. You may know that Flutter webview is a little janky in its current state on Android, so using tagsList and a simple condition, you can get the best of both worlds - choose the video widget to render on Android and the iframe webview to render on iOS.

Widget html = Html(
  data: """
  <video controls>
    <source src="https://www.w3schools.com/html/mov_bbb.mp4" />
  </video>
  <iframe src="https://www.w3schools.com/html/mov_bbb.mp4"></iframe>""",
  tagsList: Html.tags..remove(Platform.isAndroid ? "iframe" : "video")
);

Html.tags provides easy access to a list of all the tags the package can render, and you can remove specific tags from this list to blacklist them.

Example Usage - tagsList - Allowing Tags:

You may also have instances where you would only like the package to render a handful of html tags. You can do that like so:

Widget html = Html(
  data: """
    <p>Render this item</p>
    <span>Do not render this item or any other item</span>
    <img src='https://flutter.dev/images/flutter-mono-81x100.png'/>
  """,
  tagsList: ['p']
);

Here, the package will only ever render <p> and ignore all other tags.

style:

A powerful API that allows you to customize the style that should be used when rendering a specific HTMl tag.

style accepts a Map<String, Style>. The Style type is a class that allows you to set all the CSS styling the package currently supports. See here for the full list.

To use this API, set the key as the tag of the HTML element you wish to provide a custom implementation for, and set the value to be a Style with your customizations.

Example Usage - style:

Widget html = Html(
  data: """
    <h1>Table support:</h1>
    <table>
    <colgroup>
    <col width="50%" />
    <col span="2" width="25%" />
    </colgroup>
    <thead>
    <tr><th>One</th><th>Two</th><th>Three</th></tr>
    </thead>
    <tbody>
    <tr>
    <td rowspan='2'>Rowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan</td><td>Data</td><td>Data</td>
    </tr>
    <tr>
    <td colspan="2"><img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /></td>
    </tr>
    </tbody>
    <tfoot>
    <tr><td>fData</td><td>fData</td><td>fData</td></tr>
    </tfoot>
    </table>""",
  style: {
    // tables will have the below background color
    "table": Style(
      backgroundColor: Color.fromARGB(0x50, 0xee, 0xee, 0xee),
    ),
    // some other granular customizations are also possible
    "tr": Style(
      border: Border(bottom: BorderSide(color: Colors.grey)),
    ),
    "th": Style(
      padding: EdgeInsets.all(6),
      backgroundColor: Colors.grey,
    ),
    "td": Style(
      padding: EdgeInsets.all(6),
      alignment: Alignment.topLeft,
    ),
    // text that renders h1 elements will be red
    "h1": Style(color: Colors.red),
  }
);

More examples and in-depth details available here.

navigationDelegateForIframe:

Allows you to set the NavigationDelegate for the WebViews of all the iframes rendered by the Html widget. You can block or allow the loading of certain URLs with the NavigationDelegate.

Example Usage - navigationDelegateForIframe:

Widget html = Html(
  data: """
   <h3>YouTube iframe:</h3>
   <iframe src="https://google.com"></iframe>
   <h3>Google iframe:</h3>
   <iframe src="https://www.youtube.com/embed/tgbNymZ7vqY"></iframe>
   """,
  navigationDelegateForIframe: (NavigationRequest request) {
    if (request.url.contains("google.com/images")) {
      return NavigationDecision.prevent;
    } else {
      return NavigationDecision.navigate;
    }
  },
);

customImageRender:

A powerful API that allows you to customize what the Html widget does when rendering an image, down to the most minute detail.

customImageRender accepts a Map<ImageSourceMatcher, ImageRender>. ImageSourceMatcher provides the matching function, while ImageRender provides the widget to be rendered.

The default image renders are:

final Map<ImageSourceMatcher, ImageRender> defaultImageRenders = {
  base64UriMatcher(): base64ImageRender(),
  assetUriMatcher(): assetImageRender(),
  networkSourceMatcher(extension: "svg"): svgNetworkImageRender(),
  networkSourceMatcher(): networkImageRender(),
};

See the source code for details on how these are implemented.

When setting customImageRenders, the package will prioritize the custom renders first, while the default ones are used as a fallback.

Note: Order is very important when you set customImageRenders. The more specific your ImageSourceMatcher, the higher up in the customImageRender list it should be.

typedef ImageSourceMatcher

This is type defined as a function that passes the attributes as a Map<String, String> and the DOM element as dom.Element. This type is used to define how an image should be matched i.e. whether the package should override the default rendering method and instead use your custom implementation.

A typical usage would look something like this:

ImageSourceMatcher base64UriMatcher() => (attributes, element) =>
    attributes["src"] != null &&
    attributes["src"]!.startsWith("data:image") &&
    attributes["src"]!.contains("base64,");

In the above example, the matcher checks whether the image's src either starts with "data:image" or contains "base64,", since these indicate an image in base64 format.

You can also declare your own variables in the function itself, which would look like this:

ImageSourceMatcher networkSourceMatcher({
/// all three are optional, you don't need to have these in the function
  List<String> schemas: const ["https", "http"],
  List<String> domains: const ["your domain 1", "your domain 2"],
  String extension: "your extension",
}) =>
    (attributes, element) {
      final src = Uri.parse(attributes["src"] ?? "about:blank");
      return schemas.contains(src.scheme) &&
          domains.contains(src.host) &&
          src.path.endsWith(".$extension");
    };

In the above example, the possible schemas are checked against the scheme of the src, and optionally the domains and extensions are also checked. This implementation allows for extremely granular control over what images are matched, and could even be changed on the fly with a variable.

typedef ImageRender

This is a type defined as a function that passes the attributes of the image as a Map<String, String>, the current RenderContext, and the DOM element as dom.Element. This type is used to define the widget that should be rendered when used in conjunction with an ImageSourceMatcher.

A typical usage might look like this:

ImageRender base64ImageRender() => (context, attributes, element) {
      final decodedImage = base64.decode(attributes["src"] != null ?
          attributes["src"].split("base64,")[1].trim() : "about:blank");
      return Image.memory(
        decodedImage,
      );
    };

The above example should be used with the base64UriMatcher() in the examples for ImageSourceMatcher.

Just like functions for ImageSourceMatcher, you can declare your own variables in the function itself:

ImageRender networkImageRender({
  Map<String, String> headers,
  double width,
  double height,
  Widget Function(String) altWidget,
}) =>
    (context, attributes, element) {
      return Image.network(
        attributes["src"] ?? "about:blank",
        headers: headers,
        width: width,
        height: height,
        frameBuilder: (ctx, child, frame, _) {
          if (frame == null) {
            return altWidget.call(attributes["alt"]) ??
                Text(attributes["alt"] ?? "",
                    style: context.style.generateTextStyle());
          }
          return child;
        },
      );
    };

Implementing these variables allows you to customize every last detail of how the widget is rendered.

Example Usages - customImageRender:

customImageRender can be used in two different ways:

  1. Overriding a default render:
Widget html = Html(
  data: """
  <img alt='Flutter' src='https://flutter.dev/assets/flutter-lockup-1caf6476beed76adec3c477586da54de6b552b2f42108ec5bc68dc63bae2df75.png' /><br />
  <img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /><br />
  """,
  customImageRenders: {
    networkSourceMatcher(domains: ["flutter.dev"]):
        (context, attributes, element) {
      return FlutterLogo(size: 36);
    },
    networkSourceMatcher(): networkImageRender(
      headers: {"Custom-Header": "some-value"},
      altWidget: (alt) => Text(alt ?? ""),
      loadingWidget: () => Text("Loading..."),
    ),
            (attr, _) => attr["src"] != null && attr["src"]!.startsWith("/wiki"):
    networkImageRender(
            mapUrl: (url) => "https://upload.wikimedia.org" + url),
  },
);

Above, there are three custom networkSourceMatchers, which will be applied - in order - before the default implementations.

When an image with URL flutter.dev is detected, rather than displaying the image, the render will display the flutter logo. If the image is any other image, it keeps the default widget, but just sets the headers and the alt text in case that image happens to be broken. The final render handles relative paths by rewriting them, specifically prefixing them with a base url. Note that the customizations of the previous custom renders do not apply. For example, the headers that the second render would apply are not applied in this third render.

  1. Creating your own renders:
ImageSourceMatcher classAndIdMatcher({String classToMatch, String idToMatch}) => (attributes, element) =>
    attributes["class"] != null && attributes["id"] != null &&
    (attributes["class"]!.contains(classToMatch) ||
    attributes["id"]!.contains(idToMatch));

ImageRender classAndIdRender({String classToMatch, String idToMatch}) => (context, attributes, element) {
  if (attributes["class"] != null && attributes["class"]!.contains(classToMatch)) {
    return Image.asset(attributes["src"] ?? "about:blank");
  } else {
    return Image.network(
      attributes["src"] ?? "about:blank",
      semanticLabel: attributes["longdesc"] ?? "",
      width: attributes["width"],
      height: attributes["height"],
      color: context.style.color,
      frameBuilder: (ctx, child, frame, _) {
          if (frame == null) {
            return Text(attributes["alt"] ?? "", style: context.style.generateTextStyle());
          }
          return child;
        },
    ); 
  }
};

Widget html = Html(
  data: """
  <img alt='alt text' class='class1-class2' src='assets/flutter.png' /><br />
  <img alt='alt text 2' id='imageId' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /><br />
  """,
  customImageRenders: {
    classAndIdMatcher(classToMatch: "class1", idToMatch: "imageId"): classAndIdRender(classToMatch: "class1", idToMatch: "imageId")
  },
);

The above example has a matcher that checks for either a class or an id, and then returns two different widgets based on whether a class was matched or an id was matched.

The sky is the limit when using the custom image renders. You can make it as granular as you want, or as all-encompassing as you want, and you have full control of everything. Plus you get the package's style parsing to use in your custom widgets, so your code looks neat and readable!

Rendering Reference

This section will describe how certain HTML elements are rendered by this package, so you can evaluate how your HTML will be rendered and structure it accordingly.

Image

This package currently has support for base64 images, asset images, network SVGs inside an <img>, and network images.

The package uses the src of the image to determine which of the above types to render. The order is as follows:

  1. If the src is null, render the alt text of the image, if any.
  2. If the src starts with "data:image" and contains "base64," (this indicates the image data is indeed base64), render an Image.memory from the base64 data.
  3. If the src starts with "asset:", render an Image.asset from the path in the src.
  4. If the src ends with ".svg", render a SvgPicture.network (from the flutter_svg package)
  5. Otherwise, just render an Image.network.

If the rendering of any of the above fails, the package will fall back to rendering the alt text of the image, if any.

Currently the package only considers the width, height, src, and alt text while rendering an image.

Note that there currently is no support for SVGs either in base64 format or asset format.

Iframe

This package renders iframes using the webview_flutter plugin.

When rendering iframes, the package considers the width, height, and sandbox attributes.

Sandbox controls the JavaScript mode of the webview - a value of null or allow-scripts will set javascriptMode: JavascriptMode.unrestricted, otherwise it will set javascriptMode: JavascriptMode.disabled.

You can set the navigationDelegate of the webview with the navigationDelegateForIframe property - see here for more details.

Audio

This package renders audio elements using the chewie_audio plugin.

The package considers the attributes controls, loop, src, autoplay, width, and muted when rendering the audio widget.

Video

This package renders video elements using the chewie plugin.

The package considers the attributes controls, loop, src, autoplay, poster, width, height, and muted when rendering the video widget.

SVG

This package renders svg elements using the flutter_svg plugin.

When rendering SVGs, the package takes the SVG data within the <svg> tag and passes it to flutter_svg. The width and height attributes are considered while rendering, if given.

MathML

This package renders MathML elements using the flutter_math plugin.

When rendering MathML, the package takes the MathML data within the <math> tag and tries to parse it to Tex. Then, it will pass the parsed string to flutter_math.

Because this package is parsing MathML to Tex, it may not support some functionalities. The current list of supported tags can be found above, but some of these only have partial support at the moment.

If the parsing errors, you can use the onMathError API to catch the error and potentially fix it on your end - you can analyze the error and the parsed string, and finally return a new instance of Math.tex() with the corrected Tex string.

If you'd like to see more MathML features, feel free to create a PR or file a feature request!

Tex

If you have a Tex string you'd like to render inside your HTML you can do that using the same flutter_math plugin.

Use a custom tag inside your HTML (an example could be <tex>), and place your raw Tex string inside.

Then, use the customRender parameter to add the widget to render Tex. It could look like this:

Widget htmlWidget = Html(
  data: r"""<tex>i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t)</tex>""",
  customRender: {
    "tex": (RenderContext context, _) => Math.tex(
      context.tree.element!.text,
      onErrorFallback: (FlutterMathException e) {
        //return your error widget here e.g.
        return Text(e.message);
      },
    ),
  },
  tagsList: Html.tags..add('tex'),
);

Table

This package renders table elements using the flutter_layout_grid plugin.

When rendering table elements, the package tries to calculate the best fit for each element and size its cell accordingly. Rowspans and colspans are considered in this process, so cells that span across multiple rows and columns are rendered as expected. Heights are determined intrinsically to maintain an optimal aspect ratio for the cell.

Notes

  1. If you'd like to use this widget inside of a Row(), make sure to set shrinkWrap: true and place your widget inside expanded:
Widget row = Row(
   children: [
	Expanded(
	    child: Html(
          shrinkWrap: true,
          //other params
        )
	),
	//whatever other widgets
   ]
);

Migration Guides

  • For Version 1.0 - Guide

Contribution Guide

Coming soon!

Meanwhile, PRs are always welcome

Comments
  • How to make the width and height of the parent component follow the space occupied by the Html component display content

    How to make the width and height of the parent component follow the space occupied by the Html component display content

    For example, in the following code, I want the parent component Container not to set the width and height, and automatically determine the width and height of the parent component Container according to the size of the displayed content of the Html component

    Container(
        child: Html(
          data: "<div class='body_wrap'>${item}<div>",
          style: {
            ".body_wrap": Style(
              fontSize: FontSize(rpx(26)),
              color: Color(0x99303030),
            )
          },
        )
    ),
    
    opened by fengerwoo 35
  • [WEB] unusable on 1.26.0-17.0.pre or higher using --web-renderer=html

    [WEB] unusable on 1.26.0-17.0.pre or higher using --web-renderer=html

    When trying to use flutter_html on flutter 1.26.0-17.0.pre and compiling for web, the page just stays blank and CPU usage goes up until the tab eventually crashes.

    Going down to flutter 1.26.0-12.0.pre works fine. So something must have changed between those 2 versions that broken something...

    Not sure what else to give for information, as nothing shows up in the logs. Probably an endless loop somewhere?

    Let me know if I can help with something else...

    opened by jlubeck 34
  • Support inner links

    Support inner links

    Support for links to anchors inside the html document itself.

    Fixes #268 and is an alternative implementation to #535.

    This should work with links to ids applied wherever in your document tree. If you have multiple tags with the same id, errors will be thrown. Whilst this is a programmer's error, we might want to guard against that (in AnchorKey's factory constructor).

    If the Html is not used in a scroll container, the links are simply ignored (as in, nothing will happen when tapping). If the achor style link (#some-id) doesn't exist in the document, the link is ignored. Scrolling is 'instant' i.e. no smooth scrolling.

    An implementer could even link to known parts in the document by manually looking up the anchor key and scrolling to it: Scrollable.ensureVisible(AnchorKey.forId(key, "some-id").currentContext)

    opened by erickok 32
  • Accessibility and scaling

    Accessibility and scaling

    When applying the system accessibility size settings, the text in a Html widget gets scaled way more than a normal Text widget. Consider the following code:

    import 'package:flutter/material.dart';
    import 'package:flutter_html/flutter_html.dart';
    import 'package:flutter_html/style.dart';
    
    void main() {
      runApp(HtmlAccessibilityApp());
    }
    
    class HtmlAccessibilityApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('')),
            body: Column(
              children: [
                Text('Normal Text'),
                Html(data: 'Html Text'),
              ],
            ),
          ),
        );
      }
    }
    

    When you scale the text using iOS accessibility settings, you see the following:

    Simulator Screen Shot - iPhone 11 - 2020-05-30 at 08 48 24

    I mentioned this in #303 but I thought it might be worth having it's own ticket (they may end up being related).

    opened by edwardaux 32
  • Equal width of columns in a table

    Equal width of columns in a table

    In browsers, the width of the table columns is proportional to the content inside. I am using the 1.1.1 version of this package and have this situation: image of a table with equal column width

    HTML for table above:

    <table border="1" cellpadding="1" cellspacing="1" style="width:500px"> <body> 
    <tr> 
    <td> </td> <td><strong>Дашогузский велаят</strong></td><td> </td> 
    </tr> 
    <tr> 
    <td>1</td> <td>Управление здравоохранения Дашогузского велаята</td> <td>2-55-67, 2-40-76</td> 
    </tr> 
    <tr> 
    <td>2</td> <td>Акдепинская этрапская больница</td> <td>8-00-344-30-0-32, 32-2-56, 32-1-38</td> 
    </tr> 
    <tr> 
    <td>3</td> <td>Болдумсазская этрапская больница</td> <td>8-00-346-31-2-63, 31-1-29, 31-7-02</td> 
    </tr> 
    <tr> 
    <td>4</td> <td>Губадагская этрапская больница</td> <td>8-00-345-31-5-03, 32-0-13, 31-4-30, 30-2-94</td> 
    </tr>
    </table>
    

    Is there any way to shrink the first column for a nicer look?

    opened by amanokerim 31
  • Add preliminary support for MathML

    Add preliminary support for MathML

    Fixes #219

    I know we are trying to reduce the number of packages this library depends on, but I really think flutter_math is a good addition - I'm super pleased with how the implementation turned out!

    image

    I added support for a "custom" tag in the second commit. I know we are trying to adhere to HTML standard as close as possible, but I thought it would be a nice feature to directly add Tex support inside HTML if we are already using a package to render math using Tex strings. This is also useful for #207 as a temporary workaround until we decide to implement external CSS support (if that's even possible). If this doesn't seem like a good feature then I can revert the commit.

    opened by tneotia 30
  • Use SelectableText - Selectable content

    Use SelectableText - Selectable content

    opened by franvera 29
  • Support irregular tables

    Support irregular tables

    This fixes #242 #383 #406 #435 #213 #439 #447 #450

    Adds support for irregular html tables and colspan/rowspan attributes. This swaps out Flutter's Table widget for flutter_layout_grid. It still supports styling on table, tr and th/td. Columns can be sized using colgroup (percentage and fixed sizes).

    Screenshot 2020-11-20 at 12 19 22

    There is always room for improvement (for example, no row styling is applied to missing cells) but this should capture the vast majority of use cases and afaik has no regression to existing flutter_html users. I also believe this is the final thing blocking 0.11.0 users to upgrade to 1.x.

    opened by erickok 22
  • how to use customtextstyle to change the font size and line height?

    how to use customtextstyle to change the font size and line height?

    Hello, How do I use customTextstyle to change the line height and font size of the text? Below is my code:-

     Widget shortDesc(String data)
      {
       return Html(
          data: data,
          //Optional parameters:
          padding: EdgeInsets.all(2.0),
          backgroundColor: Colors.white70,
          defaultTextStyle: TextStyle(fontFamily: 'serif'),
          linkStyle: const TextStyle(
            color: Colors.redAccent,
          ),
          onLinkTap: (url) {
            // open url in a webview
          },
          onImageTap: (src) {
            // Display the image in large form.
          },
        );
      }
    
    bug high-priority 
    opened by ADLsourceCode 22
  • Add support for customRender in the `RichText` parser

    Add support for customRender in the `RichText` parser

    The RichText version of the parser needs some sort of extension mechanism. See https://github.com/Sub6Resources/flutter_html/pull/37#issuecomment-461644877.

    enhancement high-priority 1.0.0 
    opened by Sub6Resources 22
  • [BUG] Dispose the video when back to previous screen

    [BUG] Dispose the video when back to previous screen

    Well, my problem is that when I play the video and after that, while video is playing I pop up the screen and back to the previous screen but the video still is playing. I want to dispose the video but don't know how to do it.

    bug 
    opened by AliKarimiENT 19
  • [BUG] Borders on table cells are applied to underlying text

    [BUG] Borders on table cells are applied to underlying text

    Describe the bug:

    Borders on table cells are unexpectedly inherited by underlying text. I suspect this is a regression as #1037 shows borders rendering as expected. I did not find a workaround by playing with styles, but this seems correctable by changing https://github.com/Sub6Resources/flutter_html/blob/c75e0dfb1be6cb79748f719487043d12bc330c60/packages/flutter_html_table/lib/flutter_html_table.dart#L120 to only apply the inherited styles to the child:

                      style: child.style.copyOnlyInherited(
                          Style()), //TODO updated this. Does it work?
    

    HTML to reproduce the issue:

    This repros with the table provided in #1037 and also with the following HTML (abridged for brevity).

    <table class='details_table'>
    <tbody>
    <tr class='about_line1'><td class='about_col1' valign=top>Name</td><td valign=top><font face="monospace">Point Chauncey, 1.3 mi east of (depth 7 ft), San Francisco Bay, California Current</font></td></td>
    <tr><td class='about_col1' valign=top>In file</td><td valign=top><font face="monospace">harmonics-dwf-20210110-free.tcd</font></td></td>
    <tr><td class='about_col1' valign=top>Station ID context</td><td valign=top><font face="monospace">NOS</font></td></td>
    <tr><td class='about_col1' valign=top>Station ID</td><td valign=top><font face="monospace">SFB1309_11</font></td></td>
    <tr><td class='about_col1' valign=top>Date imported</td><td valign=top><font face="monospace">2021-01-05</font></td></td>
    </tbody></table>
    

    Html widget configuration:

                  Html(
                    data: snapshot.data,
                    style: {
                      'table': Style(
                        backgroundColor: const Color(0xffcbdcff),
                      ),
                      'td': Style(
                        border: Border.all(),
                        padding:
                            const EdgeInsets.symmetric(vertical: 0, horizontal: 2),
                      ),
                      '.about_col1': Style(
                        alignment: Alignment.topCenter,
                        fontWeight: FontWeight.bold,
                      ),
                    },
                    customRenders: {tableMatcher(): tableRender()},
                  )
    

    To repro with #1037, the style section can be commented out.

    Expected behavior:

    CSS border style shouldn't be inherited, so text spans shouldn't receive their own borders.

    Screenshots:

    At master: image

    With the patch to only apply inherited styles to cell children: image

    What the table in #1037 looks like at master: image

    With patch: image

    Device details and Flutter/Dart/flutter_html versions:

    • Flutter 3.6.0-1.0.pre.3
    • Dart SDK version: 2.19.0-377.0.dev
    • flutter_html: ^3.0.0-alpha.6
    • flutter_html_table: ^3.0.0-alpha.4

    Additional info: Let me know if you want a pull request for this line, though guidance towards finding/adding a pertinent test would be appreciated. Totally understand if there's a better way to fix this though.

    A picture of a cute animal (not mandatory but encouraged)

    image

    bug 
    opened by AsturaPhoenix 0
  • iFrames full screen not working[BUG]

    iFrames full screen not working[BUG]

    BUG: iFrames video(for eg., YouTube, Vimeo) full screen mode is not working. Checked out the entire issue section of this package's Github, couldn't find any relevant solution.

    HTML Content:

    \r\n

     

    \r\n

    asdf

    Html widget configuration:

    Html( data: htmlContentVariable );

    Device details and Flutter/Dart/flutter_html versions: flutter_html: ^2.2.1 Flutter version: 3.0.5

    bug 
    opened by Nary-Vip 0
  • [FEATURE]

    [FEATURE]

    The version I'm using now is flutter_html: 3.0.0-alpha.5. This version is very good and solves a lot of reference conflicts. I read the version of 3.0.0-alpha.6 that requires Min Dart SDK2.17. Is it possible to keep Min Dart SDK2.12. Because my Dart SDK is 2.14, I can't upgrade for a long time. Thank you.

    PS: the cat's name is scallions. He also likes watching the World Cup. cat

    enhancement 
    opened by xiejiajin 1
  • Is it possible to page-break after overflowing the screen, like an e-book reader?

    Is it possible to page-break after overflowing the screen, like an e-book reader?

    I like the flutter_html plugin very much, and I have been paying attention to it since it appeared in the flutter market.

    So hats off to the author!

    But I found that dragging the screen is cumbersome when rendering very long text content, and it is not suitable for reading. Is it possible to realize the page breaking function after overflowing the screen? Just like an e-book reader.

    enhancement 
    opened by sliverRing 3
  • [FEATURE] Add see more text

    [FEATURE] Add see more text

    Add support for See more text. This will be responsible for expanding the html text as per max length provided. It seems very difficult to implement from the package.

    enhancement 
    opened by Helocominc 1
  • [BUG] Latex rendering problem in web [WEB CRASH]

    [BUG] Latex rendering problem in web [WEB CRASH]

    Describe the bug:

    We are using flutter_html package to render html and latex together. But with below html string web is crashing HTML string:

    The number of distinct solutions of the equation<tex>\frac{4}{5}</tex>cos<sup>2</sup>2x+cos<sup>4</sup>x+sin<sup>4</sup>x+cos<sup>6</sup>x+sin<sup>6</sup>x=2 in the interval <tex>\left[0,2\pi\right]</tex> is<u>________</u>.t
    

    HTML to reproduce the issue:

    Html widget configuration:

       return Html(
          shrinkWrap: true,
          data:"The number of distinct solutions of the equation<tex>\frac{4}{5}</tex>cos<sup>2</sup>2x+cos<sup>4</sup>x+sin<sup>4</sup>x+cos<sup>6</sup>x+sin<sup>6</sup>x=2 in the interval <tex>\left[0,2\pi\right]</tex> is<u>________</u>.t",
          customRender: {
            'tex': (renderContext, _) {
              //some long latex equations/expressions are too long and cause an
              //overflow so we need to break them down with the instructions below
              final longEq = Math.tex(renderContext.tree.element!.text);
              final breakResult = longEq.texBreak();
              final widget = Padding(
                padding: const EdgeInsets.all(3),
                child: Wrap(
                  runSpacing: 5,
                  children: breakResult.parts,
                ),
              );
              return widget;
            }
          },
          tagsList: Html.tags..add('tex'),
        );
    

    Expected behavior:

    It should render as pic below Screenshot 2022-11-16 at 8 24 50 PM

    Screenshots:

    Device details and Flutter/Dart/flutter_html versions:

    Flutter Version: 3.3.4 flutter_html: ^2.2.1 flutter_math_fork: 0.5.0 Stacktrace/Logcat

    Additional info:

    A picture of a cute animal (not mandatory but encouraged)

    bug 
    opened by NTJ3 1
Releases(3.0.0-alpha.5)
  • 3.0.0-alpha.5(Jun 9, 2022)

  • 3.0.0-alpha.4(Apr 20, 2022)

  • 3.0.0-alpha.3(Apr 14, 2022)

    • Fixed styling not being applied to list item markers
    • [video] Fixed crash when iframe or video tags used unsupported/incorrect height or width
    Source code(tar.gz)
    Source code(zip)
  • 2.2.1(Dec 9, 2021)

    • Allow styling on ruby tags
    • Allow width/height/alignment styling on table/tr/td tags
    • Prevent images causing rebuilding and leaking memory
    • Fixes display of list items on iOS with font weights below 400
    • Prevent crash on negative margins or paddings
    Source code(tar.gz)
    Source code(zip)
  • 2.1.1(Jul 28, 2021)

  • 2.1.0(Jun 4, 2021)

    • SelectableHtml widget (supporting a subset of tags) which allow text selection
    • Fixed shrinkWrap to actually shrink the horizontal space
    • Support style tags to apply styling from inline css
    • Support applying styles from Flutter themes
    • Mouse hover on links when using Flutter Web
    • Allow custom anchor link tap implementations
    • Support additional list styling options
    • Fix several minor whitespace issues in text flow
    • Fixed specific colspan/rowspan usages in tables
    • Fixed whitespace issues around images
    • Swallow duplicate ids to prevent crashing the widget
    • Fixes crashing tap detection when using both link and image taps
    • Updates external dependencies
    • Raised minimum Flutter version to 2.2.0
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Jun 4, 2021)

  • 2.0.0-nullsafety.0(Mar 6, 2021)

    Important: this is a pre-release version

    Breaking: this requires nullsafety support, Dart >=2.12.0 and Flutter >=2.0.0

    • Nullsafety support
    • Official Flutter Web support
    • New features & fixes for lists:
      • Support start attribute (e.g. start="5";)
      • Support RTL direction
      • Support setting padding - you can remove the starting padding if you choose
      • Fixed unknown character box on iOS when font-weight is below w400
    • Upgraded link functions to provide more granular control
    • Fixed errors in text-decoration parsing
    • Fixed <audio> on iOS ("_duration called on null" exception)
    • Updated dependencies
    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Feb 22, 2021)

    Breaking: this release requires Flutter 1.26 or later

    • New image loading API
    • Image loading with request headers, from relative paths and custom loading widget
    • SVG image support from network or local assets
    • Support for <details>/<summary> tags
    • Allow returning spans from custom tag renders
    • Inline font styling
    • Content-based table column sizing
    • Respect iframe sandbox attribute
    • Fixed text flow and styling when using tags inside <a> links
    • Fixed issue where shrinkWrap property would not constrain the widget to take up the space it needs
      • See the Notes for an example usage with shrinkWrap
    • Fixed issue where iframes would not update when their srcs changed in the HTML data
    • Updated dependencies for Flutter 1.26+
    Source code(tar.gz)
    Source code(zip)
  • 1.1.1(Nov 22, 2020)

  • 1.1.0(Nov 22, 2020)

  • 1.0.2(Aug 8, 2020)

  • 1.0.0-pre.1(Dec 28, 2019)

  • 0.11.0(Oct 17, 2019)

    September 10, 2019:

    • Make it so width=100% doesn't throw error. Fixes #118.
    • You can now set width and/or height in ImageProperties to negative to ignore the width and/or height values from the html. Fixes #97
    • The img alt property now renders correctly when the image fails to load and with the correct style. Fixes #96
    • Add partial support for sub tag.
    • Add new option: shrinkToFit (#148). Fixes #75.
    Source code(tar.gz)
    Source code(zip)
Owner
Matthew Whitaker
I am a Flutter/Android developer with a passion for music, math, physics, astronomy, and programming.
Matthew Whitaker
A Flutter widget for rendering HTML and CSS as Flutter widgets

flutter_html A Flutter widget for rendering HTML and CSS as Flutter widgets. Screenshot 1 Screenshot 2 Screenshot 3 Table of Contents: Installing Curr

Vishal Raj 1 Dec 15, 2021
QR.Flutter is a Flutter library for simple and fast QR code rendering via a Widget or custom painter.

QR.Flutter is a Flutter library for simple and fast QR code rendering via a Widget or custom painter. Need help? Please do not submit an issue for a "

Yakka 605 Nov 28, 2022
A highly customizable Flutter widget to render and interact with JSON objects.

The spreadsheet with superpowers ✨ ! JSON Data Explorer A highly customizable widget to render and interact with JSON objects. Features Expand and col

rows 13 Oct 21, 2022
Cross-platform flutter plugin for reading and writing NFC tags. Not maintained anymore - not looking for new maintainer, fork instead.

nfc_in_flutter NFC in Flutter is a plugin for reading and writing NFC tags in Flutter. It works on both Android and iOS with a simple stream interface

Andi Semler 113 Sep 28, 2022
A simple tag editor for inputing tags in Flutter.

Super Tag Editor A simple tag editor for inputting tags with suggestion box Supported suggestion box Screen Shot 1 Screen Shot 2 ) Usage Add the packa

dab 5 Oct 25, 2022
Allows tags to be entered inside textfield

textfield_tags This is a widget that allows your users to create tags by entering the tag's name inside of textfield and make the tags appear in the t

Eyoel Defare 75 Nov 2, 2022
A Flutter widget to create an iOS settings-table (static TableView).

flutter_cupertino_settings A Flutter widget to create an iOS settings-table (static TableView). import 'package:flutter_cupertino_settings/flutter_cup

Matthias Rupp 233 Nov 23, 2022
Flutter app demonstrating Flutter web rendering

Flutter Plasma Flutter app demonstrating Flutter web rendering. URL: https://flutterplasma.dev Routes /: Default demo /nocredits: Demo stops before cr

Felix Blaschke 362 Nov 30, 2022
Flutter-sorted-chips-row - Flutter library for rendering a row of Material "Chip" buttons that gets sorted according to the given function

sorted_chips_row A Flutter Widget displaying a row of Material Chips, sorted according to the provided comparison function. How to use Adding dependen

Callstack Incubator 29 Jul 29, 2021
A CustomPaint example where we can draw with different colors and different stroke sizes

CustomPaint A CustomPaint example where we can draw with different colors and different stroke sizes. A Flutter application which runs on iOS, Android

Behruz Hurramov 0 Dec 27, 2021
A Flutter Package to render Mathematics, Physics and Chemistry Equations based on LaTeX

flutter_tex Contents About Demo Video Screenshots How to use? Android iOS Web Examples Quick Example TeXView Document TeXView Markdown TeXView Quiz Te

Shahzad Akram 216 Nov 12, 2022
Flutter plugin to render stacked page view

Stacked Page View! pub.dev/stacked_page_view Hi! This package will create stacked page view in your flutter app. it's as lightweight as it can get ⚡ ⚡

Navin Kodag 12 Nov 25, 2022
Flutter package to render a Material Design Speed Dial.

Flutter Speed Dial Flutter package to render a Material Design Speed Dial. Usage The SpeedDial widget is built to be placed in the Scaffold.floatingAc

null 0 May 20, 2022
A Flutter widget that shoots confetti all over the screen.

Blast some confetti all over the screen and celebrate user achievements! Demo Video showing the Confetti in Action: https://youtu.be/dGMVyUY4-6M Live

Gordon Hayes 315 Nov 18, 2022
Dart JS interop for Mermaid - The Javascript tool that makes use of a markdown based syntax to render customizable diagrams, charts and visualizations.

Mermaid (Dart) Dart JS interop for Mermaid - Javascript library that makes use of a markdown based syntax to render customizable diagrams, charts and

Tim Maffett 3 Oct 24, 2022
Flutter plugin for creating static & dynamic app shortcuts on the home screen.

Flutter Shortcuts Show some ❤️ and ⭐ the repo Why use Flutter Shortcuts? Flutter Shortcuts Plugin is known for : Flutter Shortcuts Fast, performant &

Divyanshu Shekhar 39 Sep 26, 2022
Flutter plugin for creating static & dynamic app shortcuts on the home screen.

Flutter Shortcuts Compatibility ✅ Android ❌ iOS (active issue: iOS support for quick actions) Show some ❤️ and ⭐ the repo Why use Flutter Shortcuts? F

Devs On Flutter 39 Sep 26, 2022
Flutterbodydetection - A flutter plugin that uses MLKit on iOS/Android platforms to enable body pose and mask detection using Pose Detection and Selfie Segmentation APIs for both static images and live camera stream.

body_detection A flutter plugin that uses MLKit on iOS/Android platforms to enable body pose and mask detection using Pose Detection and Selfie Segmen

null 17 Nov 24, 2022
The read-a-book app is using static data. the application I developed has a homepage and detail page

book_app A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this

Adil Ahmet Sargın 1 Nov 12, 2021