update im ack

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-07-16 12:13:44 +08:00
parent a12223b42b
commit f7cd1aa502
3 changed files with 109 additions and 50 deletions

View File

@@ -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/interfaces/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart'; import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart';
import 'package:PiliPlus/grpc/grpc_req.dart'; import 'package:PiliPlus/grpc/grpc_req.dart';
@@ -212,4 +212,16 @@ abstract final class ImGrpc {
RspTotalUnread.fromBuffer, RspTotalUnread.fromBuffer,
); );
} }
static Future<LoadingState<SessionInfo>> sessionDetail({
Int64? talkerId,
int? sessionType,
Int64? uid,
}) {
return GrpcReq.request(
GrpcUrl.sessionDetail,
ReqSessionDetail(talkerId: talkerId, sessionType: sessionType, uid: uid),
SessionInfo.fromBuffer,
);
}
} }

View File

@@ -44,6 +44,7 @@ abstract final class GrpcUrl {
static const keywordBlockingDelete = '$im2/KeywordBlockingDelete'; static const keywordBlockingDelete = '$im2/KeywordBlockingDelete';
static const syncFetchSessionMsgs = '$im/SyncFetchSessionMsgs'; static const syncFetchSessionMsgs = '$im/SyncFetchSessionMsgs';
static const getTotalUnread = '$im/GetTotalUnread'; static const getTotalUnread = '$im/GetTotalUnread';
static const sessionDetail = '$im/SessionDetail';
// view // view
static const viewunite = '/bilibili.app.viewunite.v1.View'; static const viewunite = '/bilibili.app.viewunite.v1.View';

View File

@@ -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/flutter/list_tile.dart';
import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
show Session, SessionId, SessionPageType, SessionType, UnreadStyle; show Session, SessionId, SessionPageType;
import 'package:PiliPlus/models/common/badge_type.dart'; 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/pages/whisper_secondary/view.dart';
import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/date_utils.dart';
import 'package:PiliPlus/utils/extension/num_ext.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/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart' hide ListTile; import 'package:flutter/material.dart' hide ListTile;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -34,6 +37,28 @@ class WhisperSessionItem extends StatelessWidget {
final Function(bool isMuted, Int64 talkerUid) onSetMute; final Function(bool isMuted, Int64 talkerUid) onSetMute;
final ValueChanged<int> onRemove; final ValueChanged<int> onRemove;
Future<void> _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final resource = final resource =
@@ -46,7 +71,8 @@ class WhisperSessionItem extends StatelessWidget {
Map? vipInfo = item.sessionInfo.hasVipInfo() Map? vipInfo = item.sessionInfo.hasVipInfo()
? jsonDecode(item.sessionInfo.vipInfo) ? jsonDecode(item.sessionInfo.vipInfo)
: null; : null;
final ThemeData theme = Theme.of(context);
final theme = Theme.of(context);
return ListTile( return ListTile(
safeArea: true, safeArea: true,
@@ -57,9 +83,9 @@ class WhisperSessionItem extends StatelessWidget {
: null, : null,
onLongPress: () => showDialog( onLongPress: () => showDialog(
context: context, context: context,
builder: (context) => SimpleDialog( builder: (_) => SimpleDialog(
clipBehavior: Clip.hardEdge, clipBehavior: .hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12), contentPadding: const .symmetric(vertical: 12),
children: [ children: [
DialogOption( DialogOption(
onPressed: () { onPressed: () {
@@ -68,7 +94,15 @@ class WhisperSessionItem extends StatelessWidget {
}, },
child: Text(item.isPinned ? '移除置顶' : '置顶'), 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( DialogOption(
onPressed: () { onPressed: () {
Get.back(); Get.back();
@@ -76,7 +110,6 @@ class WhisperSessionItem extends StatelessWidget {
}, },
child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'), child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'),
), ),
if (item.id.privateId.hasTalkerUid())
DialogOption( DialogOption(
onPressed: () { onPressed: () {
Get.back(); Get.back();
@@ -89,6 +122,7 @@ class WhisperSessionItem extends StatelessWidget {
}, },
child: const Text('删除'), child: const Text('删除'),
), ),
],
], ],
), ),
), ),
@@ -96,20 +130,38 @@ class WhisperSessionItem extends StatelessWidget {
? (details) => showMenu( ? (details) => showMenu(
context: context, context: context,
position: PageUtils.menuPosition(details.globalPosition), position: PageUtils.menuPosition(details.globalPosition),
items: [ items: <PopupMenuEntry<Never>>[
PopupMenuItem( PopupMenuItem(
height: 42, height: 42,
onTap: () => onSetTop(item.isPinned, item.id), onTap: () => onSetTop(item.isPinned, item.id),
child: Text(item.isPinned ? '移除置顶' : '置顶'), 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( PopupMenuItem(
height: 42, height: 42,
onTap: () => onTap: () =>
onSetMute(item.isMuted, item.id.privateId.talkerUid), onSetMute(item.isMuted, item.id.privateId.talkerUid),
child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'), child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'),
), ),
if (item.id.privateId.hasTalkerUid()) const PopupMenuDivider(height: 10),
PopupMenuItem( PopupMenuItem(
height: 42, height: 42,
onTap: () => showConfirmDialog( onTap: () => showConfirmDialog(
@@ -118,8 +170,12 @@ class WhisperSessionItem extends StatelessWidget {
onConfirm: () => onConfirm: () =>
onRemove(item.id.privateId.talkerUid.toInt()), onRemove(item.id.privateId.talkerUid.toInt()),
), ),
child: const Text('删除'), child: Text(
'删除',
style: TextStyle(color: theme.colorScheme.error),
),
), ),
],
], ],
) )
: null, : null,
@@ -147,24 +203,15 @@ class WhisperSessionItem extends StatelessWidget {
if (item.id.foldId.hasType()) { if (item.id.foldId.hasType()) {
SessionPageType? sessionPageType = switch (item.id.foldId.type) { SessionPageType? sessionPageType = switch (item.id.foldId.type) {
SessionType.SESSION_TYPE_UNKNOWN => .SESSION_TYPE_UNKNOWN => .SESSION_PAGE_TYPE_UNKNOWN,
SessionPageType.SESSION_PAGE_TYPE_UNKNOWN, .SESSION_TYPE_GROUP => .SESSION_PAGE_TYPE_GROUP,
SessionType.SESSION_TYPE_GROUP => .SESSION_TYPE_GROUP_FOLD => .SESSION_PAGE_TYPE_GROUP,
SessionPageType.SESSION_PAGE_TYPE_GROUP, .SESSION_TYPE_UNFOLLOWED => .SESSION_PAGE_TYPE_UNFOLLOWED,
SessionType.SESSION_TYPE_GROUP_FOLD => .SESSION_TYPE_STRANGER => .SESSION_PAGE_TYPE_STRANGER,
SessionPageType.SESSION_PAGE_TYPE_GROUP, .SESSION_TYPE_DUSTBIN => .SESSION_PAGE_TYPE_DUSTBIN,
SessionType.SESSION_TYPE_UNFOLLOWED => .SESSION_TYPE_CUSTOMER_FOLD => .SESSION_PAGE_TYPE_CUSTOMER,
SessionPageType.SESSION_PAGE_TYPE_UNFOLLOWED, .SESSION_TYPE_AI_FOLD => .SESSION_PAGE_TYPE_AI,
SessionType.SESSION_TYPE_STRANGER => .SESSION_TYPE_CUSTOMER_ACCOUNT => .SESSION_PAGE_TYPE_CUSTOMER,
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,
_ => null, _ => null,
}; };
if (sessionPageType != null) { if (sessionPageType != null) {
@@ -182,18 +229,18 @@ class WhisperSessionItem extends StatelessWidget {
if (item.id.hasSystemId()) { if (item.id.hasSystemId()) {
switch (item.id.systemId.type) { switch (item.id.systemId.type) {
case SessionType.SESSION_TYPE_SYSTEM: case .SESSION_TYPE_SYSTEM:
Get.toNamed('/sysMsg'); Get.toNamed('/sysMsg');
case SessionType.SESSION_TYPE_AI_FOLD: case .SESSION_TYPE_AI_FOLD:
case SessionType.SESSION_TYPE_CUSTOMER_ACCOUNT: case .SESSION_TYPE_CUSTOMER_ACCOUNT:
case SessionType.SESSION_TYPE_CUSTOMER_FOLD: case .SESSION_TYPE_CUSTOMER_FOLD:
case SessionType.SESSION_TYPE_DUSTBIN: case .SESSION_TYPE_DUSTBIN:
case SessionType.SESSION_TYPE_GROUP: case .SESSION_TYPE_GROUP:
case SessionType.SESSION_TYPE_GROUP_FOLD: case .SESSION_TYPE_GROUP_FOLD:
case SessionType.SESSION_TYPE_PRIVATE: case .SESSION_TYPE_PRIVATE:
case SessionType.SESSION_TYPE_STRANGER: case .SESSION_TYPE_STRANGER:
case SessionType.SESSION_TYPE_UNFOLLOWED: case .SESSION_TYPE_UNFOLLOWED:
case SessionType.SESSION_TYPE_UNKNOWN: case .SESSION_TYPE_UNKNOWN:
SmartDialog.showToast(item.id.systemId.type.name); SmartDialog.showToast(item.id.systemId.type.name);
} }
} }
@@ -248,7 +295,7 @@ class WhisperSessionItem extends StatelessWidget {
child: Text( child: Text(
item.sessionInfo.sessionName, item.sessionInfo.sessionName,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
color: color:
@@ -263,8 +310,8 @@ class WhisperSessionItem extends StatelessWidget {
if (item.sessionInfo.userLabel.style.borderedLabel.hasText()) if (item.sessionInfo.userLabel.style.borderedLabel.hasText())
PBadge( PBadge(
isStack: false, isStack: false,
type: PBadgeType.line_secondary, type: .line_secondary,
size: PBadgeSize.small, size: .small,
fontSize: 10, fontSize: 10,
isBold: false, isBold: false,
text: item.sessionInfo.userLabel.style.borderedLabel.text, text: item.sessionInfo.userLabel.style.borderedLabel.text,
@@ -274,7 +321,7 @@ class WhisperSessionItem extends StatelessWidget {
Assets.livingRect, Assets.livingRect,
height: 15, height: 15,
cacheHeight: 15.cacheSize(context), cacheHeight: 15.cacheSize(context),
filterQuality: FilterQuality.low, filterQuality: .low,
), ),
], ],
), ),
@@ -295,7 +342,7 @@ class WhisperSessionItem extends StatelessWidget {
child: Text( child: Text(
item.msgSummary.rawMsg, item.msgSummary.rawMsg,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: theme.textTheme.labelMedium!.copyWith( style: theme.textTheme.labelMedium!.copyWith(
color: theme.colorScheme.outline, color: theme.colorScheme.outline,
), ),
@@ -307,10 +354,9 @@ class WhisperSessionItem extends StatelessWidget {
Icons.notifications_off, Icons.notifications_off,
color: theme.colorScheme.outline, color: theme.colorScheme.outline,
) )
else if (item.hasUnread() && else if (item.hasUnread() && item.unread.style != .UNREAD_STYLE_NONE)
item.unread.style != UnreadStyle.UNREAD_STYLE_NONE)
Badge( Badge(
label: item.unread.style == UnreadStyle.UNREAD_STYLE_NUMBER label: item.unread.style == .UNREAD_STYLE_NUMBER
? Text(item.unread.number.toString()) ? Text(item.unread.number.toString())
: null, : null,
), ),