初始化

This commit is contained in:
2025-08-30 22:28:21 +08:00
commit 9c6776d9be
129 changed files with 5143 additions and 0 deletions

381
lib/main.dart Normal file
View File

@@ -0,0 +1,381 @@
// 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 = <WordPair>[];
// 用于控制历史列表动画的全局键
GlobalKey? historyListKey;
// 获取下一个单词对
void getNext() {
// 将当前单词添加到历史记录开头
history.insert(0, current);
// 获取动画列表状态并插入新项(触发动画)
var animatedList = historyListKey?.currentState as AnimatedListState?;
animatedList?.insertItem(0);
// 生成新的随机单词对
current = WordPair.random();
// 通知所有监听者状态已改变
notifyListeners();
}
// 收藏夹列表
var favorites = <WordPair>[];
// 切换收藏状态
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<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// 当前选中的页面索引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<MyAppState>();
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<MyAppState>();
// 如果收藏夹为空,显示提示信息
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<HistoryListView> createState() => _HistoryListViewState();
}
class _HistoryListViewState extends State<HistoryListView> {
/// 需要此键以便 [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<MyAppState>();
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,
),
),
),
);
},
),
);
}
}