基础组件库
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.
 
 
 
 
 

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