Showing
11 changed files
with
631 additions
and
1 deletions
lib/category/category_router.dart
0 → 100644
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 | +} |
lib/category/page/categories_page.dart
0 → 100644
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 | +} |
lib/category/page/category_list_page.dart
0 → 100644
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 | +} |
lib/category/widgets/category_item.dart
0 → 100644
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 | +} |
lib/category/widgets/category_item_page.dart
0 → 100644
File mode changed
lib/category/widgets/menu_reveal.dart
0 → 100644
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 | } | ... | ... |
-
Please register or login to post a comment