feat: record reply

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-03-08 11:25:20 +08:00
parent 1e83a23c5c
commit a1f15b5da5
10 changed files with 199 additions and 13 deletions

View File

@@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show ReplyInfo;
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/browser_ua.dart'; import 'package:PiliPlus/http/browser_ua.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
@@ -32,7 +34,10 @@ import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/recommend_filter.dart'; import 'package:PiliPlus/utils/recommend_filter.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/wbi_sign.dart'; import 'package:PiliPlus/utils/wbi_sign.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart' show compute; import 'package:flutter/foundation.dart' show compute;
@@ -524,7 +529,7 @@ abstract final class VideoHttp {
// parent num 父评论rpid 非必要 二级评论同根评论id 大于二级评论为要回复的评论id // parent num 父评论rpid 非必要 二级评论同根评论id 大于二级评论为要回复的评论id
// message str 发送评论内容 必要 最大1000字符 // message str 发送评论内容 必要 最大1000字符
// plat num 发送平台标识 非必要 1web端 2安卓客户端 3ios客户端 4wp客户端 // plat num 发送平台标识 非必要 1web端 2安卓客户端 3ios客户端 4wp客户端
static Future<LoadingState<Map>> replyAdd({ static Future<LoadingState<ReplyInfo?>> replyAdd({
required int type, required int type,
required int oid, required int oid,
required String message, required String message,
@@ -552,7 +557,19 @@ abstract final class VideoHttp {
options: Options(contentType: Headers.formUrlEncodedContentType), options: Options(contentType: Headers.formUrlEncodedContentType),
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return Success(res.data['data']); try {
final replyInfo = RequestUtils.replyCast(res.data['data']['reply']);
GStorage.reply.put(
replyInfo.id.toString(),
(replyInfo.toProto3Json() as Map)
..remove('memberV2')
..remove('trackInfo'),
);
return Success(replyInfo);
} catch (e, s) {
Utils.reportError(e, s);
return const Success(null);
}
} else { } else {
return Error(res.data['message']); return Error(res.data['message']);
} }
@@ -574,6 +591,7 @@ abstract final class VideoHttp {
options: Options(contentType: Headers.formUrlEncodedContentType), options: Options(contentType: Headers.formUrlEncodedContentType),
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
GStorage.reply.delete(rpid.toString());
return const Success(null); return const Success(null);
} else { } else {
return const Error('请退出账号后重新登录'); return const Error('请退出账号后重新登录');

View File

@@ -10,7 +10,6 @@ import 'package:PiliPlus/pages/common/publish/publish_route.dart';
import 'package:PiliPlus/pages/video/reply_new/view.dart'; import 'package:PiliPlus/pages/video/reply_new/view.dart';
import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/reply_utils.dart'; import 'package:PiliPlus/utils/reply_utils.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -174,10 +173,9 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
), ),
) )
.then( .then(
(res) { (replyInfo) {
if (res != null) { if (replyInfo is ReplyInfo) {
savedReplies.remove(key); savedReplies.remove(key);
ReplyInfo replyInfo = RequestUtils.replyCast(res);
if (loadingState.value case Success(:final response)) { if (loadingState.value case Success(:final response)) {
if (response == null) { if (response == null) {
loadingState.value = Success([replyInfo]); loadingState.value = Success([replyInfo]);

View File

@@ -160,6 +160,14 @@ class _MediaPageState extends CommonPageState<MinePage>
), ),
msgBadge(_mainController), msgBadge(_mainController),
], ],
IconButton(
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '评论记录',
onPressed: () => Get.toNamed('/myReply'),
icon: const Icon(Icons.message_outlined),
),
Obx( Obx(
() { () {
final anonymity = MineController.anonymity.value; final anonymity = MineController.anonymity.value;

View File

@@ -0,0 +1,122 @@
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart';
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/reply_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/waterfall.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:waterfall_flow/waterfall_flow.dart';
class MyReply extends StatefulWidget {
const MyReply({super.key});
@override
State<MyReply> createState() => _MyReplyState();
}
class _MyReplyState extends State<MyReply> with DynMixin {
late final List<ReplyInfo> _replies;
@override
void initState() {
super.initState();
_replies =
GStorage.reply.values
.map((e) => ReplyInfo.create()..mergeFromProto3Json(e))
.toList()
..sort((a, b) => b.ctime.compareTo(a.ctime));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('我的评论'),
actions: kDebugMode
? [
IconButton(
tooltip: 'Clear',
onPressed: () => showConfirmDialog(
context: context,
title: 'Clear Local Storage?',
onConfirm: () {
GStorage.reply.clear();
_replies.clear();
setState(() {});
},
),
icon: const Icon(Icons.clear_all),
),
const SizedBox(width: 6),
]
: null,
),
body: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
_replies.isNotEmpty
? ViewSliverSafeArea(
sliver: SliverWaterfallFlow(
gridDelegate: dynGridDelegate,
delegate: SliverChildBuilderDelegate(
childCount: _replies.length,
(context, index) => ReplyItemGrpc(
replyLevel: 0,
needDivider: false,
replyItem: _replies[index],
replyReply: _replyReply,
onDelete: (_, _) => _onDelete(index),
onCheckReply: _onCheckReply,
),
),
),
)
: const HttpError(),
],
),
);
}
void _replyReply(ReplyInfo replyInfo, int? rpid) {
switch (replyInfo.type.toInt()) {
case 1:
PiliScheme.videoPush(
replyInfo.oid.toInt(),
null,
);
case 12:
PageUtils.toDupNamed(
'/articlePage',
parameters: {
'id': replyInfo.oid.toString(),
'type': 'read',
},
);
case _:
PageUtils.pushDynFromId(
rid: replyInfo.oid.toString(),
type: replyInfo.type,
);
}
}
void _onDelete(int index) {
_replies.removeAt(index);
setState(() {});
}
void _onCheckReply(ReplyInfo replyInfo) {
ReplyUtils.onCheckReply(
replyInfo: replyInfo,
biliSendCommAntifraud: Pref.biliSendCommAntifraud,
sourceId: null,
isManual: true,
);
}
}

View File

@@ -29,6 +29,7 @@ import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/url_utils.dart'; import 'package:PiliPlus/utils/url_utils.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
@@ -865,6 +866,35 @@ class ReplyItemGrpc extends StatelessWidget {
), ),
), ),
), ),
if (kDebugMode) ...[
ListTile(
onTap: () {
Get.back();
GStorage.reply.put(
item.id.toString(),
(item.toProto3Json() as Map)
..remove('replies')
..remove('memberV2')
..remove('trackInfo'),
);
},
title: Text(
'save to local',
style: style!.copyWith(color: theme.colorScheme.primary),
),
),
ListTile(
onTap: () {
Get.back();
onDelete();
GStorage.reply.delete(item.id.toString());
},
title: Text(
'remove from local',
style: style.copyWith(color: theme.colorScheme.primary),
),
),
],
if (ownerMid == upMid || ownerMid == item.member.mid) if (ownerMid == upMid || ownerMid == item.member.mid)
ListTile( ListTile(
onTap: () async { onTap: () async {

View File

@@ -429,8 +429,8 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
); );
if (res case Success(:final response)) { if (res case Success(:final response)) {
hasPub = true; hasPub = true;
SmartDialog.showToast(response['success_toast']); SmartDialog.showToast('发送成功');
Get.back(result: response['reply']); Get.back(result: response);
} else { } else {
res.toast(); res.toast();
} }

View File

@@ -6,7 +6,6 @@ import 'package:PiliPlus/pages/common/publish/publish_route.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/pages/video/reply_new/view.dart'; import 'package:PiliPlus/pages/video/reply_new/view.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
@@ -195,10 +194,9 @@ class VideoReplyReplyController extends ReplyController
}, },
), ),
) )
.then((res) { .then((replyInfo) {
if (res != null) { if (replyInfo is ReplyInfo) {
savedReplies.remove(key); savedReplies.remove(key);
ReplyInfo replyInfo = RequestUtils.replyCast(res);
count.value += 1; count.value += 1;
loadingState loadingState

View File

@@ -43,6 +43,7 @@ import 'package:PiliPlus/pages/msg_feed_top/like_me/view.dart';
import 'package:PiliPlus/pages/msg_feed_top/reply_me/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/reply_me/view.dart';
import 'package:PiliPlus/pages/msg_feed_top/sys_msg/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/sys_msg/view.dart';
import 'package:PiliPlus/pages/music/view.dart'; import 'package:PiliPlus/pages/music/view.dart';
import 'package:PiliPlus/pages/my_reply/view.dart';
import 'package:PiliPlus/pages/popular_precious/view.dart'; import 'package:PiliPlus/pages/popular_precious/view.dart';
import 'package:PiliPlus/pages/popular_series/view.dart'; import 'package:PiliPlus/pages/popular_series/view.dart';
import 'package:PiliPlus/pages/search/view.dart'; import 'package:PiliPlus/pages/search/view.dart';
@@ -190,5 +191,6 @@ class Routes {
GetPage(name: '/sameFollowing', page: () => const FollowSamePage()), GetPage(name: '/sameFollowing', page: () => const FollowSamePage()),
GetPage(name: '/download', page: () => const DownloadPage()), GetPage(name: '/download', page: () => const DownloadPage()),
GetPage(name: '/dlna', page: () => const DLNAPage()), GetPage(name: '/dlna', page: () => const DLNAPage()),
GetPage(name: '/myReply', page: () => const MyReply()),
]; ];
} }

View File

@@ -110,6 +110,7 @@ abstract final class PageUtils {
String? id, String? id,
Object? rid, Object? rid,
bool off = false, bool off = false,
Object? type,
}) async { }) async {
assert(id != null || rid != null); assert(id != null || rid != null);
SmartDialog.showLoading(); SmartDialog.showLoading();
@@ -139,7 +140,7 @@ abstract final class PageUtils {
); );
} }
} else { } else {
res.toast(); SmartDialog.showToast('${type != null ? 'type: $type ' : ''}$res');
} }
} }

