基础组件库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

359 regels
11 KiB

  1. import 'dart:convert';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:pull_to_refresh/pull_to_refresh.dart';
  4. import 'package:tab_indicator_styler/tab_indicator_styler.dart';
  5. import 'package:zhiying_base_widget/pages/custom_page/custom_item_page.dart';
  6. import 'package:zhiying_base_widget/pages/main_page/notifier/main_page_bg_notifier.dart';
  7. import 'package:zhiying_base_widget/widgets/custom/search/custom_search_widget.dart';
  8. import 'package:zhiying_base_widget/widgets/empty/empty_widget.dart';
  9. import 'package:zhiying_comm/zhiying_comm.dart';
  10. import 'package:flutter/material.dart';
  11. import 'package:provider/provider.dart';
  12. import 'package:flutter_bloc/flutter_bloc.dart';
  13. import 'bloc/custom_page_bloc.dart';
  14. import 'bloc/custom_page_state.dart';
  15. import 'bloc/custom_page_event.dart';
  16. import 'bloc/custom_page_repository.dart';
  17. import 'dart:ui';
  18. import 'package:fluttertoast/fluttertoast.dart';
  19. ///
  20. /// 通用模块页面
  21. ///
  22. class CustomPage extends StatelessWidget {
  23. final Map<String, dynamic> data;
  24. CustomPage(this.data, {Key key}) : super(key: key);
  25. @override
  26. Widget build(BuildContext context) {
  27. // return NestedScrollDemoPage();
  28. return MultiProvider(
  29. providers: [
  30. ChangeNotifierProvider.value(value: MainPageBgNotifier()),
  31. ],
  32. child: BlocProvider<CustomPageBloc>(
  33. create: (_) => CustomPageBloc(CustomPageRepository(data: data))..add(CustomPageInitEvent()),
  34. child: _CommonPageContainer(data),
  35. // ),
  36. ),
  37. );
  38. }
  39. }
  40. class _CommonPageContainer extends StatefulWidget {
  41. final Map<String, dynamic> data;
  42. _CommonPageContainer(this.data);
  43. @override
  44. __CommonPageContainerState createState() => __CommonPageContainerState();
  45. }
  46. class __CommonPageContainerState extends State<_CommonPageContainer> with SingleTickerProviderStateMixin {
  47. TabController _tabController;
  48. /// 刷新
  49. void _onRefreshEvent() async {
  50. BlocProvider.of<CustomPageBloc>(context).add(CustomPageRefreshEvent());
  51. }
  52. @override
  53. void dispose() {
  54. _tabController?.dispose();
  55. super.dispose();
  56. }
  57. @override
  58. Widget build(BuildContext context) {
  59. return MediaQuery.removePadding(
  60. context: context,
  61. child: BlocConsumer<CustomPageBloc, CustomPageState>(
  62. listener: (context, state) {},
  63. buildWhen: (prev, current) {
  64. if (current is CustomPageErrorState) {
  65. return false;
  66. }
  67. if (current is CustomPageRefreshSuccessState) {
  68. // _refreshController.refreshCompleted(resetFooterState: true);
  69. return false;
  70. }
  71. if (current is CustomPageRefreshErrorState) {
  72. // _refreshController.refreshFailed();
  73. return false;
  74. }
  75. return true;
  76. },
  77. builder: (context, state) {
  78. /// 有数据
  79. if (state is CustomPageLoadedState) {
  80. if (EmptyUtil.isEmpty(state.model)) return _buildEmptyWidget();
  81. return _buildMainWidget(state.model);
  82. }
  83. /// 初始化失败
  84. if (state is CustomPageInitErrorState) {
  85. return _buildEmptyWidget();
  86. }
  87. /// 骨架图
  88. return _buildSkeletonWidget();
  89. },
  90. ),
  91. );
  92. }
  93. /// 有数据
  94. Widget _buildMainWidget(List<Map<String, dynamic>> model) {
  95. return Scaffold(
  96. appBar: _buildAppbar(model?.first),
  97. backgroundColor: HexColor.fromHex('#F9F9F9'),
  98. // floatingActionButton: _buildFloatWidget(),
  99. floatingActionButtonLocation: _CustomFloatingActionButtonLocation(FloatingActionButtonLocation.endFloat, 0, -100),
  100. body: Column(
  101. children: _buildFirstWidget(model),
  102. ),
  103. );
  104. }
  105. /// 骨架图
  106. Widget _buildSkeletonWidget() {
  107. return Scaffold();
  108. }
  109. /// 空数据视图
  110. Widget _buildEmptyWidget() {
  111. return Scaffold(
  112. backgroundColor: HexColor.fromHex('#F9F9F9'),
  113. appBar: AppBar(
  114. brightness: Brightness.light,
  115. backgroundColor: Colors.white,
  116. leading: IconButton(
  117. icon: Icon(
  118. Icons.arrow_back_ios,
  119. size: 22,
  120. color: HexColor.fromHex('#333333'),
  121. ),
  122. onPressed: () => Navigator.maybePop(context),
  123. ),
  124. title: Text(
  125. '',
  126. style: TextStyle(color: HexColor.fromHex('#333333'), fontSize: 18, fontWeight: FontWeight.bold),
  127. ),
  128. centerTitle: true,
  129. elevation: 0,
  130. ),
  131. body: Column(
  132. crossAxisAlignment: CrossAxisAlignment.center,
  133. mainAxisAlignment: MainAxisAlignment.center,
  134. children: <Widget>[
  135. Align(
  136. alignment: Alignment.topCenter,
  137. child: EmptyWidget(
  138. tips: '网络似乎开小差了~',
  139. ),
  140. ),
  141. GestureDetector(
  142. onTap: () => _onRefreshEvent(),
  143. behavior: HitTestBehavior.opaque,
  144. child: Container(
  145. alignment: Alignment.center,
  146. decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), border: Border.all(color: HexColor.fromHex('#999999'), width: 0.5), color: Colors.white),
  147. width: 80,
  148. height: 20,
  149. child: Text(
  150. '刷新一下',
  151. style: TextStyle(fontSize: 12, color: HexColor.fromHex('#333333'), fontWeight: FontWeight.bold),
  152. ),
  153. ),
  154. )
  155. ],
  156. ));
  157. }
  158. /// 数据,生成第一层widget
  159. List<Widget> _buildFirstWidget(List<Map<String, dynamic>> model) {
  160. List<Widget> result = [];
  161. // 分类导航的key ⚠️ 这里先写成Test 后续要改
  162. const String CATEGORY_KEY = 'category_test';
  163. // 判断是否有分类导航
  164. // 判断最后一个是否属于分类导航,如果属于,则有分类导航,如果不是,则无分类导航
  165. bool haveCategory = !EmptyUtil.isEmpty(model?.last) && model.last.containsKey('mod_name') && model.last['mod_name'] == CATEGORY_KEY;
  166. int endIndexLength = model.length;
  167. // 如果没有分类导航,则取分类导航之上的所有mod
  168. if (!haveCategory) {
  169. for (int i = 0; i < model.length; i++) {
  170. Map<String, dynamic> item = model[i];
  171. if (item['mod_name'] == CATEGORY_KEY) {
  172. endIndexLength = (i + 1);
  173. break;
  174. }
  175. }
  176. }
  177. for (int i = 0; i < endIndexLength; i++) {
  178. WidgetModel item = WidgetModel.fromJson(Map<String, dynamic>.from(model[i]));
  179. // last model
  180. if (i == endIndexLength - 1) {
  181. result.addAll(_buildTabBar(model[i]));
  182. break;
  183. }
  184. // appBar 无需在这里添加
  185. if (item.modName.contains('appbar')) {
  186. continue;
  187. }
  188. result.addAll(WidgetFactory.create(
  189. item.modName,
  190. isSliver: false,
  191. model: model[i],
  192. ));
  193. }
  194. return result;
  195. }
  196. /// appbar
  197. Widget _buildAppbar(final Map<String, dynamic> model) {
  198. if (EmptyUtil.isEmpty(model)) return null;
  199. String mobName = model['mod_name'];
  200. if (!mobName.contains('_appbar')) return null;
  201. Map<String, dynamic> data = Map<String, dynamic>();
  202. try {
  203. data = jsonDecode(model['data']);
  204. } catch (e, s) {
  205. Logger.warn(e, s);
  206. }
  207. String parentTitle = !EmptyUtil.isEmpty(widget?.data) ? widget?.data['title_1'] ?? '' : '';
  208. return AppBar(
  209. backgroundColor: HexColor.fromHex(null != data ? data['app_bar_bg_color'] ?? '#FFFFFF' : '#FFFFFF'),
  210. brightness: Brightness.light,
  211. leading: IconButton(
  212. icon: Icon(
  213. Icons.arrow_back_ios,
  214. size: 22,
  215. color: HexColor.fromHex('#333333'),
  216. ),
  217. onPressed: () => Navigator.maybePop(context),
  218. ),
  219. title: Text(
  220. null != data && data.containsKey('app_bar_name') ? data['app_bar_name'] != '自定义页面' ? data['app_bar_name'] : parentTitle : parentTitle,
  221. style: TextStyle(
  222. color: HexColor.fromHex(null != data ? data['app_bar_name_color'] ?? '#333333' : '#333333'),
  223. fontSize: 16,
  224. fontWeight: FontWeight.bold,
  225. ),
  226. ),
  227. centerTitle: true,
  228. elevation: 0,
  229. );
  230. }
  231. /// tabBar
  232. List<Widget> _buildTabBar(final Map<String, dynamic> model) {
  233. Map<String, dynamic> data = Map<String, dynamic>();
  234. List<Map<String, dynamic>> listStyle = [];
  235. List<Widget> result = [];
  236. try {
  237. data = jsonDecode(model['data']);
  238. listStyle = List.from(data['list_style']);
  239. } catch (e, s) {
  240. Logger.warn(e, s);
  241. }
  242. // 1、导航栏没开启的情况 传null进去进行获取没开启导航栏的widget集合
  243. if (EmptyUtil.isEmpty(listStyle)) {
  244. result.add(Expanded(
  245. child: CustomItemPage(null, 0, model['mod_id']?.toString() ?? null, model['mod_pid']?.toString() ?? null),
  246. ));
  247. return result;
  248. }
  249. // 2、导航栏开启的情况
  250. if (listStyle.length > 0) {
  251. // tabContorller 初始化
  252. if (null == _tabController || _tabController.length != listStyle.length) {
  253. _tabController = new TabController(length: listStyle.length, vsync: this);
  254. }
  255. result.add(Container(
  256. height: 40,
  257. width: double.infinity,
  258. color: HexColor.fromHex('#FFFFFF'),
  259. child: TabBar(
  260. controller: _tabController,
  261. isScrollable: /*listStyle.length <= 5 ? false : */ true,
  262. labelColor: HexColor.fromHex('#FF4242'),
  263. labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
  264. unselectedLabelColor: HexColor.fromHex('#999999'),
  265. unselectedLabelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
  266. indicatorSize: TabBarIndicatorSize.label,
  267. indicator: MaterialIndicator(
  268. color: HexColor.fromHex('#FF4242'),
  269. bottomLeftRadius: 1.25,
  270. topLeftRadius: 1.25,
  271. topRightRadius: 1.25,
  272. bottomRightRadius: 1.25,
  273. height: 2.5,
  274. horizontalPadding: 5,
  275. ),
  276. tabs: listStyle.map((e) => Text(e['name'])).toList(),
  277. ),
  278. ));
  279. // 最后添加TabBarView
  280. result.add(Expanded(
  281. child: TabBarView(
  282. controller: _tabController,
  283. children: _buildTabBarViewChildren(listStyle, model['mod_id']?.toString(), model['mod_pid']?.toString()),
  284. ),
  285. ));
  286. }
  287. return result;
  288. }
  289. /// 返回TabBarView的视图
  290. List<Widget> _buildTabBarViewChildren(final List<Map<String, dynamic>> listStyle, final String modId, final String modPid) {
  291. List<Widget> result = [];
  292. for (int i = 0; i < listStyle.length; i++) {
  293. result.add(CustomItemPage(listStyle[i], i, modId, modPid));
  294. }
  295. return result;
  296. }
  297. // /// 悬浮按钮
  298. // Widget _buildFloatWidget() {
  299. // return Visibility(
  300. // visible: true,
  301. // child: GestureDetector(
  302. // onTap: () => _scrollTop(),
  303. // behavior: HitTestBehavior.opaque,
  304. // child: Container(
  305. // height: 30,
  306. // width: 30,
  307. // child: Icon(Icons.arrow_upward),
  308. // ),
  309. // ),
  310. // );
  311. // }
  312. }
  313. /// 回到顶部的icon
  314. class _CustomFloatingActionButtonLocation extends FloatingActionButtonLocation {
  315. FloatingActionButtonLocation location;
  316. double offsetX; // X方向的偏移量
  317. double offsetY; // Y方向的偏移量
  318. _CustomFloatingActionButtonLocation(this.location, this.offsetX, this.offsetY);
  319. @override
  320. Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
  321. Offset offset = location.getOffset(scaffoldGeometry);
  322. return Offset(offset.dx + offsetX, offset.dy + offsetY);
  323. }
  324. }