field_control.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. import 'package:app_business/service/api.dart';
  2. import 'package:grpc/grpc.dart';
  3. import 'package:track_common/service/map_watch.dart';
  4. import 'package:track_common/track_common.dart';
  5. import 'package:track_common/view/home/field_control/field_control.dart';
  6. import 'package:track_common/view/home/field_control/field_control_controller.dart';
  7. import 'package:track_common/widget/prelude.dart';
  8. class FieldControlPageImpl extends FieldControlPage {
  9. const FieldControlPageImpl({super.key});
  10. @override
  11. Widget rightColumn(BuildContext context) {
  12. return Obx(() {
  13. final mapWatch = controller.mapWatch;
  14. debug('${identityHashCode(controller)}');
  15. return Container(
  16. width: 370,
  17. padding: const EdgeInsets.all(6.4),
  18. height: double.infinity,
  19. color: Colors.white,
  20. child: Column(
  21. children: [
  22. Container(
  23. padding: const EdgeInsets.only(bottom: 8),
  24. width: double.infinity,
  25. child: DarkButton(
  26. onPressed: mapWatch != null
  27. ? () => _onTapRegister(context, mapWatch)
  28. : null,
  29. child: const Text('注册比赛'))),
  30. const Expanded(child: EventInfoView())
  31. ],
  32. ));
  33. });
  34. }
  35. Future<void> _onTapRegister(BuildContext context, MapWatch mapWatch) async {
  36. final r = await Get.dialog(const RegisterDialog(), arguments: mapWatch.id)
  37. as RegisterInfo?;
  38. if (r != null) {
  39. Get.find<ApiService>()
  40. .stub
  41. .toMatchRegusterAdd(ToMatchRegusterAddRequest()
  42. ..actId = r.id
  43. ..regName = r.name
  44. ..startAt = r.startAt.toPb()
  45. ..stopAt = r.stopAt.toPb()
  46. ..isQueryPwd = r.passwordQuery != null
  47. ..queryPasswd = r.passwordQuery ?? '')
  48. .then((p0) {}, onError: (e) {
  49. if (e is GrpcError) {
  50. if (context.mounted) {
  51. ScaffoldMessenger.of(context)
  52. .showSnackBar(SnackBar(content: Text('注册失败:{${e.message}')));
  53. }
  54. }
  55. });
  56. }
  57. }
  58. }
  59. class EventInfo {
  60. int id = 0;
  61. String name = '';
  62. }
  63. class RegisterInfo {
  64. var id = 0;
  65. var name = '';
  66. var startAt = DateTime.now();
  67. var stopAt = DateTime.now();
  68. String? password;
  69. String? passwordQuery;
  70. }
  71. class FieldControlControllerImpl extends FieldControlController {
  72. @override
  73. void onInit() {
  74. debug('init');
  75. super.onInit();
  76. }
  77. @override
  78. void onClose() {
  79. super.onClose();
  80. debug('close');
  81. }
  82. }
  83. class RegisterDialogController extends GetxController {
  84. var registerName = '';
  85. final date = Rx<DateTime?>(null);
  86. final registerStartAt = Rx<TimeOfDay?>(null);
  87. final registerStopAt = Rx<TimeOfDay?>(null);
  88. final selected = Rx<EventInfo?>(null);
  89. final eventList = <EventInfo>[].obs;
  90. final hasPassword = false.obs;
  91. final hasPasswordQuery = false.obs;
  92. var password = '';
  93. var passwordQuery = '';
  94. late final int mapId;
  95. final api = Get.find<ApiService>();
  96. String? get dateString {
  97. final d = date.value;
  98. if (d != null) {
  99. return '${d.month}/${d.day}';
  100. }
  101. return null;
  102. }
  103. @override
  104. void onInit() {
  105. mapId = Get.arguments as int;
  106. api.stub.toActivitySelectList(IdRequest()..id = Int64(mapId)).then((r) {
  107. eventList.value = r.list
  108. .map((e) => EventInfo()
  109. ..id = e.actId
  110. ..name = e.actName)
  111. .toList();
  112. });
  113. super.onInit();
  114. }
  115. }
  116. class RegisterDialog extends GetView<RegisterDialogController> {
  117. const RegisterDialog({super.key});
  118. @override
  119. Widget build(BuildContext context) {
  120. return GetBuilder(
  121. init: RegisterDialogController(),
  122. builder: (c) {
  123. return AlertDialog(
  124. title: const Center(
  125. child: Text(
  126. '注册比赛',
  127. style: TextStyle(fontSize: 17),
  128. )),
  129. backgroundColor: Colors.white,
  130. shape: RoundedRectangleBorder(
  131. borderRadius: BorderRadius.circular(17.78)),
  132. content: SizedBox(
  133. width: 320,
  134. child: ListView(
  135. shrinkWrap: true,
  136. children: [
  137. Obx(() => SizedBox(
  138. child: DropdownMenu<EventInfo>(
  139. key: GlobalKey(),
  140. width: 320,
  141. hintText: '请选择活动',
  142. onSelected: (one) {
  143. controller.selected.value = one;
  144. },
  145. inputDecorationTheme: InputDecorationTheme(
  146. border: textBorder,
  147. isDense: true,
  148. ),
  149. dropdownMenuEntries: controller.eventList
  150. .map((e) => DropdownMenuEntry<EventInfo>(
  151. value: e, label: e.name))
  152. .toList()))),
  153. const SizedBox(height: 21.34),
  154. _TextField(
  155. hint: '请输入名称',
  156. onChanged: (v) {
  157. c.registerName = v;
  158. }),
  159. const SizedBox(height: 21.34),
  160. Row(children: [
  161. Expanded(
  162. child: Obx(() => _TextField(
  163. hint: '日期',
  164. readOnly: true,
  165. initText: c.dateString,
  166. onTap: () async {
  167. c.date.value = await _showDatePicker(
  168. context, c.date.value);
  169. }))),
  170. const SizedBox(width: 15.64),
  171. Expanded(
  172. child: Obx(() => _TextField(
  173. hint: '开始时间',
  174. readOnly: true,
  175. initText:
  176. c.registerStartAt.value?.format(context),
  177. onTap: () async {
  178. c.registerStartAt.value = await _showTimePicker(
  179. context, c.registerStartAt.value);
  180. }))),
  181. const SizedBox(width: 15.64),
  182. Expanded(
  183. child: Obx(() => _TextField(
  184. hint: '结束时间',
  185. readOnly: true,
  186. initText: c.registerStopAt.value?.format(context),
  187. onTap: () async {
  188. c.registerStopAt.value = await _showTimePicker(
  189. context, c.registerStopAt.value);
  190. }))),
  191. ]),
  192. const SizedBox(height: 21.34),
  193. Row(
  194. mainAxisSize: MainAxisSize.min,
  195. children: [
  196. Obx(() => Switch(
  197. value: c.hasPasswordQuery.value,
  198. onChanged: (v) {
  199. c.hasPasswordQuery.value = v;
  200. })),
  201. const Text('查询密码'),
  202. const SizedBox(width: 12),
  203. Obx(() => Expanded(
  204. child: Visibility(
  205. visible: c.hasPasswordQuery.value,
  206. child: _TextField(
  207. hint: '请输入密码',
  208. onChanged: (v) {
  209. c.passwordQuery = v;
  210. })))),
  211. ],
  212. ),
  213. const SizedBox(height: 6),
  214. Row(
  215. mainAxisSize: MainAxisSize.min,
  216. children: [
  217. Obx(() => Switch(
  218. value: c.hasPassword.value,
  219. onChanged: (v) {
  220. c.hasPassword.value = v;
  221. })),
  222. const Text('赛事密码'),
  223. const SizedBox(width: 12),
  224. Obx(() => Expanded(
  225. child: Visibility(
  226. visible: c.hasPassword.value,
  227. child: _TextField(
  228. hint: '请输入密码',
  229. onChanged: (v) {
  230. c.password = v;
  231. })))),
  232. ],
  233. ),
  234. const SizedBox(height: 21.34),
  235. SizedBox(
  236. width: double.infinity,
  237. child: DarkButton(
  238. onPressed: _onRegister, child: const Text('注 册')))
  239. ],
  240. )),
  241. );
  242. });
  243. }
  244. void _onRegister() {
  245. final date = controller.date.value;
  246. final timeStartAt = controller.registerStartAt.value;
  247. final timeStopAt = controller.registerStopAt.value;
  248. final selected = controller.selected.value;
  249. if (selected == null) {
  250. Get.snackbar('错误', '请选择一个活动');
  251. return;
  252. }
  253. if (controller.registerName.isEmpty) {
  254. Get.snackbar('错误', '输入名称');
  255. return;
  256. }
  257. if (date == null) {
  258. Get.snackbar('错误', '请选择日期');
  259. return;
  260. }
  261. if (timeStartAt == null) {
  262. Get.snackbar('错误', '请选择开始时间');
  263. return;
  264. }
  265. if (timeStopAt == null) {
  266. Get.snackbar('错误', '请选择结束时间');
  267. return;
  268. }
  269. final startAt =
  270. date.copyWith(hour: timeStartAt.hour, minute: timeStartAt.minute);
  271. final stopAt =
  272. date.copyWith(hour: timeStopAt.hour, minute: timeStopAt.minute);
  273. if (startAt.isAfter(stopAt)) {
  274. Get.snackbar('错误', '结束时间应晚于开始时间');
  275. return;
  276. }
  277. Get.back(
  278. result: RegisterInfo()
  279. ..id = selected.id
  280. ..name = controller.registerName
  281. ..startAt = startAt
  282. ..stopAt = stopAt
  283. ..password = controller.hasPassword.value ? controller.password : null
  284. ..passwordQuery = controller.hasPasswordQuery.value
  285. ? controller.passwordQuery
  286. : null);
  287. }
  288. Future<TimeOfDay?> _showTimePicker(
  289. BuildContext context, TimeOfDay? init) async {
  290. final TimeOfDay? time = await showTimePicker(
  291. context: context,
  292. initialTime: init ?? TimeOfDay.now(),
  293. );
  294. return time;
  295. }
  296. Future<DateTime?> _showDatePicker(
  297. BuildContext context, DateTime? init) async {
  298. final DateTime? time = await showDatePicker(
  299. context: context,
  300. initialDate: init ?? DateTime.now(),
  301. firstDate: DateTime.now(),
  302. lastDate: DateTime.now().add(365.days),
  303. );
  304. return time;
  305. }
  306. }
  307. final textBorder = OutlineInputBorder(
  308. borderSide: const BorderSide(width: 0.71, color: Color(0xff818181)),
  309. borderRadius: BorderRadius.circular(2.13),
  310. );
  311. class _TextField extends StatelessWidget {
  312. const _TextField(
  313. {required this.hint,
  314. this.onChanged,
  315. this.readOnly = false,
  316. this.onTap,
  317. this.initText});
  318. final String hint;
  319. final void Function(String)? onChanged;
  320. final bool readOnly;
  321. final void Function()? onTap;
  322. final String? initText;
  323. @override
  324. Widget build(BuildContext context) {
  325. return SizedBox(
  326. child: TextFormField(
  327. key: GlobalKey(),
  328. initialValue: initText,
  329. maxLines: 1,
  330. onChanged: onChanged,
  331. onTap: onTap,
  332. readOnly: readOnly,
  333. decoration: InputDecoration(
  334. hintText: hint,
  335. border: textBorder,
  336. isDense: true,
  337. // contentPadding: const EdgeInsets.all(8.53)
  338. )),
  339. );
  340. }
  341. }