feat:更新页面颜色
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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<HomePage> {
|
||||
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),
|
||||
|
||||
178
lib/pages/chat_page.dart
Normal file
178
lib/pages/chat_page.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,86 +40,26 @@ class _ContactsPageState extends State<ContactsPage> {
|
||||
|
||||
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,10 +82,18 @@ class _ContactsPageState extends State<ContactsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF2F2F7),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: _buildAppBar(),
|
||||
body: Column(
|
||||
children: [_buildSearchBar(), Expanded(child: _buildAzListView())],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PreferredSizeWidget _buildAppBar() {
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
backgroundColor: Color(0xFF120D25),
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
@@ -153,13 +101,9 @@ class _ContactsPageState extends State<ContactsPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
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) {
|
||||
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<ContactsPage> {
|
||||
),
|
||||
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<ContactsPage> {
|
||||
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<ContactsPage> {
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
277
lib/pages/explore_page.dart
Normal file
277
lib/pages/explore_page.dart
Normal 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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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']!),
|
||||
backgroundImage: NetworkImage(msg.avatar),
|
||||
),
|
||||
title: Text(
|
||||
msg['name'].toString(),
|
||||
msg.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF1C1C1E),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
msg['lastMsg'].toString(),
|
||||
msg.lastMsg,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF8E8E93),
|
||||
),
|
||||
style: const TextStyle(fontSize: 13, color: Color(0xFFAEAEB2)),
|
||||
),
|
||||
trailing: Text(
|
||||
msg['time'].toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF8E8E93),
|
||||
msg.time,
|
||||
style: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ✅ 假数据
|
||||
const List<Map<String, String>> _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': '昨天',
|
||||
},
|
||||
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 = [
|
||||
Message(
|
||||
avatar: 'https://picsum.photos/id/1012/200',
|
||||
name: '小刘',
|
||||
lastMsg: '今晚一起吃饭吗?',
|
||||
time: '12:30',
|
||||
),
|
||||
];
|
||||
93
pubspec.yaml
93
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
|
||||
Reference in New Issue
Block a user