// 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 'framework.dart';
import 'navigator.dart';
import 'notification_listener.dart';
import 'pop_scope.dart';

/// Enables the handling of system back gestures.
///
/// Typically wraps a nested [Navigator] widget and allows it to handle system
/// back gestures in the [onPop] callback.
///
/// {@tool dartpad}
/// This sample demonstrates how to use this widget to properly handle system
/// back gestures when using nested [Navigator]s.
///
/// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This sample demonstrates how to use this widget to properly handle system
/// back gestures with a bottom navigation bar whose tabs each have their own
/// nested [Navigator]s.
///
/// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.1.dart **
/// {@end-tool}
///
/// See also:
///
///  * [PopScope], which allows toggling the ability of a [Navigator] to
///    handle pops.
///  * [NavigationNotification], which indicates whether a [Navigator] in a
///    subtree can handle pops.
class NavigatorPopHandler extends StatefulWidget {
  /// Creates an instance of [NavigatorPopHandler].
  const NavigatorPopHandler({
    super.key,
    this.onPop,
    this.enabled = true,
    required this.child,
  });

  /// The widget to place below this in the widget tree.
  ///
  /// Typically this is a [Navigator] that will handle the pop when [onPop] is
  /// called.
  final Widget child;

  /// Whether this widget's ability to handle system back gestures is enabled or
  /// disabled.
  ///
  /// When false, there will be no effect on system back gestures. If provided,
  /// [onPop] will still be called.
  ///
  /// This can be used, for example, when the nested [Navigator] is no longer
  /// active but remains in the widget tree, such as in an inactive tab.
  ///
  /// Defaults to true.
  final bool enabled;

  /// Called when a handleable pop event happens.
  ///
  /// For example, a pop is handleable when a [Navigator] in [child] has
  /// multiple routes on its stack. It's not handleable when it has only a
  /// single route, and so [onPop] will not be called.
  ///
  /// Typically this is used to pop the [Navigator] in [child]. See the sample
  /// code on [NavigatorPopHandler] for a full example of this.
  final VoidCallback? onPop;

  @override
  State<NavigatorPopHandler> createState() => _NavigatorPopHandlerState();
}

class _NavigatorPopHandlerState extends State<NavigatorPopHandler> {
  bool _canPop = true;

  @override
  Widget build(BuildContext context) {
    // When the widget subtree indicates it can handle a pop, disable popping
    // here, so that it can be manually handled in canPop.
    return PopScope(
      canPop: !widget.enabled || _canPop,
      onPopInvoked: (bool didPop) {
        if (didPop) {
          return;
        }
        widget.onPop?.call();
      },
      // Listen to changes in the navigation stack in the widget subtree.
      child: NotificationListener<NavigationNotification>(
        onNotification: (NavigationNotification notification) {
          // If this subtree cannot handle pop, then set canPop to true so
          // that our PopScope will allow the Navigator higher in the tree to
          // handle the pop instead.
          final bool nextCanPop = !notification.canHandlePop;
          if (nextCanPop != _canPop) {
            setState(() {
              _canPop = nextCanPop;
            });
          }
          return false;
        },
        child: widget.child,
      ),
    );
  }
}