feat:增加消息记录功能
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -2,31 +2,12 @@ import 'package:azlistview/azlistview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:sweet_chat_app/apis/user_api.dart';
|
||||
import 'package:sweet_chat_app/models/contact_info.dart';
|
||||
import 'package:sweet_chat_app/models/user.dart';
|
||||
import 'package:sweet_chat_app/utils/SpUtil.dart';
|
||||
import 'package:sweet_chat_app/utils/sp_utils.dart';
|
||||
|
||||
import 'chat_page.dart';
|
||||
|
||||
class ContactInfo extends ISuspensionBean {
|
||||
final int id;
|
||||
final String name;
|
||||
final String avatar;
|
||||
String? tagIndex;
|
||||
String? namePinyin;
|
||||
|
||||
ContactInfo({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.avatar,
|
||||
this.tagIndex,
|
||||
this.namePinyin,
|
||||
});
|
||||
|
||||
@override
|
||||
String getSuspensionTag() => tagIndex ?? '#';
|
||||
}
|
||||
|
||||
/// ================= Page =================
|
||||
class ContactsPage extends StatefulWidget {
|
||||
const ContactsPage({super.key});
|
||||
|
||||
@@ -163,14 +144,7 @@ class _ContactsPageState extends State<ContactsPage> {
|
||||
void onTapContact(ContactInfo contact) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(_) => ChatPage(
|
||||
contractId: contact.id,
|
||||
contactName: contact.name,
|
||||
avatarUrl: contact.avatar,
|
||||
),
|
||||
),
|
||||
MaterialPageRoute(builder: (_) => ChatPage(contactInfo: contact, conversationId: 0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/shared/glass_page.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/surfaces/glass_bottom_bar.dart';
|
||||
import 'package:liquid_glass_widgets/widgets/surfaces/glass_scaffold.dart';
|
||||
import 'package:sweet_chat_app/pages/contacts_page.dart';
|
||||
import 'package:sweet_chat_app/config/AppConfig.dart';
|
||||
import 'package:sweet_chat_app/models/im_message.dart';
|
||||
import 'package:sweet_chat_app/pages/contact_page.dart';
|
||||
import 'package:sweet_chat_app/pages/explore_page.dart';
|
||||
import 'package:sweet_chat_app/pages/messages_page.dart';
|
||||
import 'package:sweet_chat_app/pages/message_page.dart';
|
||||
import 'package:sweet_chat_app/services/webSocket_service.dart';
|
||||
import 'package:sweet_chat_app/utils/sp_utils.dart';
|
||||
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@@ -23,7 +27,24 @@ class _HomePageState extends State<HomePage> {
|
||||
const ExplorePage(),
|
||||
const Center(child: Text('⚙ 我的', style: TextStyle(fontSize: 26))),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initWebsocket();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void initWebsocket() {
|
||||
final userId = SpUtil.getInt('userId') ?? 0;
|
||||
WebSocketService().connect('${AppConfig.wsUrl}?userId=$userId');
|
||||
WebSocketService().send(ImMessage.online(userId).toJsonString());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlassScaffold(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
import 'package:sweet_chat_app/apis/auth_api.dart';
|
||||
import 'package:sweet_chat_app/utils/SpUtil.dart';
|
||||
import 'package:sweet_chat_app/utils/sp_utils.dart';
|
||||
import 'package:sweet_chat_app/utils/toast_utils.dart';
|
||||
import 'home_page.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
@@ -18,30 +18,21 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
void _login() async {
|
||||
try {
|
||||
final auth = await loginApi(_usernameController.text, _passwordController.text);
|
||||
await SpUtil.setInt('userId', auth.userId);
|
||||
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const HomePage(),
|
||||
),
|
||||
final auth = await loginApi(
|
||||
_usernameController.text,
|
||||
_passwordController.text,
|
||||
);
|
||||
await SpUtil.setInt('userId', auth.user.id);
|
||||
await SpUtil.setString('avatarUrl', auth.user.avatarUrl);
|
||||
|
||||
Fluttertoast.showToast(
|
||||
msg: "登录成功",
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.TOP,
|
||||
backgroundColor: Colors.white,
|
||||
textColor: Colors.black,
|
||||
);
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (_) => const HomePage()));
|
||||
|
||||
ToastUtils.success("登录成功");
|
||||
} catch (e) {
|
||||
Fluttertoast.showToast(
|
||||
msg: "登录失败",
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.TOP,
|
||||
backgroundColor: Colors.white,
|
||||
textColor: Colors.black,
|
||||
);
|
||||
print(e.toString());
|
||||
ToastUtils.error("登录失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,70 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
||||
import 'package:sweet_chat_app/apis/message_api.dart';
|
||||
import 'package:sweet_chat_app/models/chat_session.dart';
|
||||
import 'package:sweet_chat_app/models/contact_info.dart';
|
||||
import 'package:sweet_chat_app/pages/chat_page.dart';
|
||||
import 'package:sweet_chat_app/services/webSocket_service.dart';
|
||||
import 'package:sweet_chat_app/utils/date_utils.dart';
|
||||
import 'package:sweet_chat_app/utils/sp_utils.dart';
|
||||
|
||||
import 'chat_page.dart';
|
||||
|
||||
class MessagePage extends StatelessWidget {
|
||||
class MessagePage extends StatefulWidget {
|
||||
const MessagePage({super.key});
|
||||
|
||||
@override
|
||||
State<MessagePage> createState() => _MessagePageState();
|
||||
}
|
||||
|
||||
class _MessagePageState extends State<MessagePage> {
|
||||
late StreamSubscription<String> _msgSub;
|
||||
late List<ChatSession> _chatSessionList;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_chatSessionList = [];
|
||||
_loadChatSession();
|
||||
_msgSub = WebSocketService().messages.listen((websocketMessage) {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_msgSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadChatSession() async {
|
||||
final userId = SpUtil.getInt('userId') ?? 0;
|
||||
final list = await getChatSessionApi(userId);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_chatSessionList = list;
|
||||
});
|
||||
}
|
||||
|
||||
void onTapSession(ChatSession chatSession) {
|
||||
final ContactInfo contactInfo = ContactInfo(
|
||||
id: chatSession.targetUserId,
|
||||
name: chatSession.title,
|
||||
avatar: chatSession.avatarUrl,
|
||||
);
|
||||
|
||||
final ChatPage chatPage = ChatPage(
|
||||
contactInfo: contactInfo,
|
||||
conversationId: chatSession.conversationId,
|
||||
);
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => chatPage)).then((
|
||||
_,
|
||||
) {
|
||||
_loadChatSession();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -25,9 +84,9 @@ class MessagePage extends StatelessWidget {
|
||||
icon: const Icon(Icons.menu, size: 22, color: Colors.white),
|
||||
splashRadius: 20,
|
||||
),
|
||||
title: const Text(
|
||||
'消息',
|
||||
style: TextStyle(
|
||||
title: Text(
|
||||
'消息 ${WebSocketService().isConnected}' ,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
@@ -59,71 +118,42 @@ class MessagePage extends StatelessWidget {
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return SizedBox(height: 10);
|
||||
},
|
||||
itemCount: _messages.length,
|
||||
itemCount: _chatSessionList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final msg = _messages[index];
|
||||
final msg = _chatSessionList[index];
|
||||
return _buildMessageItem(context, msg);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageItem(BuildContext context, Message msg) {
|
||||
Widget _buildMessageItem(BuildContext context, ChatSession session) {
|
||||
return GlassCard(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onTap:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(_) => const ChatPage(
|
||||
contractId: 0,
|
||||
contactName: '张三',
|
||||
avatarUrl: 'https://picsum.photos/id/1012/200',
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
onTap: () => onTapSession(session),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundImage: NetworkImage(msg.avatar),
|
||||
backgroundImage: NetworkImage(session.avatarUrl),
|
||||
),
|
||||
title: Text(
|
||||
msg.name,
|
||||
session.title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
msg.lastMsg,
|
||||
session.lastMessageContent,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 13, color: Color(0xFFAEAEB2)),
|
||||
),
|
||||
trailing: Text(
|
||||
msg.time,
|
||||
formatChatTime(session.lastMessageTime),
|
||||
style: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Message {
|
||||
final String name;
|
||||
final String avatar;
|
||||
final String lastMsg;
|
||||
final String time;
|
||||
|
||||
Message({
|
||||
required this.name,
|
||||
required this.avatar,
|
||||
required this.lastMsg,
|
||||
required this.time,
|
||||
});
|
||||
}
|
||||
|
||||
List<Message> _messages = [];
|
||||
Reference in New Issue
Block a user