import 'dart:async'; import 'package:fis_i18n/i18n.dart'; import 'package:fis_measure/define.dart'; import 'package:fis_measure/view/measure/measure_config/widgets/element_static.dart'; import 'package:fis_measure/view/measure/measure_config/widgets/text_field.dart'; import 'package:fis_ui/index.dart'; import 'package:fis_ui/interface/interactive_container.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:fis_theme/theme.dart'; /// 验证码表单栏控制器 class VerificationCodeFieldController extends GetxController { VerificationCodeFieldController({ this.countDown = 60, }); final int countDown; Timer? _timer; bool _hasSent = false; final _seconds = 0.obs; final _isFetching = false.obs; final Rx _btnText = Rx(''); /// 倒计时读秒 int get seconds => _seconds.value; set seconds(int val) => _seconds(val); /// 是否正在获取验证码 bool get isFetching => _isFetching.value; set isFetching(bool val) => _isFetching(val); /// 按钮文字 String get btnText => setBtnText(); set btnText(String val) => _btnText(val); /// 按钮是否禁用 bool get isBtnDisabled => isFetching || seconds > 0; /// 开启倒计时 startCountDown() { _hasSent = true; seconds = countDown; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { seconds--; if (seconds == 0) { timer.cancel(); } }); } String setBtnText() { if (seconds > 0) { _btnText.value = i18nBook.auth.sendVerificationCodeTimer.translate([seconds]); } else { _btnText.value = _hasSent ? i18nBook.auth.resendVerificationCode.t : i18nBook.auth.sendVerificationCode.t; } return _btnText.value; } ///重置 void reset() { if (_seconds.value == 0) { _timer?.cancel(); _hasSent = false; _isFetching.value = false; setBtnText(); } } @override void onClose() { _timer?.cancel(); _timer = null; super.onClose(); } } /// 验证码表单栏 class VerificationCodeField extends StatelessWidget implements FInteractiveContainer { const VerificationCodeField({ Key? key, this.tag, this.pageName = 'VerificationCodeField', required this.fetchCodeFunc, this.textController, this.textFocusNode, this.onTextChanged, this.disabled = false, this.inError = false, }) : super(key: key); @override final String pageName; final String? tag; final bool disabled; final Future Function() fetchCodeFunc; final TextEditingController? textController; final FocusNode? textFocusNode; final ValueChanged? onTextChanged; final bool inError; @override FWidget build(BuildContext context) { if (kIsMobile) { return _MobileLayout( businessParent: this, tag: tag, disabled: disabled, fetchCodeFunc: fetchCodeFunc, textController: textController, textFocusNode: textFocusNode, onTextChanged: onTextChanged, inError: inError, ); } final textField = _buildTextField(context); final button = _buildButton(context); return FFormField(builder: (state) { return FRow( children: [ FExpanded( flex: 1, child: FContainer( margin: const EdgeInsets.only(right: 5), child: textField, ), ), QuickFWidget(button), ], ); }); } Widget _buildButton(BuildContext context) { final controller = Get.find(tag: tag); const textStyle = TextStyle( fontSize: 14, color: Colors.white, height: 1, ); final btnStyle = ElevatedButton.styleFrom( elevation: 0, padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 16), minimumSize: const Size(120, 56), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ); onBtnTap() { controller.isFetching = true; fetchCodeFunc().then((value) { controller.isFetching = false; if (value) { controller.startCountDown(); } }); } return Obx(() { final isDisabled = disabled || controller.isBtnDisabled; final text = controller.btnText; return FElevatedButton( businessParent: this, onPressed: isDisabled ? null : onBtnTap, name: text, child: FText( text, style: textStyle, ), style: btnStyle, ); }); } FWidget _buildTextField(BuildContext context) { final border = FBorderTextFormField.createDesktopBorder(inError); final textField = FTextField( controller: textController, focusNode: textFocusNode, maxLength: 4, style: const TextStyle(fontSize: 18), keyboardType: TextInputType.number, inputFormatters: [PageInputFormatters.number], decoration: InputDecoration( hintText: i18nBook.auth.hint4VerificationCode.t, fillColor: TEXT_FIELD_FILL_COLOR, filled: true, hintStyle: const TextStyle( fontSize: 16, color: Colors.black54, ), alignLabelWithHint: true, counterText: '', isDense: true, contentPadding: FBorderTextFormField.createDesktopContentPadding(), enabledBorder: border, focusedBorder: border, ), onChanged: onTextChanged, ); return textField; } } /// 验证码表单栏 class _MobileLayout extends StatelessWidget implements FWidget { const _MobileLayout({ this.tag, required this.fetchCodeFunc, this.textController, this.textFocusNode, this.onTextChanged, this.disabled = false, this.inError = false, required this.businessParent, }); ///父级节点 final FInteractiveContainer businessParent; final String? tag; final bool disabled; final Future Function() fetchCodeFunc; final TextEditingController? textController; final FocusNode? textFocusNode; final ValueChanged? onTextChanged; final bool inError; @override FWidget build(BuildContext context) { final textField = _buildTextField(context); final button = _buildButton(context); return FFormField(builder: (state) { return FRow( children: [ FExpanded( flex: 1, child: FContainer( margin: const EdgeInsets.only(right: 5), child: textField, ), ), if (kIsWeb) button else FContainer( height: 32, child: button, ), ], ); }); } FWidget _buildButton(BuildContext context) { final controller = Get.find(tag: tag); const textStyle = TextStyle( fontSize: 14, color: Colors.white, ); final btnStyle = ElevatedButton.styleFrom( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), minimumSize: const Size.fromWidth(100), ); onBtnTap() { controller.isFetching = true; fetchCodeFunc().then((value) { controller.isFetching = false; if (value) { controller.startCountDown(); } }); } return FObx(() { final isDisabled = disabled || controller.isBtnDisabled; final text = controller.btnText; return FElevatedButton( onPressed: isDisabled ? null : onBtnTap, name: text, businessParent: businessParent, child: FText( controller.btnText, style: textStyle, ), style: btnStyle, ); }); } FWidget _buildTextField(BuildContext context) { final border = OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(4)), borderSide: BorderSide( color: inError ? TEXT_FIELD_BORDER_ERROR_COLOR : FTheme.ins.data.colorScheme.line, ), ); final textField = FTextField( controller: textController, focusNode: textFocusNode, maxLength: 4, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: i18nBook.auth.hint4VerificationCode.t, fillColor: TEXT_FIELD_FILL_COLOR, filled: true, hintStyle: const TextStyle( fontSize: 12, color: Colors.black54, ), alignLabelWithHint: true, counterText: '', isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 26), enabledBorder: border, focusedBorder: border, ), onChanged: onTextChanged, ); return textField; } }