reason

众妙页面框架

1 +import 'package:fluro/fluro.dart';
2 +import 'package:one_poem/routers/i_router.dart';
3 +
4 +import 'page/categories_page.dart';
5 +class CategoryRouter implements IRouterProvider{
6 +
7 + static String categoryPage = '/category';
8 +
9 + @override
10 + void initRouter(FluroRouter router) {
11 + router.define(categoryPage, handler: Handler(handlerFunc: (_, __) {
12 + return const CategoriesPage();
13 + }));
14 + }
15 +
16 +}
1 +import 'dart:convert';
2 +import 'package:one_poem/generated/json/base/json_field.dart';
3 +import 'package:one_poem/generated/json/category_item_entity.g.dart';
4 +
5 +@JsonSerializable()
6 +class CategoryItemEntity {
7 + late String icon;
8 + late String title;
9 + late int type;
10 +
11 + CategoryItemEntity({
12 + this.icon = "",
13 + this.title = "",
14 + this.type = 1,
15 + });
16 +
17 + factory CategoryItemEntity.fromJson(Map<String, dynamic> json) =>
18 + $CategoryItemEntityFromJson(json);
19 +
20 + Map<String, dynamic> toJson() => $CategoryItemEntityToJson(this);
21 +
22 + @override
23 + String toString() {
24 + return jsonEncode(this);
25 + }
26 +}
1 +import 'package:flutter/material.dart';
2 +import 'package:one_poem/category/provider/categories_page_provider.dart';
3 +import 'package:one_poem/util/theme_utils.dart';
4 +import 'package:one_poem/widgets/load_image.dart';
5 +import 'package:provider/provider.dart';
6 +
7 +import 'category_list_page.dart';
8 +
9 +/// design/4商品/index.html
10 +class CategoriesPage extends StatefulWidget {
11 + const CategoriesPage({Key? key}) : super(key: key);
12 +
13 + @override
14 + _CategoriesPageState createState() => _CategoriesPageState();
15 +}
16 +
17 +class _CategoriesPageState extends State<CategoriesPage>
18 + with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
19 + TabController? _tabController;
20 + final PageController _pageController = PageController();
21 +
22 + final GlobalKey _bodyKey = GlobalKey();
23 +
24 + CategoriesPageProvider provider = CategoriesPageProvider();
25 +
26 + @override
27 + void initState() {
28 + super.initState();
29 + _tabController = TabController(vsync: this, length: 3);
30 + }
31 +
32 + @override
33 + void dispose() {
34 + _tabController?.dispose();
35 + super.dispose();
36 + }
37 +
38 + @override
39 + Widget build(BuildContext context) {
40 + super.build(context);
41 + final Color? _iconColor = ThemeUtils.getIconColor(context);
42 + return ChangeNotifierProvider<CategoriesPageProvider>(
43 + create: (_) => provider,
44 + child: Scaffold(
45 + body: Column(
46 + key: _bodyKey,
47 + crossAxisAlignment: CrossAxisAlignment.start,
48 + children: <Widget>[
49 + Expanded(
50 + child: PageView.builder(
51 + key: const Key('pageView'),
52 + itemCount: 3,
53 + onPageChanged: _onPageChange,
54 + controller: _pageController,
55 + itemBuilder: (_, int index) =>
56 + CategoryListPage(index: index)),
57 + )
58 + ],
59 + ),
60 + ),
61 + );
62 + }
63 +
64 + void _onPageChange(int index) {
65 + _tabController?.animateTo(index);
66 + provider.setIndex(index);
67 + }
68 +
69 + @override
70 + bool get wantKeepAlive => true;
71 +}
1 +import 'package:flutter/material.dart';
2 +import 'package:one_poem/category/models/category_item_entity.dart';
3 +import 'package:one_poem/category/provider/categories_page_provider.dart';
4 +import 'package:one_poem/category/widgets/category_item.dart';
5 +import 'package:one_poem/res/constant.dart';
6 +import 'package:one_poem/widgets/my_refresh_list.dart';
7 +import 'package:one_poem/widgets/state_layout.dart';
8 +import 'package:provider/provider.dart';
9 +
10 +class CategoryListPage extends StatefulWidget {
11 +
12 + const CategoryListPage({
13 + Key? key,
14 + required this.index
15 + }): super(key: key);
16 +
17 + final int index;
18 +
19 + @override
20 + _CategoryListPageState createState() => _CategoryListPageState();
21 +}
22 +
23 +class _CategoryListPageState extends State<CategoryListPage> with AutomaticKeepAliveClientMixin<CategoryListPage>, SingleTickerProviderStateMixin {
24 +
25 + int _selectIndex = -1;
26 + late Animation<double> _animation;
27 + late AnimationController _controller;
28 + List<CategoryItemEntity> _list = [];
29 + AnimationStatus _animationStatus = AnimationStatus.dismissed;
30 +
31 + @override
32 + void initState() {
33 + super.initState();
34 + // 初始化动画控制
35 + _controller = AnimationController(duration: const Duration(milliseconds: 450), vsync: this);
36 + // 动画曲线
37 + final _curvedAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeOutSine);
38 + _animation = Tween(begin: 0.0, end: 1.1).animate(_curvedAnimation) ..addStatusListener((status) {
39 + _animationStatus = status;
40 + });
41 +
42 + //Item数量
43 + _maxPage = widget.index == 0 ? 1 : (widget.index == 1 ? 2 : 3);
44 +
45 + _onRefresh();
46 + }
47 +
48 + @override
49 + void dispose() {
50 + _controller.dispose();
51 + super.dispose();
52 + }
53 +
54 + final List<String> _imgList = [
55 + 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3130502839,1206722360&fm=26&gp=0.jpg',
56 + if (Constant.isDriverTest)
57 + 'https://img2.baidu.com/it/u=3994371075,170872697&fm=26&fmt=auto&gp=0.jpg'
58 + else
59 + 'https://xxx', // 可以使用一张无效链接,触发缺省、异常显示图片
60 + 'https://img0.baidu.com/it/u=4049693009,2577412121&fm=224&fmt=auto&gp=0.jpg',
61 + 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3659255919,3211745976&fm=26&gp=0.jpg',
62 + 'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2085939314,235211629&fm=26&gp=0.jpg',
63 + 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2441563887,1184810091&fm=26&gp=0.jpg'
64 + ];
65 +
66 + Future _onRefresh() async {
67 + await Future.delayed(const Duration(seconds: 2), () {
68 + setState(() {
69 + _page = 1;
70 + _list = List.generate(widget.index == 0 ? 3 : 10, (i) =>
71 + CategoryItemEntity(icon: _imgList[i % 6], title: '八月十五中秋月饼礼盒', type: i % 3));
72 + });
73 + _setGoodsCount(_list.length);
74 + });
75 + }
76 +
77 + Future _loadMore() async {
78 + await Future.delayed(const Duration(seconds: 2), () {
79 + setState(() {
80 + _list.addAll(List.generate(10, (i) =>
81 + CategoryItemEntity(icon: _imgList[i % 6], title: '八月十五中秋月饼礼盒', type: i % 3)));
82 + _page ++;
83 + });
84 + _setGoodsCount(_list.length);
85 + });
86 + }
87 +
88 + void _setGoodsCount(int count) {
89 +// Provider.of<GoodsPageProvider>(context, listen: false).setGoodsCount(count);
90 + /// 与上方等价,provider 4.1.0添加的拓展方法
91 + context.read<CategoriesPageProvider>().setGoodsCount(count);
92 + }
93 +
94 + int _page = 1;
95 + late int _maxPage;
96 + final StateType _stateType = StateType.loading;
97 +
98 + @override
99 + Widget build(BuildContext context) {
100 + super.build(context);
101 + return DeerListView(
102 + itemCount: _list.length,
103 + stateType: _stateType,
104 + onRefresh: _onRefresh,
105 + loadMore: _loadMore,
106 + hasMore: _page < _maxPage,
107 + itemBuilder: (_, index) {
108 + final String heroTag = 'goodsImg${widget.index}-$index';
109 + return CategoryItem(
110 + index: index,
111 + heroTag: heroTag,
112 + selectIndex: _selectIndex,
113 + item: _list[index],
114 + animation: _animation,
115 + onTapMenu: () {
116 + /// 点击其他item时,重置状态
117 + if (_selectIndex != index) {
118 + _animationStatus = AnimationStatus.dismissed;
119 + }
120 + /// 避免动画中重复执行
121 + if (_animationStatus == AnimationStatus.dismissed) {
122 + // 开始执行动画
123 + _controller.forward(from: 0.0);
124 + }
125 + setState(() {
126 + _selectIndex = index;
127 + });
128 + },
129 + onTapMenuClose: () {
130 + if (_animationStatus == AnimationStatus.completed) {
131 + _controller.reverse(from: 1.1);
132 + }
133 + _selectIndex = -1;
134 + },
135 + onTapEdit: () {
136 + setState(() {
137 + _selectIndex = -1;
138 + });
139 + },
140 + onTapOperation: () {
141 +
142 + },
143 + onTapDelete: () {
144 + _controller.reverse(from: 1.1);
145 + _selectIndex = -1;
146 + },
147 + );
148 + }
149 + );
150 + }
151 +
152 + @override
153 + bool get wantKeepAlive => true;
154 +}
1 +import 'package:flutter/material.dart';
2 +
3 +class CategoriesPageProvider extends ChangeNotifier {
4 +
5 + /// Tab的下标
6 + int _index = 0;
7 + int get index => _index;
8 + /// 商品数量
9 + final List<int> _goodsCountList = [0, 0, 0];
10 + List<int> get goodsCountList => _goodsCountList;
11 +
12 + /// 选中商品分类下标
13 + int _sortIndex = 0;
14 + int get sortIndex => _sortIndex;
15 +
16 + void setSortIndex(int sortIndex) {
17 + _sortIndex = sortIndex;
18 + notifyListeners();
19 + }
20 +
21 + void setIndex(int index) {
22 + _index = index;
23 + notifyListeners();
24 + }
25 +
26 + void setGoodsCount(int count) {
27 + _goodsCountList[index] = count;
28 + notifyListeners();
29 + }
30 +}
1 +import 'package:common_utils/common_utils.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:one_poem/category/models/category_item_entity.dart';
4 +import 'package:one_poem/res/resources.dart';
5 +import 'package:one_poem/util/device_utils.dart';
6 +import 'package:one_poem/util/other_utils.dart';
7 +import 'package:one_poem/util/theme_utils.dart';
8 +import 'package:one_poem/widgets/load_image.dart';
9 +import 'package:one_poem/widgets/my_button.dart';
10 +
11 +import 'menu_reveal.dart';
12 +
13 +/// design/4商品/index.html#artboard1
14 +class CategoryItem extends StatelessWidget {
15 +
16 + const CategoryItem({
17 + Key? key,
18 + required this.item,
19 + required this.index,
20 + required this.selectIndex,
21 + required this.onTapMenu,
22 + required this.onTapEdit,
23 + required this.onTapOperation,
24 + required this.onTapDelete,
25 + required this.onTapMenuClose,
26 + required this.animation,
27 + required this.heroTag,
28 + }): super(key: key);
29 +
30 + final CategoryItemEntity item;
31 + final int index;
32 + final int selectIndex;
33 + final VoidCallback onTapMenu;
34 + final VoidCallback onTapEdit;
35 + final VoidCallback onTapOperation;
36 + final VoidCallback onTapDelete;
37 + final VoidCallback onTapMenuClose;
38 + final Animation<double> animation;
39 + final String heroTag;
40 +
41 + @override
42 + Widget build(BuildContext context) {
43 + final Row child = Row(
44 + crossAxisAlignment: CrossAxisAlignment.start,
45 + children: <Widget>[
46 + ExcludeSemantics(
47 + child: Hero(
48 + tag: heroTag,
49 + child: LoadImage(item.icon, width: 72.0, height: 72.0),
50 + ),
51 + ),
52 + Gaps.hGap8,
53 + Expanded(
54 + child: Column(
55 + crossAxisAlignment: CrossAxisAlignment.start,
56 + children: <Widget>[
57 + Text(
58 + item.type % 3 != 0 ? '八月十五中秋月饼礼盒' : '八月十五中秋月饼礼盒八月十五中秋月饼礼盒',
59 + maxLines: 1,
60 + overflow: TextOverflow.ellipsis,
61 + ),
62 + Gaps.vGap4,
63 + Row(
64 + children: <Widget>[
65 + Visibility(
66 + // 默认为占位替换,类似于gone
67 + visible: item.type % 3 == 0,
68 + child: _GoodsItemTag(
69 + text: '立减',
70 + color: Theme.of(context).errorColor,
71 + ),
72 + ),
73 + Opacity(
74 + // 修改透明度实现隐藏,类似于invisible
75 + opacity: item.type % 2 != 0 ? 0.0 : 1.0,
76 + child: _GoodsItemTag(
77 + text: '金币抵扣',
78 + color: Theme.of(context).primaryColor,
79 + ),
80 + )
81 + ],
82 + ),
83 + Gaps.vGap16,
84 + Text(Utils.formatPrice('20.00', format: MoneyFormat.NORMAL))
85 + ],
86 + ),
87 + ),
88 + Column(
89 + crossAxisAlignment: CrossAxisAlignment.end,
90 + children: <Widget>[
91 + Semantics(
92 + /// container属性为true,防止上方ExcludeSemantics去除此处语义
93 + container: true,
94 + label: '商品操作菜单',
95 + child: GestureDetector(
96 + onTap: onTapMenu,
97 + child: Container(
98 + key: Key('goods_menu_item_$index'),
99 + width: 44.0,
100 + height: 44.0,
101 + color: Colors.transparent,
102 + padding: const EdgeInsets.only(left: 28.0, bottom: 28.0),
103 + child: const LoadAssetImage('goods/ellipsis'),
104 + ),
105 + ),
106 + ),
107 + Padding(
108 + padding: const EdgeInsets.only(top: 10.0),
109 + child: Text(
110 + '特产美味',
111 + style: Theme.of(context).textTheme.subtitle2,
112 + ),
113 + )
114 + ],
115 + )
116 + ],
117 + );
118 +
119 + return Stack(
120 + children: <Widget>[
121 + // item间的分隔线
122 + Padding(
123 + padding: const EdgeInsets.only(left: 16.0, top: 16.0),
124 + child: DecoratedBox(
125 + decoration: BoxDecoration(
126 + border: Border(
127 + bottom: Divider.createBorderSide(context, width: 0.8),
128 + ),
129 + ),
130 + child: Padding(
131 + padding: const EdgeInsets.only(right: 16.0, bottom: 16.0),
132 + child: child,
133 + ),
134 + ),
135 + ),
136 + if (selectIndex != index) Gaps.empty else _buildGoodsMenu(context),
137 + ],
138 + );
139 + }
140 +
141 + Widget _buildGoodsMenu(BuildContext context) {
142 + return Positioned.fill(
143 + child: AnimatedBuilder(
144 + animation: animation,
145 + child: _buildGoodsMenuContent(context),
146 + builder: (_, Widget? child) {
147 + return MenuReveal(
148 + revealPercent: animation.value,
149 + child: child!
150 + );
151 + }
152 + ),
153 + );
154 + }
155 +
156 + Widget _buildGoodsMenuContent(BuildContext context) {
157 + final bool isDark = context.isDark;
158 + final Color buttonColor = isDark ? Colours.dark_text : Colors.white;
159 +
160 + return InkWell(
161 + onTap: onTapMenuClose,
162 + child: Container(
163 + color: isDark ? const Color(0xB34D4D4D) : const Color(0x4D000000),
164 + child: Row(
165 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
166 + children: <Widget>[
167 + Gaps.hGap15,
168 + MyButton(
169 + key: Key('goods_edit_item_$index'),
170 + text: '编辑',
171 + fontSize: Dimens.font_sp16,
172 + radius: 24.0,
173 + minWidth: 56.0,
174 + minHeight: 56.0,
175 + padding: const EdgeInsets.symmetric(horizontal: 12.0),
176 + textColor: isDark ? Colours.dark_button_text : Colors.white,
177 + backgroundColor: isDark ? Colours.dark_app_main : Colours.app_main,
178 + onPressed: onTapEdit,
179 + ),
180 + MyButton(
181 + key: Key('goods_operation_item_$index'),
182 + text: '下架',
183 + fontSize: Dimens.font_sp16,
184 + radius: 24.0,
185 + minWidth: 56.0,
186 + minHeight: 56.0,
187 + padding: const EdgeInsets.symmetric(horizontal: 12.0),
188 + textColor: Colours.text,
189 + backgroundColor: buttonColor,
190 + onPressed: onTapOperation,
191 + ),
192 + MyButton(
193 + key: Key('goods_delete_item_$index'),
194 + text: '删除',
195 + fontSize: Dimens.font_sp16,
196 + radius: 24.0,
197 + minWidth: 56.0,
198 + minHeight: 56.0,
199 + padding: const EdgeInsets.symmetric(horizontal: 12.0),
200 + textColor: Colours.text,
201 + backgroundColor: buttonColor,
202 + onPressed: onTapDelete,
203 + ),
204 + Gaps.hGap15,
205 + ],
206 + ),
207 + ),
208 + );
209 + }
210 +}
211 +
212 +class _GoodsItemTag extends StatelessWidget {
213 +
214 + const _GoodsItemTag({
215 + Key? key,
216 + required this.color,
217 + required this.text,
218 + }): super(key: key);
219 +
220 + final Color? color;
221 + final String text;
222 +
223 + @override
224 + Widget build(BuildContext context) {
225 + return Container(
226 + padding: const EdgeInsets.symmetric(horizontal: 4.0),
227 + margin: const EdgeInsets.only(right: 4.0),
228 + decoration: BoxDecoration(
229 + color: color,
230 + borderRadius: BorderRadius.circular(2.0),
231 + ),
232 + height: 16.0,
233 + alignment: Alignment.center,
234 + child: Text(
235 + text,
236 + style: TextStyle(
237 + color: Colors.white,
238 + fontSize: Dimens.font_sp10,
239 + height: Device.isAndroid ? 1.1 : null,
240 + ),
241 + ),
242 + );
243 + }
244 +}
1 +import 'dart:math';
2 +
3 +import 'package:flutter/material.dart';
4 +
5 +
6 +//https://github.com/alibaba/flutter-go/blob/master/lib/views/fourth_page/page_reveal.dart
7 +class MenuReveal extends StatelessWidget {
8 +
9 + const MenuReveal({
10 + Key? key,
11 + required this.revealPercent,
12 + required this.child
13 + }): super(key: key);
14 +
15 + final double revealPercent;
16 + final Widget child;
17 +
18 + @override
19 + Widget build(BuildContext context) {
20 + return ClipOval(
21 + clipper: CircleRevealClipper(revealPercent),
22 + child: child,
23 + );
24 + }
25 +}
26 +
27 +class CircleRevealClipper extends CustomClipper<Rect> {
28 +
29 + CircleRevealClipper(this.revealPercent);
30 +
31 + final double revealPercent;
32 +
33 + @override
34 + Rect getClip(Size size) {
35 +
36 + // 右上角的点击点为圆心
37 + final Offset epicenter = Offset(size.width - 25.0, 25.0);
38 +
39 + final double theta = atan(epicenter.dy / epicenter.dx);
40 + final double distanceToCorner = (epicenter.dy) / sin(theta);
41 +
42 + final double radius = distanceToCorner * revealPercent;
43 + final double diameter = 2 * radius;
44 +
45 + return Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter);
46 + }
47 +
48 + @override
49 + bool shouldReclip(CustomClipper<Rect> oldClipper) {
50 + return true;
51 + }
52 +
53 +}
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
5 // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 5 // This file is automatically generated. DO NOT EDIT, all your changes would be lost.
6 import 'package:one_poem/account/models/user_entity.dart'; 6 import 'package:one_poem/account/models/user_entity.dart';
7 import 'package:one_poem/generated/json/user_entity.g.dart'; 7 import 'package:one_poem/generated/json/user_entity.g.dart';
8 +import 'package:one_poem/category/models/category_item_entity.dart';
9 +import 'package:one_poem/generated/json/category_item_entity.g.dart';
8 import 'package:one_poem/timeline/models/friend_entity.dart'; 10 import 'package:one_poem/timeline/models/friend_entity.dart';
9 import 'package:one_poem/generated/json/friend_entity.g.dart'; 11 import 'package:one_poem/generated/json/friend_entity.g.dart';
10 12
...@@ -80,6 +82,9 @@ class JsonConvert { ...@@ -80,6 +82,9 @@ class JsonConvert {
80 if(type == (UserEntity).toString()){ 82 if(type == (UserEntity).toString()){
81 return UserEntity.fromJson(json) as M; 83 return UserEntity.fromJson(json) as M;
82 } 84 }
85 + if(type == (CategoryItemEntity).toString()){
86 + return CategoryItemEntity.fromJson(json) as M;
87 + }
83 if(type == (FriendEntity).toString()){ 88 if(type == (FriendEntity).toString()){
84 return FriendEntity.fromJson(json) as M; 89 return FriendEntity.fromJson(json) as M;
85 } 90 }
...@@ -97,6 +102,9 @@ class JsonConvert { ...@@ -97,6 +102,9 @@ class JsonConvert {
97 if(<UserEntity>[] is M){ 102 if(<UserEntity>[] is M){
98 return data.map<UserEntity>((e) => UserEntity.fromJson(e)).toList() as M; 103 return data.map<UserEntity>((e) => UserEntity.fromJson(e)).toList() as M;
99 } 104 }
105 + if(<CategoryItemEntity>[] is M){
106 + return data.map<CategoryItemEntity>((e) => CategoryItemEntity.fromJson(e)).toList() as M;
107 + }
100 if(<FriendEntity>[] is M){ 108 if(<FriendEntity>[] is M){
101 return data.map<FriendEntity>((e) => FriendEntity.fromJson(e)).toList() as M; 109 return data.map<FriendEntity>((e) => FriendEntity.fromJson(e)).toList() as M;
102 } 110 }
......
1 +import 'package:one_poem/generated/json/base/json_convert_content.dart';
2 +import 'package:one_poem/category/models/category_item_entity.dart';
3 +
4 +CategoryItemEntity $CategoryItemEntityFromJson(Map<String, dynamic> json) {
5 + final CategoryItemEntity categoryItemEntity = CategoryItemEntity();
6 + final String? icon = jsonConvert.convert<String>(json['icon']);
7 + if (icon != null) {
8 + categoryItemEntity.icon = icon;
9 + }
10 + final String? title = jsonConvert.convert<String>(json['title']);
11 + if (title != null) {
12 + categoryItemEntity.title = title;
13 + }
14 + final int? type = jsonConvert.convert<int>(json['type']);
15 + if (type != null) {
16 + categoryItemEntity.type = type;
17 + }
18 + return categoryItemEntity;
19 +}
20 +
21 +Map<String, dynamic> $CategoryItemEntityToJson(CategoryItemEntity entity) {
22 + final Map<String, dynamic> data = <String, dynamic>{};
23 + data['icon'] = entity.icon;
24 + data['title'] = entity.title;
25 + data['type'] = entity.type;
26 + return data;
27 +}
...\ No newline at end of file ...\ No newline at end of file
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 import 'package:one_poem/account/page/account_page.dart'; 2 import 'package:one_poem/account/page/account_page.dart';
3 +import 'package:one_poem/category/page/categories_page.dart';
3 import 'package:one_poem/poem/page/poem_page.dart'; 4 import 'package:one_poem/poem/page/poem_page.dart';
4 import 'package:one_poem/res/resources.dart'; 5 import 'package:one_poem/res/resources.dart';
5 import 'package:one_poem/routers/not_found_page.dart'; 6 import 'package:one_poem/routers/not_found_page.dart';
...@@ -44,7 +45,7 @@ class _HomeState extends State<Home> with RestorationMixin { ...@@ -44,7 +45,7 @@ class _HomeState extends State<Home> with RestorationMixin {
44 _pageList = [ 45 _pageList = [
45 const PoemPage(), 46 const PoemPage(),
46 const TimelinesPage(), 47 const TimelinesPage(),
47 - const NotFoundPage(), 48 + const CategoriesPage(),
48 const AccountPage(), 49 const AccountPage(),
49 ]; 50 ];
50 } 51 }
......