Browse Source
feat(message): 实现消息页面的聊天和好友标签页功能
feat(message): 实现消息页面的聊天和好友标签页功能
- 添加了聊天标签页,包含推荐用户横向滚动列表和聊天列表 - 添加了好友标签页,展示好友在线状态和基本信息 - 实现了消息页面的 Tab 切换功能,支持聊天和好友两个标签页 - 更新了消息页面的 AppBar,添加搜索按钮和自定义标签样式 - 修改了 assets.dart 文件,添加了多个图片资源常量并重新排序 - 更新了 pubspec.lock 文件,将依赖源地址改为国内镜像站 https://pub.flutter-io.cn - 使用 flutter_screenutil 进行屏幕适配,优化了搜索按钮的宽度 - 禁用了 TabBarView 的手势滚动,只能通过点击标签切换页面 - 添加了好友列表项的箭头图标和在线状态指示器- 实现了聊天列表的未读消息数显示功能ios
6 changed files with 676 additions and 169 deletions
Unified View
Diff Options
-
BINassets/images/search.png
-
23lib/generated/assets.dart
-
251lib/pages/message/conversation_tab.dart
-
135lib/pages/message/friend_tab.dart
-
126lib/pages/message/message_page.dart
-
310pubspec.lock
@ -0,0 +1,251 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:dating_touchme_app/generated/assets.dart'; |
||||
|
|
||||
|
class ConversationTab extends StatefulWidget { |
||||
|
const ConversationTab({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<ConversationTab> createState() => _ConversationTabState(); |
||||
|
} |
||||
|
|
||||
|
class _ConversationTabState extends State<ConversationTab> with AutomaticKeepAliveClientMixin { |
||||
|
// 模拟数据 - 顶部推荐用户列表 |
||||
|
final List<Map<String, dynamic>> _recommendedUsers = [ |
||||
|
{"id": 1, "avatar": Assets.imagesAvatarsExample, "type": "相亲"}, |
||||
|
{"id": 2, "avatar": Assets.imagesAvatarsExample, "type": "交友"}, |
||||
|
{"id": 3, "avatar": Assets.imagesAvatarsExample, "type": "相亲"}, |
||||
|
{"id": 4, "avatar": Assets.imagesAvatarsExample, "type": "相亲"}, |
||||
|
{"id": 5, "avatar": Assets.imagesAvatarsExample, "type": "相亲"}, |
||||
|
{"id": 6, "avatar": Assets.imagesAvatarsExample, "type": "相亲"}, |
||||
|
]; |
||||
|
|
||||
|
// 模拟数据 - 聊天列表 |
||||
|
final List<Map<String, dynamic>> _chatList = [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"name": "系统通知", |
||||
|
"avatar": Assets.imagesVerifiedIcon, |
||||
|
"message": "在干嘛", |
||||
|
"time": "19:24", |
||||
|
"unreadCount": 2, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 2, |
||||
|
"name": "林园园", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"message": "在干嘛", |
||||
|
"time": "19:24", |
||||
|
"unreadCount": 2, |
||||
|
"isOnline": true, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 3, |
||||
|
"name": "李晖", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"message": "好的", |
||||
|
"time": "19:24", |
||||
|
"unreadCount": 0, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 4, |
||||
|
"name": "李哲", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"message": "在干嘛", |
||||
|
"time": "19:24", |
||||
|
"unreadCount": 0, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 5, |
||||
|
"name": "李夏", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"message": "在干嘛", |
||||
|
"time": "19:24", |
||||
|
"unreadCount": 0, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
super.build(context); |
||||
|
return Column( |
||||
|
children: [ |
||||
|
// 推荐用户横向滚动列表 |
||||
|
_buildRecommendedUsers(), |
||||
|
// 聊天列表 |
||||
|
Expanded( |
||||
|
child: ListView.builder( |
||||
|
padding: const EdgeInsets.only(top: 8), |
||||
|
itemCount: _chatList.length, |
||||
|
itemBuilder: (context, index) { |
||||
|
final chat = _chatList[index]; |
||||
|
return _buildChatItem(chat); |
||||
|
}, |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 构建推荐用户横向滚动列表 |
||||
|
Widget _buildRecommendedUsers() { |
||||
|
return SizedBox( |
||||
|
height: 100, |
||||
|
child: ListView.builder( |
||||
|
scrollDirection: Axis.horizontal, |
||||
|
padding: const EdgeInsets.symmetric(horizontal: 16), |
||||
|
itemCount: _recommendedUsers.length, |
||||
|
itemBuilder: (context, index) { |
||||
|
final user = _recommendedUsers[index]; |
||||
|
final bool isSelected = index == 1; // 模拟选中状态 |
||||
|
|
||||
|
return Container( |
||||
|
margin: const EdgeInsets.only(right: 12), |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
Stack( |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 60, |
||||
|
height: 60, |
||||
|
decoration: BoxDecoration( |
||||
|
border: Border.all( |
||||
|
color: isSelected ? Colors.blue : Colors.transparent, |
||||
|
width: 2, |
||||
|
), |
||||
|
borderRadius: BorderRadius.circular(30), |
||||
|
), |
||||
|
child: ClipRRect( |
||||
|
borderRadius: BorderRadius.circular(30), |
||||
|
child: Image.asset(user["avatar"], fit: BoxFit.cover), |
||||
|
), |
||||
|
), |
||||
|
if (user["type"] == "交友") |
||||
|
Positioned( |
||||
|
bottom: 0, |
||||
|
right: 0, |
||||
|
child: Container( |
||||
|
width: 20, |
||||
|
height: 20, |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.blue, |
||||
|
borderRadius: BorderRadius.circular(10), |
||||
|
), |
||||
|
child: const Icon( |
||||
|
Icons.people, |
||||
|
size: 12, |
||||
|
color: Colors.white, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
const SizedBox(height: 4), |
||||
|
Text(user["type"], style: const TextStyle(color: Colors.white)), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 构建聊天项 |
||||
|
Widget _buildChatItem(Map<String, dynamic> chat) { |
||||
|
return Container( |
||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), |
||||
|
padding: const EdgeInsets.all(12), |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.white, |
||||
|
borderRadius: BorderRadius.circular(8), |
||||
|
), |
||||
|
child: Row( |
||||
|
children: [ |
||||
|
// 头像 |
||||
|
Stack( |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 50, |
||||
|
height: 50, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.circular(25), |
||||
|
), |
||||
|
child: ClipRRect( |
||||
|
borderRadius: BorderRadius.circular(25), |
||||
|
child: Image.asset(chat["avatar"], fit: BoxFit.cover), |
||||
|
), |
||||
|
), |
||||
|
if (chat["isOnline"] == true) |
||||
|
Positioned( |
||||
|
bottom: 0, |
||||
|
right: 0, |
||||
|
child: Container( |
||||
|
width: 12, |
||||
|
height: 12, |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.green, |
||||
|
borderRadius: BorderRadius.circular(6), |
||||
|
border: Border.all(color: Colors.white, width: 2), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
|
||||
|
// 信息 |
||||
|
Expanded( |
||||
|
child: Container( |
||||
|
margin: const EdgeInsets.only(left: 12), |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Row( |
||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||
|
children: [ |
||||
|
Text( |
||||
|
chat["name"], |
||||
|
style: const TextStyle(fontWeight: FontWeight.bold), |
||||
|
), |
||||
|
Text( |
||||
|
chat["time"], |
||||
|
style: const TextStyle( |
||||
|
color: Colors.grey, |
||||
|
fontSize: 12, |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
const SizedBox(height: 4), |
||||
|
Text( |
||||
|
chat["message"], |
||||
|
style: const TextStyle(color: Colors.grey), |
||||
|
overflow: TextOverflow.ellipsis, |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
|
||||
|
// 未读消息数 |
||||
|
if (chat["unreadCount"] > 0) |
||||
|
Container( |
||||
|
width: 18, |
||||
|
height: 18, |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.red, |
||||
|
borderRadius: BorderRadius.circular(9), |
||||
|
), |
||||
|
alignment: Alignment.center, |
||||
|
child: Text( |
||||
|
chat["unreadCount"].toString(), |
||||
|
style: const TextStyle(color: Colors.white, fontSize: 12), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
bool get wantKeepAlive => true; |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:dating_touchme_app/generated/assets.dart'; |
||||
|
|
||||
|
class FriendTab extends StatefulWidget { |
||||
|
const FriendTab({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<FriendTab> createState() => _FriendTabState(); |
||||
|
} |
||||
|
|
||||
|
class _FriendTabState extends State<FriendTab> { |
||||
|
// 模拟数据 - 好友列表 |
||||
|
final List<Map<String, dynamic>> _friendList = [ |
||||
|
{ |
||||
|
"id": 1, |
||||
|
"name": "林园园", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"isOnline": true, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 2, |
||||
|
"name": "李晖", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"isOnline": false, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 3, |
||||
|
"name": "李哲", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"isOnline": false, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 4, |
||||
|
"name": "李夏", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"isOnline": true, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 5, |
||||
|
"name": "张雪", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"isOnline": false, |
||||
|
}, |
||||
|
{ |
||||
|
"id": 6, |
||||
|
"name": "王强", |
||||
|
"avatar": Assets.imagesAvatarsExample, |
||||
|
"isOnline": true, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return ListView.builder( |
||||
|
padding: const EdgeInsets.only(top: 8), |
||||
|
itemCount: _friendList.length, |
||||
|
itemBuilder: (context, index) { |
||||
|
final friend = _friendList[index]; |
||||
|
|
||||
|
return Container( |
||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), |
||||
|
padding: const EdgeInsets.all(12), |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.white, |
||||
|
borderRadius: BorderRadius.circular(8), |
||||
|
), |
||||
|
child: Row( |
||||
|
children: [ |
||||
|
// 头像 |
||||
|
Stack( |
||||
|
children: [ |
||||
|
Container( |
||||
|
width: 50, |
||||
|
height: 50, |
||||
|
decoration: BoxDecoration( |
||||
|
borderRadius: BorderRadius.circular(25), |
||||
|
), |
||||
|
child: ClipRRect( |
||||
|
borderRadius: BorderRadius.circular(25), |
||||
|
child: Image.asset(friend["avatar"], fit: BoxFit.cover), |
||||
|
), |
||||
|
), |
||||
|
if (friend["isOnline"] == true) |
||||
|
Positioned( |
||||
|
bottom: 0, |
||||
|
right: 0, |
||||
|
child: Container( |
||||
|
width: 12, |
||||
|
height: 12, |
||||
|
decoration: BoxDecoration( |
||||
|
color: Colors.green, |
||||
|
borderRadius: BorderRadius.circular(6), |
||||
|
border: Border.all(color: Colors.white, width: 2), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
|
||||
|
// 信息 |
||||
|
Expanded( |
||||
|
child: Container( |
||||
|
margin: const EdgeInsets.only(left: 12), |
||||
|
child: Column( |
||||
|
crossAxisAlignment: CrossAxisAlignment.start, |
||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||
|
children: [ |
||||
|
Text( |
||||
|
friend["name"], |
||||
|
style: const TextStyle( |
||||
|
fontWeight: FontWeight.bold, |
||||
|
), |
||||
|
), |
||||
|
const SizedBox(height: 4), |
||||
|
Text( |
||||
|
friend["isOnline"] == true ? "在线" : "离线", |
||||
|
style: TextStyle( |
||||
|
color: friend["isOnline"] == true ? Colors.green : Colors.grey, |
||||
|
fontSize: 12, |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
|
||||
|
// 箭头图标 |
||||
|
const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
310
pubspec.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save