flutter/engine/src/flutter/testing/dart/semantics_test.dart
LongCatIsLooong 8a1d4c0a08 [iOS] Full keyboard access scrolling (flutter/engine#56606)
This PR adds basic FKA scrolling support: when the iOS focus (the focus state is maintained separately from the framework focus, see the previous PR) switches to an item in a scrollable container that is too close to the edge of the viewport, the container will scroll to make sure the next item is visible. 

Previous PR for context: https://github.com/flutter/engine/pull/55964

https://github.com/user-attachments/assets/84ae5153-f955-4d23-9901-ce942c0e98ac

### Why the UIScrollView subclass in the focus hierarchy

The iOS focus system does not provide an API that allows apps to notify it of focus highlight changes. So if we were to keep using the transforms sent by the framework as-is and not introducing any UIViews in the focus hierarchy, the focus highlight will be positioned at the wrong location after scrolling (via FKA or via framework). That does not seem to be part of the public API and the focus system seems to only know how to properly highlight focusable UIViews.

### Things that currently may not work

1. Nested scroll views (have not tried to verify) 

The `UIScrollView`s are always subviews of the `FlutterView`. If there are nested scrollables the focus system may not be able to properly determine the focus hierarchy (in theory the iOS focus system should never depend on `UIView.parentView` but I haven't tried to verify that).

2. If the next item is too far below the bottom of the screen and there is a tab bar with focusable items, the focus will be transferred to tab bar instead of the next item in the list

Video demo (as you can see the scrolling is really finicky):

https://github.com/user-attachments/assets/51c2bfe4-d7b3-4614-aa49-4256214f8978

I've tried doing the same thing using a `UITableView` with similar configurations but it seems to have the same problem. I'll try to dig a bit deeper into this and see if there's a workaround.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
2024-11-25 21:05:18 +00:00

43 lines
1.8 KiB
Dart

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui';
import 'package:test/test.dart';
// The body of this file is the same as ../../lib/web_ui/test/engine/semantics/semantics_api_test.dart
// Please keep them in sync.
void main() {
// This must match the number of flags in lib/ui/semantics.dart
const int numSemanticsFlags = 29;
test('SemanticsFlag.values refers to all flags.', () async {
expect(SemanticsFlag.values.length, equals(numSemanticsFlags));
for (int index = 0; index < numSemanticsFlags; ++index) {
final int flag = 1 << index;
expect(SemanticsFlag.fromIndex(flag), isNotNull);
expect(SemanticsFlag.fromIndex(flag).toString(), startsWith('SemanticsFlag.'));
}
});
// This must match the number of actions in lib/ui/semantics.dart
const int numSemanticsActions = 24;
test('SemanticsAction.values refers to all actions.', () async {
expect(SemanticsAction.values.length, equals(numSemanticsActions));
for (int index = 0; index < numSemanticsActions; ++index) {
final int flag = 1 << index;
expect(SemanticsAction.fromIndex(flag), isNotNull);
expect(SemanticsAction.fromIndex(flag).toString(), startsWith('SemanticsAction.'));
}
});
test('SpellOutStringAttribute.toString', () async {
expect(SpellOutStringAttribute(range: const TextRange(start: 2, end: 5)).toString(), 'SpellOutStringAttribute(TextRange(start: 2, end: 5))');
});
test('LocaleStringAttribute.toString', () async {
expect(LocaleStringAttribute(range: const TextRange(start: 2, end: 5), locale: const Locale('test')).toString(), 'LocaleStringAttribute(TextRange(start: 2, end: 5), test)');
});
}