code_field.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import 'dart:async';
  2. import 'package:fis_i18n/i18n.dart';
  3. import 'package:fis_measure/define.dart';
  4. import 'package:fis_measure/view/measure/measure_config/widgets/element_static.dart';
  5. import 'package:fis_measure/view/measure/measure_config/widgets/text_field.dart';
  6. import 'package:fis_ui/index.dart';
  7. import 'package:fis_ui/interface/interactive_container.dart';
  8. import 'package:flutter/foundation.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:get/get.dart';
  11. import 'package:fis_theme/theme.dart';
  12. /// 验证码表单栏控制器
  13. class VerificationCodeFieldController extends GetxController {
  14. VerificationCodeFieldController({
  15. this.countDown = 60,
  16. });
  17. final int countDown;
  18. Timer? _timer;
  19. bool _hasSent = false;
  20. final _seconds = 0.obs;
  21. final _isFetching = false.obs;
  22. final Rx<String> _btnText = Rx('');
  23. /// 倒计时读秒
  24. int get seconds => _seconds.value;
  25. set seconds(int val) => _seconds(val);
  26. /// 是否正在获取验证码
  27. bool get isFetching => _isFetching.value;
  28. set isFetching(bool val) => _isFetching(val);
  29. /// 按钮文字
  30. String get btnText => setBtnText();
  31. set btnText(String val) => _btnText(val);
  32. /// 按钮是否禁用
  33. bool get isBtnDisabled => isFetching || seconds > 0;
  34. /// 开启倒计时
  35. startCountDown() {
  36. _hasSent = true;
  37. seconds = countDown;
  38. _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
  39. seconds--;
  40. if (seconds == 0) {
  41. timer.cancel();
  42. }
  43. });
  44. }
  45. String setBtnText() {
  46. if (seconds > 0) {
  47. _btnText.value =
  48. i18nBook.auth.sendVerificationCodeTimer.translate([seconds]);
  49. } else {
  50. _btnText.value = _hasSent
  51. ? i18nBook.auth.resendVerificationCode.t
  52. : i18nBook.auth.sendVerificationCode.t;
  53. }
  54. return _btnText.value;
  55. }
  56. ///重置
  57. void reset() {
  58. if (_seconds.value == 0) {
  59. _timer?.cancel();
  60. _hasSent = false;
  61. _isFetching.value = false;
  62. setBtnText();
  63. }
  64. }
  65. @override
  66. void onClose() {
  67. _timer?.cancel();
  68. _timer = null;
  69. super.onClose();
  70. }
  71. }
  72. /// 验证码表单栏
  73. class VerificationCodeField extends StatelessWidget
  74. implements FInteractiveContainer {
  75. const VerificationCodeField({
  76. Key? key,
  77. this.tag,
  78. this.pageName = 'VerificationCodeField',
  79. required this.fetchCodeFunc,
  80. this.textController,
  81. this.textFocusNode,
  82. this.onTextChanged,
  83. this.disabled = false,
  84. this.inError = false,
  85. }) : super(key: key);
  86. @override
  87. final String pageName;
  88. final String? tag;
  89. final bool disabled;
  90. final Future<bool> Function() fetchCodeFunc;
  91. final TextEditingController? textController;
  92. final FocusNode? textFocusNode;
  93. final ValueChanged<String>? onTextChanged;
  94. final bool inError;
  95. @override
  96. FWidget build(BuildContext context) {
  97. if (kIsMobile) {
  98. return _MobileLayout(
  99. businessParent: this,
  100. tag: tag,
  101. disabled: disabled,
  102. fetchCodeFunc: fetchCodeFunc,
  103. textController: textController,
  104. textFocusNode: textFocusNode,
  105. onTextChanged: onTextChanged,
  106. inError: inError,
  107. );
  108. }
  109. final textField = _buildTextField(context);
  110. final button = _buildButton(context);
  111. return FFormField(builder: (state) {
  112. return FRow(
  113. children: [
  114. FExpanded(
  115. flex: 1,
  116. child: FContainer(
  117. margin: const EdgeInsets.only(right: 5),
  118. child: textField,
  119. ),
  120. ),
  121. QuickFWidget(button),
  122. ],
  123. );
  124. });
  125. }
  126. Widget _buildButton(BuildContext context) {
  127. final controller = Get.find<VerificationCodeFieldController>(tag: tag);
  128. const textStyle = TextStyle(
  129. fontSize: 14,
  130. color: Colors.white,
  131. height: 1,
  132. );
  133. final btnStyle = ElevatedButton.styleFrom(
  134. elevation: 0,
  135. padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 16),
  136. minimumSize: const Size(120, 56),
  137. shape: RoundedRectangleBorder(
  138. borderRadius: BorderRadius.circular(8),
  139. ),
  140. );
  141. onBtnTap() {
  142. controller.isFetching = true;
  143. fetchCodeFunc().then((value) {
  144. controller.isFetching = false;
  145. if (value) {
  146. controller.startCountDown();
  147. }
  148. });
  149. }
  150. return Obx(() {
  151. final isDisabled = disabled || controller.isBtnDisabled;
  152. final text = controller.btnText;
  153. return FElevatedButton(
  154. businessParent: this,
  155. onPressed: isDisabled ? null : onBtnTap,
  156. name: text,
  157. child: FText(
  158. text,
  159. style: textStyle,
  160. ),
  161. style: btnStyle,
  162. );
  163. });
  164. }
  165. FWidget _buildTextField(BuildContext context) {
  166. final border = FBorderTextFormField.createDesktopBorder(inError);
  167. final textField = FTextField(
  168. controller: textController,
  169. focusNode: textFocusNode,
  170. maxLength: 4,
  171. style: const TextStyle(fontSize: 18),
  172. keyboardType: TextInputType.number,
  173. inputFormatters: [PageInputFormatters.number],
  174. decoration: InputDecoration(
  175. hintText: i18nBook.auth.hint4VerificationCode.t,
  176. fillColor: TEXT_FIELD_FILL_COLOR,
  177. filled: true,
  178. hintStyle: const TextStyle(
  179. fontSize: 16,
  180. color: Colors.black54,
  181. ),
  182. alignLabelWithHint: true,
  183. counterText: '',
  184. isDense: true,
  185. contentPadding: FBorderTextFormField.createDesktopContentPadding(),
  186. enabledBorder: border,
  187. focusedBorder: border,
  188. ),
  189. onChanged: onTextChanged,
  190. );
  191. return textField;
  192. }
  193. }
  194. /// 验证码表单栏
  195. class _MobileLayout extends StatelessWidget implements FWidget {
  196. const _MobileLayout({
  197. this.tag,
  198. required this.fetchCodeFunc,
  199. this.textController,
  200. this.textFocusNode,
  201. this.onTextChanged,
  202. this.disabled = false,
  203. this.inError = false,
  204. required this.businessParent,
  205. });
  206. ///父级节点
  207. final FInteractiveContainer businessParent;
  208. final String? tag;
  209. final bool disabled;
  210. final Future<bool> Function() fetchCodeFunc;
  211. final TextEditingController? textController;
  212. final FocusNode? textFocusNode;
  213. final ValueChanged<String>? onTextChanged;
  214. final bool inError;
  215. @override
  216. FWidget build(BuildContext context) {
  217. final textField = _buildTextField(context);
  218. final button = _buildButton(context);
  219. return FFormField(builder: (state) {
  220. return FRow(
  221. children: [
  222. FExpanded(
  223. flex: 1,
  224. child: FContainer(
  225. margin: const EdgeInsets.only(right: 5),
  226. child: textField,
  227. ),
  228. ),
  229. if (kIsWeb)
  230. button
  231. else
  232. FContainer(
  233. height: 32,
  234. child: button,
  235. ),
  236. ],
  237. );
  238. });
  239. }
  240. FWidget _buildButton(BuildContext context) {
  241. final controller = Get.find<VerificationCodeFieldController>(tag: tag);
  242. const textStyle = TextStyle(
  243. fontSize: 14,
  244. color: Colors.white,
  245. );
  246. final btnStyle = ElevatedButton.styleFrom(
  247. elevation: 0,
  248. shape: RoundedRectangleBorder(
  249. borderRadius: BorderRadius.circular(4),
  250. ),
  251. minimumSize: const Size.fromWidth(100),
  252. );
  253. onBtnTap() {
  254. controller.isFetching = true;
  255. fetchCodeFunc().then((value) {
  256. controller.isFetching = false;
  257. if (value) {
  258. controller.startCountDown();
  259. }
  260. });
  261. }
  262. return FObx(() {
  263. final isDisabled = disabled || controller.isBtnDisabled;
  264. final text = controller.btnText;
  265. return FElevatedButton(
  266. onPressed: isDisabled ? null : onBtnTap,
  267. name: text,
  268. businessParent: businessParent,
  269. child: FText(
  270. controller.btnText,
  271. style: textStyle,
  272. ),
  273. style: btnStyle,
  274. );
  275. });
  276. }
  277. FWidget _buildTextField(BuildContext context) {
  278. final border = OutlineInputBorder(
  279. borderRadius: const BorderRadius.all(Radius.circular(4)),
  280. borderSide: BorderSide(
  281. color: inError
  282. ? TEXT_FIELD_BORDER_ERROR_COLOR
  283. : FTheme.ins.data.colorScheme.line,
  284. ),
  285. );
  286. final textField = FTextField(
  287. controller: textController,
  288. focusNode: textFocusNode,
  289. maxLength: 4,
  290. keyboardType: TextInputType.number,
  291. decoration: InputDecoration(
  292. hintText: i18nBook.auth.hint4VerificationCode.t,
  293. fillColor: TEXT_FIELD_FILL_COLOR,
  294. filled: true,
  295. hintStyle: const TextStyle(
  296. fontSize: 12,
  297. color: Colors.black54,
  298. ),
  299. alignLabelWithHint: true,
  300. counterText: '',
  301. isDense: true,
  302. contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 26),
  303. enabledBorder: border,
  304. focusedBorder: border,
  305. ),
  306. onChanged: onTextChanged,
  307. );
  308. return textField;
  309. }
  310. }