diff --git a/lib/http/live.dart b/lib/http/live.dart index 6cec38d6a..1fcaa9601 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -37,10 +37,15 @@ abstract final class LiveHttp { required Object msg, Object? dmType, Object? emoticonOptions, + int replyMid = 0, + String replayDmid = '', }) async { String csrf = Accounts.main.csrf; final res = await Request().post( Api.sendLiveMsg, + queryParameters: await WbiSign.makSign({ + 'web_location': 444.8, + }), data: FormData.fromMap({ 'bubble': 0, 'msg': msg, @@ -52,10 +57,10 @@ abstract final class LiveHttp { else ...{ 'room_type': 0, 'jumpfrom': 0, - 'reply_mid': 0, + 'reply_mid': replyMid, 'reply_attr': 0, - 'replay_dmid': '', - 'statistics': Constants.statistics, + 'replay_dmid': replayDmid, + 'statistics': '{"appId":100,"platform":5}', 'reply_type': 0, 'reply_uname': '', }, diff --git a/lib/models_new/live/live_danmaku/danmaku_msg.dart b/lib/models_new/live/live_danmaku/danmaku_msg.dart index 229595aea..4e7a8d764 100644 --- a/lib/models_new/live/live_danmaku/danmaku_msg.dart +++ b/lib/models_new/live/live_danmaku/danmaku_msg.dart @@ -1,30 +1,60 @@ +import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/live_emote.dart'; +import 'package:PiliPlus/pages/danmaku/danmaku_model.dart'; class DanmakuMsg { - late String name; - late Object uid; - late String text; - Map? emots; - BaseEmote? uemote; + final String name; + final Object uid; + final String text; + final Map? emots; + final BaseEmote? uemote; + final LiveDanmaku extra; + final Owner? reply; - DanmakuMsg({ + const DanmakuMsg({ required this.name, required this.uid, required this.text, this.emots, this.uemote, + required this.extra, + this.reply, }); - DanmakuMsg.fromPrefetch(Map obj) { + factory DanmakuMsg.fromPrefetch(Map obj) { final user = obj['user']; - name = user['base']['name']; - uid = user['uid']; - text = obj['text']; - emots = (obj['emots'] as Map?)?.map( - (k, v) => MapEntry(k, BaseEmote.fromJson(v)), - ); + final uid = user['uid']; + BaseEmote? uemote; if ((obj['emoticon']?['emoticon_unique'] as String?)?.isNotEmpty == true) { uemote = BaseEmote.fromJson(obj['emoticon']); } + final checkInfo = obj['check_info']; + Owner? reply; + if (obj['reply'] case final Map map) { + final replyMid = map['reply_mid']; + if (replyMid != null && replyMid != 0) { + reply = Owner( + mid: replyMid, + name: map['reply_uname'], + ); + } + } + return DanmakuMsg( + name: user['base']['name'], + uid: uid, + text: obj['text'], + emots: (obj['emots'] as Map?)?.map( + (k, v) => MapEntry(k, BaseEmote.fromJson(v)), + ), + uemote: uemote, + extra: LiveDanmaku( + id: obj['id_str'], + mid: uid, + dmType: obj['dm_type'], + ts: checkInfo['ts'], + ct: checkInfo['ct'], + ), + reply: reply, + ); } } diff --git a/lib/pages/live_dm_block/controller.dart b/lib/pages/live_dm_block/controller.dart index a233e89c0..b3ed47529 100644 --- a/lib/pages/live_dm_block/controller.dart +++ b/lib/pages/live_dm_block/controller.dart @@ -41,10 +41,10 @@ class LiveDmBlockController extends GetxController updateValue(); if (response?.keywordList case final keywordList?) { - keywordList.addAll(keywordList); + this.keywordList.addAll(keywordList); } if (response?.shieldUserList case final shieldUserList?) { - shieldUserList.addAll(shieldUserList); + this.shieldUserList.addAll(shieldUserList); } } else { res.toast(); diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 3675f650d..f2ba1353f 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/super_chat_type.dart'; import 'package:PiliPlus/models/common/video/live_quality.dart'; +import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/danmaku_msg.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/live_emote.dart'; import 'package:PiliPlus/models_new/live/live_dm_info/data.dart'; @@ -441,6 +442,22 @@ class LiveRoomController extends GetxController { if (first[13] case Map map) { uemote = BaseEmote.fromJson(map); } + final checkInfo = info[9]; + final liveExtra = LiveDanmaku( + id: extra['id_str'], + mid: uid, + dmType: extra['dm_type'], + ts: checkInfo['ts'], + ct: checkInfo['ct'], + ); + Owner? reply; + final replyMid = extra['reply_mid']; + if (replyMid != null && replyMid != 0) { + reply = Owner( + mid: replyMid, + name: extra['reply_uname'], + ); + } messages.add( DanmakuMsg( name: name, @@ -450,11 +467,12 @@ class LiveRoomController extends GetxController { (k, v) => MapEntry(k, BaseEmote.fromJson(v)), ), uemote: uemote, + extra: liveExtra, + reply: reply, ), ); if (plPlayerController.showDanmaku) { - final checkInfo = info[9]; danmakuController?.addDanmaku( DanmakuContentItem( msg, @@ -464,13 +482,7 @@ class LiveRoomController extends GetxController { type: DmUtils.getPosition(extra['mode']), // extra['send_from_me'] is invalid selfSend: isLogin && uid == mid, - extra: LiveDanmaku( - id: extra['id_str'], - mid: uid, - dmType: extra['dm_type'], - ts: checkInfo['ts'], - ct: checkInfo['ct'], - ), + extra: liveExtra, ), ); if (!disableAutoScroll.value) { diff --git a/lib/pages/live_room/send_danmaku/view.dart b/lib/pages/live_room/send_danmaku/view.dart index b313c283a..0a85e86fc 100644 --- a/lib/pages/live_room/send_danmaku/view.dart +++ b/lib/pages/live_room/send_danmaku/view.dart @@ -160,11 +160,27 @@ class _ReplyPageState extends CommonRichTextPubPageState { int? dmType, emoticonOptions, }) async { + int replyMid = 0; + String replyDmid = ''; + if (message == null) { + final buffer = StringBuffer(); + for (final e in editController.items) { + if (e.type == .at) { + replyMid = int.parse(e.rawText); + replyDmid = e.id!; + } else { + buffer.write(e.rawText); + } + } + message = buffer.toString(); + } final res = await LiveHttp.sendLiveMsg( roomId: liveRoomController.roomId, - msg: message ?? editController.rawText, + msg: message, dmType: dmType, emoticonOptions: emoticonOptions, + replyMid: replyMid, + replayDmid: replyDmid, ); if (res.isSuccess) { hasPub = true; diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 74ebe9189..5a63b4bb4 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; +import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; @@ -725,6 +726,16 @@ class _LiveRoomPageState extends State isPP: isPP, roomId: _liveRoomController.roomId, liveRoomController: _liveRoomController, + onAtUser: (item) => _liveRoomController + ..savedDanmaku = [ + RichTextItem.fromStart( + '@${item.name} ', + rawText: item.uid.toString(), + type: .at, + id: item.extra.id.toString(), + ), + ] + ..onSendDanmaku(), ); return Padding( padding: EdgeInsets.only(bottom: 12, top: isPortrait ? 12 : 0), diff --git a/lib/pages/live_room/widgets/chat_panel.dart b/lib/pages/live_room/widgets/chat_panel.dart index 18fcfa584..1a0aaa838 100644 --- a/lib/pages/live_room/widgets/chat_panel.dart +++ b/lib/pages/live_room/widgets/chat_panel.dart @@ -1,12 +1,17 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/danmaku_msg.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/live_room/controller.dart'; +import 'package:PiliPlus/pages/video/widgets/header_control.dart'; +import 'package:PiliPlus/utils/accounts.dart'; +import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class LiveRoomChatPanel extends StatelessWidget { @@ -14,12 +19,15 @@ class LiveRoomChatPanel extends StatelessWidget { super.key, required this.roomId, required this.liveRoomController, - this.isPP = false, + required this.isPP, + required this.onAtUser, }); final int roomId; final LiveRoomController liveRoomController; final bool isPP; + final ValueChanged onAtUser; + bool get disableAutoScroll => liveRoomController.disableAutoScroll.value; @override @@ -31,6 +39,10 @@ class LiveRoomChatPanel extends StatelessWidget { ? Colors.white.withValues(alpha: 0.9) : Colors.white.withValues(alpha: 0.6); late final devicePixelRatio = MediaQuery.devicePixelRatioOf(context); + late final colorScheme = ColorScheme.of(context); + late final primary = colorScheme.isDark + ? colorScheme.primary + : colorScheme.inversePrimary; return Stack( children: [ Obx( @@ -67,8 +79,16 @@ class LiveRoomChatPanel extends StatelessWidget { ? null : (TapGestureRecognizer() ..onTap = () => - Get.toNamed('/member?mid=${item.uid}')), + _showMsgDialog(context, item)), ), + if (item.reply case final reply?) + TextSpan( + text: '@${reply.name} ', + style: TextStyle(color: primary, fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () => + Get.toNamed('/member?mid=${reply.mid}'), + ), _buildMsg(devicePixelRatio, item), ], ), @@ -252,4 +272,83 @@ class LiveRoomChatPanel extends StatelessWidget { ); } } + + void _showMsgDialog(BuildContext context, DanmakuMsg item) { + showDialog( + context: context, + builder: (context) => SimpleDialog( + clipBehavior: .hardEdge, + contentPadding: const .symmetric(vertical: 12), + constraints: const BoxConstraints(minWidth: 280, maxWidth: 320), + title: Column( + spacing: 4, + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + Text( + item.name, + style: const TextStyle(fontSize: 15), + ), + Text( + item.text, + style: TextStyle( + fontSize: 13, + color: ColorScheme.of(context).outline, + ), + ), + ], + ), + children: [ + ListTile( + dense: true, + onTap: () { + Get + ..back() + ..toNamed('/member?mid=${item.uid}'); + }, + title: const Text('去TA的个人空间', style: TextStyle(fontSize: 14)), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + onAtUser(item); + }, + title: const Text('@TA', style: TextStyle(fontSize: 14)), + ), + ListTile( + dense: true, + title: const Text('屏蔽发送者', style: TextStyle(fontSize: 14)), + onTap: () async { + Get.back(); + if (!Accounts.main.isLogin) return; + final res = await LiveHttp.liveShieldUser( + uid: item.uid, + roomid: roomId, + type: 1, + ); + if (res.isSuccess) { + SmartDialog.showToast('屏蔽成功'); + } else { + res.toast(); + } + }, + ), + ListTile( + dense: true, + title: const Text('举报选中弹幕', style: TextStyle(fontSize: 14)), + onTap: () { + Get.back(); + HeaderControl.reportLiveDanmaku( + context, + roomId: roomId, + msg: item.text, + extra: item.extra, + ); + }, + ), + ], + ), + ); + } } diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 95a9b1324..3635a648a 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -278,7 +278,6 @@ class HeaderControl extends StatefulWidget { required int roomId, required String msg, required LiveDanmaku extra, - required PlPlayerController ctr, }) { if (Accounts.main.isLogin) { return autoWrapReportDialog( diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index c18b9a573..59f597252 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -2476,7 +2476,6 @@ class _PLVideoPlayerState extends State .roomId, msg: item.content.text, extra: extra, - ctr: plPlayerController, ), ), ],