Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved support for VoiceOver/TalkBack #417

Merged
merged 1 commit into from Sep 2, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
133 changes: 81 additions & 52 deletions lib/src/flutter_typeahead.dart
Expand Up @@ -263,6 +263,7 @@ class TypeAheadFormField<T> extends FormField<String> {
ErrorBuilder? errorBuilder,
WidgetBuilder? noItemsFoundBuilder,
WidgetBuilder? loadingBuilder,
void Function(bool)? onSuggestionsBoxToggle,
Duration debounceDuration: const Duration(milliseconds: 300),
SuggestionsBoxDecoration suggestionsBoxDecoration:
const SuggestionsBoxDecoration(),
Expand Down Expand Up @@ -321,6 +322,7 @@ class TypeAheadFormField<T> extends FormField<String> {
),
suggestionsBoxVerticalOffset: suggestionsBoxVerticalOffset,
onSuggestionSelected: onSuggestionSelected,
onSuggestionsBoxToggle: onSuggestionsBoxToggle,
itemBuilder: itemBuilder,
suggestionsCallback: suggestionsCallback,
animationStart: animationStart,
Expand Down Expand Up @@ -676,36 +678,40 @@ class TypeAheadField<T> extends StatefulWidget {
/// Defaults to 0.
final int minCharsForSuggestions;

// Adds a callback for the suggestion box opening or closing
final void Function(bool)? onSuggestionsBoxToggle;

/// Creates a [TypeAheadField]
TypeAheadField(
{Key? key,
required this.suggestionsCallback,
required this.itemBuilder,
required this.onSuggestionSelected,
this.textFieldConfiguration: const TextFieldConfiguration(),
this.suggestionsBoxDecoration: const SuggestionsBoxDecoration(),
this.debounceDuration: const Duration(milliseconds: 300),
this.suggestionsBoxController,
this.scrollController,
this.loadingBuilder,
this.noItemsFoundBuilder,
this.errorBuilder,
this.transitionBuilder,
this.animationStart: 0.25,
this.animationDuration: const Duration(milliseconds: 500),
this.getImmediateSuggestions: false,
this.suggestionsBoxVerticalOffset: 5.0,
this.direction: AxisDirection.down,
this.hideOnLoading: false,
this.hideOnEmpty: false,
this.hideOnError: false,
this.hideSuggestionsOnKeyboardHide: true,
this.keepSuggestionsOnLoading: true,
this.keepSuggestionsOnSuggestionSelected: false,
this.autoFlipDirection: false,
this.hideKeyboard: false,
this.minCharsForSuggestions: 0})
: assert(animationStart >= 0.0 && animationStart <= 1.0),
TypeAheadField({
Key? key,
required this.suggestionsCallback,
required this.itemBuilder,
required this.onSuggestionSelected,
this.textFieldConfiguration: const TextFieldConfiguration(),
this.suggestionsBoxDecoration: const SuggestionsBoxDecoration(),
this.debounceDuration: const Duration(milliseconds: 300),
this.suggestionsBoxController,
this.scrollController,
this.loadingBuilder,
this.noItemsFoundBuilder,
this.errorBuilder,
this.transitionBuilder,
this.animationStart: 0.25,
this.animationDuration: const Duration(milliseconds: 500),
this.getImmediateSuggestions: false,
this.suggestionsBoxVerticalOffset: 5.0,
this.direction: AxisDirection.down,
this.hideOnLoading: false,
this.hideOnEmpty: false,
this.hideOnError: false,
this.hideSuggestionsOnKeyboardHide: true,
this.keepSuggestionsOnLoading: true,
this.keepSuggestionsOnSuggestionSelected: false,
this.autoFlipDirection: false,
this.hideKeyboard: false,
this.minCharsForSuggestions: 0,
this.onSuggestionsBoxToggle,
}) : assert(animationStart >= 0.0 && animationStart <= 1.0),
assert(
direction == AxisDirection.down || direction == AxisDirection.up),
assert(minCharsForSuggestions >= 0),
Expand Down Expand Up @@ -784,10 +790,12 @@ class _TypeAheadFieldState<T> extends State<TypeAheadField<T>>
if (_effectiveFocusNode!.hasFocus) {
this._suggestionsBox!.open();
} else {
if (widget.hideSuggestionsOnKeyboardHide){
this._suggestionsBox!.close();
}
if (widget.hideSuggestionsOnKeyboardHide){
this._suggestionsBox!.close();
}
}

widget.onSuggestionsBoxToggle?.call(this._suggestionsBox!.isOpened);
};

this._effectiveFocusNode!.addListener(_focusNodeListener);
Expand Down Expand Up @@ -893,26 +901,45 @@ class _TypeAheadFieldState<T> extends State<TypeAheadField<T>>
}
}

