feat:初始化工程

This commit is contained in:
2026-06-16 23:26:12 +08:00
commit 021db2efac
131 changed files with 5332 additions and 0 deletions

94
lib/main.dart Normal file
View File

@@ -0,0 +1,94 @@
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/messages_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 预编译 shader防止首帧白闪
await LiquidGlassWidgets.initialize();
// wrap() 安装无障碍桥接、全局主题、自适应质量
runApp(LiquidGlassWidgets.wrap(child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.light,
useMaterial3: true,
colorScheme: ColorScheme.light(
primary: Color(0xFF1C1C1E),
secondary: Color(0xFF007AFF),
surface: Colors.white,
),
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
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))),
];
@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),
tabs: const [
GlassBottomBarTab(
icon: Icon(Icons.chat_bubble_outline),
activeIcon: Icon(Icons.chat_bubble),
label: '消息',
),
GlassBottomBarTab(
icon: Icon(Icons.contacts_outlined),
activeIcon: Icon(Icons.contacts),
label: '通讯录',
),
GlassBottomBarTab(
icon: Icon(Icons.explore_outlined),
activeIcon: Icon(Icons.explore),
label: '发现',
),
GlassBottomBarTab(
icon: Icon(Icons.person_outline),
activeIcon: Icon(Icons.person),
label: '我的',
),
],
),
body: _pages[_index],
);
}
}

View File

@@ -0,0 +1,232 @@
import 'package:azlistview/azlistview.dart';
import 'package:flutter/material.dart';
import 'package:lpinyin/lpinyin.dart';
class ContactInfo extends ISuspensionBean {
final String name;
final String avatar;
String? tagIndex;
String? namePinyin;
ContactInfo({
required this.name,
required this.avatar,
this.tagIndex,
this.namePinyin,
});
@override
String getSuspensionTag() => tagIndex ?? '#';
}
/// ================= Page =================
class ContactsPage extends StatefulWidget {
const ContactsPage({super.key});
@override
State<ContactsPage> createState() => _ContactsPageState();
}
class _ContactsPageState extends State<ContactsPage> {
List<ContactInfo> contactList = [];
List<ContactInfo> topList = [];
@override
void initState() {
super.initState();
_loadFakeData();
}
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',
),
];
_handleList(contactList);
}
void _handleList(List<ContactInfo> list) {
for (var item in list) {
final pinyin = PinyinHelper.getPinyinE(item.name);
final tag = pinyin.substring(0, 1).toUpperCase();
item.namePinyin = pinyin;
item.tagIndex = RegExp(r'[A-Z]').hasMatch(tag) ? tag : '#';
}
SuspensionUtil.sortListBySuspensionTag(list);
SuspensionUtil.setShowSuspensionStatus(list);
setState(() {});
}
@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),
),
),
),
body: Column(
children: [_buildSearchBar(), Expanded(child: _buildAzListView())],
),
);
}
Widget _buildAzListView() {
return AzListView(
data: contactList,
itemCount: contactList.length,
itemBuilder: (_, index) {
final c = contactList[index];
return Container(
color: Colors.white,
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(c.avatar),
onBackgroundImageError: (_, __) {},
),
title: Text(
c.name,
style: const TextStyle(fontSize: 16, color: Color(0xFF1C1C1E)),
),
),
);
},
susItemBuilder: (_, index) {
final tag = contactList[index].getSuspensionTag();
return Container(
height: 28,
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.centerLeft,
color: const Color(0xFFF2F2F7),
child: Text(
tag,
style: const TextStyle(fontSize: 13, color: Color(0xFF8E8E93)),
),
);
},
indexBarData: [...kIndexBarData],
indexBarOptions: IndexBarOptions(
textStyle: const TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
selectItemDecoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.green,
),
),
);
}
Widget _buildSearchBar() {
return Container(
height: 44,
decoration: BoxDecoration(
color: Colors.white,
),
child: TextField(
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: '搜索联系人',
hintStyle: const TextStyle(fontSize: 15, color: Color(0xFF8E8E93)),
prefixIcon: const Icon(
Icons.search,
size: 20,
color: Color(0xFF8E8E93),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
),
),
);
}
}

View File

@@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
class MessagePage extends StatelessWidget {
const MessagePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F7),
appBar: _buildAppBar(),
body: _buildMessageList(),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: Colors.grey.shade200,
elevation: 0,
centerTitle: true,
leading: IconButton(
onPressed: () {},
icon: const Icon(Icons.menu, size: 22),
splashRadius: 20,
),
title: const Text(
'消息',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1C1C1E),
),
),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.search, size: 22),
splashRadius: 20,
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.add_circle_outline, size: 22),
splashRadius: 20,
),
],
);
}
Widget _buildMessageList() {
return ListView.separated(
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),
),
),
),
);
},
);
}
}
/// ✅ 假数据
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': '昨天',
},
];