From 02084ada96577545ed7eb1d6219fbb1b375e59c8 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Wed, 17 Jun 2026 13:24:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9B=B4=E6=96=B0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis_options.yaml | 29 +- android/gradle.properties | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 6 +- lib/main.dart | 23 +- lib/pages/chat_page.dart | 178 +++++++++++ lib/pages/contacts_page.dart | 156 +++------- lib/pages/explore_page.dart | 277 ++++++++++++++++++ lib/pages/messages_page.dart | 176 ++++++----- pubspec.yaml | 93 +----- 9 files changed, 601 insertions(+), 341 deletions(-) create mode 100644 lib/pages/chat_page.dart create mode 100644 lib/pages/explore_page.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..64fc40d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1 @@ -# 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 +# 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 \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..ac32459 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6dcc88d..337fc2d 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1 @@ -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 +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 \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index fab90d6..3d2f034 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.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/explore_page.dart'; import 'package:sweet_chat_app/pages/messages_page.dart'; void main() async { @@ -19,6 +20,7 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( + scaffoldBackgroundColor: const Color(0xFF120D25), brightness: Brightness.light, useMaterial3: true, colorScheme: ColorScheme.light( @@ -43,28 +45,21 @@ class _HomePageState extends State { int _index = 0; final _pages = const [ - Center(child: MessagePage()), - Center(child: ContactsPage()), - Center(child: Text('⚙ Settings', style: TextStyle(fontSize: 26, color: Colors.white))), - Center(child: Text('⚙ 我的', style: TextStyle(fontSize: 26, color: Colors.white))), + MessagePage(), + ContactsPage(), + ExplorePage(), + Center(child: Text('⚙ 我的', style: TextStyle(fontSize: 26))), ]; @override Widget build(BuildContext context) { return GlassScaffold( - background: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0xFFFFFFFF), Color(0xFFF5F5F7)], - ), - ), - ), statusBarStyle: GlassStatusBarStyle.auto, bottomBar: GlassBottomBar( selectedIndex: _index, onTabSelected: (i) => setState(() => _index = i), + selectedIconColor: Colors.purple.shade200, + unselectedIconColor: Colors.white, tabs: const [ GlassBottomBarTab( icon: Icon(Icons.chat_bubble_outline), @@ -91,4 +86,4 @@ class _HomePageState extends State { body: _pages[_index], ); } -} \ No newline at end of file +} diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart new file mode 100644 index 0000000..03bb7fc --- /dev/null +++ b/lib/pages/chat_page.dart @@ -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 createState() => _ChatPageState(); +} + +class _ChatPageState extends State { + final TextEditingController _controller = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + + late List _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), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/contacts_page.dart b/lib/pages/contacts_page.dart index 22782f6..bef879b 100644 --- a/lib/pages/contacts_page.dart +++ b/lib/pages/contacts_page.dart @@ -40,86 +40,26 @@ class _ContactsPageState extends State { void _loadFakeData() { contactList = [ - ContactInfo( - name: '张三', - avatar: 'https://randomuser.me/api/portraits/men/11.jpg', - ), - ContactInfo( - name: '李四', - avatar: 'https://randomuser.me/api/portraits/men/12.jpg', - ), - ContactInfo( - name: '王五', - avatar: 'https://randomuser.me/api/portraits/men/13.jpg', - ), - ContactInfo( - name: '赵六', - avatar: 'https://randomuser.me/api/portraits/women/14.jpg', - ), - ContactInfo( - name: '陈七', - avatar: 'https://randomuser.me/api/portraits/women/15.jpg', - ), - 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', - ), + ContactInfo(name: '张三', avatar: 'https://picsum.photos/id/1011/200'), + ContactInfo(name: '李四', avatar: 'https://picsum.photos/id/1012/200'), + ContactInfo(name: '王五', avatar: 'https://picsum.photos/id/1013/200'), + ContactInfo(name: '赵六', avatar: 'https://picsum.photos/id/1014/200'), + ContactInfo(name: '陈七', avatar: 'https://picsum.photos/id/1015/200'), + ContactInfo(name: '刘八', avatar: 'https://picsum.photos/id/1016/200'), + ContactInfo(name: '周九', avatar: 'https://picsum.photos/id/1017/200'), + ContactInfo(name: '吴十', avatar: 'https://picsum.photos/id/1018/200'), + ContactInfo(name: '欧阳峰', avatar: 'https://picsum.photos/id/1021/200'), + ContactInfo(name: '诸葛青', avatar: 'https://picsum.photos/id/1022/200'), + ContactInfo(name: '司马光', avatar: 'https://picsum.photos/id/1023/200'), + ContactInfo(name: '慕容复', avatar: 'https://picsum.photos/id/1024/200'), + ContactInfo(name: '安琪拉', avatar: 'https://picsum.photos/id/1031/200'), + ContactInfo(name: '亚瑟', avatar: 'https://picsum.photos/id/1032/200'), + ContactInfo(name: '李白', avatar: 'https://picsum.photos/id/1033/200'), + ContactInfo(name: '韩信', avatar: 'https://picsum.photos/id/1034/200'), + ContactInfo(name: '孙尚香', avatar: 'https://picsum.photos/id/1035/200'), + ContactInfo(name: '鲁班', avatar: 'https://picsum.photos/id/1036/200'), + ContactInfo(name: '妲己', avatar: 'https://picsum.photos/id/1037/200'), + ContactInfo(name: '甄姬', avatar: 'https://picsum.photos/id/1038/200'), ]; _handleList(contactList); @@ -142,27 +82,31 @@ class _ContactsPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF2F2F7), - appBar: AppBar( - automaticallyImplyLeading: false, - backgroundColor: Colors.grey.shade200, - elevation: 0, - centerTitle: true, - title: const Text( - '通讯录', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF1C1C1E), - ), - ), - ), + backgroundColor: Colors.transparent, + appBar: _buildAppBar(), body: Column( children: [_buildSearchBar(), Expanded(child: _buildAzListView())], ), ); } + 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, + ), + ), + ); + } + Widget _buildAzListView() { return AzListView( data: contactList, @@ -170,7 +114,6 @@ class _ContactsPageState extends State { itemBuilder: (_, index) { final c = contactList[index]; return Container( - color: Colors.white, child: ListTile( leading: CircleAvatar( backgroundImage: NetworkImage(c.avatar), @@ -178,7 +121,7 @@ class _ContactsPageState extends State { ), title: Text( 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 { height: 28, padding: const EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.centerLeft, - color: const Color(0xFFF2F2F7), + color: const Color(0xFF2B2744), child: Text( 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 { Widget _buildSearchBar() { return Container( height: 44, - decoration: BoxDecoration( - color: Colors.white, - ), + decoration: BoxDecoration(color: Color(0xFF2F2A47)), child: TextField( textAlignVertical: TextAlignVertical.center, + style: const TextStyle(color: Colors.white), decoration: InputDecoration( + isDense: true, hintText: '搜索联系人', - hintStyle: const TextStyle(fontSize: 15, color: Color(0xFF8E8E93)), - prefixIcon: const Icon( - Icons.search, - size: 20, - color: Color(0xFF8E8E93), - ), + hintStyle: const TextStyle(fontSize: 15, color: Colors.white), + prefixIcon: const Icon(Icons.search, size: 20, color: Colors.white), border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(vertical: 12), ), ), ); diff --git a/lib/pages/explore_page.dart b/lib/pages/explore_page.dart new file mode 100644 index 0000000..5f256f9 --- /dev/null +++ b/lib/pages/explore_page.dart @@ -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 images; + + bool liked; + int likeCount; + List 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 createState() => _ExplorePageState(); +} + +class _ExplorePageState extends State { + final List _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 createState() => _MomentItemState(); +} + +class _MomentItemState extends State { + 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(), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/messages_page.dart b/lib/pages/messages_page.dart index cdfed5e..bcee81b 100644 --- a/lib/pages/messages_page.dart +++ b/lib/pages/messages_page.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:liquid_glass_widgets/liquid_glass_widgets.dart'; + +import 'chat_page.dart'; class MessagePage extends StatelessWidget { const MessagePage({super.key}); @@ -6,20 +9,20 @@ class MessagePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: const Color(0xFFF5F5F7), + backgroundColor: Colors.transparent, appBar: _buildAppBar(), - body: _buildMessageList(), + body: _buildMessageList(context), ); } PreferredSizeWidget _buildAppBar() { return AppBar( - backgroundColor: Colors.grey.shade200, + backgroundColor: Color(0xFF120D25), elevation: 0, centerTitle: true, leading: IconButton( onPressed: () {}, - icon: const Icon(Icons.menu, size: 22), + icon: const Icon(Icons.menu, size: 22, color: Colors.white), splashRadius: 20, ), title: const Text( @@ -27,113 +30,106 @@ class MessagePage extends StatelessWidget { style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, - color: Color(0xFF1C1C1E), + color: Colors.white, ), ), actions: [ IconButton( onPressed: () {}, - icon: const Icon(Icons.search, size: 22), + icon: const Icon(Icons.search, size: 22, color: Colors.white), splashRadius: 20, ), IconButton( onPressed: () {}, - icon: const Icon(Icons.add_circle_outline, size: 22), + icon: const Icon(Icons.add, size: 22, color: Colors.white), splashRadius: 20, ), ], ); } - Widget _buildMessageList() { + Widget _buildMessageList(BuildContext context) { 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, - separatorBuilder: (_, __) => const SizedBox(height: 0), itemBuilder: (context, index) { final msg = _messages[index]; - return Container( - decoration: const BoxDecoration( - color: Colors.white, - border: Border( - bottom: BorderSide( - color: Color(0xFFE5E5EA), - width: 0.6, - ), - ), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), - child: ListTile( - contentPadding: EdgeInsets.zero, - leading: CircleAvatar( - backgroundColor: Colors.transparent, - backgroundImage: NetworkImage(msg['avatar']!), - ), - title: Text( - msg['name'].toString(), - style: const TextStyle( - fontWeight: FontWeight.w500, - color: Color(0xFF1C1C1E), - ), - ), - subtitle: Text( - msg['lastMsg'].toString(), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 13, - color: Color(0xFF8E8E93), - ), - ), - trailing: Text( - msg['time'].toString(), - style: const TextStyle( - fontSize: 12, - color: Color(0xFF8E8E93), - ), - ), - ), - ); + return _buildMessageItem(context, msg); }, ); } + + Widget _buildMessageItem(BuildContext context, Message msg) { + return GlassCard( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + child: ListTile( + contentPadding: EdgeInsets.zero, + onTap: + () => { + Navigator.push( + context, + MaterialPageRoute( + builder: + (_) => const ChatPage( + contactName: '张三', + avatarUrl: 'https://picsum.photos/id/1012/200', + ), + ), + ), + }, + leading: CircleAvatar( + backgroundColor: Colors.transparent, + backgroundImage: NetworkImage(msg.avatar), + ), + title: Text( + msg.name, + style: const TextStyle( + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + subtitle: Text( + msg.lastMsg, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 13, color: Color(0xFFAEAEB2)), + ), + trailing: Text( + msg.time, + style: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)), + ), + ), + ); + } } -/// ✅ 假数据 -const List> _messages = [ - { - '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': '昨天', - }, - { - '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': '昨天', - }, -]; \ No newline at end of file +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 _messages = [ + Message( + avatar: 'https://picsum.photos/id/1012/200', + name: '小刘', + lastMsg: '今晚一起吃饭吗?', + time: '12:30', + ), +]; diff --git a/pubspec.yaml b/pubspec.yaml index 5059b27..249817f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,92 +1 @@ -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 +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 \ No newline at end of file