dismissDialog static method

Future<void> dismissDialog({
  1. String? id,
  2. VoidCallback? onDismissed,
})

Dismisses only the dialog by its ID

Parameters:

  • id: The ID of the dialog to dismiss (required)
  • onDismissed: Optional callback executed after the dialog is dismissed

Example:

// Dismiss current dialog (less safe)
Modal.dismissDialog();

// Dismiss specific dialog by ID (recommended)
Modal.dismissDialog(id: 'settings_dialog');

Implementation

static Future<void> dismissDialog(
    {String? id, VoidCallback? onDismissed}) async {
  if (Modal.isDialogActive && !Modal.isDialogDismissing) {
    // CAPTURE the ID of the dialog we intend to dismiss at the START
    final targetDialogId = _dialogController.state?.uniqueId;
    final dialogId = _dialogController.state?.id;
    if (targetDialogId == null) {
      // debugPrint(
      //     'Modal.dismissDialog: WARNING - No dialog ID found, aborting');
      return;
    }

    // If an ID was provided, verify it matches before dismissing
    if (id != null && id != dialogId && id != targetDialogId) {
      // debugPrint(
      //     'Modal.dismissDialog: ID mismatch. Requested=$id, Active=$dialogId. Aborting.');
      return;
    }

    // debugPrint('Modal.dismissDialog: targeting dialog ID=$targetDialogId');
    Modal.isDismissing = true;
    _dialogDismissingNotifier.state = true;

    // Capture the modal type that is being dismissed NOW so we can clear
    // the correct type-specific controller after callbacks run.
    final originallyDismissedType = _activeModalController.state?.modalType;

    // Capture the modal type being dismissed so we clear the correct
    // type-specific controller even if callbacks change the active modal.
    // Note: We intentionally do not capture dismissed type here because
    // bottom sheet cleanup is handled below when we refresh the controller.

    // Reset blur animation state - BUT ONLY if no other blur-enabled modal remains active
    // If a bottom sheet with blur is still showing, we must preserve its blur
    final sheetNeedsBlur = Modal.isSheetActive &&
        (_sheetController.state?.shouldBlurBackground ?? false);
    if (!sheetNeedsBlur) {
      _blurAnimationStateNotifier.state = 0.0;
    }

    // Animate background out (if no other modals need it)
    if (!Modal.isSheetActive) {
      _backgroundAnimationTimer?.cancel();
      const animSteps = 10;
      const totalDuration = 300;
      final stepDuration = Duration(milliseconds: totalDuration ~/ animSteps);
      double startValue = _backgroundLayerAnimationNotifier.state;
      double step = startValue / animSteps;
      int currentStep = 0;

      _backgroundAnimationTimer = Timer.periodic(stepDuration, (timer) {
        currentStep++;
        if (currentStep > animSteps) {
          timer.cancel();
          _backgroundAnimationTimer = null;
          _backgroundLayerAnimationNotifier.state = 0.0;
        } else {
          _backgroundLayerAnimationNotifier.state =
              startValue - (step * currentStep);
        }
      });
    }

    // Animate out
    _dismissModalAnimationController.state = true;

    // Allow animation time, then perform cleanup and run callbacks AFTER
    // the modal is effectively torn down. This avoids race conditions
    // where callbacks show new snackbars and the cleanup later clears them.
    // debugPrint(
    //     'Modal.dismissDialog: start (activeId=${_activeModalController.state?.uniqueId}, dialogId=${_dialogController.state?.uniqueId})');
    await Future.delayed(0.4.sec, () {
      // VALIDATE: Check if the dialog ID still matches what we intended to dismiss
      final currentDialogId = _dialogController.state?.uniqueId;
      if (currentDialogId != null && currentDialogId != targetDialogId) {
        // debugPrint(
        //     'Modal.dismissDialog: WARNING - Dialog ID changed during animation! '
        //     'Target=$targetDialogId, Current=$currentDialogId. Aborting cleanup.');
        Modal.isDismissing = false;
        _dialogDismissingNotifier.state = false;
        return;
      }

      // debugPrint(
      //     'Modal.dismissDialog: animation complete, running cleanup for ID=$targetDialogId');
      // debugPrint(
      //     'Modal.dismissDialog: before cleanup: active=${Modal.controller.state?.modalType} activeId=${Modal.controller.state?.uniqueId} dialogId=${_dialogController.state?.uniqueId} snackbarQueue=${_snackbarQueueNotifier.state.length}');

      // Capture the dialog's onDismissed callback before we refresh the controller
      final dialogOnDismiss = _dialogController.state?.onDismissed;

      _dialogController.refresh();

      // Unregister from modal registry
      final updatedRegistry =
          Map<String, ModalType>.from(_modalRegistry.state);
      updatedRegistry.remove(targetDialogId);
      _modalRegistry.state = updatedRegistry;

      // Run callbacks now that the dialog has been cleaned up
      // debugPrint('Modal.dismissDialog: running callbacks (post-refresh)');
      onDismissed?.call();
      dialogOnDismiss?.call();

      // debugPrint(
      //     'Modal.dismissDialog: after callbacks: active=${Modal.controller.state?.modalType} activeId=${Modal.controller.state?.uniqueId} dialogId=${_dialogController.state?.uniqueId} snackbarQueue=${_snackbarQueueNotifier.state.length}');

      final currentQueue = _snackbarQueueNotifier.state;
      // debugPrint(
      //     'Modal.dismissDialog: snackbar queue has ${currentQueue.keys.length} positions');

      // IMPORTANT: Do NOT clear the snackbar queue when dismissing a dialog.
      // Snackbars are independent modals and should remain visible.
      // Only update the active modal controller to point to a remaining snackbar if any.
      if (currentQueue.isNotEmpty) {
        // debugPrint(
        //     'Modal.dismissDialog: snackbar queue not empty, preserving snackbars');
        // Find the first position with snackbars and make it the active modal
        Alignment? positionWithContent;
        for (final position in currentQueue.keys) {
          if (currentQueue[position]!.isNotEmpty) {
            positionWithContent = position;
            break;
          }
        }
        if (positionWithContent != null) {
          final snackbarToShow = currentQueue[positionWithContent]!.first;
          _snackbarController.state = snackbarToShow;
          Modal.controller.state = snackbarToShow;
          // Ensure the newly activated snackbar is not marked as dismissing
          _setSnackbarDismissing(snackbarToShow.uniqueId, false);
          Modal.dismissModalAnimationController.state = false;
        }
      }

      // Clear type-specific controllers based on what was dismissed
      // IMPORTANT: Only clear active modal controller if NO other modals are active
      if (currentQueue.isEmpty && !Modal.isSheetActive) {
        _snackbarController.refresh();
        _activeModalController.refresh();
      } else if (currentQueue.isEmpty && Modal.isSheetActive) {
        // Bottom sheet is still active - make it the active modal
        _snackbarController.refresh();
        Modal.controller.state = _sheetController.state;
      }
      if (originallyDismissedType == ModalType.dialog) {
        // Dialog was already refreshed above; just clear the dismissing flag
        _dialogDismissingNotifier.state = false;
      }

      // Reset dismiss animation controller if no other modals active
      if (!Modal.isSheetActive && !Modal.isSnackbarActive) {
        _dismissModalAnimationController.state = false;
      }

      // debugPrint('Modal.dismissDialog: finished');
      Modal.isDismissing = false;
      HapticFeedback.lightImpact();
    });
  }
}