Bladeren bron

修改定位获取不到的问题,发布21版本。

liujq 5 maanden geleden
bovenliggende
commit
2c5e6917a6

+ 2 - 0
README.md

@@ -20,3 +20,5 @@ samples, guidance on mobile development, and a full API reference.
20 20
 Android打包方式如下:
21 21
 - 测试环境:flutter build apk --flavor develop --release
22 22
 - 生产环境:flutter build apk --flavor product --release
23
+
24
+- 每次发布版本 需要提醒后台 更改后台app版本号(app更新用)

+ 1 - 1
android/app/build.gradle

@@ -118,7 +118,7 @@ android {
118 118
                     APP_NAME     : "国粮质检",
119 119
                     // 极光 注意测试环境的 packagename 和appkey 均不一样
120 120
                     JPUSH_PKGNAME: "com.szls.lszlgl",
121
-                    JPUSH_APPKEY : "???", // 正式环境appkey需要再申请
121
+                    JPUSH_APPKEY : "2f42e3db4d0083b0bc5a3f6c", // 正式环境appkey需要再申请
122 122
                     JPUSH_CHANNEL: "developer-default"
123 123
             ]
124 124
         }

BIN
assets/images/login_phnum.png


+ 2 - 0
lib/config/pics.dart

@@ -8,6 +8,8 @@ const String imgLoginTitle = 'assets/images/login_title.webp';
8 8
 const String imgLoginAccount = 'assets/images/login_account.webp';
9 9
 // 登录页密码icon
10 10
 const String imgLoginPwd = 'assets/images/login_pwd.webp';
11
+// 验证码icon
12
+const String imgLoginPhoNum = 'assets/images/login_phnum.png';
11 13
 
12 14
 // 导航栏图标
13 15
 const String imgNavHome = 'assets/images/nav_home.webp';

+ 37 - 9
lib/page/login/forget_password_page.dart

@@ -2,11 +2,13 @@ import 'package:flutter/material.dart';
2 2
 import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
3 3
 import 'package:lszlgl/base/base_state.dart';
4 4
 import 'package:lszlgl/config/borders.dart';
5
+import 'package:lszlgl/config/colors.dart';
5 6
 import 'package:lszlgl/main.dart';
6 7
 import 'package:lszlgl/network/my_api.dart';
7 8
 import 'package:lszlgl/utils/string_utils.dart';
8 9
 import 'package:lszlgl/widget/button.dart';
9 10
 import 'package:lszlgl/widget/countdown_button_widget.dart';
11
+import 'package:lszlgl/widget/password_textformfield.dart';
10 12
 
