feat:增加消息记录功能

This commit is contained in:
2026-06-19 21:10:03 +08:00
parent bb1160ad18
commit 5d12377606
23 changed files with 458 additions and 170 deletions

View File

@@ -1,27 +1,22 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
import 'package:sweet_chat_app/models/ImMessage.dart';
import 'package:sweet_chat_app/apis/message_api.dart';
import 'package:sweet_chat_app/models/chat_message.dart';
import 'package:sweet_chat_app/models/contact_info.dart';
import 'package:sweet_chat_app/models/im_message.dart';
import 'package:sweet_chat_app/services/webSocket_service.dart';
import 'package:sweet_chat_app/utils/SpUtil.dart';
class ChatMessage {
final String text;
final bool isMe;
final String? avatar;
ChatMessage({required this.text, required this.isMe, this.avatar});
}
import 'package:sweet_chat_app/utils/sp_utils.dart';
class ChatPage extends StatefulWidget {
final int contractId;
final String contactName;
final String avatarUrl;
final ContactInfo contactInfo;
final int conversationId;
const ChatPage({
super.key,
required this.contractId,
required this.contactName,
required this.avatarUrl,
required this.contactInfo,
required this.conversationId,
});
@override
@@ -29,79 +24,93 @@ class ChatPage extends StatefulWidget {
}
class _ChatPageState extends State<ChatPage> {
final TextEditingController _controller = TextEditingController();
late StreamSubscription<String> _msgSub;
final TextEditingController _textController = TextEditingController();
final ScrollController _scrollController = ScrollController();
late int userId;
late String avatarUrl;
late List<ChatMessage> _messages;
@override
void initState() {
super.initState();
_messages = [];
userId = SpUtil.getInt('userId') ?? 0;
final online = ImMessage(
type: "ONLINE",
from: userId.toString(),
to: 'SYSTEM',
content: '',
);
avatarUrl = SpUtil.getString('avatarUrl') ?? '';
_messages = [];
_loadChatMessage();
WebSocketService().connect('ws://172.29.100.146:5050?userId=$userId');
WebSocketService().send(online.toJsonString());
WebSocketService().messages.listen((wsMsg) {
final ImMessage imMsg = ImMessage.fromJsonString(wsMsg);
setState(() {
if (imMsg.type.contains('CHAT')) {
_messages.add(
ChatMessage(
text: imMsg.content,
isMe: userId.toString() == imMsg.from,
avatar: widget.avatarUrl,
),
);
}
});
_controller.clear();
_msgSub = WebSocketService().messages.listen((websocketMessage) {
_handleMessage(websocketMessage);
_textController.clear();
scrollToBottom();
});
}
Future<void> _loadChatMessage() async {
final list = await getChatMessageApi(widget.conversationId);
if (!mounted) return;
setState(() {
_messages = list;
});
}
@override
void dispose() {
_msgSub.cancel();
_textController.dispose();
_scrollController.dispose();
super.dispose();
WebSocketService().disconnect();
}
void _sendMessage() {
final text = _controller.text.trim();
final text = _textController.text.trim();
if (text.isEmpty) return;
final chat = ImMessage(
type: "CHAT_PRIVATE",
type: ImMessageType.chatPrivate,
from: userId.toString(),
to: widget.contractId.toString(),
to: widget.contactInfo.id.toString(),
content: text,
);
WebSocketService().send(chat.toJsonString());
}
Widget _buildAvatar(String? url) {
bool checkIsMe(ChatMessage message) {
return message.senderId == userId;
}
void _handleMessage(String wsMsg) {
if (!mounted) return;
final ImMessage imMsg = ImMessage.fromJsonString(wsMsg);
setState(() {
if (imMsg.type.value.contains('CHAT')) {
_messages.add(
ChatMessage(
msgId: DateTime.now().millisecondsSinceEpoch,
senderId: userId,
content: imMsg.content,
msgType: 1,
createTime: DateTime.now(),
),
);
}
});
}
Widget _buildAvatar(String url) {
return Padding(
padding: const EdgeInsets.only(right: 8, top: 4),
child: CircleAvatar(
radius: 18,
backgroundColor: Colors.grey[200],
backgroundImage: url != null ? NetworkImage(url) : null,
child:
url == null
? const Icon(Icons.person, size: 18, color: Colors.grey)
: null,
backgroundImage: NetworkImage(url),
),
);
}
@@ -130,7 +139,7 @@ class _ChatPageState extends State<ChatPage> {
backgroundColor: Color(0xFF120D25),
foregroundColor: Colors.white,
title: Text(
widget.contactName,
widget.contactInfo.name,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
@@ -154,14 +163,16 @@ class _ChatPageState extends State<ChatPage> {
return Row(
mainAxisAlignment:
msg.isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
checkIsMe(msg)
? MainAxisAlignment.end
: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!msg.isMe) _buildAvatar(msg.avatar),
if (!checkIsMe(msg)) _buildAvatar(widget.contactInfo.avatar),
Flexible(
child: Container(
decoration: BoxDecoration(
color: msg.isMe ? Color(0xFF2FA951): Color(0xFF030816),
color: checkIsMe(msg) ? Color(0xFF2FA951) : Color(0xFF030816),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(
@@ -169,13 +180,13 @@ class _ChatPageState extends State<ChatPage> {
vertical: 10,
),
child: Text(
msg.text,
msg.content,
style: const TextStyle(fontSize: 16, color: Colors.white),
),
),
),
const SizedBox(width: 8),
if (msg.isMe) _buildAvatar("https://picsum.photos/id/1027/200"),
if (checkIsMe(msg)) _buildAvatar(avatarUrl),
],
);
},
@@ -192,7 +203,7 @@ class _ChatPageState extends State<ChatPage> {
children: [
Expanded(
child: TextField(
controller: _controller,
controller: _textController,
onSubmitted: (_) => _sendMessage(),
style: const TextStyle(color: Colors.white),
cursorColor: Colors.white,