blob: c40a4564ba58a02be7a709964d384547f1983a78 [file] [log] [blame]
// 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.
// ignore_for_file: invalid_use_of_internal_member
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/_window.dart';
import 'package:flutter_driver/driver_extension.dart';
class _MainRegularWindowControllerDelegate
extends RegularWindowControllerDelegate {
@override
void onWindowDestroyed() {
super.onWindowDestroyed();
exit(0);
}
}
late final RegularWindowController controller;
final ValueNotifier<DialogWindowController?> dialogController = ValueNotifier(
null,
);
void main() {
final Completer<void> windowCreated = Completer();
enableFlutterDriverExtension(
handler: (String? message) async {
await windowCreated.future;
if (message == null) {
return '';
}
final jsonMap = jsonDecode(message);
if (!jsonMap.containsKey('type')) {
throw ArgumentError('Message must contain a "type" field.');
}
/// This helper method registers a listener on the controller,
/// calls [act] to perform some action on the controller, waits for
/// the [predicate] to be satisified, and finally cleans up the listener.
Future<void> awaitNotification(
VoidCallback act,
bool Function() predicate,
) async {
final StreamController<bool> streamController = StreamController();
void notificationHandler() {
streamController.add(true);
}
controller.addListener(notificationHandler);
act();
await for (final _ in streamController.stream) {
if (predicate()) {
break;
}
}
controller.removeListener(notificationHandler);
}
if (jsonMap['type'] == 'ping') {
return jsonEncode({'type': 'pong'});
} else if (jsonMap['type'] == 'get_size') {
return jsonEncode({
'width': controller.contentSize.width,
'height': controller.contentSize.height,
});
} else if (jsonMap['type'] == 'set_size') {
final Size size = Size(
jsonMap['width'].toDouble(),
jsonMap['height'].toDouble(),
);
await awaitNotification(() {
controller.setSize(size);
}, () => controller.contentSize == size);
} else if (jsonMap['type'] == 'set_constraints') {
final BoxConstraints constraints = BoxConstraints(
minWidth: jsonMap['min_width'].toDouble(),
minHeight: jsonMap['min_height'].toDouble(),
maxWidth: jsonMap['max_width'].toDouble(),
maxHeight: jsonMap['max_height'].toDouble(),
);
// We assume that this will cause a resize, which the current tests do.
final initialSize = controller.contentSize;
await awaitNotification(() {
controller.setConstraints(constraints);
}, () => controller.contentSize != initialSize);
} else if (jsonMap['type'] == 'set_fullscreen') {
await awaitNotification(() {
controller.setFullscreen(true);
}, () => controller.isFullscreen);
} else if (jsonMap['type'] == 'unset_fullscreen') {
await awaitNotification(() {
controller.setFullscreen(false);
}, () => !controller.isFullscreen);
} else if (jsonMap['type'] == 'get_fullscreen') {
return jsonEncode({'isFullscreen': controller.isFullscreen});
} else if (jsonMap['type'] == 'set_maximized') {
await awaitNotification(() {
controller.setMaximized(true);
}, () => controller.isMaximized);
} else if (jsonMap['type'] == 'unset_maximized') {
await awaitNotification(() {
controller.setMaximized(false);
}, () => !controller.isMaximized);
} else if (jsonMap['type'] == 'get_maximized') {
return jsonEncode({'isMaximized': controller.isMaximized});
} else if (jsonMap['type'] == 'set_minimized') {
await awaitNotification(() {
controller.setMinimized(true);
}, () => controller.isMinimized);
} else if (jsonMap['type'] == 'unset_minimized') {
await awaitNotification(() {
controller.setMinimized(false);
}, () => !controller.isMinimized);
} else if (jsonMap['type'] == 'get_minimized') {
return jsonEncode({'isMinimized': controller.isMinimized});
} else if (jsonMap['type'] == 'set_title') {
final String title = jsonMap['title'];
await awaitNotification(() {
controller.setTitle(title);
}, () => controller.title == title);
} else if (jsonMap['type'] == 'get_title') {
return jsonEncode({'title': controller.title});
} else if (jsonMap['type'] == 'set_activated') {
await awaitNotification(() {
controller.activate();
}, () => controller.isActivated);
} else if (jsonMap['type'] == 'get_activated') {
return jsonEncode({'isActivated': controller.isActivated});
} else if (jsonMap['type'] == 'open_dialog') {
if (dialogController.value != null) {
return jsonEncode({'result': false});
}
dialogController.value = DialogWindowController(
preferredSize: const Size(200, 200),
parent: controller,
delegate: MyDialogWindowControllerDelegate(
onDestroyed: () {
dialogController.value = null;
},
),
);
return jsonEncode({'result': true});
} else if (jsonMap['type'] == 'close_dialog') {
dialogController.value?.destroy();
return jsonEncode({'result': true});
} else {
throw ArgumentError('Unknown message type: ${jsonMap['type']}');
}
return '';
},
);
controller = RegularWindowController(
preferredSize: Size(640, 480),
title: 'Integration Test',
delegate: _MainRegularWindowControllerDelegate(),
);
windowCreated.complete();
runWidget(RegularWindow(controller: controller, child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class MyDialogWindowControllerDelegate extends DialogWindowControllerDelegate {
MyDialogWindowControllerDelegate({required this.onDestroyed});
final VoidCallback onDestroyed;
@override
void onWindowDestroyed() {
onDestroyed();
super.onWindowDestroyed();
}
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: dialogController,
builder:
(
BuildContext context,
DialogWindowController? dialogController,
Widget? child,
) {
return ViewAnchor(
view: dialogController != null
? DialogWindow(
controller: dialogController,
child: MyDialogPage(controller: dialogController),
)
: null,
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[const Text('This is the main window.')],
),
),
),
);
},
);
}
}
class MyDialogPage extends StatelessWidget {
const MyDialogPage({super.key, required this.controller});
final DialogWindowController controller;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text('Dialog'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('This is a dialog window.'),
ElevatedButton(
key: const ValueKey<String>('close_dialog'),
onPressed: () {
controller.destroy();
},
child: Text('Close Dialog'),
),
],
),
),
),
);
}
}