11 13
 class ForgetPasswordPage extends StatefulWidget {
12 14
   const ForgetPasswordPage({super.key});
@@ -67,21 +69,30 @@ class _ForgetPasswordPageState extends BaseState<ForgetPasswordPage> {
67 69
                       child: Column(
68 70
                         children: [
69 71
                           TextFormField(
72
+                            autovalidateMode: AutovalidateMode.onUserInteraction,
70 73
                             controller: phoneCtrl,
71 74
                             keyboardType: TextInputType.number,
72
-                            decoration: const InputDecoration(
75
+                            decoration: InputDecoration(
73 76
                               hintText: '手机号',
74
-                              contentPadding: EdgeInsets.symmetric(horizontal: 20,vertical: 6),
77
+                              contentPadding: const EdgeInsets.symmetric(horizontal: 20,vertical: 6),
75 78
                               border: Borders.borderA,
76 79
                               enabledBorder: Borders.borderB,
80
+                              prefixIcon: Padding(
81
+                                padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
82
+                                child: Image.asset(imgLoginAccount, height: 18),
83
+                              ),
84
+                              prefixIconConstraints: const BoxConstraints(),
77 85
                             ),
78 86
                             validator: (val){
79
-                              return StringUtils.isPhoneNum(val) ? null : '请输入手机号';
87
+                              return StringUtils.isPhoneNum(val) ? null : '请输入正确的手机号';
80 88
                             },
89
+
90
+
81 91
                           ),
82 92
                           const SizedBox(height: 16),
83 93
 
84 94
                           TextFormField(
95
+                            autovalidateMode: AutovalidateMode.onUserInteraction,
85 96
                             controller: msgCtrl,
86 97
                             keyboardType: TextInputType.number,
87 98
                             decoration: InputDecoration(
@@ -96,6 +107,11 @@ class _ForgetPasswordPageState extends BaseState<ForgetPasswordPage> {
96 107
                                   phoneController: phoneCtrl,
97 108
                                 ),
98 109
                               ),
110
+                              prefixIcon: Padding(
111
+                                padding:  const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
112
+                                child:  Image.asset(imgLoginPhoNum, height: 19),
113
+                              ),
114
+                              prefixIconConstraints: const BoxConstraints(),
99 115
                             ),
100 116
                             validator: (value){
101 117
                               return value!.trim().isEmpty ? '请输入验证码' : null;
@@ -103,14 +119,20 @@ class _ForgetPasswordPageState extends BaseState<ForgetPasswordPage> {
103 119
                           ),
104 120
                           const SizedBox(height: 16),
105 121
 
106
-                          TextFormField(
122
+                          PasswordTextFormField(
123
+                            autovalidateMode: AutovalidateMode.onUserInteraction,
107 124
                             controller: pwdCtrl,
108 125
                             obscureText: true,
109
-                            decoration: const InputDecoration(
126
+                            decoration: InputDecoration(
110 127
                               hintText: '新密码',
111
-                              contentPadding: EdgeInsets.symmetric(horizontal: 20,vertical: 6),
128
+                              contentPadding: const EdgeInsets.symmetric(horizontal: 20,vertical: 6),
112 129
                               border: Borders.borderA,
113 130
                               enabledBorder: Borders.borderB,
131
+                              prefixIcon: Padding(
132
+                                padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
133
+                                child: Image.asset(imgLoginPwd, height: 18),
134
+                              ),
135
+                              prefixIconConstraints: const BoxConstraints(),
114 136
                             ),
115 137
                             validator: (val){
116 138
                               if(val!.length < 8){
@@ -133,13 +155,19 @@ class _ForgetPasswordPageState extends BaseState<ForgetPasswordPage> {
133 155
                           ),
134 156
                           const SizedBox(height: 16),
135 157
 
136
-                          TextFormField(
158
+                          PasswordTextFormField(
159
+                            autovalidateMode: AutovalidateMode.onUserInteraction,
137 160
                             obscureText: true,
138
-                            decoration: const InputDecoration(
161
+                            decoration:  InputDecoration(
139 162
                                 hintText: '再次输入新密码',
140
-                                contentPadding: EdgeInsets.symmetric(horizontal: 20,vertical: 6),
163
+                                contentPadding: const EdgeInsets.symmetric(horizontal: 20,vertical: 6),
141 164
                                 border: Borders.borderA,
142 165
                                 enabledBorder: Borders.borderB,
166
+                              prefixIcon: Padding(
167
+                                padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
168
+                                child: Image.asset(imgLoginPwd, height: 18),
169
+                              ),
170
+                              prefixIconConstraints: const BoxConstraints(),
143 171
                             ),
144 172
                             validator: (val){
145 173
                               if(val!.trim().isEmpty){

+ 3 - 2
lib/page/login/login_page.dart

@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
3 3
 import 'package:flutter/material.dart';
4 4
 import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
5 5
 import 'package:lszlgl/base/base_lifecycle_state.dart';
6
+import 'package:lszlgl/config/app_config.dart';
6 7
 import 'package:lszlgl/main.dart';
7 8
 import 'package:lszlgl/model/req/login_req.dart';
8 9
 import 'package:lszlgl/network/my_api.dart';
@@ -109,7 +110,7 @@ class _LoginPageState extends BaseLifecycleState<LoginPage> {
109 110
     LocationUtils.updatePrivacyShow(true, true);
110 111
     LocationUtils.updatePrivacyAgree(true);
111 112
     LocationUtils.setApiKey(
112
-        kReleaseMode ? '2c783509376e267b24d63b21681686fa' : '7d0c033909f84adc14a0e60a835f044f', '');
113
+        AppConfig.env == AppEnvironment.product ? '2c783509376e267b24d63b21681686fa' : '7d0c033909f84adc14a0e60a835f044f', '');
113 114
 
114 115
     /// 获取手机设备信息
115 116
     PrintService.getDeviceInfo();
@@ -242,7 +243,7 @@ class _LoginPageState extends BaseLifecycleState<LoginPage> {
242 243
                   );
243 244
                 },
244 245
                 child: IconButton(
245
-                  icon: const Icon(Icons.remove_red_eye_outlined, size: 16, color: Color(0xFFBBBBBB)),
246
+                  icon: const Icon(Icons.remove_red_eye_outlined, size: 18, color: Color(0xFFBBBBBB)),
246 247
                   onPressed: () {
247 248
                     _showPwd.value = !_showPwd.value;
248 249
                   },

+ 7 - 2
lib/page/user_center/change_pwd_page.dart

@@ -7,6 +7,7 @@ import 'package:lszlgl/base/base_state.dart';
7 7
 import 'package:lszlgl/main.dart';
8 8
 import 'package:lszlgl/network/my_api.dart';
9 9
 import 'package:lszlgl/widget/button.dart';
10
+import 'package:lszlgl/widget/password_textfield.dart';
10 11
 
11 12
 /// 修改密码
12 13
 class ChangePwdPage extends StatefulWidget {
@@ -235,9 +236,13 @@ class _ChangePwdPageState extends BaseState<ChangePwdPage> {
235 236
     String? errorText,
236 237
     TextInputType? inputType,
237 238
   }) {
238
-    return TextField(
239
+    return PasswordTextField(
239 240
       controller: ctrl,
240 241
       decoration: InputDecoration(
242
+        prefixIcon: Padding(
243
+          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
244
+          child: Image.asset(imgLoginPwd, height: 18),
245
+        ),
241 246
         prefixIconConstraints: const BoxConstraints(),
242 247
         border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(100))),
243 248
         enabledBorder: const OutlineInputBorder(
@@ -254,7 +259,7 @@ class _ChangePwdPageState extends BaseState<ChangePwdPage> {
254 259
       maxLength: maxLength,
255 260
       style: const TextStyle(fontSize: 14),
256 261
       textInputAction: action,
257
-      obscureText: obscureText,
262
+      initialObscurity: obscureText,
258 263
       onSubmitted: onSubmit,
259 264
       onChanged: onChanged,
260 265
       inputFormatters: formatters,

+ 1 - 1
lib/service/jgpush_service.dart

@@ -9,7 +9,7 @@ class JGPushService{
9 9
   static Future<void> jgInit(AppEnvironment env) async{
10 10
     jPush.setup(
11 11
       // 极光推送 正式环境appkey需要再申请
12
-      appKey: env == AppEnvironment.product ? '???' : '2f42e3db4d0083b0bc5a3f6c',
12
+      appKey: env == AppEnvironment.product ? '2f42e3db4d0083b0bc5a3f6c' : '2f42e3db4d0083b0bc5a3f6c',
13 13
       channel: 'developer-default',
14 14
       production: env == AppEnvironment.product ? true : false,
15 15
       debug: env == AppEnvironment.develop ? true : false

+ 598 - 0
lib/widget/password_textfield.dart

@@ -0,0 +1,598 @@
1
+import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
2
+
3
+import 'package:flutter/gestures.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:flutter/services.dart';
6
+
7
+
8
+class PasswordTextField extends StatefulWidget {
9
+  const PasswordTextField({
10
+    super.key,
11
+    this.visibleIcon = Icons.remove_red_eye_outlined,
12
+    this.inVisibleIcon = Icons.remove_red_eye_outlined,
13
+    this.initialObscurity = false,
14
+    this.controller,
15
+    this.focusNode,
16
+    this.undoController,
17
+    this.decoration = const InputDecoration(),
18
+    this.keyboardType,
19
+    this.textInputAction,
20
+    this.textCapitalization = TextCapitalization.none,
21
+    this.style,
22
+    this.strutStyle,
23
+    this.textAlign = TextAlign.start,
24
+    this.textAlignVertical,
25
+    this.textDirection,
26
+    this.readOnly = false,
27
+    this.showCursor,
28
+    this.autofocus = false,
29
+    this.obscuringCharacter = '•',
30
+    this.autocorrect = true,
31
+    this.enableSuggestions = true,
32
+    this.maxLines = 1,
33
+    this.minLines,
34
+    this.expands = false,
35
+    this.maxLength,
36
+    this.maxLengthEnforcement,
37
+    this.onChanged,
38
+    this.onEditingComplete,
39
+    this.onSubmitted,
40
+    this.onAppPrivateCommand,
41
+    this.inputFormatters,
42
+    this.enabled,
43
+    this.cursorWidth = 2.0,
44
+    this.cursorHeight,
45
+    this.cursorRadius,
46
+    this.cursorOpacityAnimates,
47
+    this.cursorColor,
48
+    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
49
+    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
50
+    this.keyboardAppearance,
51
+    this.scrollPadding = const EdgeInsets.all(20.0),
52
+    this.dragStartBehavior = DragStartBehavior.start,
53
+    this.enableInteractiveSelection = true,
54
+    this.selectionControls,
55
+    this.onTap,
56
+    this.onTapOutside,
57
+    this.mouseCursor,
58
+    this.buildCounter,
59
+    this.scrollController,
60
+    this.scrollPhysics,
61
+    this.autofillHints = const <String>[],
62
+    this.contentInsertionConfiguration,
63
+    this.clipBehavior = Clip.hardEdge,
64
+    this.restorationId,
65
+    this.scribbleEnabled = true,
66
+    this.enableIMEPersonalizedLearning = true,
67
+    this.contextMenuBuilder,
68
+    this.canRequestFocus = true,
69
+    this.spellCheckConfiguration,
70
+    this.magnifierConfiguration,
71
+  });
72
+
73
+  /// Displaying toggle icon(show).
74
+  ///
75
+  /// Defaults is [Icons.visibility].
76
+  final IconData visibleIcon;
77
+
78
+  /// Displaying toggle icon(hide).
79
+  ///
80
+  /// Defaults is [Icons.visibility_off].
81
+  final IconData inVisibleIcon;
82
+
83
+  /// The obscure feature is enabled by default.
84
+  ///
85
+  /// Default is [false].
86
+  final bool initialObscurity;
87
+
88
+  /// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.intro}
89
+  ///
90
+  /// {@macro flutter.widgets.magnifier.intro}
91
+  ///
92
+  /// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.details}
93
+  ///
94
+  /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier]
95
+  /// on Android, and builds nothing on all other platforms. If it is desired to
96
+  /// suppress the magnifier, consider passing [TextMagnifierConfiguration.disabled].
97
+  ///
98
+  /// {@tool dartpad}
99
+  /// This sample demonstrates how to customize the magnifier that this text field uses.
100
+  ///
101
+  /// ** See code in examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart **
102
+  /// {@end-tool}
103
+  final TextMagnifierConfiguration? magnifierConfiguration;
104
+
105
+  /// Controls the text being edited.
106
+  ///
107
+  /// If null, this widget will create its own [TextEditingController].
108
+  final TextEditingController? controller;
109
+
110
+  /// Defines the keyboard focus for this widget.
111
+  ///
112
+  /// The [focusNode] is a long-lived object that's typically managed by a
113
+  /// [StatefulWidget] parent. See [FocusNode] for more information.
114
+  ///
115
+  /// To give the keyboard focus to this widget, provide a [focusNode] and then
116
+  /// use the current [FocusScope] to request the focus:
117
+  ///
118
+  /// ```dart
119
+  /// FocusScope.of(context).requestFocus(myFocusNode);
120
+  /// ```
121
+  ///
122
+  /// This happens automatically when the widget is tapped.
123
+  ///
124
+  /// To be notified when the widget gains or loses the focus, add a listener
125
+  /// to the [focusNode]:
126
+  ///
127
+  /// ```dart
128
+  /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); });
129
+  /// ```
130
+  ///
131
+  /// If null, this widget will create its own [FocusNode].
132
+  ///
133
+  /// ## Keyboard
134
+  ///
135
+  /// Requesting the focus will typically cause the keyboard to be shown
136
+  /// if it's not showing already.
137
+  ///
138
+  /// On Android, the user can hide the keyboard - without changing the focus -
139
+  /// with the system back button. They can restore the keyboard's visibility
140
+  /// by tapping on a text field. The user might hide the keyboard and
141
+  /// switch to a physical keyboard, or they might just need to get it
142
+  /// out of the way for a moment, to expose something it's
143
+  /// obscuring. In this case requesting the focus again will not
144
+  /// cause the focus to change, and will not make the keyboard visible.
145
+  ///
146
+  /// This widget builds an [EditableText] and will ensure that the keyboard is
147
+  /// showing when it is tapped by calling [EditableTextState.requestKeyboard()].
148
+  final FocusNode? focusNode;
149
+
150
+  /// The decoration to show around the text field.
151
+  ///
152
+  /// By default, draws a horizontal line under the text field but can be
153
+  /// configured to show an icon, label, hint text, and error text.
154
+  ///
155
+  /// Specify null to remove the decoration entirely (including the
156
+  /// extra padding introduced by the decoration to save space for the labels).
157
+  final InputDecoration decoration;
158
+
159
+  /// {@macro flutter.widgets.editableText.keyboardType}
160
+  final TextInputType? keyboardType;
161
+
162
+  /// The type of action button to use for the keyboard.
163
+  ///
164
+  /// Defaults to [TextInputAction.newline] if [keyboardType] is
165
+  /// [TextInputType.multiline] and [TextInputAction.done] otherwise.
166
+  final TextInputAction? textInputAction;
167
+
168
+  /// {@macro flutter.widgets.editableText.textCapitalization}
169
+  final TextCapitalization textCapitalization;
170
+
171
+  /// The style to use for the text being edited.
172
+  ///
173
+  /// This text style is also used as the base style for the [decoration].
174
+  ///
175
+  /// If null, defaults to the `titleMedium` text style from the current [Theme].
176
+  final TextStyle? style;
177
+
178
+  /// {@macro flutter.widgets.editableText.strutStyle}
179
+  final StrutStyle? strutStyle;
180
+
181
+  /// {@macro flutter.widgets.editableText.textAlign}
182
+  final TextAlign textAlign;
183
+
184
+  /// {@macro flutter.material.InputDecorator.textAlignVertical}
185
+  final TextAlignVertical? textAlignVertical;
186
+
187
+  /// {@macro flutter.widgets.editableText.textDirection}
188
+  final TextDirection? textDirection;
189
+
190
+  /// {@macro flutter.widgets.editableText.autofocus}
191
+  final bool autofocus;
192
+
193
+  /// {@macro flutter.widgets.editableText.obscuringCharacter}
194
+  final String obscuringCharacter;
195
+
196
+  /// {@macro flutter.widgets.editableText.autocorrect}
197
+  final bool autocorrect;
198
+
199
+  /// {@macro flutter.services.TextInputConfiguration.enableSuggestions}
200
+  final bool enableSuggestions;
201
+
202
+  /// {@macro flutter.widgets.editableText.maxLines}
203
+  ///  * [expands], which determines whether the field should fill the height of
204
+  ///    its parent.
205
+  final int? maxLines;
206
+
207
+  /// {@macro flutter.widgets.editableText.minLines}
208
+  ///  * [expands], which determines whether the field should fill the height of
209
+  ///    its parent.
210
+  final int? minLines;
211
+
212
+  /// {@macro flutter.widgets.editableText.expands}
213
+  final bool expands;
214
+
215
+  /// {@macro flutter.widgets.editableText.readOnly}
216
+  final bool readOnly;
217
+
218
+  /// {@macro flutter.widgets.editableText.showCursor}
219
+  final bool? showCursor;
220
+
221
+  /// The maximum number of characters (Unicode grapheme clusters) to allow in
222
+  /// the text field.
223
+  ///
224
+  /// If set, a character counter will be displayed below the
225
+  /// field showing how many characters have been entered. If set to a number
226
+  /// greater than 0, it will also display the maximum number allowed. If set
227
+  /// to [TextField.noMaxLength] then only the current character count is displayed.
228
+  ///
229
+  /// After [maxLength] characters have been input, additional input
230
+  /// is ignored, unless [maxLengthEnforcement] is set to
231
+  /// [MaxLengthEnforcement.none].
232
+  ///
233
+  /// The text field enforces the length with a [LengthLimitingTextInputFormatter],
234
+  /// which is evaluated after the supplied [inputFormatters], if any.
235
+  ///
236
+  /// This value must be either null, [TextField.noMaxLength], or greater than 0.
237
+  /// If null (the default) then there is no limit to the number of characters
238
+  /// that can be entered. If set to [TextField.noMaxLength], then no limit will
239
+  /// be enforced, but the number of characters entered will still be displayed.
240
+  ///
241
+  /// Whitespace characters (e.g. newline, space, tab) are included in the
242
+  /// character count.
243
+  ///
244
+  /// If [maxLengthEnforcement] is [MaxLengthEnforcement.none], then more than
245
+  /// [maxLength] characters may be entered, but the error counter and divider
246
+  /// will switch to the [decoration]'s [InputDecoration.errorStyle] when the
247
+  /// limit is exceeded.
248
+  ///
249
+  /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength}
250
+  final int? maxLength;
251
+
252
+  /// Determines how the [maxLength] limit should be enforced.
253
+  ///
254
+  /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement}
255
+  ///
256
+  /// {@macro flutter.services.textFormatter.maxLengthEnforcement}
257
+  final MaxLengthEnforcement? maxLengthEnforcement;
258
+
259
+  /// {@macro flutter.widgets.editableText.onChanged}
260
+  ///
261
+  /// See also:
262
+  ///
263
+  ///  * [inputFormatters], which are called before [onChanged]
264
+  ///    runs and can validate and change ("format") the input value.
265
+  ///  * [onEditingComplete], [onSubmitted]:
266
+  ///    which are more specialized input change notifications.
267
+  final ValueChanged<String>? onChanged;
268
+
269
+  /// {@macro flutter.widgets.editableText.onEditingComplete}
270
+  final VoidCallback? onEditingComplete;
271
+
272
+  /// {@macro flutter.widgets.editableText.onSubmitted}
273
+  ///
274
+  /// See also:
275
+  ///
276
+  ///  * [TextInputAction.next] and [TextInputAction.previous], which
277
+  ///    automatically shift the focus to the next/previous focusable item when
278
+  ///    the user is done editing.
279
+  final ValueChanged<String>? onSubmitted;
280
+
281
+  /// {@macro flutter.widgets.editableText.onAppPrivateCommand}
282
+  final AppPrivateCommandCallback? onAppPrivateCommand;
283
+
284
+  /// {@macro flutter.widgets.editableText.inputFormatters}
285
+  final List<TextInputFormatter>? inputFormatters;
286
+
287
+  /// If false the text field is "disabled": it ignores taps and its
288
+  /// [decoration] is rendered in grey.
289
+  ///
290
+  /// If non-null this property overrides the [decoration]'s
291
+  /// [InputDecoration.enabled] property.
292
+  final bool? enabled;
293
+
294
+  /// {@macro flutter.widgets.editableText.cursorWidth}
295
+  final double cursorWidth;
296
+
297
+  /// {@macro flutter.widgets.editableText.cursorHeight}
298
+  final double? cursorHeight;
299
+
300
+  /// {@macro flutter.widgets.editableText.cursorRadius}
301
+  final Radius? cursorRadius;
302
+
303
+  /// {@macro flutter.widgets.editableText.cursorOpacityAnimates}
304
+  final bool? cursorOpacityAnimates;
305
+
306
+  /// The color of the cursor.
307
+  ///
308
+  /// The cursor indicates the current location of text insertion point in
309
+  /// the field.
310
+  ///
311
+  /// If this is null it will default to the ambient
312
+  /// [DefaultSelectionStyle.cursorColor]. If that is null, and the
313
+  /// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS]
314
+  /// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use
315
+  /// the value of [ColorScheme.primary] of [ThemeData.colorScheme].
316
+  final Color? cursorColor;
317
+
318
+  /// Controls how tall the selection highlight boxes are computed to be.
319
+  ///
320
+  /// See [ui.BoxHeightStyle] for details on available styles.
321
+  final ui.BoxHeightStyle selectionHeightStyle;
322
+
323
+  /// Controls how wide the selection highlight boxes are computed to be.
324
+  ///
325
+  /// See [ui.BoxWidthStyle] for details on available styles.
326
+  final ui.BoxWidthStyle selectionWidthStyle;
327
+
328
+  /// The appearance of the keyboard.
329
+  ///
330
+  /// This setting is only honored on iOS devices.
331
+  ///
332
+  /// If unset, defaults to [ThemeData.brightness].
333
+  final Brightness? keyboardAppearance;
334
+
335
+  /// {@macro flutter.widgets.editableText.scrollPadding}
336
+  final EdgeInsets scrollPadding;
337
+
338
+  /// {@macro flutter.widgets.editableText.enableInteractiveSelection}
339
+  final bool enableInteractiveSelection;
340
+
341
+  /// {@macro flutter.widgets.editableText.selectionControls}
342
+  final TextSelectionControls? selectionControls;
343
+
344
+  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
345
+  final DragStartBehavior dragStartBehavior;
346
+
347
+  /// {@macro flutter.widgets.editableText.selectionEnabled}
348
+  bool get selectionEnabled => enableInteractiveSelection;
349
+
350
+  /// {@template flutter.material.textfield.onTap}
351
+  /// Called for each distinct tap except for every second tap of a double tap.
352
+  ///
353
+  /// The text field builds a [GestureDetector] to handle input events like tap,
354
+  /// to trigger focus requests, to move the caret, adjust the selection, etc.
355
+  /// Handling some of those events by wrapping the text field with a competing
356
+  /// GestureDetector is problematic.
357
+  ///
358
+  /// To unconditionally handle taps, without interfering with the text field's
359
+  /// internal gesture detector, provide this callback.
360
+  ///
361
+  /// If the text field is created with [enabled] false, taps will not be
362
+  /// recognized.
363
+  ///
364
+  /// To be notified when the text field gains or loses the focus, provide a
365
+  /// [focusNode] and add a listener to that.
366
+  ///
367
+  /// To listen to arbitrary pointer events without competing with the
368
+  /// text field's internal gesture detector, use a [Listener].
369
+  /// {@endtemplate}
370
+  final GestureTapCallback? onTap;
371
+
372
+  /// {@macro flutter.widgets.editableText.onTapOutside}
373
+  ///
374
+  /// {@tool dartpad}
375
+  /// This example shows how to use a `TextFieldTapRegion` to wrap a set of
376
+  /// "spinner" buttons that increment and decrement a value in the [TextField]
377
+  /// without causing the text field to lose keyboard focus.
378
+  ///
379
+  /// This example includes a generic `SpinnerField<T>` class that you can copy
380
+  /// into your own project and customize.
381
+  ///
382
+  /// ** See code in examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart **
383
+  /// {@end-tool}
384
+  ///
385
+  /// See also:
386
+  ///
387
+  ///  * [TapRegion] for how the region group is determined.
388
+  final TapRegionCallback? onTapOutside;
389
+
390
+  /// The cursor for a mouse pointer when it enters or is hovering over the
391
+  /// widget.
392
+  ///
393
+  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
394
+  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
395
+  ///
396
+  ///  * [MaterialState.error].
397
+  ///  * [MaterialState.hovered].
398
+  ///  * [MaterialState.focused].
399
+  ///  * [MaterialState.disabled].
400
+  ///
401
+  /// If this property is null, [MaterialStateMouseCursor.textable] will be used.
402
+  ///
403
+  /// The [mouseCursor] is the only property of [TextField] that controls the
404
+  /// appearance of the mouse pointer. All other properties related to "cursor"
405
+  /// stand for the text cursor, which is usually a blinking vertical line at
406
+  /// the editing position.
407
+  final MouseCursor? mouseCursor;
408
+
409
+  /// Callback that generates a custom [InputDecoration.counter] widget.
410
+  ///
411
+  /// See [InputCounterWidgetBuilder] for an explanation of the passed in
412
+  /// arguments. The returned widget will be placed below the line in place of
413
+  /// the default widget built when [InputDecoration.counterText] is specified.
414
+  ///
415
+  /// The returned widget will be wrapped in a [Semantics] widget for
416
+  /// accessibility, but it also needs to be accessible itself. For example,
417
+  /// if returning a Text widget, set the [Text.semanticsLabel] property.
418
+  ///
419
+  /// {@tool snippet}
420
+  /// ```dart
421
+  /// Widget counter(
422
+  ///   BuildContext context,
423
+  ///   {
424
+  ///     required int currentLength,
425
+  ///     required int? maxLength,
426
+  ///     required bool isFocused,
427
+  ///   }
428
+  /// ) {
429
+  ///   return Text(
430
+  ///     '$currentLength of $maxLength characters',
431
+  ///     semanticsLabel: 'character count',
432
+  ///   );
433
+  /// }
434
+  /// ```
435
+  /// {@end-tool}
436
+  ///
437
+  /// If buildCounter returns null, then no counter and no Semantics widget will
438
+  /// be created at all.
439
+  final InputCounterWidgetBuilder? buildCounter;
440
+
441
+  /// {@macro flutter.widgets.editableText.scrollPhysics}
442
+  final ScrollPhysics? scrollPhysics;
443
+
444
+  /// {@macro flutter.widgets.editableText.scrollController}
445
+  final ScrollController? scrollController;
446
+
447
+  /// {@macro flutter.widgets.editableText.autofillHints}
448
+  /// {@macro flutter.services.AutofillConfiguration.autofillHints}
449
+  final Iterable<String>? autofillHints;
450
+
451
+  /// {@macro flutter.material.Material.clipBehavior}
452
+  ///
453
+  /// Defaults to [Clip.hardEdge].
454
+  final Clip clipBehavior;
455
+
456
+  /// {@template flutter.material.textfield.restorationId}
457
+  /// Restoration ID to save and restore the state of the text field.
458
+  ///
459
+  /// If non-null, the text field will persist and restore its current scroll
460
+  /// offset and - if no [controller] has been provided - the content of the
461
+  /// text field. If a [controller] has been provided, it is the responsibility
462
+  /// of the owner of that controller to persist and restore it, e.g. by using
463
+  /// a [RestorableTextEditingController].
464
+  ///
465
+  /// The state of this widget is persisted in a [RestorationBucket] claimed
466
+  /// from the surrounding [RestorationScope] using the provided restoration ID.
467
+  ///
468
+  /// See also:
469
+  ///
470
+  ///  * [RestorationManager], which explains how state restoration works in
471
+  ///    Flutter.
472
+  /// {@endtemplate}
473
+  final String? restorationId;
474
+
475
+  /// {@macro flutter.widgets.editableText.scribbleEnabled}
476
+  final bool scribbleEnabled;
477
+
478
+  /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
479
+  final bool enableIMEPersonalizedLearning;
480
+
481
+  /// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
482
+  final ContentInsertionConfiguration? contentInsertionConfiguration;
483
+
484
+  /// {@macro flutter.widgets.EditableText.contextMenuBuilder}
485
+  ///
486
+  /// If not provided, will build a default menu based on the platform.
487
+  ///
488
+  /// See also:
489
+  ///
490
+  ///  * [AdaptiveTextSelectionToolbar], which is built by default.
491
+  final EditableTextContextMenuBuilder? contextMenuBuilder;
492
+
493
+  /// Determine whether this text field can request the primary focus.
494
+  ///
495
+  /// Defaults to true. If false, the text field will not request focus
496
+  /// when tapped, or when its context menu is displayed. If false it will not
497
+  /// be possible to move the focus to the text field with tab key.
498
+  final bool canRequestFocus;
499
+
500
+  /// {@macro flutter.widgets.undoHistory.controller}
501
+  final UndoHistoryController? undoController;
502
+
503
+  /// {@macro flutter.widgets.EditableText.spellCheckConfiguration}
504
+  ///
505
+  /// If [SpellCheckConfiguration.misspelledTextStyle] is not specified in this
506
+  /// configuration, then [materialMisspelledTextStyle] is used by default.
507
+  final SpellCheckConfiguration? spellCheckConfiguration;
508
+
509
+  @override
510
+  State<PasswordTextField> createState() => _PasswordTextFieldState();
511
+}
512
+
513
+class _PasswordTextFieldState extends State<PasswordTextField> {
514
+  late bool _obscure;
515
+
516
+  @override
517
+  void initState() {
518
+    _obscure = widget.initialObscurity;
519
+
520
+    super.initState();
521
+  }
522
+
523
+  @override
524
+  Widget build(BuildContext context) {
525
+    return TextField(
526
+      obscureText: _obscure,
527
+      decoration: widget.decoration.copyWith(
528
+        suffixIcon: IconButton(
529
+          icon: Icon(
530
+            _obscure ? widget.inVisibleIcon : widget.visibleIcon,
531
+            size: 18, color: const Color(0xFFBBBBBB)
532
+          ),
533
+          onPressed: () {
534
+            setState(() {
535
+              _obscure = !_obscure;
536
+            });
537
+          },
538
+        ),
539
+      ),
540
+      controller: widget.controller,
541
+      focusNode: widget.focusNode,
542
+      undoController: widget.undoController,
543
+      keyboardType: widget.keyboardType,
544
+      textInputAction: widget.textInputAction,
545
+      textCapitalization: widget.textCapitalization,
546
+      style: widget.style,
547
+      strutStyle: widget.strutStyle,
548
+      textAlign: widget.textAlign,
549
+      textAlignVertical: widget.textAlignVertical,
550
+      textDirection: widget.textDirection,
551
+      readOnly: widget.readOnly,
552
+      showCursor: widget.showCursor,
553
+      autofocus: widget.autofocus,
554
+      obscuringCharacter: widget.obscuringCharacter,
555
+      autocorrect: widget.autocorrect,
556
+      enableSuggestions: widget.enableSuggestions,
557
+      maxLines: widget.maxLines,
558
+      minLines: widget.minLines,
559
+      expands: widget.expands,
560
+      maxLength: widget.maxLength,
561
+      maxLengthEnforcement: widget.maxLengthEnforcement,
562
+      onChanged: widget.onChanged,
563
+      onEditingComplete: widget.onEditingComplete,
564
+      onSubmitted: widget.onSubmitted,
565
+      onAppPrivateCommand: widget.onAppPrivateCommand,
566
+      inputFormatters: widget.inputFormatters,
567
+      enabled: widget.enabled,
568
+      cursorWidth: widget.cursorWidth,
569
+      cursorHeight: widget.cursorHeight,
570
+      cursorRadius: widget.cursorRadius,
571
+      cursorOpacityAnimates: widget.cursorOpacityAnimates,
572
+      cursorColor: widget.cursorColor,
573
+      selectionHeightStyle: widget.selectionHeightStyle,
574
+      selectionWidthStyle: widget.selectionWidthStyle,
575
+      keyboardAppearance: widget.keyboardAppearance,
576
+      scrollPadding: widget.scrollPadding,
577
+      dragStartBehavior: widget.dragStartBehavior,
578
+      enableInteractiveSelection: widget.enableInteractiveSelection,
579
+      selectionControls: widget.selectionControls,
580
+      onTap: widget.onTap,
581
+      onTapOutside: widget.onTapOutside,
582
+      mouseCursor: widget.mouseCursor,
583
+      buildCounter: widget.buildCounter,
584
+      scrollController: widget.scrollController,
585
+      scrollPhysics: widget.scrollPhysics,
586
+      autofillHints: widget.autofillHints,
587
+      contentInsertionConfiguration: widget.contentInsertionConfiguration,
588
+      clipBehavior: widget.clipBehavior,
589
+      restorationId: widget.restorationId,
590
+      scribbleEnabled: widget.scribbleEnabled,
591
+      enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
592
+      contextMenuBuilder: widget.contextMenuBuilder,
593
+      canRequestFocus: widget.canRequestFocus,
594
+      spellCheckConfiguration: widget.spellCheckConfiguration,
595
+      magnifierConfiguration: widget.magnifierConfiguration,
596
+    );
597
+  }
598
+}

+ 295 - 0
lib/widget/password_textformfield.dart

@@ -0,0 +1,295 @@
1
+import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
2
+
3
+import 'package:flutter/gestures.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:flutter/services.dart';
6
+import 'package:lszlgl/widget/password_textfield.dart';
7
+
8
+/// A [FormField] that contains a [PasswordTextField].
9
+/// If you want to see the details of the API, check the [TextFormField].
10
+///
11
+/// It has almost the same API as [TextFormField], so it can be replaced.
12
+class PasswordTextFormField extends FormField<String> {
13
+  /// Creates a [FormField] that contains a [PasswordTextField].
14
+  PasswordTextFormField({
15
+    super.key,
16
+    IconData visibleIcon = Icons.remove_red_eye_outlined,
17
+    IconData inVisibleIcon = Icons.remove_red_eye_outlined,
18
+    bool initialObscurity = true,
19
+    this.controller,
20
+    String? initialValue,
21
+    FocusNode? focusNode,
22
+    InputDecoration? decoration = const InputDecoration(),
23
+    TextInputType? keyboardType,
24
+    TextCapitalization textCapitalization = TextCapitalization.none,
25
+    TextInputAction? textInputAction,
26
+    TextStyle? style,
27
+    StrutStyle? strutStyle,
28
+    TextDirection? textDirection,
29
+    TextAlign textAlign = TextAlign.start,
30
+    TextAlignVertical? textAlignVertical,
31
+    bool autofocus = false,
32
+    bool readOnly = false,
33
+    bool? showCursor,
34
+    String obscuringCharacter = '•',
35
+    bool obscureText = false,
36
+    bool autocorrect = true,
37
+    SmartDashesType? smartDashesType,
38
+    SmartQuotesType? smartQuotesType,
39
+    bool enableSuggestions = true,
40
+    MaxLengthEnforcement? maxLengthEnforcement,
41
+    int? maxLines = 1,
42
+    int? minLines,
43
+    bool expands = false,
44
+    int? maxLength,
45
+    ValueChanged<String>? onChanged,
46
+    GestureTapCallback? onTap,
47
+    TapRegionCallback? onTapOutside,
48
+    VoidCallback? onEditingComplete,
49
+    ValueChanged<String>? onFieldSubmitted,
50
+    super.onSaved,
51
+    super.validator,
52
+    List<TextInputFormatter>? inputFormatters,
53
+    bool? enabled,
54
+    double cursorWidth = 2.0,
55
+    double? cursorHeight,
56
+    Radius? cursorRadius,
57
+    Color? cursorColor,
58
+    Brightness? keyboardAppearance,
59
+    EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
60
+    bool? enableInteractiveSelection,
61
+    TextSelectionControls? selectionControls,
62
+    InputCounterWidgetBuilder? buildCounter,
63
+    ScrollPhysics? scrollPhysics,
64
+    Iterable<String>? autofillHints,
65
+    AutovalidateMode? autovalidateMode,
66
+    ScrollController? scrollController,
67
+    super.restorationId,
68
+    bool enableIMEPersonalizedLearning = true,
69
+    MouseCursor? mouseCursor,
70
+    EditableTextContextMenuBuilder? contextMenuBuilder =
71
+        _defaultContextMenuBuilder,
72
+    SpellCheckConfiguration? spellCheckConfiguration,
73
+    TextMagnifierConfiguration? magnifierConfiguration,
74
+    UndoHistoryController? undoController,
75
+    AppPrivateCommandCallback? onAppPrivateCommand,
76
+    bool? cursorOpacityAnimates,
77
+    ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight,
78
+    ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight,
79
+    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
80
+    ContentInsertionConfiguration? contentInsertionConfiguration,
81
+    Clip clipBehavior = Clip.hardEdge,
82
+    bool scribbleEnabled = true,
83
+    bool canRequestFocus = true,
84
+  }) : super(
85
+    initialValue:
86
+    controller != null ? controller.text : (initialValue ?? ''),
87
+    enabled: enabled ?? decoration?.enabled ?? true,
88
+    autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled,
89
+    builder: (FormFieldState<String> field) {
90
+      final _PasswordTextFormFieldState state =
91
+      field as _PasswordTextFormFieldState;
92
+      final InputDecoration effectiveDecoration = (decoration ??
93
+          const InputDecoration())
94
+          .applyDefaults(Theme.of(field.context).inputDecorationTheme);
95
+      void onChangedHandler(String value) {
96
+        field.didChange(value);
97
+        if (onChanged != null) {
98
+          onChanged(value);
99
+        }
100
+      }
101
+
102
+      return UnmanagedRestorationScope(
103
+        bucket: field.bucket,
104
+        child: PasswordTextField(
105
+          visibleIcon: visibleIcon,
106
+          inVisibleIcon: inVisibleIcon,
107
+          initialObscurity: initialObscurity,
108
+          restorationId: restorationId,
109
+          controller: state._effectiveController,
110
+          focusNode: focusNode,
111
+          decoration:
112
+          effectiveDecoration.copyWith(errorText: field.errorText),
113
+          keyboardType: keyboardType,
114
+          textInputAction: textInputAction,
115
+          style: style,
116
+          strutStyle: strutStyle,
117
+          textAlign: textAlign,
118
+          textAlignVertical: textAlignVertical,
119
+          textDirection: textDirection,
120
+          textCapitalization: textCapitalization,
121
+          autofocus: autofocus,
122
+          readOnly: readOnly,
123
+          showCursor: showCursor,
124
+          obscuringCharacter: obscuringCharacter,
125
+          autocorrect: autocorrect,
126
+          enableSuggestions: enableSuggestions,
127
+          maxLengthEnforcement: maxLengthEnforcement,
128
+          maxLines: maxLines,
129
+          minLines: minLines,
130
+          expands: expands,
131
+          maxLength: maxLength,
132
+          onChanged: onChangedHandler,
133
+          onTap: onTap,
134
+          onTapOutside: onTapOutside,
135
+          onEditingComplete: onEditingComplete,
136
+          onSubmitted: onFieldSubmitted,
137
+          inputFormatters: inputFormatters,
138
+          enabled: enabled ?? decoration?.enabled ?? true,
139
+          cursorWidth: cursorWidth,
140
+          cursorHeight: cursorHeight,
141
+          cursorRadius: cursorRadius,
142
+          cursorColor: cursorColor,
143
+          scrollPadding: scrollPadding,
144
+          scrollPhysics: scrollPhysics,
145
+          keyboardAppearance: keyboardAppearance,
146
+          enableInteractiveSelection:
147
+          enableInteractiveSelection ?? (!obscureText || !readOnly),
148
+          selectionControls: selectionControls,
149
+          buildCounter: buildCounter,
150
+          autofillHints: autofillHints,
151
+          scrollController: scrollController,
152
+          enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
153
+          mouseCursor: mouseCursor,
154
+          contextMenuBuilder: contextMenuBuilder,
155
+          spellCheckConfiguration: spellCheckConfiguration,
156
+          magnifierConfiguration: magnifierConfiguration,
157
+          undoController: undoController,
158
+          onAppPrivateCommand: onAppPrivateCommand,
159
+          cursorOpacityAnimates: cursorOpacityAnimates,
160
+          selectionHeightStyle: selectionHeightStyle,
161
+          selectionWidthStyle: selectionWidthStyle,
162
+          dragStartBehavior: dragStartBehavior,
163
+          contentInsertionConfiguration: contentInsertionConfiguration,
164
+          clipBehavior: clipBehavior,
165
+          scribbleEnabled: scribbleEnabled,
166
+          canRequestFocus: canRequestFocus,
167
+        ),
168
+      );
169
+    },
170
+  );
171
+
172
+  /// Controls the text being edited.
173
+  ///
174
+  /// If null, this widget will create its own [TextEditingController] and
175
+  /// initialize its [TextEditingController.text] with [initialValue].
176
+  final TextEditingController? controller;
177
+
178
+  static Widget _defaultContextMenuBuilder(
179
+      BuildContext context, EditableTextState editableTextState) {
180
+    return AdaptiveTextSelectionToolbar.editableText(
181
+      editableTextState: editableTextState,
182
+    );
183
+  }
184
+
185
+  @override
186
+  FormFieldState<String> createState() => _PasswordTextFormFieldState();
187
+}
188
+
189
+/// see [TextFormField] & [_TextFormFieldState]
190
+class _PasswordTextFormFieldState extends FormFieldState<String> {
191
+  RestorableTextEditingController? _controller;
192
+
193
+  TextEditingController get _effectiveController =>
194
+      _textFormField.controller ?? _controller!.value;
195
+
196
+  PasswordTextFormField get _textFormField =>
197
+      super.widget as PasswordTextFormField;
198
+
199
+  @override
200
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
201
+    super.restoreState(oldBucket, initialRestore);
202
+    if (_controller != null) {
203
+      _registerController();
204
+    }
205
+    // Make sure to update the internal [FormFieldState] value to sync up with
206
+    // text editing controller value.
207
+    setValue(_effectiveController.text);
208
+  }
209
+
210
+  void _registerController() {
211
+    assert(_controller != null);
212
+    registerForRestoration(_controller!, 'controller');
213
+  }
214
+
215
+  void _createLocalController([TextEditingValue? value]) {
216
+    assert(_controller == null);
217
+    _controller = value == null
218
+        ? RestorableTextEditingController()
219
+        : RestorableTextEditingController.fromValue(value);
220
+    if (!restorePending) {
221
+      _registerController();
222
+    }
223
+  }
224
+
225
+  @override
226
+  void initState() {
227
+    super.initState();
228
+    if (_textFormField.controller == null) {
229
+      _createLocalController(widget.initialValue != null
230
+          ? TextEditingValue(text: widget.initialValue!)
231
+          : null);
232
+    } else {
233
+      _textFormField.controller!.addListener(_handleControllerChanged);
234
+    }
235
+  }
236
+
237
+  @override
238
+  void didUpdateWidget(PasswordTextFormField oldWidget) {
239
+    super.didUpdateWidget(oldWidget);
240
+    if (_textFormField.controller != oldWidget.controller) {
241
+      oldWidget.controller?.removeListener(_handleControllerChanged);
242
+      _textFormField.controller?.addListener(_handleControllerChanged);
243
+
244
+      if (oldWidget.controller != null && _textFormField.controller == null) {
245
+        _createLocalController(oldWidget.controller!.value);
246
+      }
247
+
248
+      if (_textFormField.controller != null) {
249
+        setValue(_textFormField.controller!.text);
250
+        if (oldWidget.controller == null) {
251
+          unregisterFromRestoration(_controller!);
252
+          _controller!.dispose();
253
+          _controller = null;
254
+        }
255
+      }
256
+    }
257
+  }
258
+
259
+  @override
260
+  void dispose() {
261
+    _textFormField.controller?.removeListener(_handleControllerChanged);
262
+    _controller?.dispose();
263
+    super.dispose();
264
+  }
265
+
266
+  @override
267
+  void didChange(String? value) {
268
+    super.didChange(value);
269
+
270
+    if (_effectiveController.text != value) {
271
+      _effectiveController.text = value ?? '';
272
+    }
273
+  }
274
+
275
+  @override
276
+  void reset() {
277
+    // setState will be called in the superclass, so even though state is being
278
+    // manipulated, no setState call is needed here.
279
+    _effectiveController.text = widget.initialValue ?? '';
280
+    super.reset();
281
+  }
282
+
283
+  void _handleControllerChanged() {
284
+    // Suppress changes that originated from within this class.
285
+    //
286
+    // In the case where a controller has been passed in to this widget, we
287
+    // register this change listener. In these cases, we'll also receive change
288
+    // notifications for changes originating from within this class -- for
289
+    // example, the reset() method. In such cases, the FormField value will
290
+    // already have been set.
291
+    if (_effectiveController.text != value) {
292
+      didChange(_effectiveController.text);
293
+    }
294
+  }
295
+}

+ 1 - 1
pubspec.yaml

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
16 16
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 17
 # In Windows, build-name is used as the major, minor, and patch parts
18 18
 # of the product and file versions while build-number is used as the build suffix.
19
-version: 0.0.20+20
19
+version: 0.0.21+21
20 20
 
21 21
 environment:
22 22
   sdk: '>=3.1.5 <4.0.0'