blob: c57e9c178677127731d89559b1cdfc2355f9243f [file] [log] [blame] [edit]
// 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 'package:flutter/foundation.dart';
import '../foundation/_features.dart' show isWindowingEnabled;
import '_window.dart'
show
BaseWindowController,
DialogWindowController,
DialogWindowControllerDelegate,
WindowEntry,
WindowRegistry,
WindowScope;
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'navigator.dart';
import 'overlay.dart';
import 'routes.dart';
/// A builder for a route that takes the build context and the widget intended
/// to go inside the route as a parameter.
typedef RawDialogRouteBuilder<T> = Route<T> Function(BuildContext context, WidgetBuilder builder);
/// Displays a dialog over the contents of the app.
///
/// {@template flutter.widgets.showRawDialog.windowing}
/// If windowing is enabled via `flutter config --enable-windowing`,
/// then the dialog is displayed in its own window using the windowing system,
/// rather than as a modal overlay within the current window. This will only
/// function on platforms that support the dialog window type. True dialog
/// windows will lack barriers.
/// {@endtemplate}
///
/// The parameter `builder` builds the content of the dialog.
///
/// {@template flutter.widgets.showRawDialog.context}
/// The `context` argument is used to look up the [Navigator] and [Theme] for
/// the dialog. It is only used when the method is called. Its corresponding
/// widget can be safely removed from the tree before the dialog is closed.
/// {@endtemplate}
///
/// The parameter `routeBuilder` builds the [Route] that will be pushed to the
/// [Navigator]. Defaults to building a [RawDialogRoute]. When windowing is
/// available, this parameter will be silently ignored.
///
/// {@template flutter.widgets.showRawDialog.navigator}
/// The `useRootNavigator` argument is used to determine whether to push the
/// dialog to the [Navigator] furthest from or nearest to the given `context`.
/// By default, `useRootNavigator` is `true` and the dialog route created by
/// this method is pushed to the root navigator. It can not be `null`.
/// {@endtemplate}
///
/// {@template flutter.widgets.showRawDialog.routeSettings}
/// The `routeSettings` argument is passed to [showGeneralDialog],
/// see [RouteSettings] for details.
/// {@endtemplate}
///
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// See also:
///
/// * `WindowManager`, for more information on how to set up windowing.
Future<T?> showRawDialog<T>({
required BuildContext context,
required WidgetBuilder builder,
RawDialogRouteBuilder<T>? routeBuilder,
bool useRootNavigator = true,
RouteSettings? routeSettings,
bool fullscreenDialog = false,
}) {
assert(debugCheckHasWidgetsLocalizations(context));
final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
final WindowRegistry? windowRegistry = WindowRegistry.maybeOf(context);
if (windowRegistry != null && isWindowingEnabled) {
try {
final Size? parentSize = WindowScope.maybeContentSizeOf(context);
return navigator.push<T>(
_DialogWindowRoute<T>(
builder: builder,
parentController: WindowScope.maybeOf(context),
context: context,
settings: routeSettings,
preferredSize: fullscreenDialog ? parentSize : null,
),
);
} on UnsupportedError catch (error, stacktrace) {
// Fallback to normal dialog route if windowing is not supported.
FlutterError.reportError(
FlutterErrorDetails(exception: error, library: 'widgets library', stack: stacktrace),
);
}
}
final Route<T> route =
routeBuilder?.call(context, builder) ??
RawDialogRoute<T>(
pageBuilder:
(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) => builder(context),
settings: routeSettings,
fullscreenDialog: fullscreenDialog,
);
return navigator.push<T>(route);
}
class _DialogWindowDelegate extends DialogWindowControllerDelegate {
_DialogWindowDelegate(this.route);
final _DialogWindowRoute<dynamic> route;
@override
void onWindowCloseRequested(DialogWindowController controller) {
route.navigator?.pop();
}
}
class _DialogWindowRoute<T> extends Route<T> {
_DialogWindowRoute({
required this.builder,
required this.parentController,
required BuildContext context,
super.settings,
Size? preferredSize,
}) : _registry = WindowRegistry.maybeOf(context) {
_controller = DialogWindowController(
parent: parentController,
title: 'Dialog',
delegate: _DialogWindowDelegate(this),
preferredSize: preferredSize,
);
}
final WidgetBuilder builder;
final BaseWindowController? parentController;
final WindowRegistry? _registry;
DialogWindowController? _controller;
WindowEntry? _entry;
late final List<OverlayEntry> _overlayEntries;
@override
List<OverlayEntry> get overlayEntries => _overlayEntries;
@override
void install() {
super.install();
// Create a minimal transparent overlay entry to satisfy Navigator requirements.
// The actual dialog content is rendered through ViewAnchor, not through this overlay.
_overlayEntries = <OverlayEntry>[
OverlayEntry(builder: (BuildContext context) => const SizedBox.shrink()),
];
final NavigatorState? nav = navigator;
final BuildContext? routeContext = nav?.context;
if (routeContext != null && nav != null) {
_entry = WindowEntry(controller: _controller!, builder: builder);
_registry?.register(_entry!);
}
}
@override
TickerFuture didPush() {
return super.didPush();
}
@override
bool didPop(T? result) {
if (_entry != null) {
_registry?.unregister(_entry!);
}
_controller?.destroy();
return super.didPop(result);
}
@override
void dispose() {
_controller?.dispose();
_controller = null;
super.dispose();
}
}