opt: save reply (#1860)

* opt: save reply

* opt: reply save switch

* remove unneeded sort

* clear sub replies [skip ci]

---------

Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
My-Responsitories
2026-03-08 20:37:59 +08:00
committed by GitHub
parent 4ad422c3ea
commit f825f87dc1
8 changed files with 106 additions and 38 deletions

View File

@@ -41,6 +41,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/utils/wbi_sign.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart' show compute;
import 'package:protobuf/protobuf.dart';
/// view层根据 status 判断渲染逻辑
abstract final class VideoHttp {
@@ -559,11 +560,13 @@ abstract final class VideoHttp {
if (res.data['code'] == 0) {
try {
final replyInfo = RequestUtils.replyCast(res.data['data']['reply']);
GStorage.reply.put(
GStorage.reply?.put(
replyInfo.id.toString(),
(replyInfo.toProto3Json() as Map)
..remove('memberV2')
..remove('trackInfo'),
(replyInfo.deepCopy()
..unknownFields.clear()
..clearMemberV2()
..clearTrackInfo())
.writeToBuffer(),
);
return Success(replyInfo);
} catch (e, s) {
@@ -591,7 +594,7 @@ abstract final class VideoHttp {
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
GStorage.reply.delete(rpid.toString());
GStorage.reply?.delete(rpid.toString());
return const Success(null);
} else {
return const Error('请退出账号后重新登录');

View File

@@ -17,6 +17,7 @@ import 'package:PiliPlus/utils/extension/get_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide ListTile;
import 'package:get/get.dart';
@@ -160,14 +161,15 @@ class _MediaPageState extends CommonPageState<MinePage>
),
msgBadge(_mainController),
],
IconButton(
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '评论记录',
onPressed: () => Get.toNamed('/myReply'),
icon: const Icon(Icons.message_outlined),
),
if (GStorage.reply != null)
IconButton(
iconSize: iconSize,
padding: padding,
style: style,
tooltip: '评论记录',
onPressed: () => Get.toNamed('/myReply'),
icon: const Icon(Icons.message_outlined),
),
Obx(
() {
final anonymity = MineController.anonymity.value;

View File

@@ -4,6 +4,7 @@ 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/id_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/reply_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
@@ -26,11 +27,7 @@ class _MyReplyState extends State<MyReply> with DynMixin {
@override
void initState() {
super.initState();
_replies =
GStorage.reply.values
.map((e) => ReplyInfo.create()..mergeFromProto3Json(e))
.toList()
..sort((a, b) => b.ctime.compareTo(a.ctime));
_replies = GStorage.reply!.values.map(ReplyInfo.fromBuffer).toList();
}
@override
@@ -46,7 +43,7 @@ class _MyReplyState extends State<MyReply> with DynMixin {
context: context,
title: 'Clear Local Storage?',
onConfirm: () {
GStorage.reply.clear();
GStorage.reply!.clear();
_replies.clear();
setState(() {});
},
@@ -112,10 +109,14 @@ class _MyReplyState extends State<MyReply> with DynMixin {
}
void _onCheckReply(ReplyInfo replyInfo) {
final oid = replyInfo.oid.toInt();
ReplyUtils.onCheckReply(
replyInfo: replyInfo,
biliSendCommAntifraud: Pref.biliSendCommAntifraud,
sourceId: null,
sourceId: switch (oid) {
1 => IdUtils.av2bv(oid),
_ => oid.toString(),
},
isManual: true,
);
}

View File

@@ -354,6 +354,13 @@ List<SettingsModel> get extraSettings => [
setKey: SettingBoxKey.showDmChart,
defaultVal: false,
),
const SwitchModel(
title: '记录评论',
leading: Icon(Icons.message_outlined),
setKey: SettingBoxKey.saveReply,
defaultVal: true,
needReboot: true,
),
const SwitchModel(
title: '发评反诈',
subtitle: '发送评论后检查评论是否可见',

View File

@@ -39,6 +39,7 @@ import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:protobuf/protobuf.dart';
class ReplyItemGrpc extends StatelessWidget {
const ReplyItemGrpc({
@@ -840,7 +841,7 @@ class ReplyItemGrpc extends StatelessWidget {
final ownerMid = Int64(Accounts.main.mid);
final theme = Theme.of(context);
final errorColor = theme.colorScheme.error;
final style = theme.textTheme.titleSmall;
final style = theme.textTheme.titleSmall!;
return Padding(
padding: EdgeInsets.only(
@@ -866,34 +867,56 @@ class ReplyItemGrpc extends StatelessWidget {
),
),
),
if (kDebugMode) ...[
if (kDebugMode && GStorage.reply != null) ...[
ListTile(
onTap: () {
Get.back();
GStorage.reply.put(
GStorage.reply!.put(
item.id.toString(),
(item.toProto3Json() as Map)
..remove('replies')
..remove('memberV2')
..remove('trackInfo'),
(item.deepCopy()
..unknownFields.clear()
..replies.clear()
..clearMemberV2()
..clearTrackInfo())
.writeToBuffer(),
);
},
title: Text(
'save to local',
style: style!.copyWith(color: theme.colorScheme.primary),
style: style.copyWith(color: theme.colorScheme.primary),
),
),
ListTile(
onTap: () {
Get.back();
onDelete();
GStorage.reply.delete(item.id.toString());
GStorage.reply!.delete(item.id.toString());
},
title: Text(
'remove from local',
style: style.copyWith(color: theme.colorScheme.primary),
),
),
ListTile(
onTap: () {
Get.back();
final oid = item.oid.toInt();
final data =
(item.deepCopy()
..unknownFields.clear()
..replies.clear()
..clearMemberV2()
..clearTrackInfo())
.writeToBuffer();
GStorage.reply!.putAll({
for (var i = oid; i < oid + 1000; i++) i.toString(): data,
});
},
title: Text(
'save to local (x1000)',
style: style.copyWith(color: theme.colorScheme.primary),
),
),
],
if (ownerMid == upMid || ownerMid == item.member.mid)
ListTile(
@@ -959,7 +982,7 @@ class ReplyItemGrpc extends StatelessWidget {
},
minLeadingWidth: 0,
leading: Icon(Icons.delete_outlined, color: errorColor, size: 19),
title: Text('删除', style: style!.copyWith(color: errorColor)),
title: Text('删除', style: style.copyWith(color: errorColor)),
),
if (ownerMid != Int64.ZERO)
ListTile(
@@ -985,7 +1008,7 @@ class ReplyItemGrpc extends StatelessWidget {
},
minLeadingWidth: 0,
leading: Icon(Icons.error_outline, color: errorColor, size: 19),
title: Text('举报', style: style!.copyWith(color: errorColor)),
title: Text('举报', style: style.copyWith(color: errorColor)),
),
if (replyLevel == 1 && !isSubReply && ownerMid == upMid)
ListTile(

View File

@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models/user/danmaku_rule_adapter.dart';
@@ -9,6 +10,7 @@ import 'package:PiliPlus/utils/accounts/account_type_adapter.dart';
import 'package:PiliPlus/utils/accounts/cookie_jar_adapter.dart';
import 'package:PiliPlus/utils/path_utils.dart';
import 'package:PiliPlus/utils/set_int_adapter.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path/path.dart' as path;
@@ -20,7 +22,7 @@ abstract final class GStorage {
static late final Box<dynamic> setting;
static late final Box<dynamic> video;
static late final Box<int> watchProgress;
static late final Box<Map> reply;
static late final Box<Uint8List>? reply;
static Future<void> init() async {
await Hive.initFlutter(path.join(appSupportDirPath, 'hive'));
@@ -55,17 +57,24 @@ abstract final class GStorage {
Accounts.init(),
Hive.openBox<int>(
'watchProgress',
keyComparator: _intStrKeyComparator,
compactionStrategy: (entries, deletedEntries) {
return deletedEntries > 4;
},
).then((res) => watchProgress = res),
Hive.openBox<Map>(
]);
if (Pref.saveReply) {
reply = await Hive.openBox<Uint8List>(
'reply',
keyComparator: _intStrKeyComparator,
compactionStrategy: (entries, deletedEntries) {
return deletedEntries > 10;
},
).then((res) => reply = res),
]);
);
} else {
reply = null;
}
}
static String exportAllSettings() {
@@ -107,7 +116,7 @@ abstract final class GStorage {
video.compact(),
Accounts.account.compact(),
watchProgress.compact(),
reply.compact(),
?reply?.compact(),
]);
}
@@ -120,7 +129,26 @@ abstract final class GStorage {
video.close(),
Accounts.account.close(),
watchProgress.close(),
reply.close(),
?reply?.close(),
]);
}
static int _intStrKeyComparator(dynamic k1, dynamic k2) {
if (k1 is int) {
if (k2 is int) {
return k2.compareTo(k1);
} else {
return -1;
}
} else if (k2 is String) {
final lenCompare = k2.length.compareTo((k1 as String).length);
if (lenCompare == 0) {
return k2.compareTo(k1);
} else {
return lenCompare;
}
} else {
return 1;
}
}
}

View File

@@ -228,7 +228,8 @@ abstract final class SettingBoxKey {
navBarSort = 'navBarSort',
tempPlayerConf = 'tempPlayerConf',
reduceLuxColor = 'reduceLuxColor',
liveCdnUrl = 'liveCdnUrl';
liveCdnUrl = 'liveCdnUrl',
saveReply = 'saveReply';
}
abstract final class LocalCacheKey {

View File

@@ -960,4 +960,7 @@ abstract final class Pref {
static double get touchSlopH =>
_setting.get(SettingBoxKey.touchSlopH, defaultValue: 24.0);
static bool get saveReply =>
_setting.get(SettingBoxKey.saveReply, defaultValue: true);
}