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

530 line
18 KiB

  1. import 'dart:io';
  2. import 'dart:math';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:fluttertoast/fluttertoast.dart';
  6. import 'package:image_picker/image_picker.dart';
  7. import 'package:permission_handler/permission_handler.dart';
  8. import 'package:zhiying_base_widget/pages/feedback_page/bloc/feedback_bloc.dart';
  9. import 'package:zhiying_base_widget/pages/feedback_page/bloc/feedback_repository.dart';
  10. import 'package:zhiying_base_widget/pages/feedback_page/feedback_record_page.dart';
  11. import 'package:zhiying_base_widget/pages/feedback_page/model/feedback_model.dart';
  12. import 'package:zhiying_comm/zhiying_comm.dart';
  13. import 'package:zhiying_base_widget/pages/feedback_page/widgets/feedback_image.dart';
  14. import 'package:zhiying_base_widget/pages/feedback_page/widgets/feedback_tab_widget.dart';
  15. import 'package:zhiying_base_widget/widgets/others/action_selected_alert/action_selected_alert.dart';
  16. import 'package:zhiying_comm/util/log/let_log.dart';
  17. import 'package:flutter_bloc/flutter_bloc.dart';
  18. import 'bloc/feedback_bloc.dart';
  19. import 'bloc/feedback_repository.dart';
  20. import 'bloc/feedback_state.dart';
  21. import 'bloc/feedback_event.dart';
  22. import 'package:fluttertoast/fluttertoast.dart';
  23. ///
  24. /// 意见反馈
  25. ///
  26. class FeedbackPage extends StatefulWidget {
  27. final Map<String, dynamic> data;
  28. FeedbackPage(this.data);
  29. @override
  30. _FeedbackPageState createState() => _FeedbackPageState();
  31. }
  32. class _FeedbackPageState extends State<FeedbackPage> {
  33. @override
  34. Widget build(BuildContext context) {
  35. return BlocProvider<FeedbackBloc>(
  36. create: (_) => FeedbackBloc(FeedBackRepository())..add(FeedbackInitEvent()),
  37. child: _FeedbackPageContainer(),
  38. );
  39. }
  40. }
  41. class _FeedbackPageContainer extends StatefulWidget {
  42. @override
  43. __FeedbackPageContainerState createState() => __FeedbackPageContainerState();
  44. }
  45. class __FeedbackPageContainerState extends State<_FeedbackPageContainer> {
  46. List<File> _images = List();
  47. bool _submitable = false;
  48. TextEditingController _feedback;
  49. TextEditingController _title;
  50. // 是否上传中
  51. bool isUpLoading = false;
  52. @override
  53. void initState() {
  54. _feedback = TextEditingController();
  55. _title = TextEditingController();
  56. super.initState();
  57. }
  58. @override
  59. void dispose() {
  60. _feedback?.dispose();
  61. _title?.dispose();
  62. super.dispose();
  63. }
  64. /// 选择图片
  65. void _onAddImage() async {
  66. if (_images.length >= 4) {
  67. Fluttertoast.showToast(msg: '最多上传4张图片');
  68. return;
  69. }
  70. var status = await Permission.photos.status;
  71. if (status != PermissionStatus.granted) {
  72. status = await Permission.photos.request();
  73. }
  74. if (status == PermissionStatus.denied) {
  75. Fluttertoast.showToast(msg: '暂无权限,图片选择失败');
  76. return null;
  77. }
  78. final picker = ImagePicker();
  79. PickedFile file;
  80. int index = await showModalBottomSheet(
  81. context: context,
  82. builder: (context) {
  83. return ActionSelectedAlert(
  84. // title: '拍照/选择图片',
  85. actions: ['拍照', '从相册选择图片'],
  86. );
  87. },
  88. isScrollControlled: false,
  89. backgroundColor: Colors.transparent);
  90. if (index != null) {
  91. if (index == 0) {
  92. file = await picker.getImage(source: ImageSource.camera);
  93. } else {
  94. file = await picker.getImage(source: ImageSource.gallery);
  95. }
  96. if (file == null) return;
  97. if (_images.length <= 4) {
  98. setState(() {
  99. _images.add(File(file.path));
  100. });
  101. } else {
  102. Fluttertoast.showToast(msg: '最多只能选择4张');
  103. }
  104. // File resultFile = await EncodeUtil.compressImage(file, 800);
  105. }
  106. }
  107. @override
  108. Widget build(BuildContext context) {
  109. return BlocConsumer<FeedbackBloc, FeedbackState>(
  110. listener: (context, state) {},
  111. buildWhen: (prev, current) {
  112. /// 保存成功
  113. if (current is FeedbackSaveSuccessState) {
  114. Fluttertoast.showToast(msg: '反馈成功~');
  115. Navigator.pop(context);
  116. return false;
  117. }
  118. if (current is FeedbackSaveErrorState) {
  119. Fluttertoast.showToast(msg: '反馈失败~');
  120. setState(() {
  121. isUpLoading = false;
  122. });
  123. return false;
  124. }
  125. return true;
  126. },
  127. builder: (context, state) {
  128. if (state is FeedbackLoadedState) {
  129. return _getMainWidget(state?.model);
  130. }
  131. return _getEmptyWidget();
  132. },
  133. );
  134. }
  135. /// 有数据
  136. Widget _getMainWidget(FeedbackModel model) {
  137. return Scaffold(
  138. resizeToAvoidBottomInset: false,
  139. appBar: _createNav(model),
  140. body: GestureDetector(
  141. onTap: () {
  142. FocusScope.of(context).requestFocus(FocusNode());
  143. },
  144. child: SafeArea(
  145. child: Column(
  146. children: <Widget>[
  147. Expanded(
  148. child: SingleChildScrollView(
  149. child: Column(
  150. children: <Widget>[
  151. Container(
  152. width: double.infinity,
  153. margin: EdgeInsets.only(top: 10, bottom: 10, left: 12.5, right: 12.5),
  154. padding: EdgeInsets.all(12.5),
  155. decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(7.5)),
  156. child: Column(
  157. crossAxisAlignment: CrossAxisAlignment.start,
  158. mainAxisAlignment: MainAxisAlignment.start,
  159. children: <Widget>[
  160. _createTitle(model),
  161. _createUpload(model),
  162. _createSubmit(model),
  163. ],
  164. ),
  165. )
  166. ],
  167. ),
  168. ),
  169. ),
  170. Center(
  171. child: GestureDetector(
  172. behavior: HitTestBehavior.opaque,
  173. onTap: () => RouterUtil.route(model, model.toJson(), context),
  174. child: Container(
  175. width: 120,
  176. height: 30,
  177. margin: EdgeInsets.all(10),
  178. decoration: BoxDecoration(
  179. color: HexColor.fromHex(model?.feedbackListBtnBgColor ?? '#FFFFFF'),
  180. borderRadius: BorderRadius.circular(15),
  181. border: Border.all(color: HexColor.fromHex(model?.feedbackListBtnBorderColor ?? '#D1D1D1'), width: 0.5),
  182. ),
  183. child: Row(
  184. mainAxisAlignment: MainAxisAlignment.center,
  185. crossAxisAlignment: CrossAxisAlignment.center,
  186. children: <Widget>[
  187. Padding(
  188. padding: EdgeInsets.only(right: 6),
  189. child: CachedNetworkImage(
  190. imageUrl: model?.feedbackListBtnIcon ?? '',
  191. width: 16.5,
  192. ),
  193. ),
  194. Text(
  195. model?.feedbackListBtnText ?? '反馈记录',
  196. style: TextStyle(fontSize: 13, color: HexColor.fromHex(model?.feedbackListBtnTextColor ?? '#333333'), fontWeight: FontWeight.bold),
  197. )
  198. ],
  199. ),
  200. ),
  201. ),
  202. ),
  203. const SizedBox(height: 25),
  204. ],
  205. ),
  206. ),
  207. ),
  208. );
  209. }
  210. /// 没数据
  211. Widget _getEmptyWidget() {
  212. return Scaffold(
  213. resizeToAvoidBottomInset: false,
  214. appBar: _createNav(null),
  215. body: Container(),
  216. );
  217. }
  218. /// 导航栏
  219. Widget _createNav(FeedbackModel mode) {
  220. return CupertinoNavigationBar(
  221. border: Border(
  222. bottom: BorderSide(
  223. width: 0.0, // One physical pixel.
  224. style: BorderStyle.none,
  225. ),
  226. ),
  227. backgroundColor: HexColor.fromHex(mode?.appBarBgColor ?? '#FFFFFF'),
  228. leading: Navigator.canPop(context)
  229. ? GestureDetector(
  230. child: Container(
  231. padding: EdgeInsets.zero,
  232. child: Icon(
  233. Icons.arrow_back_ios,
  234. size: 20,
  235. ),
  236. ),
  237. onTap: () {
  238. if (Navigator.canPop(context)) {
  239. Navigator.pop(context);
  240. }
  241. },
  242. )
  243. : Container(),
  244. middle: Text(
  245. mode?.appBarName ?? '意见反馈',
  246. style: TextStyle(
  247. fontSize: 15,
  248. color: HexColor.fromHex(mode?.appBarNameColor ?? '#333333'),
  249. ),
  250. ),
  251. );
  252. }
  253. Widget _createTitle(FeedbackModel model) {
  254. return Column(
  255. crossAxisAlignment: CrossAxisAlignment.start,
  256. mainAxisAlignment: MainAxisAlignment.start,
  257. children: <Widget>[
  258. Text(
  259. model?.feedbackTitle ?? '反馈描述',
  260. style: TextStyle(
  261. fontSize: 14,
  262. color: Color(0xff333333),
  263. fontWeight: FontWeight.bold,
  264. ),
  265. ),
  266. const SizedBox(height: 2),
  267. /// 异常选项
  268. Padding(
  269. padding: EdgeInsets.only(top: 4, bottom: 4),
  270. child: Visibility(
  271. visible: !EmptyUtil.isEmpty(model?.feedbackTypes),
  272. child: Row(
  273. children: model.feedbackTypes
  274. .map((e) => Expanded(
  275. child: GestureDetector(
  276. behavior: HitTestBehavior.opaque,
  277. onTap: () {
  278. setState(() {
  279. model.feedbackTypes.forEach((element) {
  280. element.isSelect = false;
  281. });
  282. e.isSelect = true;
  283. });
  284. },
  285. child: Container(
  286. margin: EdgeInsets.only(left: 2, right: 2),
  287. height: 28,
  288. child: Container(
  289. decoration: BoxDecoration(
  290. borderRadius: BorderRadius.circular(2.5),
  291. border: Border.all(
  292. color: HexColor.fromHex(e.isSelect ? model?.feedbackTypeSelectedBorderColor : model?.feedbackTypeNoSelectedBorderColor),
  293. //e.isSelect ? Colors.redAccent : Color(0xffe7e7e7),
  294. width: 1,
  295. ),
  296. ),
  297. child: Stack(
  298. children: <Widget>[
  299. Center(
  300. child: Text(
  301. e.name ?? '',
  302. style: TextStyle(
  303. fontSize: 12,
  304. color: HexColor.fromHex(
  305. e.isSelect ? model?.feedbackTypeSelectedTextColor : model?.feedbackTypeNoSelectedTextColor) //e.isSelect ? Colors.redAccent : Color(0xff999999),
  306. ),
  307. ),
  308. ),
  309. Visibility(
  310. visible: e.isSelect,
  311. child: Align(
  312. alignment: Alignment.bottomRight,
  313. child: CachedNetworkImage(
  314. imageUrl: model?.feedbackTypeSelectedBorderIcon,
  315. width: 10,
  316. ),
  317. ),
  318. )
  319. ],
  320. ),
  321. )),
  322. )))
  323. .toList(),
  324. ),
  325. ),
  326. ),
  327. // 标题
  328. Container(
  329. height: 32.5,
  330. margin: EdgeInsets.only(left: 2, right: 2, top: 4, bottom: 4),
  331. child: CupertinoTextField(
  332. controller: _title,
  333. textAlignVertical: TextAlignVertical.center,
  334. style: TextStyle(fontSize: 12, color: Color(0xff333333)),
  335. maxLines: 1,
  336. placeholder: model?.feedbackInputHintText ?? '请输入反馈标题(最多15个字)',
  337. placeholderStyle: TextStyle(
  338. fontSize: 12,
  339. color: HexColor.fromHex(model?.feedbackInputHintTextColor ?? '#999999'),
  340. ),
  341. decoration: BoxDecoration(color: Color(0xfff9f9f9), borderRadius: BorderRadius.circular(7.5)),
  342. onChanged: (value) {
  343. if (value == null || value == '' || EmptyUtil.isEmpty(_feedback?.text?.toString()?.trim())) {
  344. _submitable = false;
  345. } else {
  346. _submitable = true;
  347. }
  348. setState(() {});
  349. },
  350. ),
  351. ),
  352. // 内容
  353. Container(
  354. height: 118,
  355. margin: EdgeInsets.only(left: 2, right: 2, top: 4, bottom: 4),
  356. child: CupertinoTextField(
  357. controller: _feedback,
  358. textAlignVertical: TextAlignVertical.top,
  359. style: TextStyle(fontSize: 12, color: Color(0xff333333)),
  360. maxLines: null,
  361. placeholder: model?.feedbackInputContentHintText ?? '请输入您的意见(最多100个字)',
  362. placeholderStyle: TextStyle(
  363. fontSize: 12,
  364. color: HexColor.fromHex(model?.feedbackInputContentHintTextColor ?? '#999999'),
  365. ),
  366. decoration: BoxDecoration(color: Color(0xfff9f9f9), borderRadius: BorderRadius.circular(7.5)),
  367. onChanged: (value) {
  368. if (value == null || value == '' || EmptyUtil.isEmpty(_title?.text?.toString()?.trim())) {
  369. _submitable = false;
  370. } else {
  371. _submitable = true;
  372. }
  373. setState(() {});
  374. },
  375. ),
  376. ),
  377. ],
  378. );
  379. }
  380. Widget _createUpload(FeedbackModel model) {
  381. List<Widget> images = List();
  382. _images.forEach((file) {
  383. images.add(Container(
  384. margin: EdgeInsets.only(top: 4, bottom: 4, right: 8),
  385. width: 80,
  386. height: 80,
  387. child: FeedbackImageWidget(
  388. file: file,
  389. onDelete: () {
  390. setState(() {
  391. _images.remove(file);
  392. });
  393. },
  394. ),
  395. ));
  396. });
  397. if (images.length < 4) {
  398. images.add(GestureDetector(
  399. child: Container(
  400. margin: EdgeInsets.only(top: 4, bottom: 4),
  401. decoration: BoxDecoration(borderRadius: BorderRadius.circular(7.5), color: Color(0xfff9f9f9)),
  402. height: 80,
  403. width: 80,
  404. child: Icon(
  405. Icons.add,
  406. size: 60,
  407. color: Color(0xffd8d8d8),
  408. ),
  409. ),
  410. onTap: _onAddImage,
  411. ));
  412. }
  413. return Column(
  414. mainAxisAlignment: MainAxisAlignment.center,
  415. crossAxisAlignment: CrossAxisAlignment.start,
  416. children: <Widget>[
  417. Row(
  418. children: <Widget>[
  419. Padding(
  420. padding: EdgeInsets.only(right: 10, top: 4, bottom: 4),
  421. child: Text(
  422. model?.feedbackUploadImageTitle ?? '上传图片',
  423. style: TextStyle(
  424. fontSize: 14,
  425. color: Color(0xff333333),
  426. fontWeight: FontWeight.bold,
  427. ),
  428. ),
  429. ),
  430. Text(
  431. model?.feedbackUploadImageSubtitle ?? '最多上传4张,大小不超过1M/张',
  432. style: TextStyle(
  433. fontSize: 12,
  434. color: Color(0xff999999),
  435. ),
  436. ),
  437. ],
  438. ),
  439. // Row(
  440. // children: images,
  441. // )
  442. Wrap(
  443. crossAxisAlignment: WrapCrossAlignment.center,
  444. textDirection: TextDirection.ltr,
  445. spacing: 0,
  446. runSpacing: 0,
  447. runAlignment: WrapAlignment.center,
  448. alignment: WrapAlignment.start,
  449. children: images,
  450. ),
  451. ],
  452. );
  453. }
  454. Widget _createSubmit(FeedbackModel model) {
  455. return GestureDetector(
  456. onTap: () {
  457. if (!_submitable) {
  458. return;
  459. }
  460. if(isUpLoading){
  461. // Fluttertoast.showToast(msg: '提交中...');
  462. return;
  463. }
  464. setState(() {
  465. isUpLoading = true;
  466. });
  467. Logger.debug('提交:${_feedback.text.toString()}');
  468. String selectId;
  469. model.feedbackTypes.forEach((element) {
  470. if (element.isSelect) {
  471. selectId = element.typeId;
  472. }
  473. });
  474. BlocProvider.of<FeedbackBloc>(context)
  475. .add(FeedbackSubmitEvent(model: {'title': _title?.text?.toString(), 'content': _feedback?.text?.toString(), 'type': selectId, 'files': _images}));
  476. // Fluttertoast.showToast(msg: '提交成功~');
  477. // Navigator.pop(context);
  478. },
  479. child: Container(
  480. margin: EdgeInsets.only(top: 24, bottom: 4),
  481. height: 45,
  482. decoration: BoxDecoration(
  483. color: HexColor.fromHex(_submitable ? model?.feedbackTypeSelectedBorderColor ?? '' : model?.feedbackPostBtnBgColor ?? ''),
  484. borderRadius: BorderRadius.circular(7.5),
  485. ),
  486. child: Center(
  487. child: Text(
  488. isUpLoading ? '提交意见...' : model?.feedbackPostBtnText ?? '提交意见',
  489. style: TextStyle(
  490. fontSize: 14,
  491. color: HexColor.fromHex(model?.feedbackPostBtnTextColor ?? '#FFFFFF'),
  492. ),
  493. ),
  494. ),
  495. ),
  496. );
  497. }
  498. }