video_upload.dart 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:math';
  5. import 'package:chewie/chewie.dart';
  6. import 'package:flustars/flustars.dart' as FlutterStars;
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter_screenutil/flutter_screenutil.dart';
  10. import 'package:flutter_spinkit/flutter_spinkit.dart';
  11. import 'package:image_picker/image_picker.dart';
  12. import 'package:liftmanager/common/common.dart';
  13. import 'package:liftmanager/internal/bbs/model/video_detail.dart';
  14. import 'package:liftmanager/internal/bbs/page/brand_page.dart';
  15. import 'package:liftmanager/net/api_service.dart';
  16. import 'package:liftmanager/res/resources.dart';
  17. import 'package:liftmanager/utils/fast_notification.dart';
  18. import 'package:liftmanager/utils/log_util.dart';
  19. import 'package:liftmanager/utils/oss_upload.dart';
  20. import 'package:liftmanager/utils/theme_utils.dart';
  21. import 'package:liftmanager/utils/toast.dart';
  22. import 'package:liftmanager/utils/utils.dart';
  23. import 'package:liftmanager/widgets/app_bar.dart';
  24. import 'package:liftmanager/widgets/selected_video_change.dart';
  25. import 'package:video_player/video_player.dart';
  26. class VideoUpload extends StatefulWidget {
  27. VideoUpload(this.id);
  28. final String id;
  29. @override
  30. State<StatefulWidget> createState() {
  31. return VideoUploadState();
  32. }
  33. }
  34. class VideoUploadState extends State<VideoUpload> {
  35. String brandName = "品牌";
  36. int brandIdss;
  37. String selectedBrandName = '';
  38. bool showBrandSelectionPanel = false;
  39. VideoDetailModel detailObj;
  40. VideoPlayerController _controller;
  41. VideoPlayerController _controllerFile;
  42. ChewieController _chewieController;
  43. String videoUrl;
  44. String str;
  45. String imagesUrl;
  46. double percent = 0.0;
  47. // 焦点控制
  48. FocusNode _focusNode1 = new FocusNode();
  49. GlobalKey _formKey = new GlobalKey<FormState>();
  50. TextEditingController _titleController = new TextEditingController();
  51. TextEditingController _descController = new TextEditingController();
  52. @override
  53. void initState() {
  54. super.initState();
  55. print(widget.id);
  56. if (widget.id != null && widget.id != "") {
  57. NewApiService().getVideoDetail(int.parse(widget.id), 1, onSuccess: (res) {
  58. if (res != null) {
  59. detailObj = res;
  60. LogUtil.d(jsonEncode(res));
  61. setState(() {
  62. videoUrl = null;
  63. videoUrl = res.url;
  64. _titleController.text = res.title;
  65. _descController.text = res.descr;
  66. brandIdss = res.brandId;
  67. selectedBrandName = res.brandName;
  68. });
  69. }
  70. }, onError: (code, msg) {
  71. toasts(msg);
  72. });
  73. }
  74. }
  75. upLoadFileOnce(path) {
  76. showLoading(context, "正在上传...");
  77. NewApiService().upload(path, onSuccess: (res) {
  78. // imagesUrl.add(res.path);
  79. dismissLoading(context);
  80. setState(() {
  81. // videoUrl = [];
  82. // imagesUrl = [];
  83. // videoUrl.add(res.pathUrl);
  84. // imagesUrl.add(res.coverUrl);
  85. });
  86. }, onError: (code, msg) {
  87. dismissLoading(context);
  88. toasts(msg);
  89. });
  90. }
  91. randomInt(int min, int max) {
  92. return new Random().nextInt(max) % (max - min + 1) + min;
  93. }
  94. List<dynamic> brandList;
  95. Future getBrandList() async {
  96. await NewApiService().getBrandListType(null, onSuccess: (res) {
  97. if (res != null) {
  98. brandList = res;
  99. setState(() {});
  100. }
  101. }, onError: (code, msg) {
  102. toasts(msg);
  103. });
  104. }
  105. ///选择视频
  106. void selectPicker() {
  107. showDialog(
  108. context: context,
  109. builder: (BuildContext context) {
  110. return SimpleDialog(
  111. title: Text("选择方式"),
  112. children: ["拍照", '从手机相册选择'].map((String value) {
  113. print("$value");
  114. return SimpleDialogOption(
  115. child: Text(
  116. "${value}",
  117. style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
  118. ),
  119. onPressed: () {
  120. _getImage(value == '拍照' ? 1 : 0);
  121. Navigator.of(context).pop();
  122. },
  123. );
  124. }).toList());
  125. });
  126. }
  127. void _getImage(int key) async {
  128. print(key);
  129. try {
  130. await ImagePicker.pickVideo(
  131. source: key == 1 ? ImageSource.camera : ImageSource.gallery,
  132. ).then((File f) async {
  133. if (f != null) {
  134. _controllerFile = VideoPlayerController.file(f);
  135. _controllerFile.initialize().then((val) {
  136. _controllerFile.setLooping(true);
  137. int seconds = _controllerFile.value.duration.inSeconds;
  138. print("视频时长:$seconds");
  139. int fileSize = f.lengthSync(); //单位B
  140. print("视频大小:${fileSize}");
  141. print("视频大小:${f.path}");
  142. if (seconds <= 300) {
  143. _uploadImage(f.path);
  144. // upLoadFileOnce(_imageFile.path);
  145. // setState(() {});
  146. } else {
  147. toasts("视频时长不能大于5分钟!");
  148. }
  149. }).catchError((error) {
  150. print(error);
  151. print("error");
  152. toasts("上传失败,不支持此格式");
  153. });
  154. }
  155. });
  156. } catch (e) {
  157. toasts("没有权限,无法打开相册!");
  158. }
  159. }
  160. void _uploadImage(filePath) async {
  161. showPercent(context, () {
  162. dismissLoading(context);
  163. toasts("上传失败");
  164. }, () {
  165. if (videoUrl == null && str != null) {
  166. setState(() {
  167. videoUrl = str;
  168. settingVideoVC();
  169. print("videoUrl:" + videoUrl);
  170. dismissLoading(context);
  171. toasts("上传成功");
  172. });
  173. }
  174. });
  175. String uploadName = OssUtil.instance.getImageUploadName(filePath);
  176. await NewApiService.uploadImage(context, uploadName, filePath).then((data) {
  177. if (data.statusCode == 200) {
  178. str = NewApiUrl.URL_UPLOAD_IMAGE_OSS + "/" + uploadName;
  179. print("str:" + str);
  180. print(videoUrl);
  181. if (str != null) {
  182. Map obj = {"uploadName": uploadName, "success": true};
  183. FastNotification.push("percent", obj);
  184. }
  185. } else {
  186. Map obj = {"uploadName": uploadName, "success": false};
  187. FastNotification.push("percent", obj);
  188. }
  189. }).catchError((data) {
  190. Map obj = {"uploadName": uploadName, "success": false};
  191. FastNotification.push("percent", obj);
  192. });
  193. }
  194. FocusNode focusNode1 = new FocusNode();
  195. settingVideoVC() {
  196. _controller?.pause();
  197. _controller?.dispose();
  198. _chewieController?.dispose();
  199. _controller = VideoPlayerController.network(Utils.getImagePath(videoUrl));
  200. _chewieController = ChewieController(
  201. videoPlayerController: _controller,
  202. aspectRatio: 3 / 2,
  203. allowFullScreen: false,
  204. isLive: true,
  205. autoPlay: false,
  206. looping: true,
  207. // startAt: Duration(seconds: 1,minutes: 1),
  208. showControls: true,
  209. deviceOrientationsAfterFullScreen: [DeviceOrientation.portraitUp],
  210. // 是否在 UI 构建的时候就加载视频
  211. autoInitialize: true,
  212. // 拖动条样式颜色
  213. materialProgressColors: new ChewieProgressColors(
  214. playedColor: Colors.red,
  215. handleColor: Colors.blue,
  216. backgroundColor: Colors.grey,
  217. bufferedColor: Colors.lightGreen,
  218. ),
  219. );
  220. }
  221. @override
  222. void dispose() {
  223. _controller?.pause();
  224. _controller?.dispose();
  225. _chewieController?.dispose();
  226. // TODO: implement dispose
  227. super.dispose();
  228. }
  229. @override
  230. Widget build(BuildContext context) {
  231. double width = MediaQuery.of(context).size.width;
  232. double height = MediaQuery.of(context).size.height;
  233. // 监听FocusNode
  234. _focusNode1.addListener(() {
  235. // _focusNode1.hasFocus 是否聚焦
  236. print(_focusNode1.hasFocus);
  237. });
  238. return Scaffold(
  239. resizeToAvoidBottomPadding: false, //不让键盘弹上去
  240. appBar: MyAppBar(
  241. centerTitle: "上传视频",
  242. ),
  243. body: GestureDetector(
  244. onTap: () {
  245. // 点击空白页面关闭键盘
  246. FocusScope.of(context).requestFocus(focusNode1);
  247. },
  248. child: Stack(
  249. children: <Widget>[
  250. Container(
  251. child: ListView(children: <Widget>[
  252. Form(
  253. key: _formKey, //设置globalKey,用于后面获取FormState
  254. child: Column(
  255. children: <Widget>[
  256. ChioseThisRight(
  257. label: "电梯品牌",
  258. value: selectedBrandName,
  259. fun: () {
  260. setState(() {
  261. showBrandSelectionPanel = true;
  262. });
  263. getBrandList();
  264. }),
  265. Row(
  266. crossAxisAlignment: CrossAxisAlignment.start,
  267. mainAxisAlignment: MainAxisAlignment.start,
  268. children: <Widget>[
  269. Container(
  270. margin:
  271. EdgeInsets.only(top: ScreenUtil().setWidth(13)),
  272. height: 13,
  273. width: 2,
  274. color: Color(0xff5589FF),
  275. ),
  276. Container(
  277. padding: EdgeInsets.only(
  278. left: ScreenUtil().setWidth(13),
  279. top: ScreenUtil().setWidth(10),
  280. bottom: ScreenUtil().setWidth(5)),
  281. child: Text(
  282. "视频标题",
  283. style: TextStyle(
  284. color: Color(0xff222222),
  285. // fontSize:ScreenUtil().setSp(14),
  286. ),
  287. textAlign: TextAlign.left,
  288. ),
  289. ),
  290. ],
  291. ),
  292. Container(
  293. height: 80,
  294. padding: EdgeInsets.only(
  295. left: ScreenUtil().setWidth(15),
  296. right: ScreenUtil().setWidth(15),
  297. bottom: ScreenUtil().setWidth(20)),
  298. child: TextFormField(
  299. // autofocus: true,
  300. maxLength: 50,
  301. cursorColor: Color(0xffcccccc),
  302. controller: _titleController,
  303. maxLines: 5,
  304. decoration: InputDecoration(
  305. contentPadding: EdgeInsets.all(0),
  306. hintText: '请输入你上传视频的标题',
  307. hintStyle: TextStyle(color: Color(0xffcccccc)),
  308. focusedBorder: InputBorder.none,
  309. border: InputBorder.none,
  310. // filled: true, // 背景色
  311. // fillColor: Colors.cyan.withAlpha(35),
  312. // icon: Icon(Icons.person)
  313. ),
  314. // 校验
  315. validator: (val) {
  316. return val.trim().length > 0 ? null : "不能为空";
  317. }),
  318. ),
  319. Divider(),
  320. Row(
  321. crossAxisAlignment: CrossAxisAlignment.start,
  322. mainAxisAlignment: MainAxisAlignment.start,
  323. children: <Widget>[
  324. Container(
  325. margin:
  326. EdgeInsets.only(top: ScreenUtil().setWidth(13)),
  327. height: 13,
  328. width: 2,
  329. color: Color(0xff5589FF),
  330. ),
  331. Container(
  332. padding: EdgeInsets.only(
  333. left: ScreenUtil().setWidth(13),
  334. top: ScreenUtil().setWidth(10),
  335. bottom: ScreenUtil().setWidth(5)),
  336. child: Text(
  337. "视频简介",
  338. style: TextStyle(
  339. color: Color(0xff222222),
  340. // fontSize:ScreenUtil().setSp(14),
  341. ),
  342. textAlign: TextAlign.left,
  343. ),
  344. ),
  345. ],
  346. ),
  347. Container(
  348. height: 120,
  349. padding: EdgeInsets.only(
  350. left: ScreenUtil().setWidth(15),
  351. right: ScreenUtil().setWidth(15),
  352. bottom: ScreenUtil().setWidth(20)),
  353. child: TextFormField(
  354. // autofocus: true,
  355. maxLength: 500,
  356. cursorColor: Color(0xffcccccc),
  357. controller: _descController,
  358. maxLines: 5,
  359. decoration: InputDecoration(
  360. contentPadding: EdgeInsets.all(0),
  361. hintText: '请输入您上传视频的简介',
  362. hintStyle: TextStyle(color: Color(0xffcccccc)),
  363. focusedBorder: InputBorder.none,
  364. border: InputBorder.none,
  365. // filled: true, // 背景色
  366. // fillColor: Colors.cyan.withAlpha(35),
  367. // icon: Icon(Icons.person)
  368. ),
  369. // 校验
  370. validator: (val) {
  371. return val.trim().length > 0 ? null : "不能为空";
  372. }),
  373. ),
  374. Divider(),
  375. SizedBox(
  376. height: 20,
  377. ),
  378. Container(
  379. color: ThemeUtils.getTabsBg(context),
  380. padding: EdgeInsets.symmetric(horizontal: 10),
  381. child: Row(
  382. crossAxisAlignment: CrossAxisAlignment.end,
  383. children: [
  384. Stack(
  385. children: <Widget>[
  386. Container(
  387. height: 150,
  388. width: 150,
  389. child: SelectedVideo(
  390. image: videoUrl,
  391. videoPlay: videoPlay(),
  392. onTap: () {
  393. if (videoUrl == null) {
  394. selectPicker();
  395. }
  396. },
  397. ),
  398. ),
  399. (videoUrl != null)
  400. ? Positioned(
  401. top: 0,
  402. right: 0,
  403. child: GestureDetector(
  404. onTap: () {
  405. // print(index);
  406. // imagesUrl = null;
  407. setState(() {
  408. videoUrl = null;
  409. str = null;
  410. _controller.pause();
  411. // player.reset();
  412. });
  413. },
  414. child: Icon(
  415. const IconData(0xe651,
  416. fontFamily: "Iconfont"),
  417. size: 24.0,
  418. color: Color(0xff999999),
  419. ),
  420. ))
  421. : Container()
  422. ],
  423. ),
  424. Container(
  425. padding: EdgeInsets.only(left: 10),
  426. child: Text(
  427. "(建议时长<3分钟,大小<50M)",
  428. style: TextStyle(
  429. fontSize: ScreenUtil().setSp(13),
  430. color: Color(0xffCCCCCC)),
  431. textAlign: TextAlign.left,
  432. ),
  433. ),
  434. ],
  435. )),
  436. SizedBox(
  437. height: ScreenUtil().setWidth(80),
  438. ),
  439. ],
  440. ),
  441. )
  442. ])),
  443. Positioned(
  444. bottom: 0,
  445. left: 0,
  446. child: Container(
  447. width: width,
  448. padding: EdgeInsets.only(
  449. top: ScreenUtil().setWidth(15),
  450. bottom: ScreenUtil().setWidth(15),
  451. left: ScreenUtil().setWidth(25),
  452. right: ScreenUtil().setWidth(25)),
  453. color: ThemeUtils.getDialogTextFieldColor(context),
  454. child: Container(
  455. height: ScreenUtil().setWidth(44),
  456. decoration: BoxDecoration(
  457. color: Colours.blue_app_main,
  458. borderRadius:
  459. BorderRadius.circular(ScreenUtil().setWidth(22)),
  460. // gradient: const LinearGradient(
  461. // colors: [Color(0xFF00D9FF), Color(0xFF0287FF)]),
  462. ),
  463. child: FlatButton(
  464. // padding: EdgeInsets.all(15.0),
  465. child: Text("确认上传"),
  466. textColor: Colors.white,
  467. onPressed: () {
  468. if (brandIdss == null) {
  469. toasts("请选择品牌");
  470. return;
  471. }
  472. if (videoUrl == null) {
  473. toasts("请上传视频");
  474. return;
  475. }
  476. // String videos = videoUrl.join(",");
  477. // String images = imagesUrl.join(",");
  478. if ((_formKey.currentState as FormState).validate()) {
  479. showLoading(context, "正在提交...");
  480. if (widget.id != null && widget.id != "") {
  481. dynamic objEdit = {
  482. "brandId": brandIdss,
  483. "title": _titleController.text,
  484. "descr": _descController.text,
  485. "url": videoUrl,
  486. // "cover": images,
  487. "checkFlag": 2,
  488. "statuz": 1,
  489. "platformFlag": 1,
  490. "userId": FlutterStars.SpUtil.getString(
  491. Constant.userId),
  492. "id": detailObj.id
  493. };
  494. NewApiService().editVideo(objEdit,
  495. onSuccess: (res) {
  496. dismissLoading(context);
  497. toasts("修改成功");
  498. String initThisMyVideo =
  499. randomInt(1111, 9999).toString() +
  500. DateTime.now()
  501. .millisecondsSinceEpoch
  502. .toString();
  503. FastNotification.push(
  504. "initMyVideo", initThisMyVideo);
  505. if (_controller != null) {
  506. _controller.pause();
  507. }
  508. Navigator.of(context)..pop()..pop();
  509. }, onError: (code, msg) {
  510. dismissLoading(context);
  511. toasts(msg);
  512. });
  513. } else {
  514. dynamic obj = {
  515. "brandId": brandIdss,
  516. "title": _titleController.text,
  517. "descr": _descController.text,
  518. "url": videoUrl,
  519. // "cover": images,
  520. "checkFlag": 2,
  521. "statuz": 1,
  522. "platformFlag": 1,
  523. "userId":
  524. FlutterStars.SpUtil.getString(Constant.userId)
  525. };
  526. NewApiService().addVideo(obj, onSuccess: (res) {
  527. dismissLoading(context);
  528. toasts("提交成功");
  529. String initThisMyVideo =
  530. randomInt(1111, 9999).toString() +
  531. DateTime.now()
  532. .millisecondsSinceEpoch
  533. .toString();
  534. FastNotification.push(
  535. "initMyVideo", initThisMyVideo);
  536. if (_controller != null) {
  537. _controller.pause();
  538. }
  539. Navigator.pop(context);
  540. }, onError: (code, msg) {
  541. dismissLoading(context);
  542. toasts(msg);
  543. });
  544. }
  545. }
  546. },
  547. ),
  548. ),
  549. )),
  550. if (showBrandSelectionPanel)
  551. Positioned(
  552. top: 0,
  553. left: 0,
  554. child: GestureDetector(
  555. onTap: () {
  556. setState(() {
  557. showBrandSelectionPanel = false;
  558. });
  559. },
  560. child: Container(
  561. width: width,
  562. height: height,
  563. color: Color.fromRGBO(0, 0, 0, 0.5),
  564. ),
  565. ),
  566. ),
  567. if (showBrandSelectionPanel)
  568. Positioned(
  569. top: 0,
  570. right: 0,
  571. child: Container(
  572. width: width * 0.8,
  573. height: height,
  574. color: Colors.white,
  575. child: brandList == null
  576. ? loadCircle()
  577. : BrandSelectionPanel(
  578. brandList: brandList,
  579. onTapBrand: (records) {
  580. setState(() {
  581. selectedBrandName = records.name;
  582. brandIdss = records.id;
  583. showBrandSelectionPanel = false;
  584. });
  585. },
  586. ),
  587. ),
  588. ),
  589. ],
  590. ),
  591. ),
  592. );
  593. }
  594. Widget loadCircle() {
  595. return Container(
  596. padding: EdgeInsets.only(top: 10, bottom: 10),
  597. color: ThemeUtils.getTabsBg(context),
  598. child: Center(
  599. child: SpinKitFadingCircle(
  600. color: Colors.blueAccent,
  601. size: 30.0,
  602. ),
  603. ),
  604. );
  605. }
  606. Widget videoPlay() {
  607. if (_controller == null) return Container();
  608. return Container(
  609. child: ClipRRect(
  610. borderRadius: BorderRadius.circular(5),
  611. child: new Chewie(controller: _chewieController),
  612. ));
  613. }
  614. }
  615. class ChioseThisRight extends StatelessWidget {
  616. ChioseThisRight({
  617. Key key,
  618. this.value,
  619. this.label,
  620. this.fun,
  621. this.labelText = '请选择',
  622. this.labelTextColor = const Color(0xffcccccc),
  623. }) : super(key: key);
  624. String value;
  625. String label;
  626. Function fun;
  627. String labelText;
  628. Color labelTextColor;
  629. @override
  630. Widget build(BuildContext context) {
  631. double width = MediaQuery.of(context).size.width;
  632. return InkWell(
  633. onTap: () {
  634. fun();
  635. },
  636. child: Container(
  637. padding: EdgeInsets.only(
  638. top: ScreenUtil().setWidth(15), bottom: ScreenUtil().setWidth(15)),
  639. margin: EdgeInsets.only(left: ScreenUtil().setWidth(15)),
  640. decoration: BoxDecoration(
  641. border: Border(
  642. bottom: BorderSide(width: 0.5, color: Colours.line),
  643. ),
  644. ),
  645. child: Row(
  646. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  647. children: <Widget>[
  648. Text(
  649. label,
  650. style: TextStyle(
  651. color: Color(0xff222222),
  652. // fontSize: ScreenUtil().setSp(14)
  653. ),
  654. textAlign: TextAlign.start,
  655. ),
  656. value.isEmpty
  657. ? Container(
  658. child: Row(children: <Widget>[
  659. Text(
  660. labelText,
  661. style: TextStyle(
  662. color: labelTextColor,
  663. // fontSize: ScreenUtil().setSp(14)
  664. ),
  665. textAlign: TextAlign.start,
  666. ),
  667. Container(
  668. padding: EdgeInsets.only(top: 3),
  669. child: Icon(
  670. Icons.keyboard_arrow_right,
  671. size: 20.0,
  672. color: labelTextColor,
  673. ),
  674. ),
  675. SizedBox(width: 10)
  676. ]),
  677. )
  678. : Container(
  679. padding:
  680. EdgeInsets.only(right: ScreenUtil().setWidth(15)),
  681. child: Row(children: <Widget>[
  682. Text(
  683. value,
  684. style: TextStyle(
  685. color: labelTextColor,
  686. fontSize: ScreenUtil().setSp(14)),
  687. textAlign: TextAlign.start,
  688. ),
  689. Container(
  690. padding: EdgeInsets.only(top: 3),
  691. child: Icon(
  692. Icons.keyboard_arrow_right,
  693. size: 20.0,
  694. color: labelTextColor,
  695. ),
  696. ),
  697. // SizedBox(width: 10)
  698. ]),
  699. ),
  700. ]),
  701. ),
  702. );
  703. }
  704. }