diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart
index 6733d7cb..2e97ceed 100644
--- a/lib/common/widgets/html_render.dart
+++ b/lib/common/widgets/html_render.dart
@@ -1,7 +1,7 @@
-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';
+import 'package:pilipala/common/widgets/network_img_layer.dart';
// ignore: must_be_immutable
class HtmlRender extends StatelessWidget {
@@ -20,35 +20,47 @@ class HtmlRender extends StatelessWidget {
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';
+ try {
+ Map attributes = extensionContext.attributes;
+ List key = attributes.keys.toList();
+ String? imgUrl = key.contains('src')
+ ? attributes['src']
+ : attributes['data-src'];
+ if (imgUrl!.startsWith('//')) {
+ imgUrl = 'https:$imgUrl';
+ }
+ if (imgUrl.startsWith('http://')) {
+ imgUrl = imgUrl.replaceAll('http://', 'https://');
+ }
+ imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
+ bool isEmote = imgUrl.contains('/emote/');
+ bool isMall = imgUrl.contains('/mall/');
+ if (isMall) {
+ return const 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,
+ // );
+ return NetworkImgLayer(
+ width: isEmote ? 22 : Get.size.width - 24,
+ height: isEmote ? 22 : 200,
+ src: imgUrl,
+ );
+ } catch (err) {
+ print(err);
+ return const SizedBox();
}
- 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,
- );
},
),
],
@@ -63,11 +75,13 @@ class HtmlRender extends StatelessWidget {
textDecoration: TextDecoration.none,
),
"p": Style(
- margin: Margins.only(bottom: 0),
+ margin: Margins.only(bottom: 10),
),
"span": Style(
fontSize: FontSize.medium,
+ height: Height(1.65),
),
+ "div": Style(height: Height.auto()),
"li > p": Style(
display: Display.inline,
),
@@ -75,61 +89,7 @@ class HtmlRender extends StatelessWidget {
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,
- ),
+ "img": Style(margin: Margins.only(top: 4, bottom: 4)),
},
);
}
diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart
index cc23f110..9e34cbcb 100644
--- a/lib/common/widgets/video_card_v.dart
+++ b/lib/common/widgets/video_card_v.dart
@@ -62,6 +62,15 @@ class VideoCardV extends StatelessWidget {
'heroTag': heroTag,
});
break;
+ // 动态
+ case 'picture':
+ Get.toNamed('/htmlRender', parameters: {
+ 'url': videoItem.uri,
+ 'title': videoItem.title,
+ 'id': videoItem.param.toString(),
+ 'dynamicType': 'picture'
+ });
+ break;
default:
SmartDialog.showToast(videoItem.goto);
Get.toNamed(
diff --git a/lib/http/html.dart b/lib/http/html.dart
index ec92bcfa..4a922e8f 100644
--- a/lib/http/html.dart
+++ b/lib/http/html.dart
@@ -3,8 +3,12 @@ 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");
+ // article
+ static Future reqHtml(id, dynamicType) async {
+ var response = await Request().get(
+ "https://www.bilibili.com/opus/$id",
+ extra: {'ua': 'pc'},
+ );
Document rootTree = parse(response.data);
Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!;
@@ -34,7 +38,46 @@ class HtmlHttp {
'uname': uname,
'updateTime': updateTime,
'content': opusContent,
- 'commentId': commentId
+ 'commentId': int.parse(commentId)
+ };
+ }
+
+ // read
+ static Future reqReadHtml(id, dynamicType) async {
+ var response = await Request().get(
+ "https://www.bilibili.com/$dynamicType/$id/",
+ extra: {'ua': 'pc'},
+ );
+ Document rootTree = parse(response.data);
+ Element body = rootTree.body!;
+ Element appDom = body.querySelector('#app')!;
+ Element authorHeader = appDom.querySelector('.up-left')!;
+ // 头像
+ // String avatar =
+ // authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
+ // print(avatar);
+ // avatar = 'https:${avatar.split('@')[0]}';
+ String uname = authorHeader.querySelector('.up-name')!.text.trim();
+ // 动态详情
+ Element opusDetail = appDom.querySelector('.article-content')!;
+ // 发布时间
+ // String updateTime =
+ // opusDetail.querySelector('.opus-module-author__pub__text')!.text;
+ // print(updateTime);
+
+ //
+ String opusContent =
+ opusDetail.querySelector('#read-article-holder')!.innerHtml;
+ RegExp digitRegExp = RegExp(r'\d+');
+ Iterable matches = digitRegExp.allMatches(id);
+ String number = matches.first.group(0)!;
+ return {
+ 'status': true,
+ 'avatar': '',
+ 'uname': uname,
+ 'updateTime': '',
+ 'content': opusContent,
+ 'commentId': int.parse(number)
};
}
}
diff --git a/lib/http/init.dart b/lib/http/init.dart
index f927e2e5..4af20cae 100644
--- a/lib/http/init.dart
+++ b/lib/http/init.dart
@@ -130,11 +130,19 @@ class Request {
Response response;
Options options;
ResponseType resType = ResponseType.json;
- if (extra != null) {
- resType = extra!['resType'] ?? ResponseType.json;
- }
+
options = Options();
options.responseType = resType;
+
+ if (extra != null) {
+ options.responseType = extra!['resType'] ?? ResponseType.json;
+ if (extra['ua'] != null) {
+ print(options.headers);
+ options.headers = {'user-agent': headerUa(type: extra['ua'])};
+ // options.headers!['user-agent'] = headerUa(type: extra['ua']);
+ }
+ }
+
try {
response = await dio.get(
url,
@@ -201,14 +209,19 @@ class Request {
token.cancel("cancelled");
}
- String headerUa() {
+ String headerUa({type = 'mob'}) {
String headerUa = '';
- if (Platform.isIOS) {
- headerUa =
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1';
+ if (type == 'mob') {
+ if (Platform.isIOS) {
+ headerUa =
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1';
+ } else {
+ headerUa =
+ 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
+ }
} else {
headerUa =
- 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15';
}
return headerUa;
}
diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart
index 5b524510..26ba2b22 100644
--- a/lib/pages/dynamics/controller.dart
+++ b/lib/pages/dynamics/controller.dart
@@ -149,10 +149,30 @@ class DynamicsController extends GetxController {
case 'DYNAMIC_TYPE_ARTICLE':
String title = item.modules.moduleDynamic.major.opus.title;
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
- Get.toNamed(
- '/webview',
- parameters: {'url': 'https:$url', 'type': 'note', 'pageTitle': title},
- );
+ if (url.contains('opus') || url.contains('read')) {
+ RegExp digitRegExp = RegExp(r'\d+');
+ Iterable matches = digitRegExp.allMatches(url);
+ String number = matches.first.group(0)!;
+ if (url.contains('read')) {
+ number = 'cv$number';
+ }
+ Get.toNamed('/htmlRender', parameters: {
+ 'url': url.startsWith('//') ? url.split('//').last : url,
+ 'title': title,
+ 'id': number,
+ 'dynamicType': url.split('//').last.split('/')[1]
+ });
+ } else {
+ Get.toNamed(
+ '/webview',
+ parameters: {
+ 'url': 'https:$url',
+ 'type': 'note',
+ 'pageTitle': title
+ },
+ );
+ }
+
break;
case 'DYNAMIC_TYPE_PGC':
print('番剧');
diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart
index 61422ceb..27820b27 100644
--- a/lib/pages/dynamics/widgets/rich_node_panel.dart
+++ b/lib/pages/dynamics/widgets/rich_node_panel.dart
@@ -225,8 +225,10 @@ InlineSpan richNode(item, context) {
width: box.maxWidth / 2,
height: box.maxWidth *
0.5 *
- pictureItem.height! /
- pictureItem.width!,
+ (pictureItem.height != null &&
+ pictureItem.width != null
+ ? pictureItem.height! / pictureItem.width!
+ : 1),
),
),
);
diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart
index 2ec55621..f3187828 100644
--- a/lib/pages/html/controller.dart
+++ b/lib/pages/html/controller.dart
@@ -1,19 +1,112 @@
+import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:hive/hive.dart';
import 'package:pilipala/http/html.dart';
+import 'package:pilipala/http/reply.dart';
+import 'package:pilipala/models/common/reply_sort_type.dart';
+import 'package:pilipala/models/video/reply/item.dart';
+import 'package:pilipala/utils/feed_back.dart';
+import 'package:pilipala/utils/storage.dart';
class HtmlRenderController extends GetxController {
late String id;
+ late String dynamicType;
+ late int type;
+ RxInt oid = (-1).obs;
late Map response;
+ int? floor;
+ int currentPage = 0;
+ bool isLoadingMore = false;
+ RxString noMore = ''.obs;
+ RxList replyList = [].obs;
+ RxInt acount = 0.obs;
+ final ScrollController scrollController = ScrollController();
+
+ ReplySortType _sortType = ReplySortType.time;
+ RxString sortTypeTitle = ReplySortType.time.titles.obs;
+ RxString sortTypeLabel = ReplySortType.time.labels.obs;
+ Box setting = GStrorage.setting;
@override
void onInit() {
super.onInit();
id = Get.parameters['id']!;
+ dynamicType = Get.parameters['dynamicType']!;
+ type = dynamicType == 'picture' ? 11 : 12;
}
- Future reqHtml() async {
- var res = await HtmlHttp.reqHtml(id);
+ // 请求动态内容
+ Future reqHtml(id) async {
+ late dynamic res;
+ if (dynamicType == 'opus' || dynamicType == 'picture') {
+ res = await HtmlHttp.reqHtml(id, dynamicType);
+ } else {
+ res = await HtmlHttp.reqReadHtml(id, dynamicType);
+ }
response = res;
+ oid.value = res['commentId'];
return res;
}
+
+ // 请求评论
+ Future queryReplyList({reqType = 'init'}) async {
+ var res = await ReplyHttp.replyList(
+ oid: oid.value,
+ pageNum: currentPage + 1,
+ type: type,
+ sort: _sortType.index,
+ );
+ if (res['status']) {
+ List replies = res['data'].replies;
+ acount.value = res['data'].page.acount;
+ if (replies.isNotEmpty) {
+ currentPage++;
+ noMore.value = '加载中...';
+ if (replies.length < 20) {
+ noMore.value = '没有更多了';
+ }
+ } else {
+ noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
+ }
+ if (reqType == 'init') {
+ // 添加置顶回复
+ if (res['data'].upper.top != null) {
+ bool flag = res['data']
+ .topReplies
+ .any((reply) => reply.rpid == res['data'].upper.top.rpid);
+ if (!flag) {
+ replies.insert(0, res['data'].upper.top);
+ }
+ }
+ replies.insertAll(0, res['data'].topReplies);
+ replyList.value = replies;
+ } else {
+ replyList.addAll(replies);
+ }
+ }
+ isLoadingMore = false;
+ return res;
+ }
+
+ // 排序搜索评论
+ queryBySort() {
+ feedBack();
+ switch (_sortType) {
+ case ReplySortType.time:
+ _sortType = ReplySortType.like;
+ break;
+ case ReplySortType.like:
+ _sortType = ReplySortType.reply;
+ break;
+ case ReplySortType.reply:
+ _sortType = ReplySortType.time;
+ break;
+ default:
+ }
+ sortTypeTitle.value = _sortType.titles;
+ sortTypeLabel.value = _sortType.labels;
+ currentPage = 0;
+ replyList.clear();
+ queryReplyList(reqType: 'init');
+ }
}
diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart
index f8572b14..d5e4556d 100644
--- a/lib/pages/html/view.dart
+++ b/lib/pages/html/view.dart
@@ -1,7 +1,19 @@
+import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
+import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/html_render.dart';
+import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
+import 'package:pilipala/models/common/reply_type.dart';
+import 'package:pilipala/pages/mine/index.dart';
+import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
+import 'package:pilipala/pages/video/detail/replyNew/index.dart';
+import 'package:pilipala/pages/video/detail/replyReply/index.dart';
+import 'package:pilipala/utils/feed_back.dart';
import 'controller.dart';
@@ -12,16 +24,104 @@ class HtmlRenderPage extends StatefulWidget {
State createState() => _HtmlRenderPageState();
}
-class _HtmlRenderPageState extends State {
- HtmlRenderController htmlRenderCtr = Get.put(HtmlRenderController());
+class _HtmlRenderPageState extends State
+ with TickerProviderStateMixin {
+ final HtmlRenderController _htmlRenderCtr = Get.put(HtmlRenderController());
late String title;
late String id;
+ late String url;
+ late String dynamicType;
+ late int type;
+ bool _isFabVisible = true;
+ late Future _futureBuilderFuture;
+ late ScrollController scrollController;
+ late AnimationController fabAnimationCtr;
@override
void initState() {
super.initState();
title = Get.parameters['title']!;
id = Get.parameters['id']!;
+ url = Get.parameters['url']!;
+ dynamicType = Get.parameters['dynamicType']!;
+ type = dynamicType == 'picture' ? 11 : 12;
+ _futureBuilderFuture = _htmlRenderCtr.reqHtml(id);
+ fabAnimationCtr = AnimationController(
+ vsync: this,
+ duration: const Duration(milliseconds: 300),
+ );
+ scrollListener();
+ }
+
+ void scrollListener() {
+ scrollController = _htmlRenderCtr.scrollController;
+ scrollController.addListener(
+ () {
+ // 分页加载
+ if (scrollController.position.pixels >=
+ scrollController.position.maxScrollExtent - 300) {
+ EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
+ _htmlRenderCtr.queryReplyList(reqType: 'onLoad');
+ });
+ }
+
+ // 标题
+ // if (scrollController.offset > 55 && !_visibleTitle) {
+ // _visibleTitle = true;
+ // titleStreamC.add(true);
+ // } else if (scrollController.offset <= 55 && _visibleTitle) {
+ // _visibleTitle = false;
+ // titleStreamC.add(false);
+ // }
+
+ // fab按钮
+ final ScrollDirection direction =
+ scrollController.position.userScrollDirection;
+ if (direction == ScrollDirection.forward) {
+ _showFab();
+ } else if (direction == ScrollDirection.reverse) {
+ _hideFab();
+ }
+ },
+ );
+ }
+
+ void _showFab() {
+ if (!_isFabVisible) {
+ _isFabVisible = true;
+ fabAnimationCtr.forward();
+ }
+ }
+
+ void _hideFab() {
+ if (_isFabVisible) {
+ _isFabVisible = false;
+ fabAnimationCtr.reverse();
+ }
+ }
+
+ void replyReply(replyItem) {
+ int oid = replyItem.oid;
+ int rpid = replyItem.rpid!;
+ Get.to(
+ () => Scaffold(
+ appBar: AppBar(
+ titleSpacing: 0,
+ centerTitle: false,
+ title: Text(
+ '评论详情',
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ ),
+ body: VideoReplyReplyPanel(
+ oid: oid,
+ rpid: rpid,
+ source: 'dynamic',
+ replyType: ReplyType.values[type],
+ firstFloor: replyItem,
+ ),
+ ),
+ );
}
@override
@@ -29,88 +129,328 @@ class _HtmlRenderPageState extends State {
return Scaffold(
appBar: AppBar(
centerTitle: false,
- title: Text(title),
+ titleSpacing: 0,
+ title: Text(
+ title,
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ actions: [
+ const SizedBox(width: 4),
+ IconButton(
+ onPressed: () {
+ Get.toNamed('/webview', parameters: {
+ 'url': 'https:$url',
+ 'type': 'url',
+ 'pageTitle': title,
+ });
+ },
+ icon: const Icon(Icons.open_in_browser_outlined, size: 19),
+ ),
+ PopupMenuButton(
+ icon: const Icon(Icons.more_vert),
+ itemBuilder: (BuildContext context) => [
+ PopupMenuItem(
+ onTap: () => {
+ Clipboard.setData(ClipboardData(text: url)),
+ SmartDialog.showToast('已复制'),
+ },
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(Icons.copy_rounded, size: 19),
+ SizedBox(width: 10),
+ Text('复制链接'),
+ ],
+ ),
+ ),
+ PopupMenuItem(
+ onTap: () => {},
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(Icons.share_outlined, size: 19),
+ SizedBox(width: 10),
+ Text('分享'),
+ ],
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(width: 6)
+ ],
),
- 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,
+ body: Stack(
+ children: [
+ SingleChildScrollView(
+ controller: scrollController,
+ child: Column(
+ children: [
+ FutureBuilder(
+ future: _futureBuilderFuture,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done) {
+ var data = snapshot.data;
+ fabAnimationCtr.forward();
+ if (data['status']) {
+ return Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
+ child: Row(
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,
- ),
+ 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(),
],
),
- const Spacer(),
- ],
- ),
- ),
- Padding(
- padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
- child: HtmlRender(
- htmlContent: htmlRenderCtr.response['content'],
- ),
- ),
- Container(
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(12, 8, 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 const Text('error');
+ }
+ } else {
+ // 骨架屏
+ return const SizedBox();
+ }
+ },
+ ),
+ Obx(
+ () => _htmlRenderCtr.oid.value != -1
+ ? Container(
decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
border: Border(
- bottom: BorderSide(
- width: 8,
+ top: BorderSide(
+ width: 0.6,
color: Theme.of(context)
.dividerColor
.withOpacity(0.05),
),
),
),
- ),
- ],
- );
- } else {
- return Text('error');
- }
- } else {
- // 骨架屏
- return const SizedBox();
- }
- },
+ height: 45,
+ padding: const EdgeInsets.only(left: 12, right: 6),
+ child: Row(
+ children: [
+ const Text('回复'),
+ const Spacer(),
+ SizedBox(
+ height: 35,
+ child: TextButton.icon(
+ onPressed: () => _htmlRenderCtr.queryBySort(),
+ icon: const Icon(Icons.sort, size: 16),
+ label: Obx(
+ () => Text(
+ _htmlRenderCtr.sortTypeLabel.value,
+ style: const TextStyle(fontSize: 13),
+ ),
+ ),
+ ),
+ )
+ ],
+ ),
+ )
+ : const SizedBox(),
+ ),
+ Obx(
+ () => _htmlRenderCtr.oid.value != -1
+ ? FutureBuilder(
+ future: _htmlRenderCtr.queryReplyList(),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState ==
+ ConnectionState.done) {
+ Map data = snapshot.data as Map;
+ if (snapshot.data['status']) {
+ // 请求成功
+ return Obx(
+ () => _htmlRenderCtr.replyList.isEmpty &&
+ _htmlRenderCtr.isLoadingMore
+ ? ListView.builder(
+ itemCount: 5,
+ shrinkWrap: true,
+ physics:
+ const NeverScrollableScrollPhysics(),
+ itemBuilder: (context, index) {
+ return const VideoReplySkeleton();
+ },
+ )
+ : ListView.builder(
+ shrinkWrap: true,
+ physics:
+ const NeverScrollableScrollPhysics(),
+ itemCount:
+ _htmlRenderCtr.replyList.length +
+ 1,
+ itemBuilder: (context, index) {
+ if (index ==
+ _htmlRenderCtr
+ .replyList.length) {
+ return Container(
+ padding: EdgeInsets.only(
+ bottom:
+ MediaQuery.of(context)
+ .padding
+ .bottom),
+ height: MediaQuery.of(context)
+ .padding
+ .bottom +
+ 100,
+ child: Center(
+ child: Obx(
+ () => Text(
+ _htmlRenderCtr
+ .noMore.value,
+ style: TextStyle(
+ fontSize: 12,
+ color: Theme.of(context)
+ .colorScheme
+ .outline,
+ ),
+ ),
+ ),
+ ),
+ );
+ } else {
+ return ReplyItem(
+ replyItem: _htmlRenderCtr
+ .replyList[index],
+ showReplyRow: true,
+ replyLevel: '1',
+ replyReply: (replyItem) =>
+ replyReply(replyItem),
+ replyType:
+ ReplyType.values[type],
+ addReply: (replyItem) {
+ _htmlRenderCtr
+ .replyList[index].replies!
+ .add(replyItem);
+ },
+ );
+ }
+ },
+ ),
+ );
+ } else {
+ // 请求错误
+ return CustomScrollView(
+ slivers: [
+ HttpError(
+ errMsg: data['msg'],
+ fn: () => setState(() {}),
+ )
+ ],
+ );
+ }
+ } else {
+ // 骨架屏
+ return ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: 5,
+ itemBuilder: (context, index) {
+ return const VideoReplySkeleton();
+ },
+ );
+ }
+ },
+ )
+ : const SizedBox(),
+ )
+ ],
),
- ],
- ),
+ ),
+ Positioned(
+ bottom: MediaQuery.of(context).padding.bottom + 14,
+ right: 14,
+ child: SlideTransition(
+ position: Tween(
+ begin: const Offset(0, 2),
+ end: const Offset(0, 0),
+ ).animate(CurvedAnimation(
+ parent: fabAnimationCtr,
+ curve: Curves.easeInOut,
+ )),
+ child: FloatingActionButton(
+ heroTag: null,
+ onPressed: () {
+ feedBack();
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ builder: (BuildContext context) {
+ return VideoReplyNewDialog(
+ oid: _htmlRenderCtr.oid.value,
+ root: 0,
+ parent: 0,
+ replyType: ReplyType.values[type],
+ );
+ },
+ ).then(
+ (value) => {
+ // 完成评论,数据添加
+ if (value != null && value['data'] != null)
+ {
+ _htmlRenderCtr.replyList.add(value['data']),
+ _htmlRenderCtr.acount.value++
+ }
+ },
+ );
+ },
+ tooltip: '评论动态',
+ child: const Icon(Icons.reply),
+ ),
+ ),
+ ),
+ ],
),
);
}