feat: 启用私信功能(初步)

This commit is contained in:
orz12
2024-06-17 01:19:23 +08:00
parent 052f81cf10
commit ff3484e4b5
7 changed files with 146 additions and 100 deletions

View File

@@ -1,4 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:dio/dio.dart';
import '../models/msg/account.dart'; import '../models/msg/account.dart';
import '../models/msg/session.dart'; import '../models/msg/session.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
@@ -6,7 +8,6 @@ import 'api.dart';
import 'init.dart'; import 'init.dart';
class MsgHttp { class MsgHttp {
static Future msgFeedReplyMe({int cursor = -1, int cursorTime = -1}) async { static Future msgFeedReplyMe({int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedReply, data: { var res = await Request().get(Api.msgFeedReply, data: {
'id': cursor == -1 ? null : cursor, 'id': cursor == -1 ? null : cursor,
@@ -25,6 +26,7 @@ class MsgHttp {
}; };
} }
} }
static Future msgFeedAtMe({int cursor = -1, int cursorTime = -1}) async { static Future msgFeedAtMe({int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedAt, data: { var res = await Request().get(Api.msgFeedAt, data: {
'id': cursor == -1 ? null : cursor, 'id': cursor == -1 ? null : cursor,
@@ -43,6 +45,7 @@ class MsgHttp {
}; };
} }
} }
static Future msgFeedLikeMe({int cursor = -1, int cursorTime = -1}) async { static Future msgFeedLikeMe({int cursor = -1, int cursorTime = -1}) async {
var res = await Request().get(Api.msgFeedLike, data: { var res = await Request().get(Api.msgFeedLike, data: {
'id': cursor == -1 ? null : cursor, 'id': cursor == -1 ? null : cursor,
@@ -61,6 +64,7 @@ class MsgHttp {
}; };
} }
} }
static Future msgFeedSysUserNotify() async { static Future msgFeedSysUserNotify() async {
String csrf = await Request.getCsrf(); String csrf = await Request.getCsrf();
var res = await Request().get(Api.msgSysUserNotify, data: { var res = await Request().get(Api.msgSysUserNotify, data: {
@@ -81,6 +85,7 @@ class MsgHttp {
}; };
} }
} }
static Future msgFeedSysUnifiedNotify() async { static Future msgFeedSysUnifiedNotify() async {
String csrf = await Request.getCsrf(); String csrf = await Request.getCsrf();
var res = await Request().get(Api.msgSysUnifiedNotify, data: { var res = await Request().get(Api.msgSysUnifiedNotify, data: {
@@ -136,6 +141,7 @@ class MsgHttp {
}; };
} }
} }
// 会话列表 // 会话列表
static Future sessionList({int? endTs}) async { static Future sessionList({int? endTs}) async {
Map<String, dynamic> params = { Map<String, dynamic> params = {
@@ -271,7 +277,7 @@ class MsgHttp {
dynamic content, dynamic content,
}) async { }) async {
String csrf = await Request.getCsrf(); String csrf = await Request.getCsrf();
Map<String, dynamic> params = await WbiSign().makSign({ Map<String, dynamic> base = {
'msg[sender_uid]': senderUid, 'msg[sender_uid]': senderUid,
'msg[receiver_id]': receiverId, 'msg[receiver_id]': receiverId,
'msg[receiver_type]': receiverType ?? 1, 'msg[receiver_type]': receiverType ?? 1,
@@ -286,21 +292,17 @@ class MsgHttp {
'mobi_app': 'web', 'mobi_app': 'web',
'csrf_token': csrf, 'csrf_token': csrf,
'csrf': csrf, 'csrf': csrf,
}); };
var res = Map<String, dynamic> params = await WbiSign().makSign(base);
await Request().post(Api.sendMsg, queryParameters: <String, dynamic>{ var res = await Request().post(Api.sendMsg,
...params, queryParameters: <String, dynamic>{
'csrf_token': csrf,
'csrf': csrf,
}, data: {
'w_sender_uid': params['msg[sender_uid]'], 'w_sender_uid': params['msg[sender_uid]'],
'w_receiver_id': params['msg[receiver_id]'], 'w_receiver_id': params['msg[receiver_id]'],
'w_dev_id': params['msg[dev_id]'], 'w_dev_id': params['msg[dev_id]'],
'w_rid': params['w_rid'], 'w_rid': params['w_rid'],
'wts': params['wts'], 'wts': params['wts'],
'csrf_token': csrf, },
'csrf': csrf, data: FormData.fromMap(base));
});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {
'status': true, 'status': true,

View File

@@ -195,6 +195,7 @@ class MessageItem {
this.msgStatus, this.msgStatus,
this.notifyCode, this.notifyCode,
this.newFaceVersion, this.newFaceVersion,
this.msgSource,
}); });
int? senderUid; int? senderUid;
@@ -209,6 +210,7 @@ class MessageItem {
int? msgStatus; int? msgStatus;
String? notifyCode; String? notifyCode;
int? newFaceVersion; int? newFaceVersion;
int? msgSource;
MessageItem.fromJson(Map<String, dynamic> json) { MessageItem.fromJson(Map<String, dynamic> json) {
senderUid = json['sender_uid']; senderUid = json['sender_uid'];
@@ -226,5 +228,6 @@ class MessageItem {
msgStatus = json['msg_status']; msgStatus = json['msg_status'];
notifyCode = json['notify_code']; notifyCode = json['notify_code'];
newFaceVersion = json['new_face_version']; newFaceVersion = json['new_face_version'];
msgSource = json['msg_source'];
} }
} }

View File

@@ -202,7 +202,17 @@ class ProfilePanel extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: TextButton( child: TextButton(
onPressed: () {}, onPressed: () {
Get.toNamed(
'/whisperDetail',
parameters: {
'talkerId': ctr.mid.toString(),
'name': memberInfo.name!,
'face': memberInfo.face!,
'mid': ctr.mid.toString(),
},
);
},
style: TextButton.styleFrom( style: TextButton.styleFrom(
backgroundColor: Theme.of(context) backgroundColor: Theme.of(context)
.colorScheme .colorScheme

View File

@@ -238,7 +238,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
width: double.infinity, width: double.infinity,
height: toolbarType == 'input' ? keyboardHeight : emoteHeight, height: toolbarType == 'input' ? keyboardHeight : emoteHeight,
child: EmotePanel( child: EmotePanel(
onChoose: (package, emote) => onChooseEmote(package, emote), onChoose: onChooseEmote,
), ),
), ),
if (toolbarType == 'input' && keyboardHeight == 0.0) if (toolbarType == 'input' && keyboardHeight == 0.0)

View File

@@ -153,6 +153,17 @@ class _WhisperPageState extends State<WhisperPage> {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (_, int i) { itemBuilder: (_, int i) {
dynamic content =
sessionList[i].lastMsg.content;
if (content == null || content == "") {
content = '不支持的消息类型';
} else {
content = content['text'] ??
content['content'] ??
content['title'] ??
content['reply_content'] ??
content.toString();
}
return ListTile( return ListTile(
onTap: () { onTap: () {
setState(() { setState(() {
@@ -195,28 +206,7 @@ class _WhisperPageState extends State<WhisperPage> {
), ),
title: title:
Text(sessionList[i].accountInfo.name), Text(sessionList[i].accountInfo.name),
subtitle: Text( subtitle: Text(content,
sessionList[i].lastMsg.content !=
null &&
sessionList[i]
.lastMsg
.content !=
''
? (sessionList[i]
.lastMsg
.content['text'] ??
sessionList[i]
.lastMsg
.content['content'] ??
sessionList[i]
.lastMsg
.content['title'] ??
sessionList[i]
.lastMsg
.content[
'reply_content']) ??
sessionList[i].lastMsg.content
: '不支持的消息类型',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context) style: Theme.of(context)

View File

@@ -32,7 +32,15 @@ class WhisperDetailController extends GetxController {
if (res['status']) { if (res['status']) {
messageList.value = res['data'].messages; messageList.value = res['data'].messages;
if (messageList.isNotEmpty) { if (messageList.isNotEmpty) {
if (messageList.length == 1 &&
messageList.last.msgType == 18 &&
messageList.last.msgSource == 18) {
// print(messageList.last);
// print(messageList.last.content);
//{content: [{"text":"对方主动回复或关注你前最多发送1条消息","color_day":"#9499A0","color_nig":"#9499A0"}]}
} else {
ackSessionMsg(); ackSessionMsg();
}
if (res['data'].eInfos != null) { if (res['data'].eInfos != null) {
eInfos = res['data'].eInfos; eInfos = res['data'].eInfos;
} }
@@ -68,13 +76,20 @@ class WhisperDetailController extends GetxController {
SmartDialog.showToast('请输入内容'); SmartDialog.showToast('请输入内容');
return; return;
} }
if (mid == null) {
SmartDialog.showToast('这里不能发');
return;
}
var result = await MsgHttp.sendMsg( var result = await MsgHttp.sendMsg(
senderUid: userInfo.mid, senderUid: userInfo.mid,
receiverId: int.parse(mid), receiverId: int.parse(mid),
content: {'content': message}, content: '{"content":"$message"}',
msgType: 1, msgType: 1,
); );
if (result['status']) { if (result['status']) {
print(result['data']);
querySessionMsg();
replyContentController.text = "";
SmartDialog.showToast('发送成功'); SmartDialog.showToast('发送成功');
} else { } else {
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);

View File

@@ -7,6 +7,7 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/pages/emote/index.dart'; import 'package:PiliPalaX/pages/emote/index.dart';
import 'package:PiliPalaX/pages/whisper_detail/controller.dart'; import 'package:PiliPalaX/pages/whisper_detail/controller.dart';
import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/models/video/reply/emote.dart';
import '../../utils/storage.dart'; import '../../utils/storage.dart';
import 'widget/chat_item.dart'; import 'widget/chat_item.dart';
@@ -26,7 +27,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
final _debouncer = Debouncer(milliseconds: 200); // 设置延迟时间 final _debouncer = Debouncer(milliseconds: 200); // 设置延迟时间
late double emoteHeight = 0.0; late double emoteHeight = 0.0;
double keyboardHeight = 0.0; // 键盘高度 double keyboardHeight = 0.0; // 键盘高度
String toolbarType = 'input'; String toolbarType = 'none';
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
@override @override
@@ -35,15 +36,19 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_whisperDetailController.querySessionMsg(); _whisperDetailController.querySessionMsg();
_replyContentController = _whisperDetailController.replyContentController; _replyContentController = _whisperDetailController.replyContentController;
_focuslistener(); _focusListener();
} }
_focuslistener() { _focusListener() {
replyContentFocusNode.addListener(() { replyContentFocusNode.addListener(() {
if (replyContentFocusNode.hasFocus) { if (replyContentFocusNode.hasFocus) {
setState(() { setState(() {
toolbarType = 'input'; toolbarType = 'input';
}); });
} else if (toolbarType == 'input') {
setState(() {
toolbarType = 'none';
});
} }
}); });
} }
@@ -51,8 +56,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
@override @override
void didChangeMetrics() { void didChangeMetrics() {
super.didChangeMetrics(); super.didChangeMetrics();
final String routePath = Get.currentRoute; if (!mounted) return;
if (mounted && routePath.startsWith('/whisper_detail')) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
// 键盘高度 // 键盘高度
@@ -61,15 +65,17 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
_debouncer.run(() { _debouncer.run(() {
if (!mounted) return; if (!mounted) return;
if (keyboardHeight == 0) { if (keyboardHeight == 0) {
setState(() {
emoteHeight = keyboardHeight = emoteHeight = keyboardHeight =
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight; keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
}); if (emoteHeight == 0 || emoteHeight < keyboardHeight) {
emoteHeight = keyboardHeight;
}
if (emoteHeight < 200) emoteHeight = 200;
setState(() {});
} }
}); });
}); });
} }
}
@override @override
void dispose() { void dispose() {
@@ -79,6 +85,20 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
super.dispose(); super.dispose();
} }
void onChooseEmote(Packages package, Emote emote) {
int cursorPosition = _replyContentController.selection.baseOffset;
if (cursorPosition == -1) cursorPosition = 0;
final String currentText = _replyContentController.text;
final String newText = currentText.substring(0, cursorPosition) +
emote.text! +
currentText.substring(cursorPosition);
_replyContentController.value = TextEditingValue(
text: newText,
selection:
TextSelection.collapsed(offset: cursorPosition + emote.text!.length),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -147,10 +167,10 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
), ),
body: GestureDetector( body: GestureDetector(
onTap: () { onTap: () {
FocusScope.of(context).unfocus();
setState(() { setState(() {
keyboardHeight = 0; toolbarType = 'none';
}); });
FocusScope.of(context).unfocus();
}, },
child: Obx(() { child: Obx(() {
List messageList = _whisperDetailController.messageList; List messageList = _whisperDetailController.messageList;
@@ -159,7 +179,9 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
} }
return ListView.builder( return RefreshIndicator(
onRefresh: _whisperDetailController.querySessionMsg,
child: ListView.builder(
itemCount: messageList.length, itemCount: messageList.length,
shrinkWrap: true, shrinkWrap: true,
reverse: true, reverse: true,
@@ -168,17 +190,22 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
item: messageList[i], item: messageList[i],
e_infos: _whisperDetailController.eInfos); e_infos: _whisperDetailController.eInfos);
}, },
); padding: const EdgeInsets.only(bottom: 20),
));
}), }),
), ),
// resizeToAvoidBottomInset: true, // resizeToAvoidBottomInset: true,
bottomNavigationBar: Container( bottomNavigationBar: Container(
width: double.infinity, width: double.infinity,
height: MediaQuery.of(context).padding.bottom + 70 + keyboardHeight, height: MediaQuery.of(context).padding.bottom +
70 +
(toolbarType == 'none'
? 0
: (toolbarType == 'input' ? keyboardHeight : emoteHeight)),
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 8, left: 8,
right: 12, right: 12,
top: 12, top: 10,
bottom: MediaQuery.of(context).padding.bottom, bottom: MediaQuery.of(context).padding.bottom,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -205,16 +232,19 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
IconButton( IconButton(
tooltip: '表情', tooltip: '表情',
onPressed: () { onPressed: () {
// if (toolbarType == 'input') { if (emoteHeight < 200) emoteHeight = 200;
// setState(() { if (toolbarType != 'emote') {
// toolbarType = 'emote'; setState(() {
// }); toolbarType = 'emote';
// } });
// FocusScope.of(context).unfocus(); }
FocusScope.of(context).unfocus();
}, },
icon: Icon( icon: Icon(
Icons.emoji_emotions_outlined, Icons.emoji_emotions,
color: Theme.of(context).colorScheme.outline, color: toolbarType == 'emote'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
), ),
), ),
Expanded( Expanded(
@@ -228,16 +258,15 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
borderRadius: BorderRadius.circular(40.0), borderRadius: BorderRadius.circular(40.0),
), ),
child: Semantics( child: Semantics(
label: '私信输入框(开发中)', label: '私信输入框',
child: TextField( child: TextField(
readOnly: true,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
controller: _replyContentController, controller: _replyContentController,
autofocus: false, autofocus: false,
focusNode: replyContentFocusNode, focusNode: replyContentFocusNode,
decoration: const InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, // 移除默认边框 border: InputBorder.none, // 移除默认边框
hintText: '开发中 ...', // 提示文本 hintText: '发个消息聊聊呗~', // 提示文本
contentPadding: EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 12.0), // 内边距 horizontal: 16.0, vertical: 12.0), // 内边距
), ),
@@ -246,25 +275,22 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
), ),
IconButton( IconButton(
tooltip: '发送', tooltip: '发送',
// onPressed: _whisperDetailController.sendMsg, onPressed: _whisperDetailController.sendMsg,
onPressed: null,
icon: Icon( icon: Icon(
Icons.send, Icons.send,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.primary,
), ),
), ),
// const SizedBox(width: 16), // const SizedBox(width: 16),
], ],
), ),
AnimatedSize( SizedBox(
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 300),
child: SizedBox(
width: double.infinity, width: double.infinity,
height: toolbarType == 'input' ? keyboardHeight : emoteHeight, height: toolbarType == 'none'
? 0
: (toolbarType == 'input' ? keyboardHeight : emoteHeight),
child: EmotePanel( child: EmotePanel(
onChoose: (package, emote) => {}, onChoose: onChooseEmote,
),
), ),
), ),
], ],