feat:更新页面颜色

This commit is contained in:
2026-06-17 13:24:37 +08:00
parent 021db2efac
commit 02084ada96
9 changed files with 601 additions and 341 deletions

View File

@@ -1,28 +1 @@
# This file configures the analyzer, which statically analyzes Dart code to # This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -1,3 +1 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -1,5 +1 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-all.zip

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
import 'package:sweet_chat_app/pages/contacts_page.dart'; import 'package:sweet_chat_app/pages/contacts_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/messages_page.dart';
void main() async { void main() async {
@@ -19,6 +20,7 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
scaffoldBackgroundColor: const Color(0xFF120D25),
brightness: Brightness.light, brightness: Brightness.light,
useMaterial3: true, useMaterial3: true,
colorScheme: ColorScheme.light( colorScheme: ColorScheme.light(
@@ -43,28 +45,21 @@ class _HomePageState extends State<HomePage> {
int _index = 0; int _index = 0;
final _pages = const [ final _pages = const [
Center(child: MessagePage()), MessagePage(),
Center(child: ContactsPage()), ContactsPage(),
Center(child: Text('⚙ Settings', style: TextStyle(fontSize: 26, color: Colors.white))), ExplorePage(),
Center(child: Text('⚙ 我的', style: TextStyle(fontSize: 26, color: Colors.white))), Center(child: Text('⚙ 我的', style: TextStyle(fontSize: 26))),
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GlassScaffold( return GlassScaffold(
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFFFFFFF), Color(0xFFF5F5F7)],
),
),
),
statusBarStyle: GlassStatusBarStyle.auto, statusBarStyle: GlassStatusBarStyle.auto,
bottomBar: GlassBottomBar( bottomBar: GlassBottomBar(
selectedIndex: _index, selectedIndex: _index,
onTabSelected: (i) => setState(() => _index = i), onTabSelected: (i) => setState(() => _index = i),
selectedIconColor: Colors.purple.shade200,
unselectedIconColor: Colors.white,
tabs: const [ tabs: const [
GlassBottomBarTab( GlassBottomBarTab(
icon: Icon(Icons.chat_bubble_outline), icon: Icon(Icons.chat_bubble_outline),

178
lib/pages/chat_page.dart Normal file
View File

@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
class ChatMessage {
final String text;
final bool isMe;
final String? avatar;
ChatMessage({required this.text, required this.isMe, this.avatar});
}
class ChatPage extends StatefulWidget {
final String contactName;
final String avatarUrl;
const ChatPage({
super.key,
required this.contactName,
required this.avatarUrl,
});
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
late List<ChatMessage> _messages;
@override
void initState() {
super.initState();
_messages = [
ChatMessage(text: '在吗?', isMe: false, avatar: widget.avatarUrl),
ChatMessage(text: '在的,怎么啦 😊', isMe: true),
ChatMessage(text: '今晚一起吃饭吗?', isMe: false, avatar: widget.avatarUrl),
];
}
void _sendMessage() {
final text = _controller.text.trim();
if (text.isEmpty) return;
setState(() {
_messages.add(ChatMessage(text: text, isMe: true));
});
_controller.clear();
Future.delayed(const Duration(milliseconds: 100), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
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,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
children: [
Expanded(child: _buildMessage()),
SizedBox(height: 10),
_buildInput(),
],
),
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Color(0xFF120D25),
foregroundColor: Colors.white,
title: Text(
widget.contactName,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
centerTitle: true,
);
}
Widget _buildMessage() {
return GlassCard(
child: ListView.separated(
controller: _scrollController,
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 10);
},
itemCount: _messages.length,
itemBuilder: (context, index) {
final msg = _messages[index];
return Row(
mainAxisAlignment:
msg.isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!msg.isMe) _buildAvatar(msg.avatar),
Flexible(
child: GlassCard(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
child: Text(
msg.text,
style: const TextStyle(fontSize: 16, color: Colors.white),
),
),
),
const SizedBox(width: 8),
if (msg.isMe) _buildAvatar("https://picsum.photos/id/1027/200"),
],
);
},
),
);
}
Widget _buildInput() {
return SafeArea(
child: GlassContainer(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
onSubmitted: (_) => _sendMessage(),
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
hintText: '输入消息...',
hintStyle: TextStyle(color: Colors.white),
border: InputBorder.none,
),
),
),
IconButton(
onPressed: _sendMessage,
icon: const Icon(Icons.send, color: Colors.green),
),
],
),
),
);
}
}

View File

@@ -40,86 +40,26 @@ class _ContactsPageState extends State<ContactsPage> {
void _loadFakeData() { void _loadFakeData() {
contactList = [ contactList = [
ContactInfo( ContactInfo(name: '张三', avatar: 'https://picsum.photos/id/1011/200'),
name: '张三', ContactInfo(name: '李四', avatar: 'https://picsum.photos/id/1012/200'),
avatar: 'https://randomuser.me/api/portraits/men/11.jpg', ContactInfo(name: '王五', avatar: 'https://picsum.photos/id/1013/200'),
), ContactInfo(name: '赵六', avatar: 'https://picsum.photos/id/1014/200'),
ContactInfo( ContactInfo(name: '陈七', avatar: 'https://picsum.photos/id/1015/200'),
name: '李四', ContactInfo(name: '刘八', avatar: 'https://picsum.photos/id/1016/200'),
avatar: 'https://randomuser.me/api/portraits/men/12.jpg', ContactInfo(name: '周九', avatar: 'https://picsum.photos/id/1017/200'),
), ContactInfo(name: '吴十', avatar: 'https://picsum.photos/id/1018/200'),
ContactInfo( ContactInfo(name: '欧阳峰', avatar: 'https://picsum.photos/id/1021/200'),
name: '王五', ContactInfo(name: '诸葛青', avatar: 'https://picsum.photos/id/1022/200'),
avatar: 'https://randomuser.me/api/portraits/men/13.jpg', ContactInfo(name: '司马光', avatar: 'https://picsum.photos/id/1023/200'),
), ContactInfo(name: '慕容复', avatar: 'https://picsum.photos/id/1024/200'),
ContactInfo( ContactInfo(name: '安琪拉', avatar: 'https://picsum.photos/id/1031/200'),
name: '赵六', ContactInfo(name: '亚瑟', avatar: 'https://picsum.photos/id/1032/200'),
avatar: 'https://randomuser.me/api/portraits/women/14.jpg', ContactInfo(name: '李白', avatar: 'https://picsum.photos/id/1033/200'),
), ContactInfo(name: '韩信', avatar: 'https://picsum.photos/id/1034/200'),
ContactInfo( ContactInfo(name: '孙尚香', avatar: 'https://picsum.photos/id/1035/200'),
name: '陈七', ContactInfo(name: '鲁班', avatar: 'https://picsum.photos/id/1036/200'),
avatar: 'https://randomuser.me/api/portraits/women/15.jpg', ContactInfo(name: '妲己', avatar: 'https://picsum.photos/id/1037/200'),
), ContactInfo(name: '甄姬', avatar: 'https://picsum.photos/id/1038/200'),
ContactInfo(
name: '刘八',
avatar: 'https://randomuser.me/api/portraits/men/16.jpg',
),
ContactInfo(
name: '周九',
avatar: 'https://randomuser.me/api/portraits/men/17.jpg',
),
ContactInfo(
name: '吴十',
avatar: 'https://randomuser.me/api/portraits/women/18.jpg',
),
ContactInfo(
name: '欧阳峰',
avatar: 'https://randomuser.me/api/portraits/men/21.jpg',
),
ContactInfo(
name: '诸葛青',
avatar: 'https://randomuser.me/api/portraits/men/22.jpg',
),
ContactInfo(
name: '司马光',
avatar: 'https://randomuser.me/api/portraits/men/23.jpg',
),
ContactInfo(
name: '慕容复',
avatar: 'https://randomuser.me/api/portraits/women/24.jpg',
),
ContactInfo(
name: '安琪拉',
avatar: 'https://randomuser.me/api/portraits/women/31.jpg',
),
ContactInfo(
name: '亚瑟',
avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
),
ContactInfo(
name: '李白',
avatar: 'https://randomuser.me/api/portraits/men/33.jpg',
),
ContactInfo(
name: '韩信',
avatar: 'https://randomuser.me/api/portraits/men/34.jpg',
),
ContactInfo(
name: '孙尚香',
avatar: 'https://randomuser.me/api/portraits/women/35.jpg',
),
ContactInfo(
name: '鲁班',
avatar: 'https://randomuser.me/api/portraits/men/36.jpg',
),
ContactInfo(
name: '妲己',
avatar: 'https://randomuser.me/api/portraits/women/37.jpg',
),
ContactInfo(
name: '甄姬',
avatar: 'https://randomuser.me/api/portraits/women/38.jpg',
),
]; ];
_handleList(contactList); _handleList(contactList);
@@ -142,10 +82,18 @@ class _ContactsPageState extends State<ContactsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF2F2F7), backgroundColor: Colors.transparent,
appBar: AppBar( appBar: _buildAppBar(),
body: Column(
children: [_buildSearchBar(), Expanded(child: _buildAzListView())],
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
backgroundColor: Colors.grey.shade200, backgroundColor: Color(0xFF120D25),
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
title: const Text( title: const Text(
@@ -153,13 +101,9 @@ class _ContactsPageState extends State<ContactsPage> {
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1C1C1E), color: Colors.white,
), ),
), ),
),
body: Column(
children: [_buildSearchBar(), Expanded(child: _buildAzListView())],
),
); );
} }
@@ -170,7 +114,6 @@ class _ContactsPageState extends State<ContactsPage> {
itemBuilder: (_, index) { itemBuilder: (_, index) {
final c = contactList[index]; final c = contactList[index];
return Container( return Container(
color: Colors.white,
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundImage: NetworkImage(c.avatar), backgroundImage: NetworkImage(c.avatar),
@@ -178,7 +121,7 @@ class _ContactsPageState extends State<ContactsPage> {
), ),
title: Text( title: Text(
c.name, c.name,
style: const TextStyle(fontSize: 16, color: Color(0xFF1C1C1E)), style: const TextStyle(fontSize: 16, color: Colors.white),
), ),
), ),
); );
@@ -189,10 +132,10 @@ class _ContactsPageState extends State<ContactsPage> {
height: 28, height: 28,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
color: const Color(0xFFF2F2F7), color: const Color(0xFF2B2744),
child: Text( child: Text(
tag, tag,
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93)), style: const TextStyle(fontSize: 13, color: Colors.grey),
), ),
); );
}, },
@@ -210,21 +153,16 @@ class _ContactsPageState extends State<ContactsPage> {
Widget _buildSearchBar() { Widget _buildSearchBar() {
return Container( return Container(
height: 44, height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(color: Color(0xFF2F2A47)),
color: Colors.white,
),
child: TextField( child: TextField(
textAlignVertical: TextAlignVertical.center, textAlignVertical: TextAlignVertical.center,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration( decoration: InputDecoration(
isDense: true,
hintText: '搜索联系人', hintText: '搜索联系人',
hintStyle: const TextStyle(fontSize: 15, color: Color(0xFF8E8E93)), hintStyle: const TextStyle(fontSize: 15, color: Colors.white),
prefixIcon: const Icon( prefixIcon: const Icon(Icons.search, size: 20, color: Colors.white),
Icons.search,
size: 20,
color: Color(0xFF8E8E93),
),
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
), ),
), ),
); );

277
lib/pages/explore_page.dart Normal file
View File

@@ -0,0 +1,277 @@
import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
class Comment {
final String nickname;
final String content;
Comment({required this.nickname, required this.content});
}
class Moment {
final String avatar;
final String nickname;
final String content;
final List<String> images;
bool liked;
int likeCount;
List<Comment> comments;
Moment({
required this.avatar,
required this.nickname,
required this.content,
this.images = const [],
this.liked = false,
this.likeCount = 0,
this.comments = const [],
});
}
/// ================= Explore Page =================
class ExplorePage extends StatefulWidget {
const ExplorePage({super.key});
@override
State<ExplorePage> createState() => _ExplorePageState();
}
class _ExplorePageState extends State<ExplorePage> {
final List<Moment> _moments = [
Moment(
avatar: 'https://picsum.photos/100?random=1',
nickname: '张三',
content: '今天天气真不错 ☀️',
images: [
'https://picsum.photos/300?random=1',
'https://picsum.photos/300?random=2',
'https://picsum.photos/300?random=3',
'https://picsum.photos/300?random=4',
'https://picsum.photos/300?random=5',
],
likeCount: 12,
comments: [
Comment(nickname: '李四', content: '确实不错'),
Comment(nickname: '王五', content: '想去旅游'),
],
),
Moment(
avatar: 'https://picsum.photos/100?random=2',
nickname: '李四',
content: 'Flutter 真香 🚀',
images: ['https://picsum.photos/300?random=6'],
likeCount: 8,
comments: [],
),
];
void _handleLike(int index) {
setState(() {
final m = _moments[index];
m.liked ? m.likeCount-- : m.likeCount++;
m.liked = !m.liked;
});
}
void _handleComment(int index, String text) {
setState(() {
_moments[index].comments.add(Comment(nickname: '', content: text));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
appBar: _buildAppBar(),
body: Padding(padding: const EdgeInsets.all(10), child: _buildMoment()),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
automaticallyImplyLeading: false,
backgroundColor: Color(0xFF120D25),
elevation: 0,
centerTitle: true,
title: const Text(
'朋友圈',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.add, size: 22, color: Colors.white),
splashRadius: 20,
),
],
);
}
Widget _buildMoment() {
return ListView.separated(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + 80,
),
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 10);
},
itemCount: _moments.length,
itemBuilder: (context, index) {
return MomentItem(
moment: _moments[index],
onLike: () => _handleLike(index),
onComment: (text) => _handleComment(index, text),
);
},
);
}
}
/// ================= Moment Item =================
class MomentItem extends StatefulWidget {
final Moment moment;
final VoidCallback onLike;
final Function(String) onComment;
const MomentItem({
super.key,
required this.moment,
required this.onLike,
required this.onComment,
});
@override
State<MomentItem> createState() => _MomentItemState();
}
class _MomentItemState extends State<MomentItem> {
final TextEditingController _controller = TextEditingController();
void _submit() {
if (_controller.text.trim().isEmpty) return;
widget.onComment(_controller.text.trim());
_controller.clear();
}
@override
Widget build(BuildContext context) {
final m = widget.moment;
return GlassCard(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(backgroundImage: NetworkImage(m.avatar)),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
m.nickname,
style: const TextStyle(
color: Colors.purple,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(m.content, style: const TextStyle(color: Colors.white)),
if (m.images.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8),
child: LayoutBuilder(
builder: (context, constraints) {
final isSingle = m.images.length == 1;
final itemWidth =
isSingle ? 180.0 : (constraints.maxWidth - 8) / 3;
return Wrap(
spacing: 4,
runSpacing: 4,
children:
m.images.map((url) {
return ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.network(
url,
width: itemWidth,
height: itemWidth,
fit: BoxFit.cover,
),
);
}).toList(),
);
},
),
),
const SizedBox(height: 4),
Row(
children: [
Badge(
label: Text('${m.likeCount}'),
isLabelVisible: m.likeCount > 0,
offset: const Offset(0, 0),
child: IconButton(
onPressed: widget.onLike,
icon: Icon(
m.liked ? Icons.favorite : Icons.favorite_border,
color: m.liked ? Colors.red : Colors.grey,
),
),
),
Badge(
label: Text('${m.comments.length}'),
isLabelVisible: m.comments.length > 0,
offset: const Offset(0, 0),
child: IconButton(
onPressed: () => {},
icon: Icon(Icons.comment, color: Colors.grey),
),
),
],
),
if (m.comments.isNotEmpty)
Container(
width: double.infinity,
margin: const EdgeInsets.only(top: 6),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Color(0xFF120D25),
borderRadius: BorderRadius.circular(6),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:
m.comments
.map(
(c) => Text(
'${c.nickname}${c.content}',
style: const TextStyle(
fontSize: 13,
color: Colors.white,
),
),
)
.toList(),
),
),
],
),
),
],
),
);
}
}

