fix(desktop-pin-code-issue): persist FocusNode so KeyboardListener works on macOS

Previously, every rebuild created a new FocusNode, so KeyboardListener never held focus and missed key events on macOS. This change: - Moves the FocusNode into state and initializes it in initState - Requests focus once after the first frame - Disposes of the FocusNode in dispose - Removes the inline FocusNode creation from build

Blazebrain committed Apr 17, 2025 at 07:51 UTC d9c01a5d07c2ce44064c2e9f2191ab31e93995ad
1 file changed +23 -8
lib/src/screens/pin_code/pin_code_widget.dart
+23 -8
@@ -38,6 +38,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
38 static const fourPinLength = 4;
39 final _gridViewKey = GlobalKey();
40 final _key = GlobalKey<ScaffoldState>();
41 + late final FocusNode _focusNode;
42
43 int pinLength;
44 String pin;
@@ -54,7 +55,17 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
55 pin = '';
56 title = S.current.enter_your_pin;
57 _aspectRatio = 0;
57 - WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
58 + _focusNode = FocusNode();
59 + WidgetsBinding.instance.addPostFrameCallback((_) {
60 + _focusNode.requestFocus();
61 + _afterLayout(_);
62 + });
63 + }
64 +
65 + @override
66 + void dispose() {
67 + _focusNode.dispose();
68 + super.dispose();
69 }
70
71 void setTitle(String title) => setState(() => this.title = title);
@@ -120,8 +131,8 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
131 );
132
133 return KeyboardListener(
123 - focusNode: FocusNode(),
124 - autofocus: true,
134 + focusNode: _focusNode,
135 + autofocus: false,
136 onKeyEvent: (keyEvent) {
137 if (keyEvent is KeyDownEvent) {
138 if (keyEvent.logicalKey.keyLabel == "Backspace") {
@@ -144,8 +155,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
155 style: TextStyle(
156 fontSize: 20,
157 fontWeight: FontWeight.w500,
147 - color:
148 - Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
158 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
159 Spacer(flex: 8),
160 Container(
161 width: 180,
@@ -162,7 +172,9 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
172 shape: BoxShape.circle,
173 color: isFilled
174 ? Theme.of(context).extension<CakeTextTheme>()!.titleColor
165 - : Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor
175 + : Theme.of(context)
176 + .extension<PinCodeTheme>()!
177 + .indicatorsColor
178 .withOpacity(0.25),
179 ));
180 }),
@@ -225,7 +237,8 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
237 child: TextButton(
238 onPressed: () => _pop(),
239 style: TextButton.styleFrom(
228 - backgroundColor: Theme.of(context).colorScheme.background,
240 + backgroundColor:
241 + Theme.of(context).colorScheme.background,
242 shape: CircleBorder(),
243 ),
244 child: deleteIconImage,
@@ -250,7 +263,9 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
263 style: TextStyle(
264 fontSize: 25.0,
265 fontWeight: FontWeight.w600,
253 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
266 + color: Theme.of(context)
267 + .extension<CakeTextTheme>()!
268 + .titleColor)),
269 ),
270 );
271 }),