diff --git a/analysis_options.yaml b/analysis_options.yaml index d26bda7..b31d0f0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -136,7 +136,7 @@ linter: - slash_for_doc_comments - sort_child_properties_last # - sort_constructors_first - - sort_pub_dependencies +# - sort_pub_dependencies - sort_unnamed_constructors_first - test_types_in_equals - throw_in_finally diff --git a/example/lib/main.dart b/example/lib/main.dart index e1ca41c..a500294 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -17,16 +17,21 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - const MyHomePage({Key key}) : super(key: key); + const MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { - TextEditingController _controller; + late TextEditingController _controller; int _index = -1; final List _urls = [ + "https://news.google.com/__i/rss/rd/articles/CBMiMGh0dHBzOi8vd3d3Lm1pcnJvcm1lZGlhLm1nL3N0b3J5LzIwMjExMjE1ZWRpMDExL9IBAA?oc=5", + "https://news.google.com/__i/rss/rd/articles/CBMiOmh0dHBzOi8vbmV3cy5sdG4uY29tLnR3L25ld3MvcG9saXRpY3MvYnJlYWtpbmduZXdzLzM3NjkxNTnSAT5odHRwczovL25ld3MubHRuLmNvbS50dy9hbXAvbmV3cy9wb2xpdGljcy9icmVha2luZ25ld3MvMzc2OTE1OQ?oc=5", + "https://lihkg.com/thread/2529600/page/1", + "https://youtu.be/v_hR4K4auoQ", + "https://twitter.com/sspai_com/status/1392794070704066566?s=20", "https://mp.weixin.qq.com/s/qj7gkU-Pbdcdn3zO6ZQxqg", "https://mp.weixin.qq.com/s/43GznPLxi5i3yOdvrlr1JQ", "https://m.tb.cn/h.VFcZsnK?sm=34cd13", @@ -69,11 +74,20 @@ class _MyHomePageState extends State { "https://bbs.hupu.com/36997146.html", "https://music.163.com/#/playlist?id=4944751157", ]; + @override void initState() { _controller = TextEditingController( text: "https://www.bilibili.com/video/BV1F64y1c7hd?spm_id_from=333.851.b_7265706f7274466972737431.12"); + + // test useMultithread + WebAnalyzer.getInfo( + _urls[1], + multimedia: false, + useMultithread: true, + ).then(print); + super.initState(); } @@ -140,15 +154,16 @@ class _MyHomePageState extends State { url: _controller.value.text, builder: (info) { if (info == null) return const SizedBox(); - if (info is WebImageInfo) { + if (info is WebImageInfo && WebAnalyzer.isNotEmpty(info.image)) { return CachedNetworkImage( - imageUrl: info.image, + imageUrl: info.image!, fit: BoxFit.contain, ); } - final WebInfo webInfo = info; - if (!WebAnalyzer.isNotEmpty(webInfo.title)) return const SizedBox(); + final WebInfo? webInfo = info as WebInfo; + if (webInfo == null || !WebAnalyzer.isNotEmpty(webInfo.title)) + return const SizedBox(); return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), @@ -177,7 +192,7 @@ class _MyHomePageState extends State { const SizedBox(width: 8), Expanded( child: Text( - webInfo.title, + webInfo.title ?? "", overflow: TextOverflow.ellipsis, ), ), @@ -186,7 +201,7 @@ class _MyHomePageState extends State { if (WebAnalyzer.isNotEmpty(webInfo.description)) ...[ const SizedBox(height: 8), Text( - webInfo.description, + webInfo.description ?? "", maxLines: 5, overflow: TextOverflow.ellipsis, ), @@ -194,7 +209,7 @@ class _MyHomePageState extends State { if (WebAnalyzer.isNotEmpty(webInfo.image)) ...[ const SizedBox(height: 8), CachedNetworkImage( - imageUrl: webInfo.image, + imageUrl: webInfo.image!, fit: BoxFit.contain, ), ] diff --git a/example/pubspec.lock b/example/pubspec.lock index 31e4282..8f0a886 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,81 +1,116 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" cached_network_image: dependency: "direct main" description: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+1" + version: "3.2.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.2.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" + version: "1.15.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.1" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.17.0" + fast_gbk: + dependency: transitive + description: + name: fast_gbk + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" file: dependency: transitive description: name: file url: "https://pub.dartlang.org" source: hosted - version: "5.1.0" + version: "6.1.2" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.0" flutter_cache_manager: dependency: transitive description: name: flutter_cache_manager url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "3.3.0" flutter_link_preview: dependency: "direct main" description: @@ -83,104 +118,132 @@ packages: relative: true source: path version: "1.5.6" - gbk2utf8: - dependency: transitive - description: - name: gbk2utf8 - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" html: dependency: transitive description: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+3" + version: "0.15.0" http: dependency: transitive description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.1" + version: "0.13.4" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" - intl: + version: "4.0.0" + meta: dependency: transitive description: - name: intl + name: meta url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" - meta: + version: "1.7.0" + octo_image: dependency: transitive description: - name: meta + name: octo_image url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.0.1" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.8.0" path_provider: dependency: transitive description: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.9" + version: "2.0.8" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+3" + version: "2.0.4" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.0" + version: "1.11.0" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "2.0.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" rxdart: dependency: transitive description: name: rxdart url: "https://pub.dartlang.org" source: hosted - version: "0.24.1" + version: "0.27.3" sky_engine: dependency: transitive description: flutter @@ -192,63 +255,77 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" sqflite: dependency: transitive description: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.0+2" + version: "2.0.0+3" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0+2" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" synchronized: dependency: transitive description: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" uuid: dependency: transitive description: name: uuid url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "3.0.5" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.1" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + dart: ">=2.14.0 <3.0.0" + flutter: ">=2.5.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d2e859e..16367c9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -26,7 +26,7 @@ dependencies: flutter_link_preview: path: ../ - cached_network_image: ^2.2.0+1 + cached_network_image: ^3.2.0 dev_dependencies: diff --git a/lib/flutter_link_preview.dart b/lib/flutter_link_preview.dart index 18e5e37..d62394b 100644 --- a/lib/flutter_link_preview.dart +++ b/lib/flutter_link_preview.dart @@ -3,9 +3,10 @@ library flutter_link_preview; import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; + +import 'package:fast_gbk/fast_gbk.dart'; import 'package:flutter/material.dart'; -import 'package:gbk2utf8/gbk2utf8.dart'; -import 'package:html/dom.dart' hide Text; +import 'package:html/dom.dart' as html_dom; import 'package:html/parser.dart' as parser; import 'package:http/http.dart'; import 'package:http/io_client.dart'; @@ -15,8 +16,8 @@ part 'web_analyzer.dart'; /// Link Preview Widget class FlutterLinkPreview extends StatefulWidget { const FlutterLinkPreview({ - Key key, - @required this.url, + Key? key, + required this.url, this.cache = const Duration(hours: 24), this.builder, this.titleStyle, @@ -32,13 +33,13 @@ class FlutterLinkPreview extends StatefulWidget { final Duration cache; /// Customized rendering methods - final Widget Function(InfoBase info) builder; + final Widget Function(InfoBase? info)? builder; /// Title style - final TextStyle titleStyle; + final TextStyle? titleStyle; /// Content style - final TextStyle bodyStyle; + final TextStyle? bodyStyle; /// Show image or video final bool showMultimedia; @@ -51,8 +52,8 @@ class FlutterLinkPreview extends StatefulWidget { } class _FlutterLinkPreviewState extends State { - String _url; - InfoBase _info; + late String _url; + InfoBase? _info; @override void initState() { @@ -79,19 +80,23 @@ class _FlutterLinkPreviewState extends State { @override Widget build(BuildContext context) { if (widget.builder != null) { - return widget.builder(_info); + return widget.builder!(_info); } if (_info == null) return const SizedBox(); if (_info is WebImageInfo) { - return Image.network( - (_info as WebImageInfo).image, - fit: BoxFit.contain, - ); + if((_info as WebImageInfo).image == null){ + return const SizedBox(); + }else { + return Image.network( + (_info as WebImageInfo).image!, + fit: BoxFit.contain, + ); + } } - final WebInfo info = _info; + final WebInfo info = _info as WebInfo; if (!WebAnalyzer.isNotEmpty(info.title)) return const SizedBox(); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -109,7 +114,7 @@ class _FlutterLinkPreviewState extends State { const SizedBox(width: 8), Expanded( child: Text( - info.title, + info.title ?? "", maxLines: 1, overflow: TextOverflow.ellipsis, style: widget.titleStyle, @@ -120,7 +125,7 @@ class _FlutterLinkPreviewState extends State { if (WebAnalyzer.isNotEmpty(info.description)) ...[ const SizedBox(height: 8), Text( - info.description, + info.description ?? "", maxLines: 5, overflow: TextOverflow.ellipsis, style: widget.bodyStyle, diff --git a/lib/web_analyzer.dart b/lib/web_analyzer.dart index 299a348..8392b34 100644 --- a/lib/web_analyzer.dart +++ b/lib/web_analyzer.dart @@ -1,16 +1,16 @@ part of flutter_link_preview; abstract class InfoBase { - DateTime _timeout; + late DateTime _timeout; } /// Web Information class WebInfo extends InfoBase { - final String title; - final String icon; - final String description; - final String image; - final String redirectUrl; + final String? title; + final String? icon; + final String? description; + final String? image; + final String? redirectUrl; WebInfo({ this.title, @@ -23,14 +23,14 @@ class WebInfo extends InfoBase { /// Image Information class WebImageInfo extends InfoBase { - final String image; + final String? image; - WebImageInfo({this.image}); + WebImageInfo({required this.image}); } /// Video Information class WebVideoInfo extends WebImageInfo { - WebVideoInfo({String image}) : super(image: image); + WebVideoInfo({String? image}) : super(image: image); } /// Web analyzer @@ -51,14 +51,14 @@ class WebAnalyzer { static final RegExp _spaceReg = RegExp(r"\s+"); /// Is it an empty string - static bool isNotEmpty(String str) { + static bool isNotEmpty(String? str) { return str != null && str.isNotEmpty; } /// Get web information /// return [InfoBase] - static InfoBase getInfoFromCache(String url) { - final InfoBase info = _map[url]; + static InfoBase? getInfoFromCache(String url) { + final InfoBase? info = _map[url]; if (info != null) { if (!info._timeout.isAfter(DateTime.now())) { _map.remove(url); @@ -69,13 +69,13 @@ class WebAnalyzer { /// Get web information /// return [InfoBase] - static Future getInfo(String url, + static Future getInfo(String url, {Duration cache = const Duration(hours: 24), bool multimedia = true, bool useMultithread = false}) async { // final start = DateTime.now(); - InfoBase info = getInfoFromCache(url); + InfoBase? info = getInfoFromCache(url); if (info != null) return info; try { if (useMultithread) @@ -96,13 +96,13 @@ class WebAnalyzer { return info; } - static Future _getInfo(String url, bool multimedia) async { + static Future _getInfo(String url, bool multimedia) async { final response = await _requestUrl(url); if (response == null) return null; // print("$url ${response.statusCode}"); if (multimedia) { - final String contentType = response.headers["content-type"]; + final String? contentType = response.headers["content-type"]; if (contentType != null) { if (contentType.contains("image/")) { return WebImageInfo(image: url); @@ -115,20 +115,25 @@ class WebAnalyzer { return _getWebInfo(response, url, multimedia); } - static Future _getInfoByIsolate(String url, bool multimedia) async { + static Future _getInfoByIsolate( + String url, bool multimedia) async { final sender = ReceivePort(); final Isolate isolate = await Isolate.spawn(_isolate, sender.sendPort); final sendPort = await sender.first as SendPort; final answer = ReceivePort(); sendPort.send([answer.sendPort, url, multimedia]); - final List res = await answer.first; + final List? res = await answer.first; - InfoBase info; + InfoBase? info; if (res != null) { if (res[0] == "0") { info = WebInfo( - title: res[1], description: res[2], icon: res[3], image: res[4]); + title: res[1], + description: res[2], + icon: res[3], + image: res[4], + ); } else if (res[0] == "1") { info = WebVideoInfo(image: res[1]); } else if (res[0] == "2") { @@ -174,28 +179,36 @@ class WebAnalyzer { static bool _certificateCheck(X509Certificate cert, String host, int port) => true; - static Future _requestUrl(String url, - {int count = 0, String cookie, useDesktopAgent = true}) async { + static Future _requestUrl( + String url, { + int count = 0, + String? cookie, + useDesktopAgent = true, + }) async { if (url.contains("m.toutiaoimg.cn")) useDesktopAgent = false; - Response res; + Response? res; final uri = Uri.parse(url); final ioClient = HttpClient()..badCertificateCallback = _certificateCheck; final client = IOClient(ioClient); + /* + Twitter website doesn't have open graph tags? + https://stackoverflow.com/a/64332370/5588637 + */ final request = Request('GET', uri) ..followRedirects = false - ..headers["User-Agent"] = useDesktopAgent - ? "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36" - : "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" + ..headers["User-Agent"] = useDesktopAgent ? "WhatsApp/2" : "WhatsApp/2" ..headers["cache-control"] = "no-cache" - ..headers["Cookie"] = cookie ?? _cookies[uri.host] + ..headers["Cookie"] = cookie ?? _cookies[uri.host] ?? "" ..headers["accept"] = "*/*"; + // print(request.headers); final stream = await client.send(request); if (stream.statusCode == HttpStatus.movedTemporarily || - stream.statusCode == HttpStatus.movedPermanently) { + stream.statusCode == HttpStatus.movedPermanently || + stream.statusCode == HttpStatus.seeOther) { if (stream.isRedirect && count < 6) { - final String location = stream.headers['location']; + final String? location = stream.headers['location']; if (location != null) { url = location; if (location.startsWith("/")) { @@ -227,10 +240,10 @@ class WebAnalyzer { return res; } - static Future _getWebInfo( + static Future _getWebInfo( Response response, String url, bool multimedia) async { if (response.statusCode == HttpStatus.ok) { - String html; + String? html; try { html = const Utf8Decoder().convert(response.bodyBytes); } catch (e) { @@ -263,19 +276,19 @@ class WebAnalyzer { } String title = _analyzeTitle(document); - String description = + String? description = _analyzeDescription(document, html)?.replaceAll(r"\x0a", " "); if (!isNotEmpty(title)) { - title = description; + title = description ?? ""; description = null; } final info = WebInfo( title: title, icon: _analyzeIcon(document, uri), - description: description, + description: description ?? "", image: _analyzeImage(document, uri), - redirectUrl: response.request.url.toString(), + redirectUrl: response.request?.url.toString(), ); return info; } @@ -286,17 +299,17 @@ class WebAnalyzer { html = html.replaceFirst(_bodyReg, ""); final matchs = _metaReg.allMatches(html); final StringBuffer head = StringBuffer(""); - if (matchs != null) { - matchs.forEach((element) { - final String str = element.group(0); + matchs.forEach((element) { + final String? str = element.group(0); + if (str != null) { if (str.contains(_titleReg)) head.writeln(str); - }); - } + } + }); head.writeln(""); return head.toString(); } - static InfoBase _analyzeGif(Document document, Uri uri) { + static InfoBase? _analyzeGif(html_dom.Document document, Uri uri) { if (_getMetaContent(document, "property", "og:image:type") == "image/gif") { final gif = _getMetaContent(document, "property", "og:image"); if (gif != null) return WebImageInfo(image: _handleUrl(uri, gif)); @@ -304,33 +317,33 @@ class WebAnalyzer { return null; } - static InfoBase _analyzeVideo(Document document, Uri uri) { + static InfoBase? _analyzeVideo(html_dom.Document document, Uri uri) { final video = _getMetaContent(document, "property", "og:video"); if (video != null) return WebVideoInfo(image: _handleUrl(uri, video)); return null; } - static String _getMetaContent( - Document document, String property, String propertyValue) { - final meta = document.head.getElementsByTagName("meta"); - final ele = meta.firstWhere((e) => e.attributes[property] == propertyValue, - orElse: () => null); + static String? _getMetaContent( + html_dom.Document document, String property, String propertyValue) { + final meta = document.head?.getElementsByTagName("meta"); + final html_dom.Element? ele = + meta?.firstWhereOrNull((e) => e.attributes[property] == propertyValue); if (ele != null) return ele.attributes["content"]?.trim(); return null; } - static String _analyzeTitle(Document document) { + static String _analyzeTitle(html_dom.Document document) { final title = _getMetaContent(document, "property", "og:title"); if (title != null) return title; - final list = document.head.getElementsByTagName("title"); - if (list.isNotEmpty) { - final tagTitle = list.first.text; + final list = document.head?.getElementsByTagName("title"); + if (list != null && list.isNotEmpty) { + final tagTitle = list.isNotEmpty ? list.first.text : null; if (tagTitle != null) return tagTitle.trim(); } return ""; } - static String _analyzeDescription(Document document, String html) { + static String? _analyzeDescription(html_dom.Document document, String html) { final desc = _getMetaContent(document, "property", "og:description"); if (desc != null) return desc; @@ -350,31 +363,34 @@ class WebAnalyzer { return description; } - static String _analyzeIcon(Document document, Uri uri) { - final meta = document.head.getElementsByTagName("link"); - String icon = ""; + static String _analyzeIcon(html_dom.Document document, Uri uri) { + final meta = document.head?.getElementsByTagName("link"); + String? icon = ""; + html_dom.Element? metaIcon; // get icon first - var metaIcon = meta.firstWhere((e) { - final rel = (e.attributes["rel"] ?? "").toLowerCase(); - if (rel == "icon") { - icon = e.attributes["href"]; - if (icon != null && !icon.toLowerCase().contains(".svg")) { - return true; + if (meta != null) { + metaIcon = meta.firstWhereOrNull((e) { + final rel = (e.attributes["rel"] ?? "").toLowerCase(); + if (rel == "icon") { + icon = e.attributes["href"]; + if (icon != null && !icon!.toLowerCase().contains(".svg")) { + return true; + } } - } - return false; - }, orElse: () => null); - - metaIcon ??= meta.firstWhere((e) { - final rel = (e.attributes["rel"] ?? "").toLowerCase(); - if (rel == "shortcut icon") { - icon = e.attributes["href"]; - if (icon != null && !icon.toLowerCase().contains(".svg")) { - return true; + return false; + }); + + metaIcon ??= meta.firstWhereOrNull((e) { + final rel = (e.attributes["rel"] ?? "").toLowerCase(); + if (rel == "shortcut icon") { + icon = e.attributes["href"]; + if (icon != null && !icon!.toLowerCase().contains(".svg")) { + return true; + } } - } - return false; - }, orElse: () => null); + return false; + }); + } if (metaIcon != null) { icon = metaIcon.attributes["href"]; @@ -382,12 +398,12 @@ class WebAnalyzer { return "${uri.origin}/favicon.ico"; } - return _handleUrl(uri, icon); + return _handleUrl(uri, icon ?? ""); } - static String _analyzeImage(Document document, Uri uri) { + static String _analyzeImage(html_dom.Document document, Uri uri) { final image = _getMetaContent(document, "property", "og:image"); - return _handleUrl(uri, image); + return _handleUrl(uri, image ?? ""); } static String _handleUrl(Uri uri, String source) { @@ -405,3 +421,13 @@ class WebAnalyzer { return source; } } + +// https://github.com/dart-lang/sdk/issues/42947#issuecomment-642308224 +extension FirstWhereOrNullExtension on Iterable { + E? firstWhereOrNull(bool Function(E) test) { + for (final E element in this) { + if (test(element)) return element; + } + return null; + } +} diff --git a/pubspec.lock b/pubspec.lock index 3ba8f4a..b41a153 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,88 +1,88 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.2.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.15.0" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.17.0" + fast_gbk: + dependency: "direct main" + description: + name: fast_gbk + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - gbk2utf8: - dependency: "direct main" - description: - name: gbk2utf8 - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" html: dependency: "direct main" description: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+3" + version: "0.15.0" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.1" + version: "0.13.4" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.7.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.0" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -94,34 +94,34 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.1" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" + dart: ">=2.14.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 281d517..d10c750 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,14 +4,14 @@ version: 1.5.6 homepage: https://github.com/yungzhu/flutter_link_preview environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: - flutter: - sdk: flutter - http: ^0.12.1 - html: ^0.14.0+3 - gbk2utf8: ^1.0.1 + flutter: + sdk: flutter + fast_gbk: ^1.0.0 + html: ^0.15.0 + http: ^0.13.4 dev_dependencies: @@ -20,32 +20,32 @@ dev_dependencies: # The following section is specific to Flutter. flutter: - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages +# To add assets to your package, add an assets section, like this: +# assets: +# - images/a_dot_burr.jpeg +# - images/a_dot_ham.jpeg +# +# For details regarding assets in packages, see +# https://flutter.dev/assets-and-images/#from-packages +# +# An image asset can refer to one or more resolution-specific "variants", see +# https://flutter.dev/assets-and-images/#resolution-aware. +# To add custom fonts to your package, add a fonts section here, +# in this "flutter" section. Each entry in this list should have a +# "family" key with the font family name, and a "fonts" key with a +# list giving the asset and other descriptors for the font. For +# example: +# fonts: +# - family: Schyler +# fonts: +# - asset: fonts/Schyler-Regular.ttf +# - asset: fonts/Schyler-Italic.ttf +# style: italic +# - family: Trajan Pro +# fonts: +# - asset: fonts/TrajanPro.ttf +# - asset: fonts/TrajanPro_Bold.ttf +# weight: 700 +# +# For details regarding fonts in packages, see +# https://flutter.dev/custom-fonts/#from-packages