View File

@@ -1,4 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
import 'chat_page.dart';
class MessagePage extends StatelessWidget { class MessagePage extends StatelessWidget {
const MessagePage({super.key}); const MessagePage({super.key});
@@ -6,20 +9,20 @@ class MessagePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF5F5F7), backgroundColor: Colors.transparent,
appBar: _buildAppBar(), appBar: _buildAppBar(),
body: _buildMessageList(), body: _buildMessageList(context),
); );
} }
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar() {
return AppBar( return AppBar(
backgroundColor: Colors.grey.shade200, backgroundColor: Color(0xFF120D25),
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
leading: IconButton( leading: IconButton(
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.menu, size: 22), icon: const Icon(Icons.menu, size: 22, color: Colors.white),
splashRadius: 20, splashRadius: 20,
), ),
title: const Text( title: const Text(
@@ -27,113 +30,106 @@ class MessagePage extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF1C1C1E), color: Colors.white,
), ),
), ),
actions: [ actions: [
IconButton( IconButton(
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.search, size: 22), icon: const Icon(Icons.search, size: 22, color: Colors.white),
splashRadius: 20, splashRadius: 20,
), ),
IconButton( IconButton(
onPressed: () {}, onPressed: () {},
icon: const Icon(Icons.add_circle_outline, size: 22), icon: const Icon(Icons.add, size: 22, color: Colors.white),
splashRadius: 20, splashRadius: 20,
), ),
], ],
); );
} }
Widget _buildMessageList() { Widget _buildMessageList(BuildContext context) {
return ListView.separated( return ListView.separated(
padding: EdgeInsets.only(
left: 10,
right: 10,
top: 10,
bottom: MediaQuery.of(context).padding.bottom + 80,
),
separatorBuilder: (BuildContext context, int index) {
return SizedBox(height: 10);
},
itemCount: _messages.length, itemCount: _messages.length,
separatorBuilder: (_, __) => const SizedBox(height: 0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final msg = _messages[index]; final msg = _messages[index];
return Container( return _buildMessageItem(context, msg);
decoration: const BoxDecoration( },
color: Colors.white, );
border: Border( }
bottom: BorderSide(
color: Color(0xFFE5E5EA), Widget _buildMessageItem(BuildContext context, Message msg) {
width: 0.6, return GlassCard(
),
),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
child: ListTile( child: ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
onTap:
() => {
Navigator.push(
context,
MaterialPageRoute(
builder:
(_) => const ChatPage(
contactName: '张三',
avatarUrl: 'https://picsum.photos/id/1012/200',
),
),
),
},
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
backgroundImage: NetworkImage(msg['avatar']!), backgroundImage: NetworkImage(msg.avatar),
), ),
title: Text( title: Text(
msg['name'].toString(), msg.name,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Color(0xFF1C1C1E), color: Colors.white,
), ),
), ),
subtitle: Text( subtitle: Text(
msg['lastMsg'].toString(), msg.lastMsg,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(fontSize: 13, color: Color(0xFFAEAEB2)),
fontSize: 13,
color: Color(0xFF8E8E93),
),
), ),
trailing: Text( trailing: Text(
msg['time'].toString(), msg.time,
style: const TextStyle( style: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
fontSize: 12,
color: Color(0xFF8E8E93),
), ),
), ),
),
);
},
); );
} }
} }
/// ✅ 假数据 class Message {
const List<Map<String, String>> _messages = [ final String name;
{ final String avatar;
'avatar': 'https://randomuser.me/api/portraits/men/32.jpg', final String lastMsg;
'name': '小刘', final String time;
'lastMsg': '今晚一起吃饭吗?',
'time': '12:30', Message({
}, required this.name,
{ required this.avatar,
'avatar': 'https://randomuser.me/api/portraits/women/44.jpg', required this.lastMsg,
'name': 'AI 助手', required this.time,
'lastMsg': '已为你生成总结', });
'time': '11:20', }
},
{ List<Message> _messages = [
'avatar': 'https://randomuser.me/api/portraits/men/65.jpg', Message(
'name': '产品经理', avatar: 'https://picsum.photos/id/1012/200',
'lastMsg': '新版本评审定在周五', name: '小刘',
'time': '昨天', lastMsg: '今晚一起吃饭吗?',
}, time: '12:30',
{ ),
'avatar': 'https://randomuser.me/api/portraits/men/32.jpg',
'name': '小刘',
'lastMsg': '今晚一起吃饭吗?',
'time': '12:30',
},
{
'avatar': 'https://randomuser.me/api/portraits/women/44.jpg',
'name': 'AI 助手',
'lastMsg': '已为你生成总结',
'time': '11:20',
},
{
'avatar': 'https://randomuser.me/api/portraits/men/65.jpg',
'name': '产品经理',
'lastMsg': '新版本评审定在周五',
'time': '昨天',
},
]; ];

View File

@@ -1,92 +1 @@
name: sweet_chat_app name: sweet_chat_app
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.7.0
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
liquid_glass_widgets: ^0.16.1
azlistview: ^2.0.0
lpinyin: ^2.0.3
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package