Skip to content

Commit db66141

Browse files
committed
feature: implemented modifyRegular + fixed a bug where a widget tree was mandatory on accident
1 parent 9bd57f3 commit db66141

File tree

2 files changed

+179
-40
lines changed

2 files changed

+179
-40
lines changed

packages/flutter/lib/src/widgets/window.dart

+31-19
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,22 @@ abstract class WindowController with ChangeNotifier {
4242
_size = metadata.size;
4343
notifyListeners();
4444

45-
SchedulerBinding.instance.addPostFrameCallback((_) async {
46-
_listener = _WindowListener(
47-
viewId: metadata.view.viewId,
48-
onChanged: (_WindowChangeProperties properties) {
49-
if (properties.size != null) {
50-
_size = properties.size!;
51-
notifyListeners();
52-
}
53-
},
54-
onDestroyed: () {
55-
_view = null;
56-
_isPendingDestroy = false;
45+
_listener = _WindowListener(
46+
viewId: metadata.view.viewId,
47+
onChanged: (_WindowChangeProperties properties) {
48+
if (properties.size != null) {
49+
_size = properties.size!;
5750
notifyListeners();
58-
onDestroyed?.call();
59-
},
60-
);
61-
_WindowingAppGlobalData.instance._listen(_listener);
62-
});
51+
}
52+
},
53+
onDestroyed: () {
54+
_view = null;
55+
_isPendingDestroy = false;
56+
notifyListeners();
57+
onDestroyed?.call();
58+
},
59+
);
60+
_WindowingAppGlobalData.instance._listen(_listener);
6361
})
6462
.catchError((Object? error) {
6563
onError?.call(error.toString());
@@ -138,8 +136,12 @@ class RegularWindowController extends WindowController {
138136
WindowArchetype get type => WindowArchetype.regular;
139137

140138
/// Modify the properties of the window.
141-
Future<void> modify({Size? size}) {
142-
throw UnimplementedError();
139+
/// [size] the new size of the window
140+
/// [title] the new title of the window
141+
/// [state] the new state of the window
142+
Future<void> modify({Size? size, String? title, WindowState? state}) {
143+
assert(isReady, 'Window is not ready');
144+
return _modifyRegular(viewId: view.viewId, size: size, title: title, state: state);
143145
}
144146
}
145147

@@ -266,6 +268,16 @@ Future<_WindowCreationResult> _createRegular({
266268
);
267269
}
268270

271+
Future<void> _modifyRegular({required int viewId, Size? size, String? title, WindowState? state}) {
272+
assert(size != null || title != null || state != null);
273+
return SystemChannels.windowing.invokeMethod('modifyRegular', <String, dynamic>{
274+
'viewId': viewId,
275+
'size': size != null ? <double>[size.width, size.height] : null,
276+
'title': title,
277+
'state': state?.toString(),
278+
});
279+
}
280+
269281
Future<_WindowCreationResult> _createWindow({
270282
required WindowArchetype archetype,
271283
required Future<Map<Object?, Object?>> Function(MethodChannel channel) viewBuilder,

packages/flutter/test/widgets/window_test.dart

+148-21
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import 'package:flutter/services.dart';
66
import 'package:flutter/widgets.dart';
77
import 'package:flutter_test/flutter_test.dart';
88

9-
Future<Object?>? Function(MethodCall)? _createWindowMethodCallHandler(WidgetTester tester) {
9+
import '../rendering/proxy_box_test.dart';
10+
11+
Future<Object?>? Function(MethodCall)? _createWindowMethodCallHandler({
12+
required WidgetTester tester,
13+
void Function(MethodCall)? onMethodCall,
14+
}) {
1015
return (MethodCall call) async {
16+
onMethodCall?.call(call);
1117
final Map<Object?, Object?> args = call.arguments as Map<Object?, Object?>;
1218
if (call.method == 'createWindow') {
1319
final List<Object?> size = args['size']! as List<Object?>;
1420
final String state = args['state'] as String? ?? WindowState.restored.toString();
1521

1622
return <String, Object?>{'viewId': tester.view.viewId, 'size': size, 'state': state};
23+
} else if (call.method == 'modifyRegular') {
24+
return null;
1725
} else if (call.method == 'destroyWindow') {
1826
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
1927
SystemChannels.windowing.name,
@@ -38,18 +46,10 @@ void main() {
3846

3947
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
4048
SystemChannels.windowing,
41-
_createWindowMethodCallHandler(tester),
49+
_createWindowMethodCallHandler(tester: tester),
4250
);
4351

4452
final RegularWindowController controller = RegularWindowController(size: windowSize);
45-
await tester.pumpWidget(
46-
wrapWithView: false,
47-
Builder(
48-
builder: (BuildContext context) {
49-
return RegularWindow(controller: controller, child: Container());
50-
},
51-
),
52-
);
5353

5454
await tester.pump();
5555

@@ -89,7 +89,7 @@ void main() {
8989

9090
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
9191
SystemChannels.windowing,
92-
_createWindowMethodCallHandler(tester),
92+
_createWindowMethodCallHandler(tester: tester),
9393
);
9494

9595
bool destroyed = false;
@@ -123,19 +123,10 @@ void main() {
123123

124124
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
125125
SystemChannels.windowing,
126-
_createWindowMethodCallHandler(tester),
126+
_createWindowMethodCallHandler(tester: tester),
127127
);
128128

129129
final RegularWindowController controller = RegularWindowController(size: initialSize);
130-
await tester.pumpWidget(
131-
wrapWithView: false,
132-
Builder(
133-
builder: (BuildContext context) {
134-
return RegularWindow(controller: controller, child: Container());
135-
},
136-
),
137-
);
138-
139130
await tester.pump();
140131

141132
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
@@ -153,4 +144,140 @@ void main() {
153144
expect(controller.size, newSize);
154145
},
155146
);
147+
148+
testWidgets('RegularWindowController.modify can be called when provided with a "size" argument', (
149+
WidgetTester tester,
150+
) async {
151+
const Size initialSize = Size(800, 600);
152+
const Size newSize = Size(400, 300);
153+
154+
bool wasCalled = false;
155+
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
156+
SystemChannels.windowing,
157+
_createWindowMethodCallHandler(
158+
tester: tester,
159+
onMethodCall: (MethodCall call) {
160+
if (call.method != 'modifyRegular') {
161+
return;
162+
}
163+
164+
final Map<Object?, Object?> args = call.arguments as Map<Object?, Object?>;
165+
final int viewId = args['viewId']! as int;
166+
final List<Object?>? size = args['size'] as List<Object?>?;
167+
final String? title = args['title'] as String?;
168+
final String? state = args['state'] as String?;
169+
expect(viewId, tester.view.viewId);
170+
expect(size, <double>[newSize.width, newSize.height]);
171+
expect(title, null);
172+
expect(state, null);
173+
wasCalled = true;
174+
},
175+
),
176+
);
177+
178+
final RegularWindowController controller = RegularWindowController(size: initialSize);
179+
await tester.pump();
180+
181+
await controller.modify(size: newSize);
182+
await tester.pump();
183+
184+
expect(wasCalled, true);
185+
});
186+
187+
testWidgets(
188+
'RegularWindowController.modify can be called when provided with a "title" argument',
189+
(WidgetTester tester) async {
190+
const Size initialSize = Size(800, 600);
191+
const String newTitle = 'New Title';
192+
193+
bool wasCalled = false;
194+
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
195+
SystemChannels.windowing,
196+
_createWindowMethodCallHandler(
197+
tester: tester,
198+
onMethodCall: (MethodCall call) {
199+
if (call.method != 'modifyRegular') {
200+
return;
201+
}
202+
203+
final Map<Object?, Object?> args = call.arguments as Map<Object?, Object?>;
204+
final int viewId = args['viewId']! as int;
205+
final List<Object?>? size = args['size'] as List<Object?>?;
206+
final String? title = args['title'] as String?;
207+
final String? state = args['state'] as String?;
208+
expect(viewId, tester.view.viewId);
209+
expect(size, null);
210+
expect(title, newTitle);
211+
expect(state, null);
212+
wasCalled = true;
213+
},
214+
),
215+
);
216+
217+
final RegularWindowController controller = RegularWindowController(size: initialSize);
218+
await tester.pump();
219+
220+
await controller.modify(title: newTitle);
221+
await tester.pump();
222+
223+
expect(wasCalled, true);
224+
},
225+
);
226+
227+
testWidgets(
228+
'RegularWindowController.modify can be called when provided with a "state" argument',
229+
(WidgetTester tester) async {
230+
const Size initialSize = Size(800, 600);
231+
const WindowState newState = WindowState.minimized;
232+
233+
bool wasCalled = false;
234+
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
235+
SystemChannels.windowing,
236+
_createWindowMethodCallHandler(
237+
tester: tester,
238+
onMethodCall: (MethodCall call) {
239+
if (call.method != 'modifyRegular') {
240+
return;
241+
}
242+
243+
final Map<Object?, Object?> args = call.arguments as Map<Object?, Object?>;
244+
final int viewId = args['viewId']! as int;
245+
final List<Object?>? size = args['size'] as List<Object?>?;
246+
final String? title = args['title'] as String?;
247+
final String? state = args['state'] as String?;
248+
expect(viewId, tester.view.viewId);
249+
expect(size, null);
250+
expect(title, null);
251+
expect(state, newState.toString());
252+
wasCalled = true;
253+
},
254+
),
255+
);
256+
257+
final RegularWindowController controller = RegularWindowController(size: initialSize);
258+
await tester.pump();
259+
260+
await controller.modify(state: newState);
261+
await tester.pump();
262+
263+
expect(wasCalled, true);
264+
},
265+
);
266+
267+
testWidgets('RegularWindowController.modify throws when no arguments are provided', (
268+
WidgetTester tester,
269+
) async {
270+
const Size initialSize = Size(800, 600);
271+
const WindowState newState = WindowState.minimized;
272+
273+
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
274+
SystemChannels.windowing,
275+
_createWindowMethodCallHandler(tester: tester),
276+
);
277+
278+
final RegularWindowController controller = RegularWindowController(size: initialSize);
279+
await tester.pump();
280+
281+
expect(() async => controller.modify(), throwsA(isA<AssertionError>()));
282+
});
156283
}

0 commit comments

Comments
 (0)