return Positioned(
width: w,
child: CompositedTransformFollower(
link: this._layerLink,
showWhenUnlinked: false,
offset: Offset(
widget.suggestionsBoxDecoration.offsetX,
_suggestionsBox!.direction == AxisDirection.down
? _suggestionsBox!.textBoxHeight +
widget.suggestionsBoxVerticalOffset
: _suggestionsBox!.directionUpOffset),
child: _suggestionsBox!.direction == AxisDirection.down
? suggestionsList
: FractionalTranslation(
translation:
Offset(0.0, -1.0), // visually flips list to go up
child: suggestionsList,
),
),
final Widget compositedFollower = CompositedTransformFollower(
link: this._layerLink,
showWhenUnlinked: false,
offset: Offset(
widget.suggestionsBoxDecoration.offsetX,
_suggestionsBox!.direction == AxisDirection.down
? _suggestionsBox!.textBoxHeight +
widget.suggestionsBoxVerticalOffset
: _suggestionsBox!.directionUpOffset),
child: _suggestionsBox!.direction == AxisDirection.down
? suggestionsList
: FractionalTranslation(
translation: Offset(0.0, -1.0), // visually flips list to go up
child: suggestionsList,
),
);

// When wrapped in the Positioned widget, the suggestions box widget
// is placed before the Scaffold semantically. In order to have the
// suggestions box navigable from the search input or keyboard,
// Semantics > Align > ConstrainedBox are needed. This does not change
// the style visually. However, when VO/TB are not enabled it is
// necessary to use the Positioned widget to allow the elements to be
// properly tappable.
return MediaQuery.of(context).accessibleNavigation
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause appium to be unable to select item

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? What exactly is the issue? To a human this appears to look decent and okay ...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need more time to pinpoint the root cause, but for now it looks like Align is causing the problem.

? Semantics(
container: true,
child: Align(
alignment: Alignment.topLeft,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: w),
child: compositedFollower,
),
),
)
: Positioned(
width: w,
child: compositedFollower,
);
});
}

Expand All @@ -935,7 +962,8 @@ class _TypeAheadFieldState<T> extends State<TypeAheadField<T>>
textAlignVertical: widget.textFieldConfiguration.textAlignVertical,
minLines: widget.textFieldConfiguration.minLines,
maxLength: widget.textFieldConfiguration.maxLength,
maxLengthEnforcement: widget.textFieldConfiguration.maxLengthEnforcement,
maxLengthEnforcement:
widget.textFieldConfiguration.maxLengthEnforcement,
obscureText: widget.textFieldConfiguration.obscureText,
onChanged: widget.textFieldConfiguration.onChanged,
onSubmitted: widget.textFieldConfiguration.onSubmitted,
Expand Down Expand Up @@ -1273,6 +1301,7 @@ class _SuggestionsListState<T> extends State<_SuggestionsList<T>>
reverse: widget.suggestionsBox!.direction == AxisDirection.down
? false
: true, // reverses the list to start at the bottom
semanticChildCount: this._suggestions?.length ?? 0,
children: this._suggestions!.map((T suggestion) {
return InkWell(
child: widget.itemBuilder!(context, suggestion),
Expand Down