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
Split 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