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