picker.dart 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  1. /*
  2. * @Author: XianKaiQun
  3. * @Date: 2020-08-25 16:49:31
  4. * @LastEditors : WuWei
  5. * @LastEditTime : 2023-11-27 15:28:53
  6. * @Description:
  7. */
  8. import 'package:flutter/cupertino.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:wisdom_cli/new/utils/month_picker/season_picker.dart';
  11. import 'package:wisdom_cli/wisdom_cli.dart';
  12. import 'month_picker/date_period.dart';
  13. import 'month_picker/month_picker.dart';
  14. import 'month_picker/styles/date_picker_styles.dart';
  15. import 'month_picker/week_picker.dart';
  16. import 'month_picker/year_picker.dart';
  17. class WPickerUtil {
  18. WPickerUtil._();
  19. ///单选
  20. static Future<T?> single<T extends WPickerEntity>(
  21. BuildContext context, {
  22. Widget? title,
  23. required WSinglePickerGetData<T> getData,
  24. T? initialValue,
  25. // List selectValue,
  26. bool? isSearch,
  27. }) {
  28. List<T> _value = [];
  29. bool _isSearch = isSearch ?? false;
  30. if (initialValue != null) {
  31. _value = List.of([initialValue]);
  32. }
  33. // if (selectValue != null) {
  34. // _value = selectValue;
  35. // }
  36. void onChanged(List<T>? value, bool isTap) {
  37. _value = value!;
  38. if (isTap) Navigator.pop(context, _value[0]);
  39. }
  40. return WBottomModalUtil.show<T>(
  41. context,
  42. title: title,
  43. plain: true,
  44. body: WPicker<T>(
  45. multiple: false,
  46. initialValue: _value,
  47. getData: getData,
  48. onChanged: onChanged,
  49. isSearch: _isSearch,
  50. ),
  51. bodyConstraints: _isSearch
  52. ? BoxConstraints(
  53. maxHeight: 450,
  54. minHeight: 450,
  55. )
  56. : BoxConstraints(
  57. maxHeight: 350,
  58. minHeight: 250,
  59. ),
  60. );
  61. }
  62. ///多选
  63. static Future<List<T>?> multiple<T extends WPickerEntity>(
  64. BuildContext context, {
  65. Widget? title,
  66. required WSinglePickerGetData<T>? getData,
  67. List<T>? initialValue = const [],
  68. bool? isSearch = false,
  69. }) {
  70. List<T> _value = List.of(initialValue ?? []);
  71. void onCancel(context) => Navigator.pop(context);
  72. void onConfirm(context) {
  73. Navigator.pop(context, _value);
  74. }
  75. bool _isSearch = isSearch ?? false;
  76. return WBottomModalUtil.show<List<T>?>(
  77. context,
  78. title: title,
  79. plain: false,
  80. onCancel: onCancel,
  81. onConfirm: onConfirm,
  82. body: WPicker<T>(
  83. multiple: true,
  84. initialValue: _value,
  85. // enableValue: enableValue,
  86. getData: getData,
  87. isSearch: _isSearch,
  88. onChanged: (value, _) {
  89. _value = value!;
  90. },
  91. ),
  92. bodyConstraints: _isSearch
  93. ? BoxConstraints(
  94. maxHeight: 450,
  95. minHeight: 450,
  96. )
  97. : BoxConstraints(
  98. maxHeight: 350,
  99. minHeight: 250,
  100. ),
  101. );
  102. }
  103. ///无限单选(级联选择)
  104. static Future<List<T>?> infinity<T extends WPickerEntity>(
  105. BuildContext context, {
  106. Widget? title,
  107. required Future<List<T>> Function(List<T> item) getData,
  108. ///判断是否已选择到了最后一项,跳出选择
  109. required Future<bool> Function(List<T>)? isLast,
  110. List<T> initialValue = const [],
  111. }) {
  112. List<T>? _value = List.of(initialValue);
  113. int index = (_value.length - 1 < 0) ? 0 : initialValue.length - 1;
  114. bool showFutrue = _value.length == 0;
  115. T? currentValue() {
  116. if (index >= _value.length) {
  117. return null;
  118. } else {
  119. return _value[index];
  120. }
  121. }
  122. void onCancel(context) => Navigator.pop(context);
  123. void onConfirm(context) {
  124. if (_value.length != 0) Navigator.pop(context, _value);
  125. }
  126. return WBottomModalUtil.show<List<T>>(
  127. context,
  128. title: title,
  129. plain: true,
  130. onCancel: onCancel,
  131. onConfirm: onConfirm,
  132. body: StatefulBuilder(
  133. builder: (BuildContext context, setState) {
  134. add(List<T> items, isTap) async {
  135. if (isTap) {
  136. showFutrue = true;
  137. if (index <= _value.length - 1) {
  138. _value.removeRange(index, _value.length);
  139. }
  140. index++;
  141. _value.add(items[0]);
  142. if (isLast != null && await isLast(_value) == true) {
  143. onConfirm(context);
  144. } else {
  145. setState(() {});
  146. }
  147. }
  148. }
  149. behind(int i) {
  150. index = i;
  151. setState(() {});
  152. }
  153. final primaryColor = Theme.of(context).primaryColor;
  154. Widget tab(i) {
  155. String text =
  156. (_value.length > i) ? '${_value[i].selectName}' : '请选择';
  157. return Wisdom.column(
  158. onTap: () => behind(i),
  159. maxWidth: 100.pt,
  160. mainAxisAlignment: MainAxisAlignment.center,
  161. padding: EdgeInsets.symmetric(horizontal: 12.5.pt),
  162. children: [
  163. Spacer(),
  164. Text(
  165. text,
  166. overflow: TextOverflow.ellipsis,
  167. style: TextStyle(
  168. fontSize: 15.pt,
  169. fontWeight: i == index ? null : FontWeight.bold,
  170. color: i == index ? primaryColor : null,
  171. ),
  172. ),
  173. Spacer(),
  174. SizedBox(
  175. height: 4.pt,
  176. child: (i == index)
  177. ? WisActiveness(
  178. margin: EdgeInsets.only(top: 0),
  179. color: primaryColor,
  180. )
  181. : null,
  182. ),
  183. ],
  184. );
  185. }
  186. Widget tabs() {
  187. return Container(
  188. height: 44.pt,
  189. margin: EdgeInsets.symmetric(horizontal: 2.5.pt),
  190. child: ListView(
  191. scrollDirection: Axis.horizontal,
  192. children: [
  193. if (showFutrue)
  194. for (var i = 0; i < _value.length + 1; i++) tab(i)
  195. else
  196. for (var i = 0; i < _value.length; i++) tab(i),
  197. ],
  198. ),
  199. );
  200. }
  201. return Column(
  202. mainAxisSize: MainAxisSize.min,
  203. crossAxisAlignment: CrossAxisAlignment.stretch,
  204. children: [
  205. tabs(),
  206. Divider(height: 1),
  207. WPicker<T>(
  208. key: UniqueKey(),
  209. multiple: false,
  210. initialValue: currentValue() == null ? [] : [currentValue()!],
  211. getData: () =>
  212. getData([for (var i = 0; i < index; i++) _value[i]]),
  213. onChanged: (selects, isTap) => add(selects!, isTap),
  214. ).asExpanded(),
  215. ],
  216. );
  217. },
  218. ),
  219. );
  220. }
  221. /// 级联选择-------------------------------------
  222. static Future<List<T>?> cascade<T extends SinglePickerEntity>(
  223. BuildContext context, {
  224. Widget? title,
  225. bool? isSearch,
  226. /// 是否多选
  227. bool isMulti = false,
  228. required List<T> data,
  229. required int maxLength,
  230. String? label,
  231. String? value,
  232. List<T>? initialValue = const [],
  233. }) {
  234. List names = initialValue != null && initialValue.length > 0
  235. ? initialValue.last.name!.split(',')
  236. : [];
  237. List uniques = initialValue != null && initialValue.length > 0
  238. ? initialValue.last.unique.toString().split(',')
  239. : [];
  240. _getValue() {
  241. List tempList = [];
  242. if (initialValue!.length > 0) {
  243. for (int i = 0; i < names.length; i++) {
  244. tempList.add(
  245. SinglePickerEntity(
  246. name: names[i],
  247. unique: uniques[i],
  248. child: null,
  249. ),
  250. );
  251. }
  252. }
  253. List<dynamic> _list = List.of(initialValue);
  254. _list.last = tempList;
  255. return _list;
  256. }
  257. List<dynamic> _value = isMulti == true
  258. ? initialValue!.length > 0
  259. ? _getValue()
  260. : []
  261. : List.of(initialValue ?? []);
  262. List<T>? curlist = data;
  263. String? _searchValue;
  264. int index = (_value.length - 1 < 0) ? 0 : initialValue!.length - 1;
  265. bool showFutrue = _value.length == 0;
  266. bool _isSearch = isSearch ?? false;
  267. List<T>? currentValue() {
  268. if (index >= _value.length) {
  269. return null;
  270. } else {
  271. return _value[index] is List
  272. ? List<T>.from((_value[index] ?? []).map(
  273. (item) => SinglePickerEntity(
  274. name: item.name,
  275. unique: item.unique,
  276. child: item.child ?? null,
  277. ),
  278. ))
  279. : [_value[index]];
  280. }
  281. }
  282. void onCancel(context) => Navigator.pop(context);
  283. void onConfirm(context) {
  284. if (_value.length != 0 && _value.length == maxLength) {
  285. if (isMulti == true) {
  286. List tempList = _value;
  287. String name = tempList.last.map((item) => item.name).join(',');
  288. String value = tempList.last.map((item) => item.unique).join(',');
  289. tempList.last = SinglePickerEntity(name: name, unique: value);
  290. tempList = List<SinglePickerEntity>.from((tempList).map(
  291. (item) => SinglePickerEntity(
  292. name: item.name,
  293. unique: item.unique,
  294. child: item.child ?? null,
  295. ),
  296. ));
  297. Navigator.pop(context, tempList);
  298. } else {
  299. List tempList = _value;
  300. tempList = List<SinglePickerEntity>.from((tempList).map(
  301. (item) => SinglePickerEntity(
  302. name: item.name,
  303. unique: item.unique,
  304. child: item.child ?? null,
  305. ),
  306. ));
  307. Navigator.pop(context, tempList);
  308. }
  309. }
  310. }
  311. Future<List<T>> getList() async {
  312. if (index == 0 || index < 0) {
  313. curlist = data;
  314. } else {
  315. List<T>? tempList = data;
  316. for (var i = 0; i < (index); i++) {
  317. tempList!.forEach((listitem) {
  318. if (listitem.unique == _value[i].unique) {
  319. tempList = List<T>.from((listitem.child ?? []).map(
  320. (item) => SinglePickerEntity(
  321. name: item[label],
  322. unique: item[value],
  323. child: item['child'] ?? null,
  324. ),
  325. ));
  326. }
  327. });
  328. }
  329. curlist = tempList;
  330. }
  331. return curlist!;
  332. }
  333. /// 获取当前标签列表值
  334. _getName() {
  335. List names = [];
  336. _value.last.forEach((element) {
  337. names.add(element.name);
  338. });
  339. return names.join(',');
  340. }
  341. // List<T> tempList = [];
  342. // listLoop(List<T> data, v, count) {
  343. // while (count <= maxLength) {
  344. // data.forEach((item) => {
  345. // if (count == maxLength)
  346. // {
  347. // if (item.name.indexOf(v) != -1)
  348. // {
  349. // tempList.add(item),
  350. // }
  351. // }
  352. // else if (item.child != null && item.child.length > 0)
  353. // {
  354. // listLoop(
  355. // List<SinglePickerEntity>.from((item.child ?? []).map(
  356. // (item) => SinglePickerEntity(
  357. // name: item['name'],
  358. // unique: item['unique'],
  359. // child: item['child'] ?? null,
  360. // ),
  361. // )),
  362. // v,
  363. // count + 1),
  364. // }
  365. // });
  366. // }
  367. // }
  368. return WBottomModalUtil.show<List<T>>(
  369. context,
  370. title: title,
  371. plain: isMulti == true ? false : true,
  372. onCancel: onCancel,
  373. onConfirm: onConfirm,
  374. body: StatefulBuilder(
  375. builder: (BuildContext context, setState) {
  376. add(List<T> items, isTap) async {
  377. if (isTap) {
  378. showFutrue = true;
  379. if (index <= _value.length - 1) {
  380. _value.removeRange(index, _value.length);
  381. }
  382. if (index == (maxLength - 1)) {
  383. if (isMulti == true) {
  384. if (_value.length >= maxLength) {
  385. _value.last = items;
  386. } else {
  387. _value.add(items);
  388. }
  389. } else {
  390. _value.add(items[0]);
  391. onConfirm(context);
  392. }
  393. } else {
  394. index++;
  395. _value.add(items[0]);
  396. setState(() {});
  397. }
  398. }
  399. }
  400. behind(int i) {
  401. index = i;
  402. setState(() {});
  403. }
  404. final primaryColor = Theme.of(context).primaryColor;
  405. Widget tab(i) {
  406. String text = (_value.length > i)
  407. ? '${_value[i] is List ? _getName() : _value[i].selectName}'
  408. : '请选择';
  409. return Wisdom.column(
  410. onTap: () => behind(i),
  411. maxWidth: 100.pt,
  412. mainAxisAlignment: MainAxisAlignment.center,
  413. padding: EdgeInsets.symmetric(horizontal: 12.5.pt),
  414. children: [
  415. Spacer(),
  416. Text(
  417. text,
  418. overflow: TextOverflow.ellipsis,
  419. style: TextStyle(
  420. fontSize: 15.pt,
  421. fontWeight: i == index ? null : FontWeight.bold,
  422. color: i == index ? primaryColor : null,
  423. ),
  424. ),
  425. Spacer(),
  426. SizedBox(
  427. height: 4.pt,
  428. child: (i == index)
  429. ? WisActiveness(
  430. margin: EdgeInsets.only(top: 0),
  431. color: primaryColor,
  432. )
  433. : null,
  434. ),
  435. ],
  436. );
  437. }
  438. Widget tabs() {
  439. return Container(
  440. height: 44.pt,
  441. margin: EdgeInsets.symmetric(horizontal: 2.5.pt),
  442. child: ListView(
  443. scrollDirection: Axis.horizontal,
  444. children: [
  445. if (showFutrue) ...[
  446. for (var i = 0; i < _value.length + 1; i++)
  447. if (i < maxLength) ...[tab(i)],
  448. ] else ...[
  449. for (var i = 0; i < _value.length; i++)
  450. if (i < maxLength) ...[tab(i)],
  451. ],
  452. ],
  453. ),
  454. );
  455. }
  456. Future<List<SinglePickerEntity>?> getSearchList() async {
  457. if (_searchValue == null || _searchValue == '') {
  458. return curlist;
  459. } else {
  460. List<SinglePickerEntity> temp = [];
  461. data.forEach((element1) {
  462. if (element1.child != null && element1.child!.length > 0) {
  463. element1.child!.forEach((element2) {
  464. if (element2['child'] != null &&
  465. element2['child'].length > 0) {
  466. element2['child'].forEach((element3) {
  467. if (element3['name'].indexOf(_searchValue) != -1) {
  468. temp.add(SinglePickerEntity(
  469. name: element3['name'],
  470. unique: element3['unique'],
  471. cascade: [
  472. SinglePickerEntity(
  473. name: element1.name,
  474. unique: element1.unique,
  475. cascade: [],
  476. child: element1.child,
  477. ),
  478. SinglePickerEntity(
  479. name: element2['name'],
  480. unique: element2['unique'],
  481. cascade: [],
  482. child: element2['child'],
  483. )
  484. ],
  485. child: null,
  486. ));
  487. }
  488. });
  489. }
  490. });
  491. }
  492. });
  493. return temp;
  494. }
  495. }
  496. return Column(
  497. mainAxisSize: MainAxisSize.min,
  498. crossAxisAlignment: CrossAxisAlignment.stretch,
  499. children: [
  500. WSearch(
  501. hintText: '输入关键词搜索',
  502. onSubmitted: (v) => {
  503. _searchValue = v,
  504. setState(() {}),
  505. },
  506. moonlight: false,
  507. padding: EdgeInsets.symmetric(vertical: 10.pt),
  508. isDark: true,
  509. backColor: Colors.white,
  510. ),
  511. if (_searchValue == null || _searchValue == '') ...[
  512. tabs(),
  513. ],
  514. Divider(height: 1),
  515. WPicker<T>(
  516. key: UniqueKey(),
  517. multiple: isMulti == true,
  518. isSearch: false,
  519. initialValue: currentValue() ?? [],
  520. getData: () => getList(),
  521. onChanged: (selects, isTap) => add(selects!, isTap),
  522. ).asExpanded(),
  523. ],
  524. );
  525. },
  526. ),
  527. bodyConstraints: _isSearch
  528. ? BoxConstraints(
  529. maxHeight: 450,
  530. minHeight: 450,
  531. )
  532. : BoxConstraints(
  533. maxHeight: 350,
  534. minHeight: 250,
  535. ),
  536. );
  537. }
  538. /// 树-------------------------------------
  539. /// 级联
  540. static Future<List<T>?> tree<T extends WPickerEntity>(
  541. BuildContext context, {
  542. Widget? title,
  543. required List data,
  544. required String label,
  545. required String value,
  546. required String child,
  547. required String firstName,
  548. required String firstValue,
  549. bool? isSearch,
  550. /// 是否多选
  551. bool? isMulti,
  552. List? initialValue = const [],
  553. }) {
  554. List _value = List.of(initialValue ?? []);
  555. List<SinglePickerEntity> _selectValue = [];
  556. bool _isSearch = isSearch ?? false;
  557. void onCancel(context) => Navigator.pop(context);
  558. void onConfirm(context) {
  559. Navigator.pop(context, _selectValue);
  560. }
  561. void onChanged(List values) {
  562. _value = values;
  563. _selectValue = [];
  564. _value.forEach((item) {
  565. _selectValue
  566. .add(SinglePickerEntity(name: item[label], unique: item[value]));
  567. });
  568. if (isMulti != null && isMulti == false) {
  569. Navigator.pop(
  570. context,
  571. _selectValue.length == 0
  572. ? null
  573. : _selectValue,
  574. );
  575. }
  576. }
  577. return WBottomModalUtil.show<List<T>>(
  578. context,
  579. title: title,
  580. plain: false,
  581. onCancel: onCancel,
  582. onConfirm: onConfirm,
  583. body: WTreePicker<T>(
  584. key: UniqueKey(),
  585. labelname: label,
  586. valuename: value,
  587. childname: child,
  588. firstName: firstName,
  589. firstValue: firstValue,
  590. multiple: isMulti ?? false,
  591. isSearch: isSearch ?? false,
  592. initialValue: initialValue,
  593. data: data,
  594. onChanged: (value, _) => onChanged(value),
  595. ),
  596. bodyConstraints: _isSearch
  597. ? BoxConstraints(
  598. maxHeight: 400,
  599. minHeight: 400,
  600. )
  601. : BoxConstraints(
  602. maxHeight: 350,
  603. minHeight: 250,
  604. ),
  605. );
  606. }
  607. /// 周选择
  608. static Future<DatePeriod?> week(
  609. BuildContext context, {
  610. Widget? title,
  611. DateTime? firstDate,
  612. DateTime? lastDate,
  613. DatePeriod? initialDate,
  614. }) {
  615. final _firstDate = firstDate ?? DateTime(2015);
  616. final _lastDate = firstDate ?? DateTime(2100);
  617. final now = DateTime.now().toLocal();
  618. DatePeriod _value = initialDate ??
  619. DatePeriod(
  620. DateTime(now.year, now.month, now.day),
  621. DateTime(now.year, now.month, now.day + 1),
  622. );
  623. return WBottomModalUtil.show<DatePeriod?>(
  624. context,
  625. title: title,
  626. plain: false,
  627. onCancel: (_) => Navigator.pop(context),
  628. onConfirm: (_) => Navigator.pop(context, _value),
  629. body: StatefulBuilder(
  630. builder: (BuildContext context, setState) {
  631. return UtilWeekPicker(
  632. firstDate: _firstDate,
  633. lastDate: _lastDate,
  634. selectedDate: _value.start,
  635. onChanged: (DatePeriod value) {
  636. _value = value;
  637. setState(() {});
  638. },
  639. datePickerStyles: DatePickerRangeStyles(firstDayOfWeekIndexs: 1),
  640. );
  641. },
  642. ),
  643. );
  644. }
  645. ///月份选择
  646. ///
  647. static Future<DateTime?> month(
  648. BuildContext context, {
  649. Widget? title,
  650. DateTime? firstDate,
  651. DateTime? lastDate,
  652. DateTime? initialDate,
  653. DatePickerStyles? datePickerStyles,
  654. }) {
  655. final _firstDate = firstDate ?? DateTime(2015);
  656. final now = DateTime.now().toLocal();
  657. DateTime _value = initialDate ?? DateTime(now.year, now.month);
  658. DateTime _lastDate = lastDate ?? DateTime(2100);
  659. return WBottomModalUtil.show<DateTime?>(
  660. context,
  661. title: title,
  662. plain: false,
  663. onCancel: (_) => Navigator.pop(context),
  664. onConfirm: (_) => Navigator.pop(context, _value),
  665. body: StatefulBuilder(
  666. builder: (BuildContext context, setState) {
  667. return WMonthPicker.single(
  668. datePickerStyles: datePickerStyles,
  669. firstDate: _firstDate,
  670. lastDate: _lastDate,
  671. selectedDate: _value,
  672. onChanged: (value) => {
  673. _value = value,
  674. setState(() {}),
  675. });
  676. },
  677. ),
  678. );
  679. }
  680. static Future<DateTime?> monthRange(
  681. BuildContext context, {
  682. Widget? title,
  683. DateTime? firstDate,
  684. DateTime? lastDate,
  685. List<DateTime>? initialDate,
  686. }) {
  687. final _firstDate = firstDate ?? DateTime(2015);
  688. // final now = DateTime.now().toLocal();
  689. List<DateTime> _value = initialDate ?? [DateTime.now(), DateTime.now()];
  690. DateTime _lastDate = lastDate ?? DateTime(2100);
  691. return WBottomModalUtil.show<DateTime?>(
  692. context,
  693. title: title,
  694. plain: false,
  695. onCancel: (_) => Navigator.pop(context),
  696. onConfirm: (_) => Navigator.pop(context, _value),
  697. body: StatefulBuilder(
  698. builder: (BuildContext context, setState) {
  699. return WMonthPicker.multi(
  700. firstDate: _firstDate,
  701. lastDate: _lastDate,
  702. selectedDates: _value,
  703. onChanged: (value) => {
  704. _value = value,
  705. setState(() {}),
  706. });
  707. },
  708. ),
  709. );
  710. }
  711. //年份选择
  712. ///
  713. static Future<DateTime?> year(
  714. BuildContext context, {
  715. Widget? title,
  716. DateTime? firstDate,
  717. DateTime? lastDate,
  718. DateTime? initialDate,
  719. }) {
  720. final _firstDate = firstDate ?? DateTime(2015);
  721. final now = DateTime.now().toLocal();
  722. DateTime _value = initialDate ?? DateTime(now.year, now.month);
  723. DateTime _lastDate = lastDate ?? DateTime(2100);
  724. return WBottomModalUtil.show<DateTime?>(
  725. context,
  726. title: title,
  727. plain: false,
  728. onCancel: (_) => Navigator.pop(context),
  729. onConfirm: (_) => Navigator.pop(context, _value),
  730. body: StatefulBuilder(
  731. builder: (BuildContext context, setState) {
  732. return UtilYearPicker.single(
  733. firstDate: _firstDate,
  734. lastDate: _lastDate,
  735. selectedDate: _value,
  736. onChanged: (DateTime value) {
  737. _value = value;
  738. setState(() {});
  739. },
  740. );
  741. },
  742. ),
  743. );
  744. }
  745. /// 季度选择
  746. static Future<DateTime?> season(
  747. BuildContext context, {
  748. Widget? title,
  749. DateTime? firstDate,
  750. DateTime? lastDate,
  751. DateTime? initialDate,
  752. }) {
  753. final _firstDate = firstDate ?? DateTime(2015);
  754. final now = DateTime.now().toLocal();
  755. DateTime _value = initialDate ?? DateTime(now.year, now.month);
  756. DateTime _lastDate = lastDate ?? DateTime(2100);
  757. return WBottomModalUtil.show<DateTime>(
  758. context,
  759. title: title,
  760. plain: false,
  761. onCancel: (_) => Navigator.pop(context),
  762. onConfirm: (_) => Navigator.pop(context, _value),
  763. body: StatefulBuilder(
  764. builder: (BuildContext context, setState) {
  765. return UtilSeasonPicker.single(
  766. firstDate: _firstDate,
  767. lastDate: _lastDate,
  768. selectedDate: _value,
  769. onChanged: (DateTime value) {
  770. _value = value;
  771. setState(() {});
  772. },
  773. );
  774. },
  775. ),
  776. );
  777. }
  778. ///日期选择
  779. ///
  780. static Future<DateTime?> date(
  781. BuildContext context, {
  782. Widget? title,
  783. DateTime? firstDate,
  784. DateTime? lastDate,
  785. DateTime? initialDate,
  786. }) {
  787. final _firstDate = firstDate ?? DateTime(2015);
  788. final now = DateTime.now().toLocal();
  789. DateTime _value = initialDate ?? DateTime(now.year, now.month, now.day);
  790. if (firstDate != null && lastDate == null) {
  791. _value = initialDate ?? firstDate.add(Duration(seconds: 1));
  792. }
  793. if (lastDate != null && firstDate == null) {
  794. _value = initialDate ?? lastDate.add(Duration(seconds: -1));
  795. }
  796. if (lastDate != null && firstDate != null) {
  797. _value = (now.difference(_firstDate).inMilliseconds > 0) &&
  798. (now.difference(lastDate).inMilliseconds < 0)
  799. ? now
  800. : (now.difference(_firstDate).inMilliseconds.abs() <
  801. now.difference(lastDate).inMilliseconds.abs())
  802. ? initialDate ?? firstDate
  803. : initialDate ?? lastDate;
  804. }
  805. DateTime _lastDate =
  806. lastDate ?? DateTime(now.year + 50, now.month, now.day);
  807. return WBottomModalUtil.show<DateTime?>(
  808. context,
  809. title: title,
  810. plain: true,
  811. onCancel: (_) => Navigator.pop(context),
  812. onConfirm: (_) => Navigator.pop(context, _value),
  813. body: CalendarDatePicker(
  814. initialDate: _value,
  815. firstDate: _firstDate,
  816. lastDate: _lastDate,
  817. onDateChanged: (value) {
  818. _value = value.toLocal();
  819. Navigator.pop(context, _value);
  820. },
  821. ),
  822. );
  823. }
  824. ///时间日期选择
  825. ///
  826. static Future<DateTime?> dateAndTime(
  827. BuildContext context, {
  828. Widget? title,
  829. DateTime? firstDate,
  830. DateTime? lastDate,
  831. DateTime? initialDate,
  832. ///分钟间隔,
  833. ///
  834. ///如若设置此参数之后,
  835. ///`firstDate`、`lastDate`、`initialDate`的minute(分钟)必须都要可整除此参数
  836. int minuteInterval = 1,
  837. }) {
  838. final _firstDate = firstDate ?? DateTime(2015);
  839. final now = DateTime.now().toLocal();
  840. final now2 = DateTime(
  841. now.year,
  842. now.month,
  843. now.day,
  844. now.hour,
  845. now.minute % minuteInterval < minuteInterval
  846. ? (now.minute - (now.minute % minuteInterval)) + minuteInterval
  847. : now.minute); //转换一下,去掉秒、微秒等
  848. DateTime _value = initialDate ?? now2;
  849. DateTime _lastDate = lastDate ??
  850. DateTime(
  851. now.year + 50,
  852. now.month,
  853. now.day,
  854. now.hour,
  855. minuteInterval == 5 && now.minute % 5 < 5
  856. ? now.minute - (now.minute % 5)
  857. : now.minute); //转换一下,去掉秒、微秒等;
  858. ///设置日期
  859. void setDate(DateTime dateTime) {
  860. _value = DateTime(
  861. dateTime.year,
  862. dateTime.month,
  863. dateTime.day,
  864. _value.hour,
  865. _value.minute,
  866. );
  867. }
  868. ///设置时间
  869. void setTime(DateTime dateTime) {
  870. _value = DateTime(
  871. _value.year,
  872. _value.month,
  873. _value.day,
  874. dateTime.hour,
  875. dateTime.minute,
  876. );
  877. }
  878. int indexedStack = 0;
  879. StateSetter? setstate1;
  880. StateSetter? setstate2;
  881. return WBottomModalUtil.show<DateTime>(
  882. context,
  883. title: StatefulBuilder(builder: (_, s) {
  884. final primaryColor = Theme.of(context).primaryColor;
  885. setstate1 = s;
  886. return Row(
  887. children: [
  888. Wisdom(
  889. onTap: () {
  890. indexedStack = 0;
  891. setstate1!(() {});
  892. setstate2!(() {});
  893. },
  894. padding: EdgeInsets.all(5.pt),
  895. child: Text(
  896. _value.toFormat(WisDateTimeFormat.date),
  897. style: TextStyle(
  898. color: indexedStack == 0 ? primaryColor : null,
  899. ),
  900. ),
  901. ),
  902. SizedBox(width: 5.pt),
  903. Wisdom(
  904. onTap: () {
  905. indexedStack = 1;
  906. setstate1!(() {});
  907. setstate2!(() {});
  908. },
  909. padding: EdgeInsets.all(5.pt),
  910. child: Text(
  911. _value.toFormat('{hh}:{mm}'),
  912. style: TextStyle(
  913. color: indexedStack == 1 ? primaryColor : null,
  914. ),
  915. ),
  916. ),
  917. ],
  918. );
  919. }),
  920. plain: false,
  921. onCancel: null,
  922. onConfirm: (_) => Navigator.pop(context, _value),
  923. body: StatefulBuilder(
  924. builder: (_, s) {
  925. setstate2 = s;
  926. return IndexedStack(
  927. index: indexedStack,
  928. children: [
  929. CalendarDatePicker(
  930. initialDate: _value,
  931. firstDate: _firstDate,
  932. lastDate: _lastDate,
  933. onDateChanged: (datetime) {
  934. setDate(datetime);
  935. indexedStack = 1;
  936. setstate1!(() {});
  937. setstate2!(() {});
  938. },
  939. ),
  940. CupertinoDatePicker(
  941. mode: CupertinoDatePickerMode.time,
  942. use24hFormat: true,
  943. minimumDate: '${_value.year}${_value.month}${_value.day}' ==
  944. '${_firstDate.year}${_firstDate.month}${_firstDate.day}'
  945. ? _firstDate
  946. : null,
  947. minuteInterval: minuteInterval,
  948. initialDateTime: _value,
  949. onDateTimeChanged: (datetime) {
  950. setTime(datetime);
  951. setstate1!(() {});
  952. setstate2!(() {});
  953. },
  954. )
  955. ],
  956. );
  957. },
  958. ),
  959. );
  960. }
  961. /// 选择时间
  962. static Future<DateTime?> time(
  963. BuildContext context, {
  964. Widget? title,
  965. DateTime? initialDate,
  966. ///分钟间隔,
  967. ///
  968. ///如若设置此参数之后,
  969. ///`firstDate`、`lastDate`、`initialDate`的minute(分钟)必须都要可整除此参数
  970. int minuteInterval = 1,
  971. }) {
  972. final now = DateTime.now().toLocal();
  973. final now2 = DateTime(
  974. now.year,
  975. now.month,
  976. now.day,
  977. now.hour,
  978. minuteInterval == 5 && now.minute % 5 < 5
  979. ? now.minute - (now.minute % 5)
  980. : now.minute); //转换一下,去掉秒、微秒等
  981. DateTime _value = initialDate ?? now2;
  982. ///设置时间
  983. void setTime(DateTime dateTime) {
  984. _value = DateTime(
  985. _value.year,
  986. _value.month,
  987. _value.day,
  988. dateTime.hour,
  989. dateTime.minute,
  990. );
  991. }
  992. StateSetter? setstate1;
  993. return WBottomModalUtil.show<DateTime>(
  994. context,
  995. title: StatefulBuilder(builder: (_, s) {
  996. final primaryColor = Theme.of(context).primaryColor;
  997. setstate1 = s;
  998. return Text(
  999. _value.toFormat('{hh}:{mm}'),
  1000. style: TextStyle(
  1001. color: primaryColor,
  1002. ),
  1003. );
  1004. }),
  1005. plain: false,
  1006. onCancel: null,
  1007. onConfirm: (_) => Navigator.pop(context, _value),
  1008. body: StatefulBuilder(
  1009. builder: (_, s) {
  1010. return CupertinoDatePicker(
  1011. mode: CupertinoDatePickerMode.time,
  1012. use24hFormat: true,
  1013. minuteInterval: minuteInterval,
  1014. initialDateTime: _value,
  1015. onDateTimeChanged: (datetime) {
  1016. setTime(datetime);
  1017. setstate1!(() {});
  1018. },
  1019. );
  1020. },
  1021. ),
  1022. );
  1023. }
  1024. }