Commit d13e7637 authored by 汪林玲's avatar 汪林玲

版本1.3.0

parents
version: 2.1
orbs:
codecov: codecov/codecov@1.0.2
jobs:
build:
docker:
- image: cirrusci/flutter
steps:
- checkout
- run: flutter --version
- run: flutter test --coverage
- codecov/upload:
file: coverage/lcov.info
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: flutter_html
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with a single custom sponsorship URL
.DS_Store
# Created by https://www.gitignore.io/api/flutter
# Edit at https://www.gitignore.io/?templates=flutter
### Flutter ###
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
build/
pubspec.lock
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# End of https://www.gitignore.io/api/flutter
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
# End of https://www.gitignore.io/api/flutter,jetbrains+all
**/.flutter-plugins-dependencies
**/flutter_export_environment.sh
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 20e59316b8b8474554b38493b8ca888794b0234a
channel: stable
project_type: package
## [1.3.0] - February 16, 2021:
* 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](https://github.com/Sub6Resources/flutter_html#notes) for an example usage with `shrinkWrap`
* Fixed issue where iframes would not update when their `src`s changed in the HTML data
* Updated dependencies for Flutter 1.26+
## [1.2.0] - January 14, 2021:
* Support irregular table sizes
* Allow for returning `null` from a customRender function to disable the widget
## [1.1.1] - November 22, 2020:
* Update dependencies
## [1.1.0] - November 22, 2020:
* Add support for inline styles
* Update dependencies
## [1.0.2] - August 8, 2020:
* Fix text scaling issues
* Update dependencies
## [1.0.1] - August 8, 2020:
* Fixed flutter_svg: ^0.18.0
# [1.0.0]
* BREAKING CHANGES (see the [Migration Guide](https://github.com/Sub6Resources/flutter_html/wiki/1.0.0-Migration-Guide) for a full overview of breaking changes.):
* The default parser has been completely rewritten and the RichText parser has been removed.
* `useRichText` no longer is necessary (The new parser uses RichText under the hood)
* `customRender` now works for the default parser.
* Adds support for `<audio>`, `<video>`, `<iframe>`, `<svg>`, `<ruby>`, `<rt>`, `<rp>`, `<sub>`, and `<sup>`
* Adds support for over 20 CSS attributes when using the `style` parameter.
* Fixes many many issues (see the list at [#122](https://github.com/Sub6Resources/flutter_html/pull/122))
* The following parameters of `Html` have been removed and should no longer be used (see the migration guide):
* `useRichText`
* `padding`
* `backgroundColor`
* `defaultTextStyle`
* `renderNewlines`
* `customEdgeInsets`
* `customTextStyle`
* `blockSpacing`
* `customTextAlign`
* `linkStyle`
* `imageProperties`
* `showImages`
* The default text style now matches the app's Material `TextTheme.bodyText2` (Fixes [#18](https://github.com/Sub6Resources/flutter_html/issues/18)).
* Requires Flutter v1.17.0 or greater
* Fixed quite a few issues with `img`
* Added a fancy new `style` attribute (this should be used in place of the deprecated styling parameters).
## [1.0.0-pre.1] - December 27, 2019
* For a list of pre-release changes, including several BREAKING CHANGES, see release notes for 1.0.0 above.
## [0.11.1] - December 14, 2019:
* Add support for `AssetImage`s using the `asset:` prefix ([#162](https://github.com/Sub6Resources/flutter_html/pull/162)).
## [0.11.0] - September 10, 2019:
* Make it so `width=100%` doesn't throw error. Fixes [#118](https://github.com/Sub6Resources/flutter_html/issues/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](https://github.com/Sub6Resources/flutter_html/issues/97)
* The `img` `alt` property now renders correctly when the image fails to load and with the correct style. Fixes [#96](https://github.com/Sub6Resources/flutter_html/issues/96)
* Add partial support for `sub` tag.
* Add new option: `shrinkToFit` ([#148](https://github.com/Sub6Resources/flutter_html/pull/148)). Fixes [#75](https://github.com/Sub6Resources/flutter_html/issues/75).
## [0.10.4] - June 22, 2019:
* Add support for `customTextStyle` to block and specialty HTML elements.
## [0.10.3] - June 20, 2019:
* Add `src` to the `onImageTap` callback ([#93](https://github.com/Sub6Resources/flutter_html/pull/93))
## [0.10.2] - June 19, 2019:
* Add `customTextAlign` property ([#112](https://github.com/Sub6Resources/flutter_html/pull/112))
* Use `tryParse` instead of `parse` for image width and height attributes so that `%` values are ignored safely. Fixes [#98](https://github.com/Sub6Resources/flutter_html/issues/98)
## [0.10.1] - May 20, 2019:
* Image properties and onImageTap for the richTextParser, plus some fixes ([#90](https://github.com/Sub6Resources/flutter_html/pull/90))
* Hotfix 1 (June 6, 2019): Fixes [#100](https://github.com/Sub6Resources/flutter_html/issues/100)
## [0.10.0] - May 18, 2019:
* **BREAKING:** `useRichText` now defaults to `true`
* Support for `aside`, `bdi`, `big`, `cite`, `data`, `ins`, `kbd`, `mark`, `nav`, `noscript`, `q`, `rp`, `rt`, `ruby`, `s`, `samp`, `strike`, `template`, `time`, `tt`, and `var` added to `RichText` parser.
## [0.9.9] - May 17, 2019:
* Fixes extra padding issue ([#87](https://github.com/Sub6Resources/flutter_html/issues/87))
## [0.9.8] - May 14, 2019:
* Add support for `address` tag in `RichText` parser.
## [0.9.7] - May 13, 2019:
* Added onImageError callback
* Added custom textstyle and edgeinsets callback ([#72](https://github.com/Sub6Resources/flutter_html/pull/72))
* Update dependency versions ([#84](https://github.com/Sub6Resources/flutter_html/issues/84))
* Fixes [#82](https://github.com/Sub6Resources/flutter_html/issues/82) and [#86](https://github.com/Sub6Resources/flutter_html/issues/86)
## [0.9.6] - March 11, 2019:
* Fix whitespace issue. ([#59](https://github.com/Sub6Resources/flutter_html/issues/59))
## [0.9.5] - March 11, 2019:
* Add support for `span` in `RichText` parser. ([#61](https://github.com/Sub6Resources/flutter_html/issues/61))
* Adds `linkStyle` attribute. ([#70](https://github.com/Sub6Resources/flutter_html/pull/70))
* Adds tests for `header`, `hr`, and `i` ([#62](https://github.com/Sub6Resources/flutter_html/issues/62))
## [0.9.4] - February 5, 2019:
* Fixes `table` error in `RichText` parser. ([#58](https://github.com/Sub6Resources/flutter_html/issues/58))
## [0.9.3] - January 31, 2019:
* Adds support for base64 encoded images
## [0.9.2] - January 31, 2019:
* Adds partial support for deprecated `font` tag.
## [0.9.1] - January 31, 2019:
* Adds full support for `sub` and `sup`. ([#46](https://github.com/Sub6Resources/flutter_html/pull/46))
* Fixes weak warning caught by Pub analysis ([#54](https://github.com/Sub6Resources/flutter_html/issues/54))
## [0.9.0] - January 31, 2019:
* Adds an alternate `RichText` parser and `useRichText` parameter. ([#37](https://github.com/Sub6Resources/flutter_html/pull/37))
## [0.8.2] - November 1, 2018:
* Removes debug prints.
## [0.8.1] - October 19, 2018:
* Adds `typedef` for `onLinkTap` function.
## [0.8.0] - October 18, 2018:
* Adds custom tag callback
* Logging no longer shows up in production.
## [0.7.1] - September 11, 2018:
* Fixes issue with text nodes that contain only a space. ([#24](https://github.com/Sub6Resources/flutter_html/issues/24))
* Fixes typo in README.md from 0.7.0.
## [0.7.0] - September 10, 2018:
* Adds full support for `ul` and `ol`
## [0.6.2] - September 5, 2018:
* Adds check for `img src` before trying to load it.
* Adds support for `img alt` attribute.
## [0.6.1] - September 4, 2018:
* Fixed minor typo
## [0.6.0] - September 4, 2018:
* Update README.md and example
* GitHub version 0.6.0 milestone reached
## [0.5.6] - September 4, 2018:
* Adds partial support for `center` and a `renderNewlines` property on the `Html` widget.
## [0.5.5] - September 4, 2018:
* Adds support for `acronym`, and `big`.
## [0.5.4] - August 31, 2018:
* Adds `onLinkTap` callback.
## [0.5.3] - August 25, 2018:
* Adds support for `strike`, and `tt`.
## [0.5.2] - August 25, 2018:
* Adds support for `bdi` and `bdo`
## [0.5.1] - August 25, 2018:
* Fixed issue with table rows not lining up correctly ([#4](https://github.com/Sub6Resources/flutter_html/issues/4))
## [0.5.0] - August 23, 2018:
* Major refactor that makes entire tree a Widget and eliminates the need to distinguish between inline and block elements.
* Fixed [#7](https://github.com/Sub6Resources/flutter_html/issues/7), [#9](https://github.com/Sub6Resources/flutter_html/issues/9), [#10](https://github.com/Sub6Resources/flutter_html/issues/10), and [#11](https://github.com/Sub6Resources/flutter_html/issues/11).
## [0.4.1] - August 15, 2018:
* Fixed issue with images not loading when inside of `p` tag ([#6](https://github.com/Sub6Resources/flutter_html/issues/6))
## [0.4.0] - August 15, 2018:
* Adds support for `table`, `tbody`, `tfoot`, `thead`, `tr`, `td`, `th`, and `caption`
## [0.3.1] - August 15, 2018:
* Fixed issue where `p` was not rendered with the `defaultTextStyle`.
## [0.3.0] - August 15, 2018:
* Adds support for `abbr`, `address`, `article`, `aside`, `blockquote`, `br`, `cite`, `code`, `data`, `dd`,
`del`, `dfn`, `dl`, `dt`, `figcaption`, `figure`, `footer`, `header`, `hr`, `img`, `ins`, `kbd`, `li`,
`main`, `mark`, `nav`, `noscript`, `pre`, `q`, `rp`, `rt`, `ruby`, `s`, `samp`, `section`, `small`, `span`,
`template`, `time`, and `var`
* Adds partial support for `a`, `ol`, and `ul`
## [0.2.0] - August 14, 2018:
* Adds support for `img`.
## [0.1.1] - August 14, 2018:
* Fixed `b` to be bold, not italic...
* Adds support for `em`, and `strong`
* Adds support for a default `TextStyle`
## [0.1.0] - August 14, 2018:
* Renamed widget from `HtmlWidget` to `Html`
* Adds support for `p`, `h1`, `h2`, `h3`, `h4`, `h5`, and `h6`.
## [0.0.1] - August 14, 2018:
* Adds support for `body`, `div`, `b`, `i`, and `u`.
Thanks for your interest in contributing to `flutter_html`!
I'm pretty busy, so in order to help me best make use of the time I spend working on this project, please adhere to the following guidelines when contributing:
1. In general, don't submit a pull request without discussing the feature with me, in an issue, first. I don't want you to have to do a whole bunch of work for nothing. This also makes it so there won't be a whole bunch of changes I make you do in order to have your pull request merged.
2. Please read the [wiki](https://github.com/Sub6Resources/flutter_html/wiki) before contributing (there are only two pages at the moment). This will give you an idea of what my plans are for this repository, and what you do and don't need to work on.
More specific guidelines will be added soon.
MIT License
Copyright (c) 2019 Matthew Whitaker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This diff is collapsed.
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.packages
.pub-cache/
.pub/
/build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
**/.flutter-plugins-dependencies
**/ios/Flutter/flutter_export_environment.sh
\ No newline at end of file
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 20e59316b8b8474554b38493b8ca888794b0234a
channel: stable
project_type: app
# Example
Detailed Example for flutter_html package
# Basic Example
```dart
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>
""",
//Optional parameters:
backgroundColor: Colors.white70,
onLinkTap: (url) {
// open url in a webview
},
style: {
"div": Style(
block: Block(
margin: EdgeInsets.all(16),
border: Border.all(width: 6),
backgroundColor: Colors.grey,
),
textStyle: TextStyle(
color: Colors.red,
),
),
},
onImageTap: (src) {
// Display the image in large form.
},
);
```
# Detailed Example
## Example HTML data string/body
```html
const htmlData = """
<h1>Header 1</h1>
<h2>Header 2</h2>
<h3>Header 3</h3>
<h4>Header 4</h4>
<h5>Header 5</h5>
<h6>Header 6</h6>
<h3>Ruby Support:</h3>
<p>
<ruby>
<rt>かん</rt>
<rt></rt>
</ruby>
&nbsp;is Japanese Kanji.
</p>
<h3>Support for <code>sub</code>/<code>sup</code></h3>
Solve for <var>x<sub>n</sub></var>: log<sub>2</sub>(<var>x</var><sup>2</sup>+<var>n</var>) = 9<sup>3</sup>
<p>One of the most <span>common</span> equations in all of physics is <br /><var>E</var>=<var>m</var><var>c</var><sup>2</sup>.</p>
<h3>Table support (with custom styling!):</h3>
<p>
<q>Famous quote...</q>
</p>
<table>
<colgroup>
<col width="50%" />
<col width="25%" />
<col width="25%" />
</colgroup>
<thead>
<tr><th>One</th><th>Two</th><th>Three</th></tr>
</thead>
<tbody>
<tr>
<td>Data</td><td>Data</td><td>Data</td>
</tr>
<tr>
<td>Data</td><td>Data</td><td>Data</td>
</tr>
</tbody>
<tfoot>
<tr><td>fData</td><td>fData</td><td>fData</td></tr>
</tfoot>
</table>
<h3>Custom Element Support:</h3>
<flutter></flutter>
<flutter horizontal></flutter>
<h3>SVG support:</h3>
<svg id='svg1' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
<circle r="32" cx="35" cy="65" fill="#F00" opacity="0.5"/>
<circle r="32" cx="65" cy="65" fill="#0F0" opacity="0.5"/>
<circle r="32" cx="50" cy="35" fill="#00F" opacity="0.5"/>
</svg>
<h3>List support:</h3>
<ol>
<li>This</li>
<li><p>is</p></li>
<li>an</li>
<li>
ordered
<ul>
<li>With<br /><br />...</li>
<li>a</li>
<li>nested</li>
<li>unordered
<ol>
<li>With a nested</li>
<li>ordered list.</li>
</ol>
</li>
<li>list</li>
</ul>
</li>
<li>list! Lorem ipsum dolor sit amet.</li>
<li><h2>Header 2</h2></li>
<h2><li>Header 2</li></h2>
</ol>
<h3>Link support:</h3>
<p>
Linking to <a href='https://github.com'>websites</a> has never been easier.
</p>
<h3>Image support:</h3>
<p>
<img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />
<a href='https://google.com'><img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /></a>
<img alt='Alt Text of an intentionally broken image' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30d' />
</p>
<!--
<h3>Video support:</h3>
<video controls>
<source src="https://www.w3schools.com/html/mov_bbb.mp4" />
</video>
<h3>Audio support:</h3>
<audio controls>
<source src="https://www.w3schools.com/html/horse.mp3" />
</audio>
<h3>IFrame support:</h3>
<iframe src="https://google.com"></iframe>
-->
""";
```
## How to use
```dart
return new Scaffold(
appBar: AppBar(
title: Text('flutter_html Example'),
centerTitle: true,
),
body: SingleChildScrollView(
child: Html(
data: htmlData,
//Optional parameters:
style: {
"html": Style(
backgroundColor: Colors.black12,
),
"table": Style(
backgroundColor: Color.fromARGB(0x50, 0xee, 0xee, 0xee),
),
"tr": Style(
border: Border(bottom: BorderSide(color: Colors.grey)),
),
"th": Style(
padding: EdgeInsets.all(6),
backgroundColor: Colors.grey,
),
"td": Style(
padding: EdgeInsets.all(6),
),
"var": Style(fontFamily: 'serif'),
},
customRender: {
"flutter": (RenderContext context, Widget child, attributes, _) {
return FlutterLogo(
style: (attributes['horizontal'] != null)
? FlutterLogoStyle.horizontal
: FlutterLogoStyle.markOnly,
textColor: context.style.color,
size: context.style.fontSize.size * 5,
);
},
},
onLinkTap: (url) {
print("Opening $url...");
},
onImageTap: (src) {
print(src);
},
onImageError: (exception, stackTrace) {
print(exception);
},
),
),
);
```
\ No newline at end of file
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
#Thu Nov 19 14:42:53 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
<svg viewBox='0 0 90 100' xmlns='http://www.w3.org/2000/svg'>
<path d='M62,0c2,10-9,24-20,24c-3-14,9-22,20-24M5,36c5-8,13-12,21-12c7,0,12,4,19,4c6,0,10-4,19-4c6,0,14,3,19,10c-16,4-15,35,3,39c-7,17-18,27-24,27c-7,0-8-5-17-5c-9,0-11,5-17,5c-7-1-13-7-17-13c-9-10-15-40-6-51' fill='#AAA'/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
PODS:
- Flutter (1.0.0)
- video_player (0.0.1):
- Flutter
- wakelock (0.0.1):
- Flutter
- webview_flutter (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- video_player (from `.symlinks/plugins/video_player/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- webview_flutter (from `.symlinks/plugins/webview_flutter/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
video_player:
:path: ".symlinks/plugins/video_player/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
webview_flutter:
:path: ".symlinks/plugins/webview_flutter/ios"
SPEC CHECKSUMS:
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
wakelock: bfc7955c418d0db797614075aabbc58a39ab5107
webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96
PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d
COCOAPODS: 1.10.1
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>io.flutter.embedded_views_preview</key>
<string>YES</string>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
\ No newline at end of file
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/image_render.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepPurple,
),
home: new MyHomePage(title: 'flutter_html Example'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
const htmlData = """
<h1>Header 1</h1>
<h2>Header 2</h2>
<h3>Header 3</h3>
<h4>Header 4</h4>
<h5>Header 5</h5>
<h6>Header 6</h6>
<h3>Ruby Support:</h3>
<p>
<ruby>
漢<rt>かん</rt>
字<rt>じ</rt>
</ruby>
&nbsp;is Japanese Kanji.
</p>
<h3>Support for <code>sub</code>/<code>sup</code></h3>
Solve for <var>x<sub>n</sub></var>: log<sub>2</sub>(<var>x</var><sup>2</sup>+<var>n</var>) = 9<sup>3</sup>
<p>One of the most <span>common</span> equations in all of physics is <br /><var>E</var>=<var>m</var><var>c</var><sup>2</sup>.</p>
<h3>Inline Styles:</h3>
<p>The should be <span style='color: blue;'>BLUE style='color: blue;'</span></p>
<p>The should be <span style='color: red;'>RED style='color: red;'</span></p>
<p>The should be <span style='color: rgba(0, 0, 0, 0.10);'>BLACK with 10% alpha style='color: rgba(0, 0, 0, 0.10);</span></p>
<p>The should be <span style='color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
<p>The should be <span style='background-color: red; color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
<p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<p style="text-align: right;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<p style="text-align: justify;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
<h3>Table support (with custom styling!):</h3>
<p>
<q>Famous quote...</q>
</p>
<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>
<h3>Custom Element Support (inline: <bird></bird> and as block):</h3>
<flutter></flutter>
<flutter horizontal></flutter>
<h3>SVG support:</h3>
<svg id='svg1' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
<circle r="32" cx="35" cy="65" fill="#F00" opacity="0.5"/>
<circle r="32" cx="65" cy="65" fill="#0F0" opacity="0.5"/>
<circle r="32" cx="50" cy="35" fill="#00F" opacity="0.5"/>
</svg>
<h3>List support:</h3>
<ol>
<li>This</li>
<li><p>is</p></li>
<li>an</li>
<li>
ordered
<ul>
<li>With<br /><br />...</li>
<li>a</li>
<li>nested</li>
<li>unordered
<ol>
<li>With a nested</li>
<li>ordered list.</li>
</ol>
</li>
<li>list</li>
</ul>
</li>
<li>list! Lorem ipsum dolor sit amet.</li>
<li><h2>Header 2</h2></li>
<h2><li>Header 2</li></h2>
</ol>
<h3>Link support:</h3>
<p>
Linking to <a href='https://github.com'>websites</a> has never been easier.
</p>
<h3>Image support:</h3>
<h3>Network png</h3>
<img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />
<h3>Network svg</h3>
<img src='https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg' />
<h3>Local asset png</h3>
<img src='asset:assets/html5.png' width='100' />
<h3>Local asset svg</h3>
<img src='asset:assets/mac.svg' width='100' />
<h3>Base64</h3>
<img alt='Red dot' src='data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' />
<h3>Custom source matcher (relative paths)</h3>
<img src='/wikipedia/commons/thumb/e/ef/Octicons-logo-github.svg/200px-Octicons-logo-github.svg.png' />
<h3>Custom image render (flutter.dev)</h3>
<img src='https://flutter.dev/images/flutter-mono-81x100.png' />
<h3>No image source</h3>
<img alt='No source' />
<img alt='Empty source' src='' />
<h3>Broken network image</h3>
<img alt='Broken image' src='https://www.notgoogle.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />
""";
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('flutter_html Example'),
centerTitle: true,
),
body: SingleChildScrollView(
child: Html(
data: htmlData,
//Optional parameters:
customImageRenders: {
networkSourceMatcher(domains: ["flutter.dev"]):
(context, attributes, element) {
return FlutterLogo(size: 36);
},
networkSourceMatcher(domains: ["mydomain.com"]): networkImageRender(
headers: {"Custom-Header": "some-value"},
altWidget: (alt) => Text(alt),
loadingWidget: () => Text("Loading..."),
),
// On relative paths starting with /wiki, prefix with a base url
(attr, _) => attr["src"] != null && attr["src"].startsWith("/wiki"):
networkImageRender(
mapUrl: (url) => "https://upload.wikimedia.org" + url),
// Custom placeholder image for broken links
networkSourceMatcher(): networkImageRender(altWidget: (_) => FlutterLogo()),
},
onLinkTap: (url) {
print("Opening $url...");
},
onImageTap: (src) {
print(src);
},
onImageError: (exception, stackTrace) {
print(exception);
},
),
),
);
}
}
name: example
description: flutter_html example app.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter_html:
path: ..
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/html5.png
- assets/mac.svg
\ No newline at end of file
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
library flutter_html;
import 'package:flutter/material.dart';
import 'package:flutter_html/html_parser.dart';
import 'package:flutter_html/image_render.dart';
import 'package:flutter_html/style.dart';
import 'package:webview_flutter/webview_flutter.dart';
class Html extends StatelessWidget {
/// The `Html` widget takes HTML as input and displays a RichText
/// tree of the parsed HTML content.
///
/// **Attributes**
/// **data** *required* takes in a String of HTML data.
///
///
/// **onLinkTap** This function is called whenever a link (`<a href>`)
/// is tapped.
/// **customRender** This function allows you to return your own widgets
/// for existing or custom HTML tags.
/// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/All-About-customRender) for more info.
///
/// **onImageError** This is called whenever an image fails to load or
/// display on the page.
///
/// **shrinkWrap** This makes the Html widget take up only the width it
/// needs and no more.
///
/// **onImageTap** This is called whenever an image is tapped.
///
/// **blacklistedElements** Tag names in this array are ignored during parsing and rendering.
///
/// **style** Pass in the style information for the Html here.
/// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/Style) for more info.
Html({
Key key,
@required this.data,
this.onLinkTap,
this.customRender,
this.customImageRenders = const {},
this.onImageError,
this.shrinkWrap = false,
this.onImageTap,
this.blacklistedElements = const [],
this.style,
this.navigationDelegateForIframe,
}) : super(key: key);
final String data;
final OnTap onLinkTap;
final Map<ImageSourceMatcher, ImageRender> customImageRenders;
final ImageErrorListener onImageError;
final bool shrinkWrap;
/// Properties for the Image widget that gets rendered by the rich text parser
final OnTap onImageTap;
final List<String> blacklistedElements;
/// Either return a custom widget for specific node types or return null to
/// fallback to the default rendering.
final Map<String, CustomRender> customRender;
/// Fancy New Parser parameters
final Map<String, Style> style;
/// Decides how to handle a specific navigation request in the WebView of an
/// Iframe. It's necessary to use the webview_flutter package inside the app
/// to use NavigationDelegate.
final NavigationDelegate navigationDelegateForIframe;
@override
Widget build(BuildContext context) {
final double width = shrinkWrap ? null : MediaQuery.of(context).size.width;
return Container(
width: width,
child: HtmlParser(
htmlData: data,
onLinkTap: onLinkTap,
onImageTap: onImageTap,
onImageError: onImageError,
shrinkWrap: shrinkWrap,
style: style,
customRender: customRender,
imageRenders: {}
..addAll(customImageRenders)
..addAll(defaultImageRenders),
blacklistedElements: blacklistedElements,
navigationDelegateForIframe: navigationDelegateForIframe,
),
);
}
}
This diff is collapsed.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_html/html_parser.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:html/dom.dart' as dom;
typedef ImageSourceMatcher = bool Function(
Map<String, String> attributes,
dom.Element element,
);
ImageSourceMatcher base64DataUriMatcher() => (attributes, element) =>
_src(attributes) != null &&
_src(attributes).startsWith("data:image") &&
_src(attributes).contains("base64,");
ImageSourceMatcher networkSourceMatcher({
List<String> schemas: const ["https", "http"],
List<String> domains,
String extension,
}) =>
(attributes, element) {
if (_src(attributes) == null) return false;
try {
final src = Uri.parse(_src(attributes));
return schemas.contains(src.scheme) &&
(domains == null || domains.contains(src.host)) &&
(extension == null || src.path.endsWith(".$extension"));
} catch (e) {
return false;
}
};
ImageSourceMatcher assetUriMatcher() => (attributes, element) =>
_src(attributes) != null && _src(attributes).startsWith("asset:");
typedef ImageRender = Widget Function(
RenderContext context,
Map<String, String> attributes,
dom.Element element,
);
ImageRender base64ImageRender() => (context, attributes, element) {
final decodedImage =
base64.decode(_src(attributes).split("base64,")[1].trim());
precacheImage(
MemoryImage(decodedImage),
context.buildContext,
onError: (exception, StackTrace stackTrace) {
context.parser.onImageError?.call(exception, stackTrace);
},
);
return Image.memory(
decodedImage,
frameBuilder: (ctx, child, frame, _) {
if (frame == null) {
return Text(_alt(attributes) ?? "",
style: context.style.generateTextStyle());
}
return child;
},
);
};
ImageRender assetImageRender({
double width,
double height,
}) =>
(context, attributes, element) {
final assetPath = _src(attributes).replaceFirst('asset:', '');
if (_src(attributes).endsWith(".svg")) {
return SvgPicture.asset(assetPath);
} else {
return Image.asset(
assetPath,
width: width ?? _width(attributes),
height: height ?? _height(attributes),
frameBuilder: (ctx, child, frame, _) {
if (frame == null) {
return Text(_alt(attributes) ?? "",
style: context.style.generateTextStyle());
}
return child;
},
);
}
};
ImageRender networkImageRender({
Map<String, String> headers,
String Function(String) mapUrl,
double width,
double height,
Widget Function(String) altWidget,
Widget Function() loadingWidget,
}) =>
(context, attributes, element) {
final src = mapUrl?.call(_src(attributes)) ?? _src(attributes);
precacheImage(
NetworkImage(
src,
headers: headers,
),
context.buildContext,
onError: (exception, StackTrace stackTrace) {
context.parser.onImageError?.call(exception, stackTrace);
},
);
Completer<Size> completer = Completer();
Image image =
Image.network(src, frameBuilder: (ctx, child, frame, _) {
if (frame == null) {
if (!completer.isCompleted) {
completer.completeError("error");
}
return child;
} else {
return child;
}
});
image.image.resolve(ImageConfiguration()).addListener(
ImageStreamListener((ImageInfo image, bool synchronousCall) {
var myImage = image.image;
Size size =
Size(myImage.width.toDouble(), myImage.height.toDouble());
if (!completer.isCompleted) {
completer.complete(size);
}
}, onError: (object, stacktrace) {
if (!completer.isCompleted) {
completer.completeError(object);
}
}),
);
return FutureBuilder<Size>(
future: completer.future,
builder: (BuildContext buildContext, AsyncSnapshot<Size> snapshot) {
if (snapshot.hasData) {
return Image.network(
src,
headers: headers,
width: width ?? _width(attributes) ?? snapshot.data.width,
height: height ?? _height(attributes),
frameBuilder: (ctx, child, frame, _) {
if (frame == null) {
return altWidget?.call(_alt(attributes)) ??
Text(_alt(attributes) ?? "",
style: context.style.generateTextStyle());
}
return child;
},
);
} else if (snapshot.hasError) {
return altWidget?.call(_alt(attributes)) ?? Text(_alt(attributes) ?? "",
style: context.style.generateTextStyle());
} else {
return loadingWidget?.call() ?? const CircularProgressIndicator();
}
},
);
};
ImageRender svgNetworkImageRender() => (context, attributes, element) {
return SvgPicture.network(attributes["src"]);
};
final Map<ImageSourceMatcher, ImageRender> defaultImageRenders = {
base64DataUriMatcher(): base64ImageRender(),
assetUriMatcher(): assetImageRender(),
networkSourceMatcher(extension: "svg"): svgNetworkImageRender(),
networkSourceMatcher(): networkImageRender(),
};
String _src(Map<String, String> attributes) {
return attributes["src"];
}
String _alt(Map<String, String> attributes) {
return attributes["alt"];
}
double _height(Map<String, String> attributes) {
final heightString = attributes["height"];
return heightString == null ? heightString : double.tryParse(heightString);
}
double _width(Map<String, String> attributes) {
final widthString = attributes["width"];
return widthString == null ? widthString : double.tryParse(widthString);
}
This diff is collapsed.
export 'styled_element.dart';
export 'interactable_element.dart';
export 'replaced_element.dart';
const STYLED_ELEMENTS = [
"abbr",
"acronym",
"address",
"b",
"bdi",
"bdo",
"big",
"cite",
"code",
"data",
"del",
"dfn",
"em",
"font",
"i",
"ins",
"kbd",
"mark",
"q",
"s",
"samp",
"small",
"span",
"strike",
"strong",
"sub",
"sup",
"time",
"tt",
"u",
"var",
"wbr",
//BLOCK ELEMENTS
"article",
"aside",
"blockquote",
"body",
"center",
"dd",
"div",
"dl",
"dt",
"figcaption",
"figure",
"footer",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"header",
"hr",
"html",
"li",
"main",
"nav",
"noscript",
"ol",
"p",
"pre",
"section",
"summary",
"ul",
];
const INTERACTABLE_ELEMENTS = [
"a",
];
const REPLACED_ELEMENTS = [
"audio",
"br",
"head",
"iframe",
"img",
"svg",
"template",
"video",
"rp",
"rt",
"ruby",
];
const LAYOUT_ELEMENTS = [
"details",
"table",
"tr",
"tbody",
"tfoot",
"thead",
];
const TABLE_CELL_ELEMENTS = ["th", "td"];
const TABLE_DEFINITION_ELEMENTS = ["col", "colgroup"];
/**
Here is a list of elements with planned support:
a - i [x]
abbr - s [x]
acronym - s [x]
address - s [x]
audio - c [x]
article - b [x]
aside - b [x]
b - s [x]
bdi - s [x]
bdo - s [x]
big - s [x]
blockquote- b [x]
body - b [x]
br - b [x]
button - i [ ]
caption - b [ ]
center - b [x]
cite - s [x]
code - s [x]
data - s [x]
dd - b [x]
del - s [x]
dfn - s [x]
div - b [x]
dl - b [x]
dt - b [x]
em - s [x]
figcaption- b [x]
figure - b [x]
font - s [x]
footer - b [x]
h1 - b [x]
h2 - b [x]
h3 - b [x]
h4 - b [x]
h5 - b [x]
h6 - b [x]
head - e [x]
header - b [x]
hr - b [x]
html - b [x]
i - s [x]
img - c [x]
ins - s [x]
kbd - s [x]
li - b [x]
main - b [x]
mark - s [x]
nav - b [x]
noscript - b [x]
ol - b [x] post
p - b [x]
pre - b [x]
q - s [x] post
rp - s [x]
rt - s [x]
ruby - s [x]
s - s [x]
samp - s [x]
section - b [x]
small - s [x]
source - [-] child of content
span - s [x]
strike - s [x]
strong - s [x]
sub - s [x]
sup - s [x]
svg - c [x]
table - b [x]
tbody - b [x]
td - s [ ]
template - e [x]
tfoot - b [x]
th - s [ ]
thead - b [x]
time - s [x]
tr - ? [ ]
track - [-] child of content
tt - s [x]
u - s [x]
ul - b [x] post
var - s [x]
video - c [x]
wbr - s [x]
*/
import 'package:flutter/material.dart';
import 'package:flutter_html/src/html_elements.dart';
import 'package:flutter_html/style.dart';
import 'package:html/dom.dart' as dom;
/// An [InteractableElement] is a [StyledElement] that takes user gestures (e.g. tap).
class InteractableElement extends StyledElement {
String href;
InteractableElement({
String name,
List<StyledElement> children,
Style style,
this.href,
dom.Node node,
}) : super(name: name, children: children, style: style, node: node);
}
/// A [Gesture] indicates the type of interaction by a user.
enum Gesture {
TAP,
}
InteractableElement parseInteractableElement(
dom.Element element, List<StyledElement> children) {
InteractableElement interactableElement = InteractableElement(
name: element.localName,
children: children,
node: element,
);
switch (element.localName) {
case "a":
interactableElement.href = element.attributes['href'];
interactableElement.style = Style(
color: Colors.blue,
textDecoration: TextDecoration.underline,
);
break;
}
return interactableElement;
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import 'package:flutter/gestures.dart';
class Context<T> {
T data;
Context(this.data);
}
// This class is a workaround so that both an image
// and a link can detect taps at the same time.
class MultipleTapGestureRecognizer extends TapGestureRecognizer {
bool _ready = false;
@override
void addAllowedPointer(PointerDownEvent event) {
if (state == GestureRecognizerState.ready) {
_ready = true;
}
super.addAllowedPointer(event);
}
@override
void handlePrimaryPointer(PointerEvent event) {
if (event is PointerCancelEvent) {
_ready = false;
}
super.handlePrimaryPointer(event);
}
@override
void resolve(GestureDisposition disposition) {
if (_ready && disposition == GestureDisposition.rejected) {
_ready = false;
}
super.resolve(disposition);
}
@override
void rejectGesture(int pointer) {
if (_ready) {
acceptGesture(pointer);
_ready = false;
}
}
}
This diff is collapsed.
name: flutter_html
description: A Flutter widget rendering static HTML and CSS as Flutter widgets.
version: 1.3.0
homepage: https://github.com/Sub6Resources/flutter_html
environment:
sdk: '>=2.2.2 <3.0.0'
flutter: '>=1.17.0'
dependencies:
# Plugin for parsing html
html: ^0.14.0+4
# Plugins for parsing css
csslib: ^0.16.2
css_colors: ^1.0.2
# Plugins for rendering the <table> tag.
flutter_layout_grid: ^0.10.5
# Plugins for rendering the <video> tag.
video_player: ^1.0.1
chewie: ^0.12.2
# Plugin for rendering the <iframe> tag.
webview_flutter: ^1.0.7
# Plugins for rendering the <audio> tag.
chewie_audio: ^1.1.2
# Plugins for rendering the <svg> tag.
flutter_svg: 0.19.1
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_data.dart';
class TestApp extends StatelessWidget {
final Widget body;
TestApp(this.body);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: body,
appBar: AppBar(title: Text('flutter_html')),
),
);
}
}
void testHtml(String name, String htmlData) {
testWidgets('$name golden test', (WidgetTester tester) async {
await tester.pumpWidget(
TestApp(
Html(
data: htmlData,
),
),
);
expect(find.byType(Html), findsOneWidget);
// await expectLater(find.byType(Html), matchesGoldenFile('./goldens/$name.png'));
});
}
void main() {
//Test each HTML element
testData.forEach((key, value) {
testHtml(key, value);
});
//Test whitespace processing:
testWidgets('whitespace golden test', (WidgetTester tester) async {
await tester.pumpWidget(
TestApp(
Html(data: """
<p id='whitespace'>
These two lines should have an identical length:<br /><br />
The quick <b> brown </b><u><i> fox </i></u> jumped over the
lazy
dog.<br />
The quick brown fox jumped over the lazy dog.
</p>
"""),
),
);
// await expectLater(find.byType(Html), matchesGoldenFile('./goldens/whitespace.png'));
});
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment