// Copyright 2014 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:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'android_platform_view.dart'; import 'future_data_handler.dart'; import 'page.dart'; class NestedViewEventPage extends PageWidget { const NestedViewEventPage({super.key}) : super('Nested View Event Tests', const ValueKey('NestedViewEventTile')); @override Widget build(BuildContext context) => const NestedViewEventBody(); } class NestedViewEventBody extends StatefulWidget { const NestedViewEventBody({super.key}); @override State createState() => NestedViewEventBodyState(); } enum _LastTestStatus { pending, success, error } class NestedViewEventBodyState extends State { MethodChannel? viewChannel; _LastTestStatus _lastTestStatus = _LastTestStatus.pending; String? lastError; int? id; int nestedViewClickCount = 0; bool showPlatformView = true; bool useHybridComposition = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Nested view event')), body: Column( children: [ SizedBox( height: 300, child: Stack( alignment: Alignment.topCenter, children: [ if (showPlatformView) AndroidPlatformView( key: const ValueKey('PlatformView'), viewType: 'simple_view', onPlatformViewCreated: onPlatformViewCreated, useHybridComposition: useHybridComposition, ), // The overlapping widget stabilizes the view tree by ensuring // that there is widget content on top of the platform view. // Without this widget, we rely on the UI elements down below // to "incidentally" draw on top of the PlatformView which // is not a reliable behavior as we eliminate non-visible // rendering operations throughout the framework and engine. const Positioned( top: 50, child: Text('overlapping widget', style: TextStyle(color: Colors.yellow)), ), ], ), ), if (_lastTestStatus != _LastTestStatus.pending) _statusWidget(), if (viewChannel != null) ...[ Row( children: [ Expanded( child: ElevatedButton( key: const ValueKey('ShowAlertDialog'), onPressed: onShowAlertDialogPressed, child: const Text('SHOW ALERT DIALOG'), ), ), Expanded( child: ElevatedButton( key: const ValueKey('TogglePlatformView'), onPressed: onTogglePlatformView, child: const Text('TOGGLE PLATFORM VIEW'), ), ), ], ), Row( children: [ Expanded( child: ElevatedButton( key: const ValueKey('ToggleHybridComposition'), child: const Text('TOGGLE HC'), onPressed: () { setState(() { useHybridComposition = !useHybridComposition; }); }, ), ), Expanded( child: ElevatedButton( key: const ValueKey('AddChildView'), onPressed: onChildViewPressed, child: const Text('ADD CHILD VIEW'), ), ), Expanded( child: ElevatedButton( key: const ValueKey('TapChildView'), onPressed: onTapChildViewPressed, child: const Text('TAP CHILD VIEW'), ), ), ], ), Text( 'Click count: $nestedViewClickCount', key: const ValueKey('NestedViewClickCount'), ), ], ], ), ); } Widget _statusWidget() { assert(_lastTestStatus != _LastTestStatus.pending); final String message = _lastTestStatus == _LastTestStatus.success ? 'Success' : lastError!; return ColoredBox( color: _lastTestStatus == _LastTestStatus.success ? Colors.green : Colors.red, child: Text( message, key: const ValueKey('Status'), style: TextStyle(color: _lastTestStatus == _LastTestStatus.error ? Colors.yellow : null), ), ); } Future onShowAlertDialogPressed() async { if (_lastTestStatus != _LastTestStatus.pending) { setState(() { _lastTestStatus = _LastTestStatus.pending; }); } try { await viewChannel!.invokeMethod('showAndHideAlertDialog'); setState(() { _lastTestStatus = _LastTestStatus.success; }); } catch (e) { setState(() { _lastTestStatus = _LastTestStatus.error; lastError = '$e'; }); } } Future onTogglePlatformView() async { setState(() { showPlatformView = !showPlatformView; }); } Future onChildViewPressed() async { try { await viewChannel!.invokeMethod('addChildViewAndWaitForClick'); setState(() { nestedViewClickCount++; }); } catch (e) { setState(() { _lastTestStatus = _LastTestStatus.error; lastError = '$e'; }); } } Future onTapChildViewPressed() async { await Future.delayed(const Duration(seconds: 1)); // Dispatch a tap event on the child view inside the platform view. // // Android mutates `MotionEvent` instances, so in this case *do not* dispatch // new instances as it won't cover the `MotionEventTracker` class in the embedding // which tracks events. // // See the issue this prevents: https://github.com/flutter/flutter/issues/61169 await Process.run('input', const ['tap', '250', '550']); } void onPlatformViewCreated(int id) { this.id = id; setState(() { viewChannel = MethodChannel('simple_view/$id'); }); driverDataHandler .registerHandler('hierarchy') .complete(() async => (await channel.invokeMethod('getViewHierarchy'))!); } }