fav.dart 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import 'package:english_words/english_words.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:provider/provider.dart';
  4. void main() {
  5. runApp(MyApp());
  6. }
  7. class MyApp extends StatelessWidget {
  8. const MyApp({super.key});
  9. @override
  10. Widget build(BuildContext context) {
  11. return ChangeNotifierProvider(
  12. create: (context) => MyAppState(),
  13. child: MaterialApp(
  14. title: 'Namer App',
  15. theme: ThemeData(
  16. useMaterial3: true,
  17. colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
  18. ),
  19. home: MyHomePage(),
  20. ),
  21. );
  22. }
  23. }
  24. class MyAppState extends ChangeNotifier {
  25. var current = WordPair.random();
  26. var history = <WordPair>[];
  27. GlobalKey? historyListKey;
  28. void getNext() {
  29. history.insert(0, current);
  30. var animatedList = historyListKey?.currentState as AnimatedListState?;
  31. animatedList?.insertItem(0);
  32. current = WordPair.random();
  33. notifyListeners();
  34. }
  35. var favorites = <WordPair>[];
  36. void toggleFavorite([WordPair? pair]) {
  37. pair = pair ?? current;
  38. if (favorites.contains(pair)) {
  39. favorites.remove(pair);
  40. } else {
  41. favorites.add(pair);
  42. }
  43. notifyListeners();
  44. }
  45. void removeFavorite(WordPair pair) {
  46. favorites.remove(pair);
  47. notifyListeners();
  48. }
  49. }
  50. class MyHomePage extends StatefulWidget {
  51. @override
  52. State<MyHomePage> createState() => _MyHomePageState();
  53. }
  54. class _MyHomePageState extends State<MyHomePage> {
  55. var selectedIndex = 0;
  56. @override
  57. Widget build(BuildContext context) {// ← 1
  58. var colorScheme = Theme.of(context).colorScheme;
  59. Widget page;
  60. switch (selectedIndex) {
  61. case 0:
  62. page = GeneratorPage();
  63. break;
  64. case 1:
  65. page = FavoritesPage();
  66. break;
  67. default:
  68. throw UnimplementedError('no widget for $selectedIndex');
  69. }
  70. var mainArea = ColoredBox(
  71. color: colorScheme.surfaceVariant,
  72. child: AnimatedSwitcher(
  73. duration: Duration(milliseconds: 200),
  74. child: page,
  75. ),
  76. );
  77. return Scaffold(
  78. body: LayoutBuilder(
  79. builder: (context, constraints) {
  80. if (constraints.maxWidth < 450) {
  81. return Column(
  82. children: [
  83. Expanded(child: mainArea),
  84. SafeArea(
  85. child: BottomNavigationBar(
  86. items: [
  87. BottomNavigationBarItem(
  88. icon: Icon(Icons.home),
  89. label: 'Home',
  90. ),
  91. BottomNavigationBarItem(
  92. icon: Icon(Icons.favorite),
  93. label: 'Favorites',
  94. ),
  95. ],
  96. currentIndex: selectedIndex,
  97. onTap: (value) {
  98. setState(() {
  99. selectedIndex = value;
  100. });
  101. },
  102. ),
  103. )
  104. ],
  105. );
  106. } else {
  107. return Row(
  108. children: [
  109. SafeArea(
  110. child: NavigationRail(
  111. extended: constraints.maxWidth >= 600,
  112. destinations: [
  113. NavigationRailDestination(
  114. icon: Icon(Icons.home),
  115. label: Text('Home'),
  116. ),
  117. NavigationRailDestination(
  118. icon: Icon(Icons.favorite),
  119. label: Text('Favorites'),
  120. ),
  121. ],
  122. selectedIndex: selectedIndex,
  123. onDestinationSelected: (value) {
  124. setState(() {
  125. selectedIndex = value;
  126. });
  127. },
  128. ),
  129. ),
  130. Expanded(child: mainArea),
  131. ],
  132. );
  133. }
  134. },
  135. ),
  136. );
  137. }
  138. }
  139. class GeneratorPage extends StatelessWidget {
  140. @override
  141. Widget build(BuildContext context) {
  142. var appState = context.watch<MyAppState>();
  143. var pair = appState.current;
  144. IconData icon;
  145. if (appState.favorites.contains(pair)) {
  146. icon = Icons.favorite;
  147. } else {
  148. icon = Icons.favorite_border;
  149. }
  150. return Center(
  151. child: Column(
  152. mainAxisAlignment: MainAxisAlignment.center,
  153. children: [
  154. Expanded(
  155. flex: 3,
  156. child: HistoryListView(),
  157. ),
  158. SizedBox(height: 10),
  159. BigCard(pair: pair),
  160. SizedBox(height: 10,),
  161. Row(
  162. mainAxisSize: MainAxisSize.min,
  163. children: [
  164. ElevatedButton.icon(
  165. onPressed: () {
  166. appState.toggleFavorite();
  167. },
  168. icon: Icon(icon),
  169. label: Text('Like'),
  170. ),
  171. SizedBox(width: 10,),
  172. ElevatedButton(
  173. onPressed: () {
  174. appState.getNext();
  175. },
  176. child: Text('Next'),
  177. ),
  178. ],
  179. ),
  180. Spacer(flex: 2),
  181. ],
  182. ),
  183. );
  184. }
  185. }
  186. class BigCard extends StatelessWidget {
  187. const BigCard({
  188. Key? key,
  189. required this.pair,
  190. }) : super(key: key);
  191. final WordPair pair;
  192. @override
  193. Widget build(BuildContext context) {
  194. var theme = Theme.of(context);
  195. var style = theme.textTheme.displayMedium!.copyWith(
  196. color: theme.colorScheme.onPrimary,
  197. );
  198. return Card(
  199. color: theme.colorScheme.primary,
  200. child: Padding(
  201. padding: const EdgeInsets.all(20),
  202. child: AnimatedSize(
  203. duration: Duration(milliseconds: 200),
  204. child: MergeSemantics(
  205. child: Wrap(
  206. children: [
  207. Text(
  208. pair.first,
  209. style: style.copyWith(fontWeight: FontWeight.w200),
  210. ),
  211. Text(
  212. pair.second,
  213. style: style.copyWith(fontWeight: FontWeight.bold),
  214. )
  215. ],
  216. ),
  217. ),
  218. ),
  219. ),
  220. );
  221. }
  222. }
  223. class FavoritesPage extends StatelessWidget {
  224. @override
  225. Widget build(BuildContext context) {
  226. var theme = Theme.of(context);
  227. var appState = context.watch<MyAppState>();
  228. if (appState.favorites.isEmpty) {
  229. return Center(
  230. child: Text('No favorites yet.'),
  231. );
  232. }
  233. return Column(
  234. crossAxisAlignment: CrossAxisAlignment.start,
  235. children: [
  236. Padding(
  237. padding: const EdgeInsets.all(30),
  238. child: Text('You have '
  239. '${appState.favorites.length} favorites:'),
  240. ),
  241. Expanded(
  242. child: GridView(
  243. gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
  244. maxCrossAxisExtent: 400,
  245. childAspectRatio: 400 / 80,
  246. ),
  247. children: [
  248. for (var pair in appState.favorites)
  249. ListTile(
  250. leading: IconButton(
  251. icon: Icon(Icons.delete_outline, semanticLabel: 'Delete'),
  252. color: theme.colorScheme.primary,
  253. onPressed: () {
  254. appState.removeFavorite(pair);
  255. },
  256. ),
  257. title: Text(
  258. pair.asLowerCase,
  259. semanticsLabel: pair.asPascalCase,
  260. ),
  261. ),
  262. ],
  263. ),
  264. ),
  265. ],
  266. );
  267. }
  268. }
  269. class HistoryListView extends StatefulWidget {
  270. const HistoryListView({Key? key}) : super(key: key);
  271. @override
  272. State<HistoryListView> createState() => _HistoryListViewState();
  273. }
  274. class _HistoryListViewState extends State<HistoryListView> {
  275. final _key = GlobalKey();
  276. static const Gradient _maskingGradient = LinearGradient(
  277. colors: [Colors.transparent, Colors.black],
  278. stops: [0.0, 0.5],
  279. begin: Alignment.topCenter,
  280. end: Alignment.bottomCenter,
  281. );
  282. @override
  283. Widget build(BuildContext context) {
  284. final appState = context.watch<MyAppState>();
  285. appState.historyListKey = _key;
  286. return ShaderMask(
  287. shaderCallback: (bounds) => _maskingGradient.createShader(bounds),
  288. blendMode: BlendMode.dstIn,
  289. child: AnimatedList(
  290. key: _key,
  291. reverse: true,
  292. padding: EdgeInsets.only(top: 100),
  293. initialItemCount: appState.history.length,
  294. itemBuilder: (context, index, animation) {
  295. final pair = appState.history[index];
  296. return SizeTransition(
  297. sizeFactor: animation,
  298. child: Center(
  299. child: TextButton.icon(
  300. onPressed: () {
  301. appState.toggleFavorite(pair);
  302. },
  303. icon: appState.favorites.contains(pair)
  304. ? Icon(Icons.favorite, size: 12)
  305. : SizedBox(),
  306. label: Text(
  307. pair.asLowerCase,
  308. semanticsLabel: pair.asPascalCase,
  309. ),
  310. ),
  311. ),
  312. );
  313. },
  314. ),
  315. );
  316. }
  317. }