runNotificationPermissionFlow method

Future<NotificationFlowResult> runNotificationPermissionFlow(
  1. BuildContext context, {
  2. NotificationFlowConfig? config,
})

Run the complete notification permission flow with built-in dialogs.

This handles the entire flow:

  1. If already granted → initialize silently
  2. If not determined → show value proposition → request permission
  3. If denied (can retry) → check timing → maybe show ask-again dialog
  4. If permanently denied → show go-to-settings dialog

context is required for showing dialogs. config allows customization of strings, timing, and dialog builders.

Returns a NotificationFlowResult indicating how the flow completed.

Example:

final result = await notificationService.runNotificationPermissionFlow(context);
if (result == NotificationFlowResult.granted ||
    result == NotificationFlowResult.alreadyGranted) {
  showSnackbar('Notifications enabled!');
}

Implementation

Future<NotificationFlowResult> runNotificationPermissionFlow(
  BuildContext context, {
  NotificationFlowConfig? config,
}) async {
  final effectiveConfig = config ?? NotificationFlowConfig.fromAppConfig();
  // Check FCM enabled
  if (!AppConfigBase.useFCM) {
    logd('runNotificationPermissionFlow: FCM is disabled');
    return NotificationFlowResult.fcmDisabled;
  }

  try {
    final status = await getPermissionStatus();
    final canPromptAgain = await _permissionHelper.canPromptForPermission();
    // Treat denial as permanent if we can't prompt again.
    // On web, most browsers treat first denial as permanent, so we route to
    // the go-to-settings flow which returns shownWebInstructions.
    final isPermanentDenied = status == NotificationPermissionStatus.denied && !canPromptAgain;

    switch (status) {
      case NotificationPermissionStatus.authorized:
      case NotificationPermissionStatus.provisional:
        // Already have permission - just initialize
        await initializeNotifications();
        logd('runNotificationPermissionFlow: Already granted');
        return NotificationFlowResult.alreadyGranted;

      case NotificationPermissionStatus.notDetermined:
        if (!context.mounted) {
          logd('runNotificationPermissionFlow: Context unmounted before value proposition');
          return NotificationFlowResult.error;
        }
        // Show value proposition first
        final shouldProceedFuture = effectiveConfig.valuePropositionBuilder != null
            ? effectiveConfig.valuePropositionBuilder!(context)
            : _showValuePropositionDialog(context, effectiveConfig.strings);
        final shouldProceed = await shouldProceedFuture;

        if (!shouldProceed) {
          logd('runNotificationPermissionFlow: User declined value proposition');
          return NotificationFlowResult.declinedValueProposition;
        }

        // Request permission
        final result = await initializeNotifications();
        return _mapInitResultToFlowResult(result);

      case NotificationPermissionStatus.denied:
        // If effectively permanent, route to go-to-settings path
        if (!context.mounted) {
          logd('runNotificationPermissionFlow: Context unmounted before denied flow');
          return NotificationFlowResult.error;
        }
        if (isPermanentDenied) {
          return await _handlePermanentlyDeniedFlow(context, effectiveConfig);
        }

        // Check if we should ask again
        final denialInfo = await getNotificationDenialInfo();
        if (!context.mounted) {
          logd('runNotificationPermissionFlow: Context unmounted before ask-again');
          return NotificationFlowResult.error;
        }
        if (!_shouldAskAgain(denialInfo, effectiveConfig)) {
          logd('runNotificationPermissionFlow: Skipping ask-again (config limits)');
          return NotificationFlowResult.skippedAskAgain;
        }

        // Show ask-again dialog
        final shouldAskFuture = effectiveConfig.askAgainBuilder != null
            ? effectiveConfig.askAgainBuilder!(context, denialInfo!)
            : _showAskAgainDialog(context, effectiveConfig.strings, denialInfo);
        final shouldAsk = await shouldAskFuture;

        if (!shouldAsk) {
          logd('runNotificationPermissionFlow: User declined ask-again');
          return NotificationFlowResult.skippedAskAgain;
        }

        final result = await initializeNotifications();
        return _mapInitResultToFlowResult(result);
    }
  } catch (e, stackTrace) {
    loge(e, 'Error in runNotificationPermissionFlow', stackTrace);
    _onError?.call('Error in runNotificationPermissionFlow: $e', stackTrace);
    return NotificationFlowResult.error;
  }
}