adar2378 / pin_code_fields

A flutter package which will help you to generate pin code fields with beautiful design and animations. Can be useful for OTP or pin code inputs 🤓🤓
https://pub.dev/packages/pin_code_fields
MIT License
690 stars 336 forks source link

Text field has accessibility issues #312

Closed ajanec01 closed 1 year ago

ajanec01 commented 1 year ago

I've run into a couple of issues when using the assistive tech (VoiceOver, Voice Control, Voice Access, and Talkback). In essence they do not recognise the visually hidden text field because of AbsorbPointer; the text field does not receive events so assistive technologies decide not to expose it, or expose it without saying it's a text field, what it's value is and all that other stuff.

It's difficult to make this work outside of the component and it certainly is better to work with the native text form field as it comes with all of the necessary accessibility stuff built in.

I've done a few tweaks that made it work:

  1. Removed AbsorbPointer (all seemed to have worked fine but might be missing out on something as I'm relatively new to Flutter),
  2. I wrapped the GestureDetector widget that contains the Row.children = _generateFields() inside ExcludeSemantics so that it is not exposed as an empty thing to VoiceOver users.
  3. Specified final String? semanticLabel for PinCodeTextField (to allow passing in semantic labels for the text field),
  4. Set decoration.labelText: widget.semanticLabel on the visually hidden TextFormField so that the text field gets labelled.

Can you please look into it as a matter of priority? This would be much appreciated!!!

build method after modifications:

  @override
  Widget build(BuildContext context) {
    Directionality textField = Directionality(
      textDirection: widget.errorTextDirection,
      child: Padding(
        padding: widget.errorTextMargin,
        child: TextFormField(
          textInputAction: widget.textInputAction,
          controller: _textEditingController,
          focusNode: _focusNode,
          enabled: widget.enabled,
          autofillHints: widget.enablePinAutofill && widget.enabled
              ? <String>[AutofillHints.oneTimeCode]
              : null,
          autofocus: widget.autoFocus,
          autocorrect: false,
          keyboardType: widget.keyboardType,
          keyboardAppearance: widget.keyboardAppearance,
          textCapitalization: widget.textCapitalization,
          validator: widget.validator,
          onSaved: widget.onSaved,
          autovalidateMode: widget.autovalidateMode,
          inputFormatters: [
            ...widget.inputFormatters,
            LengthLimitingTextInputFormatter(
              widget.length,
            ), // this limits the input length
          ],
          // trigger on the complete event handler from the keyboard
          onFieldSubmitted: widget.onSubmitted,
          onEditingComplete: widget.onEditingComplete,
          enableInteractiveSelection: false,
          showCursor: false,
          // using same as background color so tha it can blend into the view
          cursorWidth: 0.01,
          decoration: InputDecoration(
            contentPadding: const EdgeInsets.all(0),
            border: InputBorder.none,
            fillColor: widget.backgroundColor,
            enabledBorder: InputBorder.none,
            focusedBorder: InputBorder.none,
            disabledBorder: InputBorder.none,
            labelText: widget.semanticLabel,
          ),
          style: TextStyle(
            color: Colors.transparent,
            height: .01,
            fontSize: kIsWeb
                ? 1
                : 0.01, // it is a hidden textfield which should remain transparent and extremely small
          ),
          scrollPadding: widget.scrollPadding,
          readOnly: widget.readOnly,
          obscureText: widget.obscureText,
        ),
      ),
    );

    return SlideTransition(
      position: _offsetAnimation,
      child: Container(
        // adding the extra space at the bottom to show the error text from validator
        height: (widget.autovalidateMode == AutovalidateMode.disabled &&
                widget.validator == null)
            ? widget.pinTheme.fieldHeight
            : widget.pinTheme.fieldHeight + widget.errorTextSpace,
        color: widget.backgroundColor,
        child: Stack(
          alignment: Alignment.bottomCenter,
          children: <Widget>[
            widget.useExternalAutoFillGroup
                ? textField
                : AutofillGroup(
                    onDisposeAction: widget.onAutoFillDisposeAction,
                    child: textField,
                  ),
            Positioned(
              top: 0,
              left: 0,
              right: 0,
              child: ExcludeSemantics(
                child: GestureDetector(
                  onTap: () {
                    if (widget.onTap != null) widget.onTap!();
                    _onFocus();
                  },
                  onLongPress: widget.enabled
                      ? () async {
                          var data = await Clipboard.getData("text/plain");
                          if (data?.text?.isNotEmpty ?? false) {
                            if (widget.beforeTextPaste != null) {
                              if (widget.beforeTextPaste!(data!.text)) {
                                _showPasteDialog(data.text!);
                              }
                            } else {
                              _showPasteDialog(data!.text!);
                            }
                          }
                        }
                      : null,
                  child: Row(
                    mainAxisAlignment: widget.mainAxisAlignment,
                    children: _generateFields(),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.