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

19
lib/apis/message_api.dart Normal file
View File

@@ -0,0 +1,19 @@
import 'package:sweet_chat_app/models/chat_message.dart';
import 'package:sweet_chat_app/models/chat_session.dart';
import 'package:sweet_chat_app/services/dio_service.dart';
Future<List<ChatSession>> getChatSessionApi(int userId) async {
final res = await dioService.get('/message/session/$userId');
if (res.data == null) return [];
return (res.data as List).map((e) => ChatSession.fromJson(e)).toList();
}
Future<List<ChatMessage>> getChatMessageApi(int conversationId) async {
final res = await dioService.get('/message/session/$conversationId/detail');
if (res.data == null) return [];
return (res.data as List).map((e) => ChatMessage.fromJson(e)).toList();
}

View File

@@ -0,0 +1,4 @@
class AppConfig {
static const String baseUrl = 'http://192.168.31.108:2836/api';
static const String wsUrl = 'ws://192.168.31.108:5050';
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
import 'package:sweet_chat_app/pages/login.dart';
import 'package:sweet_chat_app/utils/SpUtil.dart';
import 'package:sweet_chat_app/utils/sp_utils.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();

View File

@@ -1,12 +1,13 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:sweet_chat_app/models/user.dart';
part 'auth.g.dart';
@JsonSerializable()
class Auth {
final int userId;
final User user;
Auth({required this.userId});
Auth({required this.user});
factory Auth.fromJson(Map<String, dynamic> json) => _$AuthFromJson(json);

View File

@@ -7,8 +7,8 @@ part of 'auth.dart';
// **************************************************************************
Auth _$AuthFromJson(Map<String, dynamic> json) =>
Auth(userId: (json['userId'] as num).toInt());
Auth(user: User.fromJson(json['user'] as Map<String, dynamic>));
Map<String, dynamic> _$AuthToJson(Auth instance) => <String, dynamic>{
'userId': instance.userId,
'user': instance.user,
};

View File

@@ -0,0 +1,26 @@
import 'package:json_annotation/json_annotation.dart';
part 'chat_message.g.dart';
@JsonSerializable()
class ChatMessage {
final int msgId;
final int senderId;
final String content;
/// 消息类型1文本 2图片 3语音
final int msgType;
final DateTime createTime;
ChatMessage({
required this.msgId,
required this.senderId,
required this.content,
required this.msgType,
required this.createTime,
});
factory ChatMessage.fromJson(Map<String, dynamic> json) =>
_$ChatMessageFromJson(json);
Map<String, dynamic> toJson() => _$ChatMessageToJson(this);
}

View File

@@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_message.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ChatMessage _$ChatMessageFromJson(Map<String, dynamic> json) => ChatMessage(
msgId: (json['msgId'] as num).toInt(),
senderId: (json['senderId'] as num).toInt(),
content: json['content'] as String,
msgType: (json['msgType'] as num).toInt(),
createTime: DateTime.parse(json['createTime'] as String),
);
Map<String, dynamic> _$ChatMessageToJson(ChatMessage instance) =>
<String, dynamic>{
'msgId': instance.msgId,
'senderId': instance.senderId,
'content': instance.content,
'msgType': instance.msgType,
'createTime': instance.createTime.toIso8601String(),
};

View File

@@ -0,0 +1,30 @@
import 'package:json_annotation/json_annotation.dart';
part 'chat_session.g.dart';
@JsonSerializable()
class ChatSession {
final int conversationId;
final int targetUserId;
final String title;
final String avatarUrl;
final int msgType;
final String lastMessageContent;
final DateTime lastMessageTime;
final int unreadCount;
ChatSession({
required this.conversationId,
required this.targetUserId,
required this.title,
required this.avatarUrl,
required this.msgType,
required this.lastMessageContent,
required this.lastMessageTime,
required this.unreadCount,
});
factory ChatSession.fromJson(Map<String, dynamic> json) => _$ChatSessionFromJson(json);
Map<String, dynamic> toJson() => _$ChatSessionToJson(this);
}

View File

@@ -0,0 +1,30 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chat_session.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ChatSession _$ChatSessionFromJson(Map<String, dynamic> json) => ChatSession(
conversationId: (json['conversationId'] as num).toInt(),
targetUserId: (json['targetUserId'] as num).toInt(),
title: json['title'] as String,
avatarUrl: json['avatarUrl'] as String,
msgType: (json['msgType'] as num).toInt(),
lastMessageContent: json['lastMessageContent'] as String,
lastMessageTime: DateTime.parse(json['lastMessageTime'] as String),
unreadCount: (json['unreadCount'] as num).toInt(),
);
Map<String, dynamic> _$ChatSessionToJson(ChatSession instance) =>
<String, dynamic>{
'conversationId': instance.conversationId,
'targetUserId': instance.targetUserId,
'title': instance.title,
'avatarUrl': instance.avatarUrl,
'msgType': instance.msgType,
'lastMessageContent': instance.lastMessageContent,
'lastMessageTime': instance.lastMessageTime.toIso8601String(),
'unreadCount': instance.unreadCount,
};

View File

@@ -0,0 +1,20 @@
import 'package:azlistview/azlistview.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 ?? '#';
}

View File

@@ -1,7 +1,20 @@
import 'dart:convert';
enum ImMessageType {
chatPrivate('CHAT_PRIVATE'),
chatGroup('CHAT_GROUP'),
online('ONLINE'),
heartbeat('HEARTBEAT'),
system('SYSTEM'),
notice('NOTICE');
final String value;
const ImMessageType(this.value);
}
class ImMessage {
final String type;
final ImMessageType type;
final String from;
final String to;
final String content;
@@ -19,7 +32,7 @@ class ImMessage {
factory ImMessage.fromJson(Map<String, dynamic> json) {
return ImMessage(
type: json['type'],
type: ImMessageType.values.firstWhere((e) => e.value == json['type']),
from: json['from'],
to: json['to'],
content: json['content'],
@@ -27,7 +40,7 @@ class ImMessage {
}
Map<String, dynamic> toJson() => {
'type': type,
'type': type.value,
'from': from,
'to': to,
'content': content,
@@ -36,4 +49,13 @@ class ImMessage {
String toJsonString() {
return jsonEncode(toJson());
}
static ImMessage online(int userId) {
return ImMessage(
type: ImMessageType.online,
from: userId.toString(),
to: 'SYSTEM',
content: '',
);
}
}

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') ?? '';
WebSocketService().connect('ws://172.29.100.146:5050?userId=$userId');
WebSocketService().send(online.toJsonString());
_messages = [];
_loadChatMessage();
WebSocketService().messages.listen((wsMsg) {
final ImMessage imMsg = ImMessage.fromJsonString(wsMsg);
_msgSub = WebSocketService().messages.listen((websocketMessage) {
_handleMessage(websocketMessage);
_textController.clear();
scrollToBottom();
});
}
Future<void> _loadChatMessage() async {
final list = await getChatMessageApi(widget.conversationId);
if (!mounted) return;
setState(() {
if (imMsg.type.contains('CHAT')) {
_messages.add(
ChatMessage(
text: imMsg.content,
isMe: userId.toString() == imMsg.from,
avatar: widget.avatarUrl,
),
);
}
});
_controller.clear();
scrollToBottom();
_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,

View File

@@ -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)),
);
}
}

View File

@@ -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 {
@@ -24,6 +28,23 @@ class _HomePageState extends State<HomePage> {
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(

View File

@@ -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("登录失败");
}
}

View File

@@ -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 = [];

View File

@@ -1,7 +1,8 @@
import 'package:dio/dio.dart';
import 'package:sweet_chat_app/config/AppConfig.dart';
final dioService = Dio(BaseOptions(
baseUrl: 'http://172.29.100.146:2836/api',
connectTimeout: Duration(seconds: 10),
baseUrl: AppConfig.baseUrl,
connectTimeout: Duration(seconds: 5),
headers: {'Content-Type': 'application/json'},
));

View File

@@ -0,0 +1,13 @@
import 'package:sweet_chat_app/models/im_message.dart';
class MessageStore {
static final Map<String, List<ImMessage>> _data = {};
static void add(ImMessage msg) {
_data.putIfAbsent(msg.to, () => []).add(msg);
_data.putIfAbsent(msg.from, () => []).add(msg);
}
static List<ImMessage> messagesFor(String userId) =>
_data[userId] ?? [];
}

26
lib/utils/date_utils.dart Normal file
View File

@@ -0,0 +1,26 @@
import 'package:intl/intl.dart';
String formatChatTime(DateTime time) {
final now = DateTime.now();
final t = time.toLocal();
final today = DateTime(now.year, now.month, now.day);
final d = DateTime(t.year, t.month, t.day);
final diff = today.difference(d).inDays;
final hm = DateFormat('HH:mm').format(t);
if (diff == 0) return hm;
if (diff == 1) return '昨天 $hm';
// 本周
if (diff < 7 && d.weekday <= now.weekday) {
return '${DateFormat('EEEEE', 'zh_CN').format(t)} $hm';
}
if (t.year == now.year) {
return DateFormat('M月d日 HH:mm').format(t);
}
return DateFormat('yyyy年M月d日 HH:mm').format(t);
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ToastUtils {
static void _show({
required String msg,
Color backgroundColor = Colors.black87,
Color textColor = Colors.white,
ToastGravity gravity = ToastGravity.CENTER,
Toast toastLength = Toast.LENGTH_SHORT,
}) {
Fluttertoast.showToast(
msg: msg,
toastLength: toastLength,
gravity: gravity,
backgroundColor: backgroundColor,
textColor: textColor,
fontSize: 14.0,
);
}
static void success(String msg) {
_show(msg: msg, backgroundColor: Colors.green);
}
static void error(String msg) {
_show(msg: msg, backgroundColor: Colors.redAccent);
}
static void warning(String msg) {
_show(msg: msg, backgroundColor: Colors.orange);
}
static void info(String msg) {
_show(msg: msg, backgroundColor: Colors.grey[800]!);
}
}

View File

@@ -288,6 +288,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.19.0"
io:
dependency: transitive
description:

View File

@@ -1 +1 @@
name: sweet_chat_app
name: sweet_chat_app