From be29f70b30bcf72b2274a42e3b56ef09ffc9b28f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 19 Oct 2024 18:16:04 +0800 Subject: [PATCH] faat: message to comment --- lib/models/video/reply/data.dart | 5 + lib/pages/message/like/view.dart | 33 +-- lib/pages/message/reply/view.dart | 28 +-- lib/pages/message/utils/index.dart | 89 +++++++ .../video/detail/reply_reply/controller.dart | 9 +- lib/pages/video/detail/reply_reply/view.dart | 219 +++++++++--------- lib/utils/app_scheme.dart | 8 +- 7 files changed, 230 insertions(+), 161 deletions(-) create mode 100644 lib/pages/message/utils/index.dart diff --git a/lib/models/video/reply/data.dart b/lib/models/video/reply/data.dart index 6e069c50..69a13d88 100644 --- a/lib/models/video/reply/data.dart +++ b/lib/models/video/reply/data.dart @@ -101,6 +101,7 @@ class ReplyReplyData { this.page, this.config, this.replies, + this.root, this.topReplies, this.upper, }); @@ -108,6 +109,7 @@ class ReplyReplyData { ReplyPage? page; ReplyConfig? config; late List? replies; + ReplyItemModel? root; late List? topReplies; ReplyUpper? upper; @@ -120,6 +122,9 @@ class ReplyReplyData { (item) => ReplyItemModel.fromJson(item, json['upper']['mid'])) .toList() : []; + root = json['root'] != null + ? ReplyItemModel.fromJson(json['root'], false) + : null; topReplies = json['top_replies'] != null ? json['top_replies'] .map((item) => ReplyItemModel.fromJson( diff --git a/lib/pages/message/like/view.dart b/lib/pages/message/like/view.dart index 273e49f2..6a7ba8a4 100644 --- a/lib/pages/message/like/view.dart +++ b/lib/pages/message/like/view.dart @@ -1,13 +1,11 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; -import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/msg/like.dart'; import 'package:pilipala/utils/utils.dart'; - +import '../utils/index.dart'; import 'controller.dart'; class MessageLikePage extends StatefulWidget { @@ -122,39 +120,13 @@ class LikeItem extends StatelessWidget { final nickNameList = item.users!.map((e) => e.nickname).take(2).toList(); int usersLen = item.users!.length > 3 ? 3 : item.users!.length; final Uri uri = Uri.parse(item.item!.uri!); - final String path = uri.path; - final String bvid = path.split('/').last; /// bilibili:// final Uri nativeUri = Uri.parse(item.item!.nativeUri!); - final Map queryParameters = nativeUri.queryParameters; final String type = item.item!.type!; - // cid - final String? argCid = queryParameters['cid']; - // 页码 - final String? page = queryParameters['page']; - // 根评论id - final String? commentRootId = queryParameters['comment_root_id']; - // 二级评论id - final String? commentSecondaryId = queryParameters['comment_secondary_id']; - return InkWell( onTap: () async { - try { - final int cid = argCid != null - ? int.parse(argCid) - : await SearchHttp.ab2c(bvid: bvid); - final String heroTag = Utils.makeHeroTag(bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', - arguments: { - 'pic': '', - 'heroTag': heroTag, - }, - ); - } catch (e) { - SmartDialog.showToast('视频可能失效了$e'); - } + MessageUtils.onClickMessage(context, uri, nativeUri, type); }, child: Stack( children: [ @@ -243,6 +215,7 @@ class LikeItem extends StatelessWidget { width: 60, height: 60, src: item.item!.image, + radius: 6, ), ], ), diff --git a/lib/pages/message/reply/view.dart b/lib/pages/message/reply/view.dart index b9112bfc..63a4de4a 100644 --- a/lib/pages/message/reply/view.dart +++ b/lib/pages/message/reply/view.dart @@ -4,10 +4,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; -import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/msg/reply.dart'; +import 'package:pilipala/pages/message/utils/index.dart'; import 'package:pilipala/utils/utils.dart'; - import 'controller.dart'; class MessageReplyPage extends StatefulWidget { @@ -112,28 +111,14 @@ class ReplyItem extends StatelessWidget { Widget build(BuildContext context) { Color outline = Theme.of(context).colorScheme.outline; final String heroTag = Utils.makeHeroTag(item.user!.mid); - final String bvid = item.item!.uri!.split('/').last; - // 页码 - final String page = - item.item!.nativeUri!.split('page=').last.split('&').first; - // 根评论id - final String commentRootId = - item.item!.nativeUri!.split('comment_root_id=').last.split('&').first; - // 二级评论id - final String commentSecondaryId = - item.item!.nativeUri!.split('comment_secondary_id=').last; + final Uri uri = Uri.parse(item.item!.uri!); + /// bilibili:// + final Uri nativeUri = Uri.parse(item.item!.nativeUri!); + final String type = item.item!.type!; return InkWell( onTap: () async { - final int cid = await SearchHttp.ab2c(bvid: bvid); - final String heroTag = Utils.makeHeroTag(bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', - arguments: { - 'pic': '', - 'heroTag': heroTag, - }, - ); + MessageUtils.onClickMessage(context, uri, nativeUri, type); }, child: Padding( padding: const EdgeInsets.all(14), @@ -217,6 +202,7 @@ class ReplyItem extends StatelessWidget { width: 60, height: 60, src: item.item!.image, + radius: 6, ), ], ), diff --git a/lib/pages/message/utils/index.dart b/lib/pages/message/utils/index.dart new file mode 100644 index 00000000..6cfa3334 --- /dev/null +++ b/lib/pages/message/utils/index.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/models/common/reply_type.dart'; +import 'package:pilipala/pages/video/detail/reply_reply/index.dart'; +import 'package:pilipala/utils/app_scheme.dart'; +import 'package:pilipala/utils/utils.dart'; + +class MessageUtils { + // 回复我的、收到的赞点击 + static void onClickMessage( + BuildContext context, Uri uri, Uri nativeUri, String type) async { + final String path = uri.path; + final String bvid = path.split('/').last; + final String nativePath = nativeUri.path; + final String oid = nativePath.split('/').last; + final Map queryParameters = nativeUri.queryParameters; + final String? argCid = queryParameters['cid']; + // final String? page = queryParameters['page']; + final String? commentRootId = queryParameters['comment_root_id']; + // final String? commentSecondaryId = queryParameters['comment_secondary_id']; + switch (type) { + case 'video': + case 'danmu': + try { + final int cid = argCid != null + ? int.parse(argCid) + : await SearchHttp.ab2c(bvid: bvid); + final String heroTag = Utils.makeHeroTag(bvid); + Get.toNamed( + '/video?bvid=$bvid&cid=$cid', + arguments: { + 'pic': '', + 'heroTag': heroTag, + }, + ); + } catch (e) { + SmartDialog.showToast('视频可能失效了$e'); + } + break; + case 'reply': + debugPrint('commentRootId: $oid, $commentRootId'); + navigateToComment( + context, oid, commentRootId!, ReplyType.video, nativeUri); + break; + default: + break; + } + } + + // 跳转查看评论 + static void navigateToComment( + BuildContext context, + String oid, + String rpid, + ReplyType replyType, + Uri nativeUri, + ) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + appBar: AppBar( + title: const Text('评论详情'), + actions: [ + IconButton( + tooltip: '查看原内容', + onPressed: () { + PiliSchame.routePush(nativeUri); + }, + icon: const Icon(Icons.open_in_new_outlined), + ), + const SizedBox(width: 10), + ], + ), + body: VideoReplyReplyPanel( + oid: int.tryParse(oid), + rpid: int.tryParse(rpid), + source: 'routePush', + replyType: replyType, + firstFloor: null, + showRoot: true, + ), + ), + ), + ); + } +} diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index 3d5644e8..506e530e 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -5,13 +5,15 @@ import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; class VideoReplyReplyController extends GetxController { - VideoReplyReplyController(this.aid, this.rpid, this.replyType); + VideoReplyReplyController(this.aid, this.rpid, this.replyType, this.showRoot); final ScrollController scrollController = ScrollController(); // 视频aid 请求时使用的oid int? aid; // rpid 请求楼中楼回复 String? rpid; ReplyType replyType = ReplyType.video; + bool showRoot = false; + ReplyItemModel? rootReply; RxList replyList = [].obs; // 当前页 int currentPage = 0; @@ -42,6 +44,7 @@ class VideoReplyReplyController extends GetxController { ); if (res['status']) { final List replies = res['data'].replies; + ReplyItemModel? root = res['data'].root; if (replies.isNotEmpty) { noMore.value = '加载中...'; if (replies.length == res['data'].page.count) { @@ -60,7 +63,9 @@ class VideoReplyReplyController extends GetxController { return; } replyList.addAll(replies); - // res['data'].replies.addAll(replyList); + } + if (showRoot && root != null) { + rootReply = root; } } if (replyList.isNotEmpty && currentReply != null) { diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index 06a40cd6..8ac24302 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -8,7 +8,6 @@ import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart'; import 'package:pilipala/utils/storage.dart'; - import 'controller.dart'; class VideoReplyReplyPanel extends StatefulWidget { @@ -22,6 +21,7 @@ class VideoReplyReplyPanel extends StatefulWidget { this.sheetHeight, this.currentReply, this.loadMore = true, + this.showRoot = false, super.key, }); final int? oid; @@ -33,6 +33,7 @@ class VideoReplyReplyPanel extends StatefulWidget { final double? sheetHeight; final dynamic currentReply; final bool loadMore; + final bool showRoot; @override State createState() => _VideoReplyReplyPanelState(); @@ -49,7 +50,11 @@ class _VideoReplyReplyPanelState extends State { void initState() { _videoReplyReplyController = Get.put( VideoReplyReplyController( - widget.oid, widget.rpid.toString(), widget.replyType!), + widget.oid, + widget.rpid.toString(), + widget.replyType!, + widget.showRoot, + ), tag: widget.rpid.toString()); super.initState(); @@ -80,6 +85,93 @@ class _VideoReplyReplyPanelState extends State { super.dispose(); } + Widget _buildAppBar() { + return AppBar( + toolbarHeight: 45, + automaticallyImplyLeading: false, + centerTitle: false, + title: Text( + '评论详情', + style: Theme.of(context).textTheme.titleSmall, + ), + actions: [ + IconButton( + icon: const Icon(Icons.close, size: 20), + onPressed: () { + _videoReplyReplyController.currentPage = 0; + widget.closePanel?.call(); + Navigator.pop(context); + }, + ), + const SizedBox(width: 14), + ], + ); + } + + Widget _buildReplyItem(ReplyItemModel? replyItem, String replyLevel) { + return ReplyItem( + replyItem: replyItem, + replyLevel: replyLevel, + showReplyRow: false, + addReply: (replyItem) { + _videoReplyReplyController.replyList.add(replyItem); + }, + replyType: widget.replyType, + replyReply: (replyItem) => replyReply(replyItem), + ); + } + + Widget _buildSliverList() { + return Obx( + () => SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index == 0) { + return _videoReplyReplyController.rootReply != null + ? Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: + Theme.of(context).dividerColor.withOpacity(0.1), + width: 6, + ), + ), + ), + child: _buildReplyItem( + _videoReplyReplyController.rootReply, '1'), + ) + : const SizedBox(); + } + int adjustedIndex = index - 1; + if (adjustedIndex == _videoReplyReplyController.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( + _videoReplyReplyController.noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + ); + } else { + return _buildReplyItem( + _videoReplyReplyController.replyList[adjustedIndex], '2'); + } + }, + childCount: _videoReplyReplyController.replyList.length + 2, + ), + ), + ); + } + @override Widget build(BuildContext context) { return Container( @@ -87,27 +179,7 @@ class _VideoReplyReplyPanelState extends State { color: Theme.of(context).colorScheme.surface, child: Column( children: [ - if (widget.source == 'videoDetail') - AppBar( - toolbarHeight: 45, - automaticallyImplyLeading: false, - centerTitle: false, - title: Text( - '评论详情', - style: Theme.of(context).textTheme.titleSmall, - ), - actions: [ - IconButton( - icon: const Icon(Icons.close, size: 20), - onPressed: () { - _videoReplyReplyController.currentPage = 0; - widget.closePanel?.call; - Navigator.pop(context); - }, - ), - const SizedBox(width: 14), - ], - ), + if (widget.source == 'videoDetail') _buildAppBar(), Expanded( child: RefreshIndicator( onRefresh: () async { @@ -120,28 +192,22 @@ class _VideoReplyReplyPanelState extends State { child: CustomScrollView( controller: _videoReplyReplyController.scrollController, slivers: [ - if (widget.firstFloor != null) ...[ - // const SliverToBoxAdapter(child: SizedBox(height: 10)), + if (widget.firstFloor != null) SliverToBoxAdapter( - child: ReplyItem( - replyItem: widget.firstFloor, - replyLevel: '2', - showReplyRow: false, - addReply: (replyItem) { - _videoReplyReplyController.replyList.add(replyItem); - }, - replyType: widget.replyType, - replyReply: (replyItem) => replyReply(replyItem), + child: Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context) + .dividerColor + .withOpacity(0.1), + width: 6, + ), + ), + ), + child: _buildReplyItem(widget.firstFloor, '2'), ), ), - SliverToBoxAdapter( - child: Divider( - height: 20, - color: Theme.of(context).dividerColor.withOpacity(0.1), - thickness: 6, - ), - ), - ], widget.loadMore ? FutureBuilder( future: _futureBuilderFuture, @@ -150,76 +216,21 @@ class _VideoReplyReplyPanelState extends State { ConnectionState.done) { Map? data = snapshot.data; if (data != null && data['status']) { - // 请求成功 - return Obx( - () => SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (index == - _videoReplyReplyController - .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( - _videoReplyReplyController - .noMore.value, - style: TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .outline, - ), - ), - ), - ), - ); - } else { - return ReplyItem( - replyItem: - _videoReplyReplyController - .replyList[index], - replyLevel: '2', - showReplyRow: false, - addReply: (replyItem) { - _videoReplyReplyController - .replyList - .add(replyItem); - }, - replyType: widget.replyType, - replyReply: (replyItem) => - replyReply(replyItem), - ); - } - }, - childCount: _videoReplyReplyController - .replyList.length + - 1, - ), - ), - ); + return _buildSliverList(); } else { - // 请求错误 return HttpError( errMsg: data?['msg'] ?? '请求错误', fn: () => setState(() {}), ); } } else { - // 骨架屏 return SliverList( delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return const VideoReplySkeleton(); - }, childCount: 8), + (BuildContext context, int index) { + return const VideoReplySkeleton(); + }, + childCount: 8, + ), ); } }, @@ -237,7 +248,7 @@ class _VideoReplyReplyPanelState extends State { ), ), ), - ) + ), ], ), ), diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index f26eff77..bf100075 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -15,26 +15,26 @@ class PiliSchame { /// final SchemeEntity? value = await appScheme.getInitScheme(); if (value != null) { - _routePush(value); + routePush(value); } /// 完整链接进入 b23.无效 appScheme.getLatestScheme().then((SchemeEntity? value) { if (value != null) { - _routePush(value); + routePush(value); } }); /// 注册从外部打开的Scheme监听信息 # appScheme.registerSchemeListener().listen((SchemeEntity? event) { if (event != null) { - _routePush(event); + routePush(event); } }); } /// 路由跳转 - static void _routePush(value) async { + static void routePush(value) async { final String scheme = value.scheme; final String host = value.host; final String path = value.path;