From f7cd1aa502df0490c350a6098885f51a48554c1d Mon Sep 17 00:00:00 2001 From: dom Date: Thu, 16 Jul 2026 12:13:44 +0800 Subject: [PATCH] update im ack Signed-off-by: dom --- lib/grpc/im.dart | 14 ++- lib/grpc/url.dart | 1 + lib/pages/whisper/widgets/item.dart | 144 ++++++++++++++++++---------- 3 files changed, 109 insertions(+), 50 deletions(-) diff --git a/lib/grpc/im.dart b/lib/grpc/im.dart index 306d9495d..b04cda0cd 100644 --- a/lib/grpc/im.dart +++ b/lib/grpc/im.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'; +import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' hide SessionInfo; import 'package:PiliPlus/grpc/bilibili/im/interfaces/v1.pb.dart'; import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart'; import 'package:PiliPlus/grpc/grpc_req.dart'; @@ -212,4 +212,16 @@ abstract final class ImGrpc { RspTotalUnread.fromBuffer, ); } + + static Future> sessionDetail({ + Int64? talkerId, + int? sessionType, + Int64? uid, + }) { + return GrpcReq.request( + GrpcUrl.sessionDetail, + ReqSessionDetail(talkerId: talkerId, sessionType: sessionType, uid: uid), + SessionInfo.fromBuffer, + ); + } } diff --git a/lib/grpc/url.dart b/lib/grpc/url.dart index 78238ef02..6064b1157 100644 --- a/lib/grpc/url.dart +++ b/lib/grpc/url.dart @@ -44,6 +44,7 @@ abstract final class GrpcUrl { static const keywordBlockingDelete = '$im2/KeywordBlockingDelete'; static const syncFetchSessionMsgs = '$im/SyncFetchSessionMsgs'; static const getTotalUnread = '$im/GetTotalUnread'; + static const sessionDetail = '$im/SessionDetail'; // view static const viewunite = '/bilibili.app.viewunite.v1.View'; diff --git a/lib/pages/whisper/widgets/item.dart b/lib/pages/whisper/widgets/item.dart index 98f2d9b53..c6a8f442f 100644 --- a/lib/pages/whisper/widgets/item.dart +++ b/lib/pages/whisper/widgets/item.dart @@ -7,8 +7,10 @@ import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' - show Session, SessionId, SessionPageType, SessionType, UnreadStyle; -import 'package:PiliPlus/models/common/badge_type.dart'; + show Session, SessionId, SessionPageType; +import 'package:PiliPlus/grpc/im.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/pages/whisper_secondary/view.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; @@ -16,6 +18,7 @@ import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:fixnum/fixnum.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart' hide ListTile; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -34,6 +37,28 @@ class WhisperSessionItem extends StatelessWidget { final Function(bool isMuted, Int64 talkerUid) onSetMute; final ValueChanged onRemove; + Future _updateAck(BuildContext context) async { + final talkerUid = item.id.privateId.talkerUid; + final res = await ImGrpc.sessionDetail(talkerId: talkerUid, sessionType: 1); + if (res case Success(:final response)) { + final res = await MsgHttp.ackSessionMsg( + talkerId: talkerUid.toInt(), + ackSeqno: response.ackSeqno.toInt(), + ); + if (res.isSuccess) { + SmartDialog.showToast('已标为已读'); + item.clearUnread(); + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + } else { + res.toast(); + } + } else { + res.toast(); + } + } + @override Widget build(BuildContext context) { final resource = @@ -46,7 +71,8 @@ class WhisperSessionItem extends StatelessWidget { Map? vipInfo = item.sessionInfo.hasVipInfo() ? jsonDecode(item.sessionInfo.vipInfo) : null; - final ThemeData theme = Theme.of(context); + + final theme = Theme.of(context); return ListTile( safeArea: true, @@ -57,9 +83,9 @@ class WhisperSessionItem extends StatelessWidget { : null, onLongPress: () => showDialog( context: context, - builder: (context) => SimpleDialog( - clipBehavior: Clip.hardEdge, - contentPadding: const EdgeInsets.symmetric(vertical: 12), + builder: (_) => SimpleDialog( + clipBehavior: .hardEdge, + contentPadding: const .symmetric(vertical: 12), children: [ DialogOption( onPressed: () { @@ -68,7 +94,15 @@ class WhisperSessionItem extends StatelessWidget { }, child: Text(item.isPinned ? '移除置顶' : '置顶'), ), - if (item.id.privateId.hasTalkerUid()) + if (item.id.privateId.hasTalkerUid()) ...[ + if (kDebugMode || item.hasUnread()) + DialogOption( + onPressed: () { + Get.back(); + _updateAck(context); + }, + child: const Text('标为已读'), + ), DialogOption( onPressed: () { Get.back(); @@ -76,7 +110,6 @@ class WhisperSessionItem extends StatelessWidget { }, child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'), ), - if (item.id.privateId.hasTalkerUid()) DialogOption( onPressed: () { Get.back(); @@ -89,6 +122,7 @@ class WhisperSessionItem extends StatelessWidget { }, child: const Text('删除'), ), + ], ], ), ), @@ -96,20 +130,38 @@ class WhisperSessionItem extends StatelessWidget { ? (details) => showMenu( context: context, position: PageUtils.menuPosition(details.globalPosition), - items: [ + items: >[ PopupMenuItem( height: 42, onTap: () => onSetTop(item.isPinned, item.id), child: Text(item.isPinned ? '移除置顶' : '置顶'), ), - if (item.id.privateId.hasTalkerUid()) + if (item.id.privateId.hasTalkerUid()) ...[ + if (kDebugMode || item.hasUnread()) + PopupMenuItem( + height: 42, + onTap: () => _updateAck(context), + child: const Text('标为已读'), + ), + // if (kDebugMode) + // PopupMenuItem( + // height: 42, + // onTap: () { + // item.unread = Unread( + // style: .UNREAD_STYLE_NUMBER, + // number: .ONE, + // ); + // (context as Element).markNeedsBuild(); + // }, + // child: const Text('标为未读'), + // ), PopupMenuItem( height: 42, onTap: () => onSetMute(item.isMuted, item.id.privateId.talkerUid), child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'), ), - if (item.id.privateId.hasTalkerUid()) + const PopupMenuDivider(height: 10), PopupMenuItem( height: 42, onTap: () => showConfirmDialog( @@ -118,8 +170,12 @@ class WhisperSessionItem extends StatelessWidget { onConfirm: () => onRemove(item.id.privateId.talkerUid.toInt()), ), - child: const Text('删除'), + child: Text( + '删除', + style: TextStyle(color: theme.colorScheme.error), + ), ), + ], ], ) : null, @@ -147,24 +203,15 @@ class WhisperSessionItem extends StatelessWidget { if (item.id.foldId.hasType()) { SessionPageType? sessionPageType = switch (item.id.foldId.type) { - SessionType.SESSION_TYPE_UNKNOWN => - SessionPageType.SESSION_PAGE_TYPE_UNKNOWN, - SessionType.SESSION_TYPE_GROUP => - SessionPageType.SESSION_PAGE_TYPE_GROUP, - SessionType.SESSION_TYPE_GROUP_FOLD => - SessionPageType.SESSION_PAGE_TYPE_GROUP, - SessionType.SESSION_TYPE_UNFOLLOWED => - SessionPageType.SESSION_PAGE_TYPE_UNFOLLOWED, - SessionType.SESSION_TYPE_STRANGER => - SessionPageType.SESSION_PAGE_TYPE_STRANGER, - SessionType.SESSION_TYPE_DUSTBIN => - SessionPageType.SESSION_PAGE_TYPE_DUSTBIN, - SessionType.SESSION_TYPE_CUSTOMER_FOLD => - SessionPageType.SESSION_PAGE_TYPE_CUSTOMER, - SessionType.SESSION_TYPE_AI_FOLD => - SessionPageType.SESSION_PAGE_TYPE_AI, - SessionType.SESSION_TYPE_CUSTOMER_ACCOUNT => - SessionPageType.SESSION_PAGE_TYPE_CUSTOMER, + .SESSION_TYPE_UNKNOWN => .SESSION_PAGE_TYPE_UNKNOWN, + .SESSION_TYPE_GROUP => .SESSION_PAGE_TYPE_GROUP, + .SESSION_TYPE_GROUP_FOLD => .SESSION_PAGE_TYPE_GROUP, + .SESSION_TYPE_UNFOLLOWED => .SESSION_PAGE_TYPE_UNFOLLOWED, + .SESSION_TYPE_STRANGER => .SESSION_PAGE_TYPE_STRANGER, + .SESSION_TYPE_DUSTBIN => .SESSION_PAGE_TYPE_DUSTBIN, + .SESSION_TYPE_CUSTOMER_FOLD => .SESSION_PAGE_TYPE_CUSTOMER, + .SESSION_TYPE_AI_FOLD => .SESSION_PAGE_TYPE_AI, + .SESSION_TYPE_CUSTOMER_ACCOUNT => .SESSION_PAGE_TYPE_CUSTOMER, _ => null, }; if (sessionPageType != null) { @@ -182,18 +229,18 @@ class WhisperSessionItem extends StatelessWidget { if (item.id.hasSystemId()) { switch (item.id.systemId.type) { - case SessionType.SESSION_TYPE_SYSTEM: + case .SESSION_TYPE_SYSTEM: Get.toNamed('/sysMsg'); - case SessionType.SESSION_TYPE_AI_FOLD: - case SessionType.SESSION_TYPE_CUSTOMER_ACCOUNT: - case SessionType.SESSION_TYPE_CUSTOMER_FOLD: - case SessionType.SESSION_TYPE_DUSTBIN: - case SessionType.SESSION_TYPE_GROUP: - case SessionType.SESSION_TYPE_GROUP_FOLD: - case SessionType.SESSION_TYPE_PRIVATE: - case SessionType.SESSION_TYPE_STRANGER: - case SessionType.SESSION_TYPE_UNFOLLOWED: - case SessionType.SESSION_TYPE_UNKNOWN: + case .SESSION_TYPE_AI_FOLD: + case .SESSION_TYPE_CUSTOMER_ACCOUNT: + case .SESSION_TYPE_CUSTOMER_FOLD: + case .SESSION_TYPE_DUSTBIN: + case .SESSION_TYPE_GROUP: + case .SESSION_TYPE_GROUP_FOLD: + case .SESSION_TYPE_PRIVATE: + case .SESSION_TYPE_STRANGER: + case .SESSION_TYPE_UNFOLLOWED: + case .SESSION_TYPE_UNKNOWN: SmartDialog.showToast(item.id.systemId.type.name); } } @@ -248,7 +295,7 @@ class WhisperSessionItem extends StatelessWidget { child: Text( item.sessionInfo.sessionName, maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, style: TextStyle( fontSize: 15, color: @@ -263,8 +310,8 @@ class WhisperSessionItem extends StatelessWidget { if (item.sessionInfo.userLabel.style.borderedLabel.hasText()) PBadge( isStack: false, - type: PBadgeType.line_secondary, - size: PBadgeSize.small, + type: .line_secondary, + size: .small, fontSize: 10, isBold: false, text: item.sessionInfo.userLabel.style.borderedLabel.text, @@ -274,7 +321,7 @@ class WhisperSessionItem extends StatelessWidget { Assets.livingRect, height: 15, cacheHeight: 15.cacheSize(context), - filterQuality: FilterQuality.low, + filterQuality: .low, ), ], ), @@ -295,7 +342,7 @@ class WhisperSessionItem extends StatelessWidget { child: Text( item.msgSummary.rawMsg, maxLines: 1, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, style: theme.textTheme.labelMedium!.copyWith( color: theme.colorScheme.outline, ), @@ -307,10 +354,9 @@ class WhisperSessionItem extends StatelessWidget { Icons.notifications_off, color: theme.colorScheme.outline, ) - else if (item.hasUnread() && - item.unread.style != UnreadStyle.UNREAD_STYLE_NONE) + else if (item.hasUnread() && item.unread.style != .UNREAD_STYLE_NONE) Badge( - label: item.unread.style == UnreadStyle.UNREAD_STYLE_NUMBER + label: item.unread.style == .UNREAD_STYLE_NUMBER ? Text(item.unread.number.toString()) : null, ),