diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart
new file mode 100644
index 00000000..6733d7cb
--- /dev/null
+++ b/lib/common/widgets/html_render.dart
@@ -0,0 +1,136 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:get/get.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_html/flutter_html.dart';
+
+// ignore: must_be_immutable
+class HtmlRender extends StatelessWidget {
+ String? htmlContent;
+ final int? imgCount;
+ final List? imgList;
+
+ HtmlRender({
+ this.htmlContent,
+ this.imgCount,
+ this.imgList,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Html(
+ data: htmlContent,
+ // tagsList: Html.tags..addAll(["form", "label", "input"]),
+ onLinkTap: (url, buildContext, attributes) => {},
+ extensions: [
+ TagExtension(
+ tagsToExtend: {"img"},
+ builder: (extensionContext) {
+ String? imgUrl = extensionContext.attributes['src'];
+ if (imgUrl!.startsWith('//')) {
+ imgUrl = 'https:$imgUrl';
+ }
+ if (imgUrl.startsWith('http://')) {
+ imgUrl = imgUrl.replaceAll('http://', 'https://');
+ }
+
+ print(imgUrl);
+ bool isEmote = imgUrl.contains('/emote/');
+ bool isMall = imgUrl.contains('/mall/');
+ if (isMall) {
+ return SizedBox();
+ }
+ // bool inTable =
+ // extensionContext.element!.previousElementSibling == null ||
+ // extensionContext.element!.nextElementSibling == null;
+ // imgUrl = Utils().imageUrl(imgUrl!);
+ return Image.network(
+ imgUrl,
+ width: isEmote ? 22 : null,
+ height: isEmote ? 22 : null,
+ );
+ },
+ ),
+ ],
+ style: {
+ "html": Style(
+ fontSize: FontSize.medium,
+ lineHeight: LineHeight.percent(140),
+ ),
+ "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero),
+ "a": Style(
+ color: Theme.of(context).colorScheme.primary,
+ textDecoration: TextDecoration.none,
+ ),
+ "p": Style(
+ margin: Margins.only(bottom: 0),
+ ),
+ "span": Style(
+ fontSize: FontSize.medium,
+ ),
+ "li > p": Style(
+ display: Display.inline,
+ ),
+ "li": Style(
+ padding: HtmlPaddings.only(bottom: 4),
+ textAlign: TextAlign.justify,
+ ),
+ "image": Style(margin: Margins.only(top: 4, bottom: 4)),
+ "p > img": Style(margin: Margins.only(top: 4, bottom: 4)),
+ "code": Style(
+ backgroundColor: Theme.of(context).colorScheme.onInverseSurface),
+ "code > span": Style(textAlign: TextAlign.start),
+ "hr": Style(
+ margin: Margins.zero,
+ padding: HtmlPaddings.zero,
+ border: Border(
+ top: BorderSide(
+ width: 1.0,
+ color:
+ Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
+ ),
+ ),
+ ),
+ 'table': Style(
+ border: Border(
+ right: BorderSide(
+ width: 0.5,
+ color:
+ Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
+ ),
+ bottom: BorderSide(
+ width: 0.5,
+ color:
+ Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
+ ),
+ ),
+ ),
+ 'tr': Style(
+ border: Border(
+ top: BorderSide(
+ width: 1.0,
+ color:
+ Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
+ ),
+ left: BorderSide(
+ width: 1.0,
+ color:
+ Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
+ ),
+ ),
+ ),
+ 'thead': Style(
+ backgroundColor: Theme.of(context).colorScheme.background,
+ ),
+ 'th': Style(
+ padding: HtmlPaddings.only(left: 3, right: 3),
+ ),
+ 'td': Style(
+ padding: HtmlPaddings.all(4.0),
+ alignment: Alignment.center,
+ textAlign: TextAlign.center,
+ ),
+ },
+ );
+ }
+}
diff --git a/lib/http/html.dart b/lib/http/html.dart
new file mode 100644
index 00000000..ec92bcfa
--- /dev/null
+++ b/lib/http/html.dart
@@ -0,0 +1,40 @@
+import 'package:html/dom.dart';
+import 'package:html/parser.dart';
+import 'package:pilipala/http/index.dart';
+
+class HtmlHttp {
+ static Future reqHtml(id) async {
+ var response = await Request().get("https://www.bilibili.com/opus/$id");
+ Document rootTree = parse(response.data);
+ Element body = rootTree.body!;
+ Element appDom = body.querySelector('#app')!;
+ Element authorHeader = appDom.querySelector('.fixed-author-header')!;
+ // 头像
+ String avatar = authorHeader.querySelector('img')!.attributes['src']!;
+ avatar = 'https:${avatar.split('@')[0]}';
+ String uname =
+ authorHeader.querySelector('.fixed-author-header__author__name')!.text;
+ // 动态详情
+ Element opusDetail = appDom.querySelector('.opus-detail')!;
+ // 发布时间
+ String updateTime =
+ opusDetail.querySelector('.opus-module-author__pub__text')!.text;
+ //
+ String opusContent =
+ opusDetail.querySelector('.opus-module-content')!.innerHtml;
+ String commentId = opusDetail
+ .querySelector('.bili-comment-container')!
+ .className
+ .split(' ')[1]
+ .split('-')[2];
+ // List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
+ return {
+ 'status': true,
+ 'avatar': avatar,
+ 'uname': uname,
+ 'updateTime': updateTime,
+ 'content': opusContent,
+ 'commentId': commentId
+ };
+ }
+}
diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart
new file mode 100644
index 00000000..2ec55621
--- /dev/null
+++ b/lib/pages/html/controller.dart
@@ -0,0 +1,19 @@
+import 'package:get/get.dart';
+import 'package:pilipala/http/html.dart';
+
+class HtmlRenderController extends GetxController {
+ late String id;
+ late Map response;
+
+ @override
+ void onInit() {
+ super.onInit();
+ id = Get.parameters['id']!;
+ }
+
+ Future reqHtml() async {
+ var res = await HtmlHttp.reqHtml(id);
+ response = res;
+ return res;
+ }
+}
diff --git a/lib/pages/html/index.dart b/lib/pages/html/index.dart
new file mode 100644
index 00000000..c62e60b7
--- /dev/null
+++ b/lib/pages/html/index.dart
@@ -0,0 +1,4 @@
+library html_render;
+
+export './controller.dart';
+export './view.dart';
diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart
new file mode 100644
index 00000000..f8572b14
--- /dev/null
+++ b/lib/pages/html/view.dart
@@ -0,0 +1,117 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:pilipala/common/widgets/html_render.dart';
+import 'package:pilipala/common/widgets/network_img_layer.dart';
+
+import 'controller.dart';
+
+class HtmlRenderPage extends StatefulWidget {
+ const HtmlRenderPage({super.key});
+
+ @override
+ State createState() => _HtmlRenderPageState();
+}
+
+class _HtmlRenderPageState extends State {
+ HtmlRenderController htmlRenderCtr = Get.put(HtmlRenderController());
+ late String title;
+ late String id;
+
+ @override
+ void initState() {
+ super.initState();
+ title = Get.parameters['title']!;
+ id = Get.parameters['id']!;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ centerTitle: false,
+ title: Text(title),
+ ),
+ body: SingleChildScrollView(
+ child: Column(
+ children: [
+ FutureBuilder(
+ future: htmlRenderCtr.reqHtml(),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done) {
+ var data = snapshot.data;
+ if (data['status']) {
+ return Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
+ child: Row(
+ children: [
+ NetworkImgLayer(
+ width: 40,
+ height: 40,
+ type: 'avatar',
+ src: htmlRenderCtr.response['avatar']!,
+ ),
+ const SizedBox(width: 10),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(htmlRenderCtr.response['uname'],
+ style: TextStyle(
+ fontSize: Theme.of(context)
+ .textTheme
+ .titleSmall!
+ .fontSize,
+ )),
+ Text(
+ htmlRenderCtr.response['updateTime'],
+ style: TextStyle(
+ color:
+ Theme.of(context).colorScheme.outline,
+ fontSize: Theme.of(context)
+ .textTheme
+ .labelSmall!
+ .fontSize,
+ ),
+ ),
+ ],
+ ),
+ const Spacer(),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
+ child: HtmlRender(
+ htmlContent: htmlRenderCtr.response['content'],
+ ),
+ ),
+ Container(
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(
+ width: 8,
+ color: Theme.of(context)
+ .dividerColor
+ .withOpacity(0.05),
+ ),
+ ),
+ ),
+ ),
+ ],
+ );
+ } else {
+ return Text('error');
+ }
+ } else {
+ // 骨架屏
+ return const SizedBox();
+ }
+ },
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart
index 7edb435b..03088ca4 100644
--- a/lib/router/app_pages.dart
+++ b/lib/router/app_pages.dart
@@ -14,6 +14,7 @@ import 'package:pilipala/pages/follow/index.dart';
import 'package:pilipala/pages/history/index.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/hot/index.dart';
+import 'package:pilipala/pages/html/index.dart';
import 'package:pilipala/pages/later/index.dart';
import 'package:pilipala/pages/liveRoom/view.dart';
import 'package:pilipala/pages/member/index.dart';
@@ -107,6 +108,8 @@ class Routes {
name: '/displayModeSetting', page: () => const SetDiaplayMode()),
// 关于
CustomGetPage(name: '/about', page: () => const AboutPage()),
+ //
+ CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
];
}
diff --git a/pubspec.lock b/pubspec.lock
index d30fa01b..8d83afb9 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -257,6 +257,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
+ csslib:
+ dependency: transitive
+ description:
+ name: csslib
+ sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.17.3"
cupertino_icons:
dependency: "direct main"
description:
@@ -454,6 +462,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
+ flutter_html:
+ dependency: "direct main"
+ description:
+ name: flutter_html
+ sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0-beta.2"
flutter_launcher_icons:
dependency: "direct dev"
description:
@@ -581,6 +597,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
+ html:
+ dependency: "direct main"
+ description:
+ name: html
+ sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.15.4"
http:
dependency: transitive
description:
@@ -669,6 +693,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
+ list_counter:
+ dependency: transitive
+ description:
+ name: list_counter
+ sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.2"
loading_more_list:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e32da85c..a9e48867 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -119,6 +119,11 @@ dependencies:
system_proxy: ^0.1.0
# pip
floating: ^2.0.1
+ # html解析
+ html: ^0.15.4
+ # html渲染
+ flutter_html: ^3.0.0-beta.2
+
dev_dependencies:
flutter_test: