// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'package:english_words/english_words.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return ChangeNotifierProvider( // 创建并提供应用状态实例 create: (context) => MyAppState(), child: MaterialApp( title: 'HeurAMS - Flutter Shell', theme: ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed(seedColor: const Color.fromARGB(255, 22, 96, 165)), ), home: MyHomePage(), ), ); } } class MyAppState extends ChangeNotifier { // 当前显示的单词对 var current = WordPair.random(); // 历史记录列表 var history = []; // 用于控制历史列表动画的全局键 GlobalKey? historyListKey; // 获取下一个单词对 void getNext() { // 将当前单词添加到历史记录开头 history.insert(0, current); // 获取动画列表状态并插入新项(触发动画) var animatedList = historyListKey?.currentState as AnimatedListState?; animatedList?.insertItem(0); // 生成新的随机单词对 current = WordPair.random(); // 通知所有监听者状态已改变 notifyListeners(); } // 收藏夹列表 var favorites = []; // 切换收藏状态 void toggleFavorite([WordPair? pair]) { pair = pair ?? current; if (favorites.contains(pair)) { // 如果已收藏,则移除 favorites.remove(pair); } else { // 如果未收藏,则添加 favorites.add(pair); } // 通知所有监听者状态已改变 notifyListeners(); } // 移除收藏 void removeFavorite(WordPair pair) { favorites.remove(pair); // 通知所有监听者状态已改变 notifyListeners(); } } class MyHomePage extends StatefulWidget { @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { // 当前选中的页面索引(0:生成器,1:收藏夹) var selectedIndex = 0; @override Widget build(BuildContext context) { var colorScheme = Theme.of(context).colorScheme; Widget page; // 根据选中的索引决定显示哪个页面 switch (selectedIndex) { case 0: page = GeneratorPage(); break; case 1: page = FavoritesPage(); break; default: throw UnimplementedError('没有为 $selectedIndex 实现的组件'); } // 当前页面的容器,带有背景色和 subtle 切换动画 var mainArea = ColoredBox( color: colorScheme.surfaceContainerHighest, child: AnimatedSwitcher( duration: Duration(milliseconds: 200), child: page, ), ); return Scaffold( body: LayoutBuilder( builder: (context, constraints) { // 窄屏设备(如手机)使用底部导航栏 if (constraints.maxWidth < 450) { return Column( children: [ Expanded(child: mainArea), SafeArea( child: BottomNavigationBar( items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: '主页', ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: '收藏夹', ), ], currentIndex: selectedIndex, onTap: (value) { setState(() { selectedIndex = value; }); }, ), ) ], ); } else { // 宽屏设备(如平板、桌面)使用左侧导航栏 return Row( children: [ SafeArea( child: NavigationRail( extended: constraints.maxWidth >= 600, destinations: [ NavigationRailDestination( icon: Icon(Icons.home), label: Text('主页'), ), NavigationRailDestination( icon: Icon(Icons.favorite), label: Text('收藏夹'), ), ], selectedIndex: selectedIndex, onDestinationSelected: (value) { setState(() { selectedIndex = value; }); }, ), ), Expanded(child: mainArea), ], ); } }, ), ); } } class GeneratorPage extends StatelessWidget { @override Widget build(BuildContext context) { // 监听应用状态变化 var appState = context.watch(); var pair = appState.current; IconData icon; // 根据当前单词是否被收藏决定显示哪个图标 if (appState.favorites.contains(pair)) { icon = Icons.favorite; } else { icon = Icons.favorite_border; } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( flex: 3, child: HistoryListView(), ), SizedBox(height: 10), BigCard(pair: pair), SizedBox(height: 10), Row( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton.icon( onPressed: () { appState.toggleFavorite(); }, icon: Icon(icon), label: Text('收藏'), ), SizedBox(width: 10), ElevatedButton( onPressed: () { appState.getNext(); }, child: Text('下一个'), ), ], ), Spacer(flex: 2), ], ), ); } } class BigCard extends StatelessWidget { const BigCard({ Key? key, required this.pair, }) : super(key: key); final WordPair pair; @override Widget build(BuildContext context) { var theme = Theme.of(context); var style = theme.textTheme.displayMedium!.copyWith( color: theme.colorScheme.onPrimary, ); return Card( color: theme.colorScheme.primary, child: Padding( padding: const EdgeInsets.all(20), child: AnimatedSize( duration: Duration(milliseconds: 200), // 确保复合单词在窗口过窄时正确换行 child: MergeSemantics( child: Wrap( children: [ Text( pair.first, style: style.copyWith(fontWeight: FontWeight.w200), ), Text( pair.second, style: style.copyWith(fontWeight: FontWeight.bold), ) ], ), ), ), ), ); } } class FavoritesPage extends StatelessWidget { @override Widget build(BuildContext context) { var theme = Theme.of(context); var appState = context.watch(); // 如果收藏夹为空,显示提示信息 if (appState.favorites.isEmpty) { return Center( child: Text('无收藏'), ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(30), child: Text('你有 ' '${appState.favorites.length} 个收藏:'), ), Expanded( // 在宽窗口中使用网格布局以更好地利用空间 child: GridView( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 400, childAspectRatio: 400 / 80, ), children: [ for (var pair in appState.favorites) ListTile( leading: IconButton( icon: Icon(Icons.delete_outline, semanticLabel: '删除'), color: theme.colorScheme.primary, onPressed: () { appState.removeFavorite(pair); }, ), title: Text( pair.asLowerCase, semanticsLabel: pair.asPascalCase, ), ), ], ), ), ], ); } } class HistoryListView extends StatefulWidget { const HistoryListView({Key? key}) : super(key: key); @override State createState() => _HistoryListViewState(); } class _HistoryListViewState extends State { /// 需要此键以便 [MyAppState] 可以告诉下面的 [AnimatedList] 对新增项进行动画处理 final _key = GlobalKey(); /// 用于在顶部“淡化”历史记录项,暗示列表的延续性 static const Gradient _maskingGradient = LinearGradient( // 此渐变从完全透明到完全不透明的黑色... colors: [Colors.transparent, Colors.black], // ...从顶部(透明)到底部中途(0.5) stops: [0.0, 0.5], begin: Alignment.topCenter, end: Alignment.bottomCenter, ); @override Widget build(BuildContext context) { final appState = context.watch(); appState.historyListKey = _key; return ShaderMask( shaderCallback: (bounds) => _maskingGradient.createShader(bounds), // 此混合模式获取着色器的不透明度(即我们的渐变)并将其应用于目标(即我们的动画列表) blendMode: BlendMode.dstIn, child: AnimatedList( key: _key, reverse: true, padding: EdgeInsets.only(top: 100), initialItemCount: appState.history.length, itemBuilder: (context, index, animation) { final pair = appState.history[index]; return SizeTransition( sizeFactor: animation, child: Center( child: TextButton.icon( onPressed: () { appState.toggleFavorite(pair); }, icon: appState.favorites.contains(pair) ? Icon(Icons.favorite, size: 12) : SizedBox(), label: Text( pair.asLowerCase, semanticsLabel: pair.asPascalCase, ), ), ), ); }, ), ); } }