diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bcf2e10f8..e8c18d329e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Bug fixes +* Fix `page.window.maximized = True` intermittently reverting to unmaximized right after startup on macOS, when set in the same patch as `page.title` (e.g. `page.title = "My App"; page.window.maximized = True` in `main()`) by @davidlawson. * Fix `flet build` picking a non-decodable icon/splash image when several files share a base name, producing a machine-dependent `NoDecoderForImageFormatException` from `flutter_launcher_icons`. When an app's `assets` held, say, both `icon.png` and `icon.svg`, `find_platform_image` selected the first match from `glob.glob(...)` — whose order is filesystem-dependent — so the same app could pick `icon.png` on one machine and `icon.svg` on another (SVG is vector and can't be decoded by the raster icon/splash generators), turning a working build into a crash purely based on directory listing order. Candidates are now filtered to formats the generators can actually decode (`.svg` is dropped everywhere; `.icns` stays macOS-only and `.ico` Windows-only) and ranked so a raster image (`.png` first) always wins, making the choice deterministic across machines. When the only supplied image is an SVG (no raster sibling), it's skipped with a build-log warning and the default Flet icon is used instead of crashing by @FeodorFitsner. * Fix modal controls (`AlertDialog`, `CupertinoAlertDialog`, `BottomSheet`, `CupertinoBottomSheet`) crashing to a black screen with "setState()/markNeedsBuild() called during build" when they close in the same frame that another route or overlay opens — e.g. dismissing a bottom sheet and showing a `SnackBar` from one handler. The close path popped the route synchronously during `build`, so the exit animation notified a listener that was mid-build. Each modal now tracks its own `ModalRoute` and closes it in a post-frame callback, popping *that* route (never the topmost one); `View`'s confirm-pop pops its own route too, so a modal dismissed in the same tick as a view pop can no longer dismiss the wrong one by @FeodorFitsner. diff --git a/packages/flet/lib/src/services/window.dart b/packages/flet/lib/src/services/window.dart index 075d5aabf8..11eb1e5f1b 100644 --- a/packages/flet/lib/src/services/window.dart +++ b/packages/flet/lib/src/services/window.dart @@ -17,6 +17,7 @@ import '../utils/window.dart'; class WindowService extends FletService with WindowListener { final Completer _initWindowStateCompleter = Completer(); Future _pendingWindowUpdate = Future.value(); + bool _updateScheduled = false; String? _title; Color? _bgColor; double? _width; @@ -111,7 +112,25 @@ class WindowService extends FletService with WindowListener { } void _scheduleWindowUpdate() { + // Coalesce calls that land before the previously-scheduled run has + // actually started: both `update()` (the Window control's own patch) + // and `_onPageChanged()` (a title change on the *page*, a different + // control) call this method, and a single patch that changes both the + // window and the title fires both — `_updateWindow` re-reads every + // property live from `control`/`parent` when it runs, so a second run + // queued back-to-back before the first even starts would just re-apply + // the same already-current values. Without this, two runs execute in + // sequence for one patch, and if the first starts an animated native + // operation (e.g. macOS NSWindow.zoom for maximize/unmaximize), the + // second can reissue that same call while it's still animating — + // zoom: toggles, so a single `maximized = true` can silently animate + // back to unmaximized right after being applied. + if (_updateScheduled) { + return; + } + _updateScheduled = true; _pendingWindowUpdate = _pendingWindowUpdate.catchError((_) {}).then((_) { + _updateScheduled = false; if (_initWindowStateCompleter.isCompleted) { return _updateWindow(control.backend); }