my_text_field.dart
6.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:one_poem/generated/l10n.dart';
import 'package:one_poem/res/resources.dart';
import 'package:one_poem/util/device_utils.dart';
import 'package:one_poem/widgets/load_image.dart';
import 'package:one_poem/widgets/my_button.dart';
/// 登录模块的输入框封装
class MyTextField extends StatefulWidget {
const MyTextField({
Key? key,
required this.controller,
this.maxLength = 16,
this.autoFocus = false,
this.keyboardType = TextInputType.text,
this.hintText = '',
this.focusNode,
this.isInputPwd = false,
this.getVCode,
this.keyName
}): super(key: key);
final TextEditingController controller;
final int maxLength;
final bool autoFocus;
final TextInputType keyboardType;
final String hintText;
final FocusNode? focusNode;
final bool isInputPwd;
final Future<bool> Function()? getVCode;
/// 用于集成测试寻找widget
final String? keyName;
@override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
bool _isShowPwd = false;
bool _isShowDelete = false;
bool _clickable = true;
/// 倒计时秒数
final int _second = 30;
/// 当前秒数
late int _currentSecond;
StreamSubscription? _subscription;
@override
void initState() {
/// 获取初始化值
_isShowDelete = widget.controller.text.isNotEmpty;
/// 监听输入改变
widget.controller.addListener(isEmpty);
super.initState();
}
void isEmpty() {
final bool isNotEmpty = widget.controller.text.isNotEmpty;
/// 状态不一样在刷新,避免重复不必要的setState
if (isNotEmpty != _isShowDelete) {
setState(() {
_isShowDelete = isNotEmpty;
});
}
}
@override
void dispose() {
_subscription?.cancel();
widget.controller.removeListener(isEmpty);
super.dispose();
}
Future _getVCode() async {
final bool isSuccess = await widget.getVCode!();
if (isSuccess) {
setState(() {
_currentSecond = _second;
_clickable = false;
});
_subscription = Stream.periodic(const Duration(seconds: 1), (int i) => i).take(_second).listen((int i) {
setState(() {
_currentSecond = _second - i - 1;
_clickable = _currentSecond < 1;
});
});
}
}
@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final bool isDark = themeData.brightness == Brightness.dark;
Widget textField = TextField(
focusNode: widget.focusNode,
maxLength: widget.maxLength,
obscureText: widget.isInputPwd && !_isShowPwd,
autofocus: widget.autoFocus,
controller: widget.controller,
textInputAction: TextInputAction.done,
keyboardType: widget.keyboardType,
// 数字、手机号限制格式为0到9, 密码限制不包含汉字
inputFormatters: (widget.keyboardType == TextInputType.number || widget.keyboardType == TextInputType.phone) ?
[FilteringTextInputFormatter.allow(RegExp('[0-9]'))] : [FilteringTextInputFormatter.deny(RegExp('[\u4e00-\u9fa5]'))],
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(vertical: 16.0),
hintText: widget.hintText,
counterText: '',
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: themeData.primaryColor,
width: 0.8,
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerTheme.color!,
width: 0.8,
),
),
),
);
/// 个别Android机型(华为、vivo)的密码安全键盘不弹出问题(已知小米正常),临时修复方法:https://github.com/flutter/flutter/issues/68571 (issues/61446)
/// 怀疑是安全键盘与三方输入法之间的切换冲突问题。
if (Device.isAndroid) {
textField = Listener(
onPointerDown: (e) => FocusScope.of(context).requestFocus(widget.focusNode),
child: textField,
);
}
Widget? clearButton;
if (_isShowDelete) {
clearButton = Semantics(
label: '清空',
hint: '清空输入框',
child: GestureDetector(
child: LoadAssetImage('login/qyg_shop_icon_delete',
key: Key('${widget.keyName}_delete'),
width: 18.0,
height: 40.0,
),
onTap: () => widget.controller.text = '',
),
);
}
late Widget pwdVisible;
if (widget.isInputPwd) {
pwdVisible = Semantics(
label: '密码可见开关',
hint: '密码是否可见',
child: GestureDetector(
child: LoadAssetImage(
_isShowPwd ? 'login/qyg_shop_icon_display' : 'login/qyg_shop_icon_hide',
key: Key('${widget.keyName}_showPwd'),
width: 18.0,
height: 40.0,
),
onTap: () {
setState(() {
_isShowPwd = !_isShowPwd;
});
},
),
);
}
late Widget getVCodeButton;
if (widget.getVCode != null) {
getVCodeButton = MyButton(
key: const Key('getVerificationCode'),
onPressed: _clickable ? _getVCode : null,
fontSize: Dimens.font_sp12,
text: _clickable ? S.of(context).getVerificationCode : '($_currentSecond s)',
textColor: themeData.primaryColor,
disabledTextColor: isDark ? Colours.dark_text : Colors.white,
backgroundColor: Colors.transparent,
disabledBackgroundColor: isDark ? Colours.dark_text_gray : Colours.text_gray_c,
radius: 1.0,
minHeight: 26.0,
minWidth: 76.0,
padding: const EdgeInsets.symmetric(horizontal: 8.0),
side: BorderSide(
color: _clickable ? themeData.primaryColor : Colors.transparent,
width: 0.8,
),
);
}
return Stack(
alignment: Alignment.centerRight,
children: <Widget>[
textField,
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
/// _isShowDelete参数动态变化,为了不破坏树结构使用Visibility,false时放一个空Widget。
/// 对于其他参数,为初始配置参数,基本可以确定树结构,就不做空Widget处理。
Visibility(
visible: _isShowDelete,
child: clearButton ?? Gaps.empty,
),
if (widget.isInputPwd) Gaps.hGap15,
if (widget.isInputPwd) pwdVisible,
if (widget.getVCode != null) Gaps.hGap15,
if (widget.getVCode != null) getVCodeButton,
],
)
],
);
}
}