Skip to content

Commit 2911b87

Browse files
authored
Pointer interceptor implementations (#5594)
This is essentially the same PR as #5500, with pubspec updates Addresses flutter/flutter#30143 by adding an iOS implementation This PR is Part 2 of #5233
1 parent 15584a3 commit 2911b87

File tree

130 files changed

+4048
-269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+4048
-269
lines changed

packages/pointer_interceptor/pointer_interceptor/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## NEXT
2+
3+
* Adds iOS implementation.
4+
15
## 0.9.3+7
26

37
* Updates metadata to point to new source folder

packages/pointer_interceptor/pointer_interceptor/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
# pointer_interceptor
22

3-
`PointerInterceptor` is a widget that prevents mouse events (in web) from being captured by an underlying [`HtmlElementView`](https://api.flutter.dev/flutter/widgets/HtmlElementView-class.html).
3+
| | iOS | Web |
4+
|-------------|---------|-----|
5+
| **Support** | iOS 11+ | Any |
46

5-
You can use this widget in a cross-platform app freely. In mobile, where the issue that this plugin fixes does not exist, the widget acts as a pass-through of its `children`, without adding anything to the render tree.
7+
`PointerInterceptor` is a widget that prevents mouse events from being captured by an underlying [`HtmlElementView`](https://api.flutter.dev/flutter/widgets/HtmlElementView-class.html) in web, or an underlying [`PlatformView`](https://api.flutter.dev/flutter/widgets/PlatformViewLink-class.html) on iOS.
68

79
## What is the problem?
810

9-
When overlaying Flutter widgets on top of `HtmlElementView` widgets that respond to mouse gestures (handle clicks, for example), the clicks will be consumed by the `HtmlElementView`, and not relayed to Flutter.
11+
When overlaying Flutter widgets on top of `HtmlElementView`/`PlatformView` widgets that respond to mouse gestures (handle clicks, for example), the clicks will be consumed by the `HtmlElementView`/`PlatformView`, and not relayed to Flutter.
1012

11-
The result is that Flutter widget's `onTap` (and other) handlers won't fire as expected, but they'll affect the underlying webview.
13+
The result is that Flutter widget's `onTap` (and other) handlers won't fire as expected, but they'll affect the underlying native platform view.
1214

1315
|The problem...|
1416
|:-:|
1517
|![Depiction of problematic areas](https://raw.githubusercontent.com/flutter/packages/main/packages/pointer_interceptor/doc/img/affected-areas.png)|
1618
|_In the dashed areas, mouse events won't work as expected. The `HtmlElementView` will consume them before Flutter sees them._|
1719

18-
1920
## How does this work?
2021

21-
`PointerInterceptor` creates a platform view consisting of an empty HTML element. The element has the size of its `child` widget, and is inserted in the layer tree _behind_ its child in paint order.
22+
In web, `PointerInterceptor` creates a platform view consisting of an empty HTML element, while on iOS it creates an empty `UIView` instead. The element has the size of its `child` widget, and is inserted in the layer tree _behind_ its child in paint order.
2223

2324
This empty platform view doesn't do anything with mouse events, other than preventing them from reaching other platform views underneath it.
2425

packages/pointer_interceptor/pointer_interceptor/example/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
/build/
3333

3434
# Web related
35-
lib/generated_plugin_registrant.dart
3635

3736
# Symbolication related
3837
app.*.symbols

packages/pointer_interceptor/pointer_interceptor/example/.metadata

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@
44
# This file should be version controlled and should not be manually edited.
55

66
version:
7-
revision: e6bd95bc5caa5e34c5b0285a559673984374b7ea
8-
channel: master
7+
revision: "969911d1d09d6c4f145e9ce27c08093e8c285561"
8+
channel: "main"
99

1010
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 969911d1d09d6c4f145e9ce27c08093e8c285561
17+
base_revision: 969911d1d09d6c4f145e9ce27c08093e8c285561
18+
- platform: ios
19+
create_revision: 969911d1d09d6c4f145e9ce27c08093e8c285561
20+
base_revision: 969911d1d09d6c4f145e9ce27c08093e8c285561
21+
22+
# User provided section
23+
24+
# List of Local paths (relative to this file) that should be
25+
# ignored by the migrate tool.
26+
#
27+
# Files that are not part of the templates will be ignored by default.
28+
unmanaged_files:
29+
- 'lib/main.dart'
30+
- 'ios/Runner.xcodeproj/project.pbxproj'

packages/pointer_interceptor/pointer_interceptor/example/integration_test/widget_test.dart

Lines changed: 4 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -4,176 +4,13 @@
44

55
// ignore_for_file: avoid_print
66

7-
import 'dart:html' as html;
8-
9-
// Imports the Flutter Driver API.
10-
import 'package:flutter/src/widgets/framework.dart';
117
import 'package:flutter_test/flutter_test.dart';
128
import 'package:integration_test/integration_test.dart';
139

14-
import 'package:pointer_interceptor_example/main.dart' as app;
15-
16-
final Finder nonClickableButtonFinder =
17-
find.byKey(const Key('transparent-button'));
18-
final Finder clickableWrappedButtonFinder =
19-
find.byKey(const Key('wrapped-transparent-button'));
20-
final Finder clickableButtonFinder = find.byKey(const Key('clickable-button'));
21-
final Finder backgroundFinder =
22-
find.byKey(const ValueKey<String>('background-widget'));
23-
2410
void main() {
2511
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
26-
27-
group('Without semantics', () {
28-
testWidgets(
29-
'on wrapped elements, the browser does not hit the background-html-view',
30-
(WidgetTester tester) async {
31-
app.main();
32-
await tester.pumpAndSettle();
33-
34-
final html.Element element =
35-
_getHtmlElementAtCenter(clickableButtonFinder, tester);
36-
37-
expect(element.id, isNot('background-html-view'));
38-
}, semanticsEnabled: false);
39-
40-
testWidgets(
41-
'on wrapped elements with intercepting set to false, the browser hits the background-html-view',
42-
(WidgetTester tester) async {
43-
app.main();
44-
await tester.pumpAndSettle();
45-
46-
final html.Element element =
47-
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester);
48-
49-
expect(element.id, 'background-html-view');
50-
}, semanticsEnabled: false);
51-
52-
testWidgets(
53-
'on unwrapped elements, the browser hits the background-html-view',
54-
(WidgetTester tester) async {
55-
app.main();
56-
await tester.pumpAndSettle();
57-
58-
final html.Element element =
59-
_getHtmlElementAtCenter(nonClickableButtonFinder, tester);
60-
61-
expect(element.id, 'background-html-view');
62-
}, semanticsEnabled: false);
63-
64-
testWidgets('on background directly', (WidgetTester tester) async {
65-
app.main();
66-
await tester.pumpAndSettle();
67-
68-
final html.Element element =
69-
_getHtmlElementAt(tester.getTopLeft(backgroundFinder));
70-
71-
expect(element.id, 'background-html-view');
72-
}, semanticsEnabled: false);
73-
});
74-
75-
group('With semantics', () {
76-
testWidgets('finds semantics of wrapped widgets',
77-
(WidgetTester tester) async {
78-
app.main();
79-
await tester.pumpAndSettle();
80-
81-
if (!_newSemanticsAvailable()) {
82-
print('Skipping test: Needs flutter > 2.10');
83-
return;
84-
}
85-
86-
final html.Element element =
87-
_getHtmlElementAtCenter(clickableButtonFinder, tester);
88-
89-
expect(element.tagName.toLowerCase(), 'flt-semantics');
90-
expect(element.getAttribute('aria-label'), 'Works As Expected');
91-
});
92-
93-
testWidgets(
94-
'finds semantics of wrapped widgets with intercepting set to false',
95-
(WidgetTester tester) async {
96-
app.main();
97-
await tester.pumpAndSettle();
98-
99-
if (!_newSemanticsAvailable()) {
100-
print('Skipping test: Needs flutter > 2.10');
101-
return;
102-
}
103-
104-
final html.Element element =
105-
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester);
106-
107-
expect(element.tagName.toLowerCase(), 'flt-semantics');
108-
expect(element.getAttribute('aria-label'),
109-
'Never calls onPressed transparent');
110-
});
111-
112-
testWidgets('finds semantics of unwrapped elements',
113-
(WidgetTester tester) async {
114-
app.main();
115-
await tester.pumpAndSettle();
116-
117-
if (!_newSemanticsAvailable()) {
118-
print('Skipping test: Needs flutter > 2.10');
119-
return;
120-
}
121-
122-
final html.Element element =
123-
_getHtmlElementAtCenter(nonClickableButtonFinder, tester);
124-
125-
expect(element.tagName.toLowerCase(), 'flt-semantics');
126-
expect(element.getAttribute('aria-label'), 'Never calls onPressed');
127-
});
128-
129-
// Notice that, when hit-testing the background platform view, instead of
130-
// finding a semantics node, the platform view itself is found. This is
131-
// because the platform view does not add interactive semantics nodes into
132-
// the framework's semantics tree. Instead, its semantics is determined by
133-
// the HTML content of the platform view itself. Flutter's semantics tree
134-
// simply allows the hit test to land on the platform view by making itself
135-
// hit test transparent.
136-
testWidgets('on background directly', (WidgetTester tester) async {
137-
app.main();
138-
await tester.pumpAndSettle();
139-
140-
final html.Element element =
141-
_getHtmlElementAt(tester.getTopLeft(backgroundFinder));
142-
143-
expect(element.id, 'background-html-view');
144-
});
145-
});
146-
}
147-
148-
// Calls [_getHtmlElementAt] passing it the center of the widget identified by
149-
// the `finder`.
150-
html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) {
151-
final Offset point = tester.getCenter(finder);
152-
return _getHtmlElementAt(point);
153-
}
154-
155-
// Locates the DOM element at the given `point` using `elementFromPoint`.
156-
//
157-
// `elementFromPoint` is an approximate proxy for a hit test, although it's
158-
// sensitive to the presence of shadow roots and browser quirks (not all
159-
// browsers agree on what it should return in all situations). Since this test
160-
// runs only in Chromium, it relies on Chromium's behavior.
161-
html.Element _getHtmlElementAt(Offset point) {
162-
// Probe at the shadow so the browser reports semantics nodes in addition to
163-
// platform view elements. If probed from `html.document` the browser hides
164-
// the contents of <flt-glass-name> as an implementation detail.
165-
final html.ShadowRoot glassPaneShadow =
166-
html.document.querySelector('flt-glass-pane')!.shadowRoot!;
167-
return glassPaneShadow.elementFromPoint(point.dx.toInt(), point.dy.toInt())!;
168-
}
169-
170-
// TODO(dit): Remove this after flutter master (2.13) lands into stable.
171-
// This detects that we can do new semantics assertions by looking at the 'id'
172-
// attribute on flt-semantics elements (it is now set in 2.13 and up).
173-
bool _newSemanticsAvailable() {
174-
final html.ShadowRoot glassPaneShadow =
175-
html.document.querySelector('flt-glass-pane')!.shadowRoot!;
176-
final List<html.Element> elements =
177-
glassPaneShadow.querySelectorAll('flt-semantics[id]');
178-
return elements.isNotEmpty;
12+
// TODO(louisehsu): given the difficulty of making the same integration tests
13+
// work for both web and ios implementations, please find tests in their respective
14+
// platform implementation packages.
15+
testWidgets('placeholder test', (WidgetTester tester) async {});
17916
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
**/dgph
2+
*.mode1v3
3+
*.mode2v3
4+
*.moved-aside
5+
*.pbxuser
6+
*.perspectivev3
7+
**/*sync/
8+
.sconsign.dblite
9+
.tags*
10+
**/.vagrant/
11+
**/DerivedData/
12+
Icon?
13+
**/Pods/
14+
**/.symlinks/
15+
profile
16+
xcuserdata
17+
**/.generated/
18+
Flutter/App.framework
19+
Flutter/Flutter.framework
20+
Flutter/Flutter.podspec
21+
Flutter/Generated.xcconfig
22+
Flutter/ephemeral/
23+
Flutter/app.flx
24+
Flutter/app.zip
25+
Flutter/flutter_assets/
26+
Flutter/flutter_export_environment.sh
27+
ServiceDefinitions.json
28+
Runner/GeneratedPluginRegistrant.*
29+
30+
# Exceptions to above rules.
31+
!default.mode1v3
32+
!default.mode2v3
33+
!default.pbxuser
34+
!default.perspectivev3
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>App</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>io.flutter.flutter.app</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>App</string>
15+
<key>CFBundlePackageType</key>
16+
<string>FMWK</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleSignature</key>
20+
<string>????</string>
21+
<key>CFBundleVersion</key>
22+
<string>1.0</string>
23+
<key>MinimumOSVersion</key>
24+
<string>11.0</string>
25+
</dict>
26+
</plist>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2+
#include "Generated.xcconfig"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2+
#include "Generated.xcconfig"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Uncomment this line to define a global platform for your project
2+
# platform :ios, '11.0'
3+
4+
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5+
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6+
7+
project 'Runner', {
8+
'Debug' => :debug,
9+
'Profile' => :release,
10+
'Release' => :release,
11+
}
12+
13+
def flutter_root
14+
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15+
unless File.exist?(generated_xcode_build_settings_path)
16+
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17+
end
18+
19+
File.foreach(generated_xcode_build_settings_path) do |line|
20+
matches = line.match(/FLUTTER_ROOT\=(.*)/)
21+
return matches[1].strip if matches
22+
end
23+
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24+
end
25+
26+
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27+
28+
flutter_ios_podfile_setup
29+
30+
target 'Runner' do
31+
use_frameworks!
32+
use_modular_headers!
33+
34+
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35+
end
36+
37+
post_install do |installer|
38+
installer.pods_project.targets.each do |target|
39+
flutter_additional_ios_build_settings(target)
40+
end
41+
end

0 commit comments

Comments
 (0)