faat: message to comment

This commit is contained in:
guozhigq
2024-10-19 18:16:04 +08:00
parent b4ccd2f4bf
commit be29f70b30
7 changed files with 230 additions and 161 deletions

View File

@ -101,6 +101,7 @@ class ReplyReplyData {
this.page, this.page,
this.config, this.config,
this.replies, this.replies,
this.root,
this.topReplies, this.topReplies,
this.upper, this.upper,
}); });
@ -108,6 +109,7 @@ class ReplyReplyData {
ReplyPage? page; ReplyPage? page;
ReplyConfig? config; ReplyConfig? config;
late List<ReplyItemModel>? replies; late List<ReplyItemModel>? replies;
ReplyItemModel? root;
late List<ReplyItemModel>? topReplies; late List<ReplyItemModel>? topReplies;
ReplyUpper? upper; ReplyUpper? upper;
@ -120,6 +122,9 @@ class ReplyReplyData {
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'])) (item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
.toList() .toList()
: []; : [];
root = json['root'] != null
? ReplyItemModel.fromJson(json['root'], false)
: null;
topReplies = json['top_replies'] != null topReplies = json['top_replies'] != null
? json['top_replies'] ? json['top_replies']
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson( .map<ReplyItemModel>((item) => ReplyItemModel.fromJson(

View File

@ -1,13 +1,11 @@
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.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/models/msg/like.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../utils/index.dart';
import 'controller.dart'; import 'controller.dart';
class MessageLikePage extends StatefulWidget { class MessageLikePage extends StatefulWidget {
@ -122,39 +120,13 @@ class LikeItem extends StatelessWidget {
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList(); final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
int usersLen = item.users!.length > 3 ? 3 : item.users!.length; int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
final Uri uri = Uri.parse(item.item!.uri!); final Uri uri = Uri.parse(item.item!.uri!);
final String path = uri.path;
final String bvid = path.split('/').last;
/// bilibili:// /// bilibili://
final Uri nativeUri = Uri.parse(item.item!.nativeUri!); final Uri nativeUri = Uri.parse(item.item!.nativeUri!);
final Map<String, String> queryParameters = nativeUri.queryParameters;
final String type = item.item!.type!; 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( return InkWell(
onTap: () async { onTap: () async {
try { MessageUtils.onClickMessage(context, uri, nativeUri, type);
final int cid = argCid != null
? int.parse(argCid)
: await SearchHttp.ab2c(bvid: bvid);
final String heroTag = Utils.makeHeroTag(bvid);
Get.toNamed<dynamic>(
'/video?bvid=$bvid&cid=$cid',
arguments: <String, String?>{
'pic': '',
'heroTag': heroTag,
},
);
} catch (e) {
SmartDialog.showToast('视频可能失效了$e');
}
}, },
child: Stack( child: Stack(
children: [ children: [
@ -243,6 +215,7 @@ class LikeItem extends StatelessWidget {
width: 60, width: 60,
height: 60, height: 60,
src: item.item!.image, src: item.item!.image,
radius: 6,
), ),
], ],
), ),

View File

@ -4,10 +4,9 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.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/models/msg/reply.dart';
import 'package:pilipala/pages/message/utils/index.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'controller.dart'; import 'controller.dart';
class MessageReplyPage extends StatefulWidget { class MessageReplyPage extends StatefulWidget {
@ -112,28 +111,14 @@ class ReplyItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color outline = Theme.of(context).colorScheme.outline; Color outline = Theme.of(context).colorScheme.outline;
final String heroTag = Utils.makeHeroTag(item.user!.mid); final String heroTag = Utils.makeHeroTag(item.user!.mid);
final String bvid = item.item!.uri!.split('/').last; final Uri uri = Uri.parse(item.item!.uri!);
// 页码
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;
/// bilibili://
final Uri nativeUri = Uri.parse(item.item!.nativeUri!);
final String type = item.item!.type!;
return InkWell( return InkWell(
onTap: () async { onTap: () async {
final int cid = await SearchHttp.ab2c(bvid: bvid); MessageUtils.onClickMessage(context, uri, nativeUri, type);
final String heroTag = Utils.makeHeroTag(bvid);
Get.toNamed<dynamic>(
'/video?bvid=$bvid&cid=$cid',
arguments: <String, String?>{
'pic': '',
'heroTag': heroTag,
},
);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
@ -217,6 +202,7 @@ class ReplyItem extends StatelessWidget {
width: 60, width: 60,
height: 60, height: 60,
src: item.item!.image, src: item.item!.image,
radius: 6,
), ),
], ],
), ),

View File

@ -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<String, String> 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<dynamic>(
'/video?bvid=$bvid&cid=$cid',
arguments: <String, String?>{
'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,
),
),
),
);
}
}

View File

@ -5,13 +5,15 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
class VideoReplyReplyController extends GetxController { class VideoReplyReplyController extends GetxController {
VideoReplyReplyController(this.aid, this.rpid, this.replyType); VideoReplyReplyController(this.aid, this.rpid, this.replyType, this.showRoot);
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
// 视频aid 请求时使用的oid // 视频aid 请求时使用的oid
int? aid; int? aid;
// rpid 请求楼中楼回复 // rpid 请求楼中楼回复
String? rpid; String? rpid;
ReplyType replyType = ReplyType.video; ReplyType replyType = ReplyType.video;
bool showRoot = false;
ReplyItemModel? rootReply;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
// 当前页 // 当前页
int currentPage = 0; int currentPage = 0;
@ -42,6 +44,7 @@ class VideoReplyReplyController extends GetxController {
); );
if (res['status']) { if (res['status']) {
final List<ReplyItemModel> replies = res['data'].replies; final List<ReplyItemModel> replies = res['data'].replies;
ReplyItemModel? root = res['data'].root;
if (replies.isNotEmpty) { if (replies.isNotEmpty) {
noMore.value = '加载中...'; noMore.value = '加载中...';
if (replies.length == res['data'].page.count) { if (replies.length == res['data'].page.count) {
@ -60,7 +63,9 @@ class VideoReplyReplyController extends GetxController {
return; return;
} }
replyList.addAll(replies); replyList.addAll(replies);
// res['data'].replies.addAll(replyList); }
if (showRoot && root != null) {
rootReply = root;
} }
} }
if (replyList.isNotEmpty && currentReply != null) { if (replyList.isNotEmpty && currentReply != null) {

View File

@ -8,7 +8,6 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart'; import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'controller.dart'; import 'controller.dart';
class VideoReplyReplyPanel extends StatefulWidget { class VideoReplyReplyPanel extends StatefulWidget {
@ -22,6 +21,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
this.sheetHeight, this.sheetHeight,
this.currentReply, this.currentReply,
this.loadMore = true, this.loadMore = true,
this.showRoot = false,
super.key, super.key,
}); });
final int? oid; final int? oid;
@ -33,6 +33,7 @@ class VideoReplyReplyPanel extends StatefulWidget {
final double? sheetHeight; final double? sheetHeight;
final dynamic currentReply; final dynamic currentReply;
final bool loadMore; final bool loadMore;
final bool showRoot;
@override @override
State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState(); State<VideoReplyReplyPanel> createState() => _VideoReplyReplyPanelState();
@ -49,7 +50,11 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
void initState() { void initState() {
_videoReplyReplyController = Get.put( _videoReplyReplyController = Get.put(
VideoReplyReplyController( VideoReplyReplyController(
widget.oid, widget.rpid.toString(), widget.replyType!), widget.oid,
widget.rpid.toString(),
widget.replyType!,
widget.showRoot,
),
tag: widget.rpid.toString()); tag: widget.rpid.toString());
super.initState(); super.initState();
@ -80,6 +85,93 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
super.dispose(); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -87,27 +179,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: Column( child: Column(
children: [ children: [
if (widget.source == 'videoDetail') if (widget.source == 'videoDetail') _buildAppBar(),
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),
],
),
Expanded( Expanded(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@ -120,28 +192,22 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
child: CustomScrollView( child: CustomScrollView(
controller: _videoReplyReplyController.scrollController, controller: _videoReplyReplyController.scrollController,
slivers: <Widget>[ slivers: <Widget>[
if (widget.firstFloor != null) ...[ if (widget.firstFloor != null)
// const SliverToBoxAdapter(child: SizedBox(height: 10)),
SliverToBoxAdapter( SliverToBoxAdapter(
child: ReplyItem( child: Container(
replyItem: widget.firstFloor, decoration: BoxDecoration(
replyLevel: '2', border: Border(
showReplyRow: false, bottom: BorderSide(
addReply: (replyItem) { color: Theme.of(context)
_videoReplyReplyController.replyList.add(replyItem); .dividerColor
}, .withOpacity(0.1),
replyType: widget.replyType, width: 6,
replyReply: (replyItem) => replyReply(replyItem), ),
),
),
child: _buildReplyItem(widget.firstFloor, '2'),
), ),
), ),
SliverToBoxAdapter(
child: Divider(
height: 20,
color: Theme.of(context).dividerColor.withOpacity(0.1),
thickness: 6,
),
),
],
widget.loadMore widget.loadMore
? FutureBuilder( ? FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
@ -150,76 +216,21 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
ConnectionState.done) { ConnectionState.done) {
Map? data = snapshot.data; Map? data = snapshot.data;
if (data != null && data['status']) { if (data != null && data['status']) {
// 请求成功 return _buildSliverList();
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,
),
),
);
} else { } else {
// 请求错误
return HttpError( return HttpError(
errMsg: data?['msg'] ?? '请求错误', errMsg: data?['msg'] ?? '请求错误',
fn: () => setState(() {}), fn: () => setState(() {}),
); );
} }
} else { } else {
// 骨架屏
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return const VideoReplySkeleton(); return const VideoReplySkeleton();
}, childCount: 8), },
childCount: 8,
),
); );
} }
}, },
@ -237,7 +248,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
), ),
), ),
), ),
) ),
], ],
), ),
), ),

View File

@ -15,26 +15,26 @@ class PiliSchame {
/// ///
final SchemeEntity? value = await appScheme.getInitScheme(); final SchemeEntity? value = await appScheme.getInitScheme();
if (value != null) { if (value != null) {
_routePush(value); routePush(value);
} }
/// 完整链接进入 b23.无效 /// 完整链接进入 b23.无效
appScheme.getLatestScheme().then((SchemeEntity? value) { appScheme.getLatestScheme().then((SchemeEntity? value) {
if (value != null) { if (value != null) {
_routePush(value); routePush(value);
} }
}); });
/// 注册从外部打开的Scheme监听信息 # /// 注册从外部打开的Scheme监听信息 #
appScheme.registerSchemeListener().listen((SchemeEntity? event) { appScheme.registerSchemeListener().listen((SchemeEntity? event) {
if (event != null) { if (event != null) {
_routePush(event); routePush(event);
} }
}); });
} }
/// 路由跳转 /// 路由跳转
static void _routePush(value) async { static void routePush(value) async {
final String scheme = value.scheme; final String scheme = value.scheme;
final String host = value.host; final String host = value.host;
final String path = value.path; final String path = value.path;