View File

@@ -20,6 +20,7 @@ abstract final class GStorage {
static late final Box<dynamic> setting; static late final Box<dynamic> setting;
static late final Box<dynamic> video; static late final Box<dynamic> video;
static late final Box<int> watchProgress; static late final Box<int> watchProgress;
static late final Box<Map> reply;
static Future<void> init() async { static Future<void> init() async {
await Hive.initFlutter(path.join(appSupportDirPath, 'hive')); await Hive.initFlutter(path.join(appSupportDirPath, 'hive'));
@@ -58,6 +59,12 @@ abstract final class GStorage {
return deletedEntries > 4; return deletedEntries > 4;
}, },
).then((res) => watchProgress = res), ).then((res) => watchProgress = res),
Hive.openBox<Map>(
'reply',
compactionStrategy: (entries, deletedEntries) {
return deletedEntries > 10;
},
).then((res) => reply = res),
]); ]);
} }
@@ -100,6 +107,7 @@ abstract final class GStorage {
video.compact(), video.compact(),
Accounts.account.compact(), Accounts.account.compact(),
watchProgress.compact(), watchProgress.compact(),
reply.compact(),
]); ]);
} }
@@ -112,6 +120,7 @@ abstract final class GStorage {
video.close(), video.close(),
Accounts.account.close(), Accounts.account.close(),
watchProgress.close(), watchProgress.close(),
reply.close(),
]); ]);
} }
} }