Files
HeurAMS-F/lib/main.dart
2025-08-30 22:28:21 +08:00

382 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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,
),
),
),
);
},
),
);
}
}