swift_notifications 1.0.2 copy "swift_notifications: ^1.0.2" to clipboard
swift_notifications: ^1.0.2 copied to clipboard

A unified Flutter plugin for rich push and local notifications on Android, iOS, and macOS with no native code required.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:swift_notifications/swift_notifications.dart';
import 'package:screen_launch_by_notfication/screen_launch_by_notfication.dart';
import 'notification_detail_screen.dart';
import 'dart:async';
import 'dart:convert';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Enable verbose logging for debugging
  SwiftNotifications.enableVerboseLogging();
  
  // Initialize notifications
  // Firebase is automatically initialized if configured (google-services.json / GoogleService-Info.plist)
  // Background message handler is automatically set up by the plugin
  final notifications = SwiftNotifications();
  await notifications.initialize(verbose: true, initializeFirebase: true);
  
  runApp(MyApp(notifications: notifications));
}

class MyApp extends StatelessWidget {
  final SwiftNotifications notifications;
  
  const MyApp({super.key, required this.notifications});

  @override
  Widget build(BuildContext context) {
    return SwiftFlutterMaterial(
      materialApp: MaterialApp(
        title: 'Swift Notifications Example',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        routes: {
          '/': (context) => HomeScreen(notifications: notifications),
          '/message': (context) => MessageScreen(),
          '/order': (context) => OrderScreen(),
          '/settings': (context) => SettingsScreen(notifications: notifications),
          '/test-cold-start': (context) => ColdStartTestScreen(notifications: notifications),
          '/notification-detail': (context) {
            final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
            return NotificationDetailScreen(payload: args ?? {});
          },
        },
        initialRoute: '/',
      ),
      onNotificationLaunch: ({required isFromNotification, required payload}) {
        print('🔔 App launched from notification: $isFromNotification');
        print('📦 Payload: $payload');
        
        if (isFromNotification && payload.isNotEmpty) {
          // Route based on notification type
          if (payload.containsKey('type')) {
            final type = payload['type']?.toString();
            
            switch (type) {
              case 'message':
              case 'chat':
                return SwiftRouting(
                  route: '/message',
                  payload: {
                    'messageId': payload['messageId']?.toString(),
                    'senderId': payload['senderId']?.toString(),
                    'senderName': payload['senderName']?.toString(),
                    'message': payload['message']?.toString(),
                  },
                );
                
              case 'order':
                return SwiftRouting(
                  route: '/order',
                  payload: {
                    'orderId': payload['orderId']?.toString(),
                    'orderStatus': payload['orderStatus']?.toString(),
                  },
                );
                
              case 'notification':
              default:
                return SwiftRouting(
                  route: '/notification-detail',
                  payload: payload,
                );
            }
          }
          
          // Default: route to notification detail screen
          return SwiftRouting(
            route: '/notification-detail',
            payload: payload,
          );
        }
        
        // Not from notification, use default route
        return null;
      },
    );
  }
}

class HomeScreen extends StatefulWidget {
  final SwiftNotifications notifications;
  
  const HomeScreen({super.key, required this.notifications});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String _status = 'Initializing...';
  NotificationPermissionStatus _permissionStatus = NotificationPermissionStatus.notDetermined;
  StreamSubscription<NotificationResponse>? _notificationSubscription;
  List<NotificationResponse> _notificationHistory = [];

  @override
  void initState() {
    super.initState();
    _checkPermission();
    _listenToNotifications();
  }

  Future<void> _checkPermission() async {
    try {
      final status = await widget.notifications.checkPermission();
      setState(() {
        _permissionStatus = status;
        _status = 'Permission: ${status.name}';
      });
    } catch (e) {
      setState(() {
        _status = 'Error: $e';
      });
    }
  }

  Future<void> _requestPermission() async {
    try {
      final status = await widget.notifications.requestPermission();
      setState(() {
        _permissionStatus = status;
        _status = 'Permission: ${status.name}';
      });
      _showSnackBar('Permission status: ${status.name}');
    } catch (e) {
      _showSnackBar('Error requesting permission: $e');
    }
  }

  void _listenToNotifications() {
    _notificationSubscription = widget.notifications.onNotificationResponse.listen((response) {
    setState(() {
        _notificationHistory.insert(0, response);
        if (_notificationHistory.length > 10) {
          _notificationHistory.removeLast();
        }
      });
      
      _showNotificationDialog(response);
    });
  }

  void _showNotificationDialog(NotificationResponse response) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Notification Response'),
        content: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('Notification ID: ${response.notificationId}'),
              const SizedBox(height: 8),
              Text('Action ID: ${response.actionId ?? "Tapped (body)"}'),
              if (response.payload != null) ...[
                const SizedBox(height: 8),
                const Text('Payload:', style: TextStyle(fontWeight: FontWeight.bold)),
                Text(JsonEncoder.withIndent('  ').convert(response.payload!)),
              ],
              if (response.actionPayload != null) ...[
                const SizedBox(height: 8),
                const Text('Action Payload:', style: TextStyle(fontWeight: FontWeight.bold)),
                Text(JsonEncoder.withIndent('  ').convert(response.actionPayload!)),
              ],
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _notificationSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Swift Notifications'),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: () => Navigator.pushNamed(context, '/settings'),
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status Card
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  children: [
                    const Text(
                      'Status',
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    Text(_status),
                    const SizedBox(height: 8),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        ElevatedButton(
                          onPressed: _checkPermission,
                          child: const Text('Check Permission'),
                        ),
                        ElevatedButton(
                          onPressed: _requestPermission,
                          child: const Text('Request Permission'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            
            // Basic Notifications Section
            _buildSection(
              title: 'Basic Notifications',
              children: [
                _buildButton(
                  'Simple Notification',
                  () => _showSimpleNotification(),
                ),
                _buildButton(
                  'Notification with Payload',
                  () => _showNotificationWithPayload(),
                ),
                _buildButton(
                  'Test Auto-Navigation (Message)',
                  () => _showRoutingNotification(),
                  color: Colors.green,
                ),
                _buildButton(
                  'Test Auto-Navigation (Order)',
                  () => _showOrderRoutingNotification(),
                  color: Colors.green,
                ),
                _buildButton(
                  'Notification for Cold Start Test',
                  () => _showColdStartNotification(),
                ),
              ],
            ),
            
            // Rich Notifications Section
            _buildSection(
              title: 'Rich Notifications',
              children: [
                _buildButton(
                  'Notification with Image',
                  () => _showImageNotification(),
                ),
                _buildButton(
                  'Notification with Buttons',
                  () => _showNotificationWithButtons(),
                ),
                _buildButton(
                  'Rich Notification (Image + Buttons)',
                  () => _showRichNotification(),
                ),
              ],
            ),
            
            // Advanced Features Section
            _buildSection(
              title: 'Advanced Features',
              children: [
                _buildButton(
                  'Test Cold Start Handling',
                  () => Navigator.pushNamed(context, '/test-cold-start'),
                ),
                _buildButton(
                  'Notification with Custom Category',
                  () => _showNotificationWithCategory(),
                ),
                _buildButton(
                  'Notification with Priority',
                  () => _showNotificationWithPriority(),
                ),
              ],
            ),
            
            // Firebase Remote Push Section
            _buildSection(
              title: 'Firebase Remote Push',
              children: [
                _buildButton(
                  'Get FCM Token',
                  () => _getFCMToken(),
                ),
                _buildButton(
                  'Test Remote Push Format',
                  () => _showRemotePushInfo(),
                ),
              ],
            ),
            
            // Actions Section
            _buildSection(
              title: 'Actions',
              children: [
                _buildButton(
                  'Cancel All Notifications',
                  () => _cancelAllNotifications(),
                  color: Colors.red,
                ),
              ],
            ),
            
            // Notification History
            if (_notificationHistory.isNotEmpty) ...[
              const SizedBox(height: 24),
              const Text(
                'Notification History',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              ..._notificationHistory.map((response) => Card(
                margin: const EdgeInsets.only(bottom: 8),
                child: ListTile(
                  title: Text('ID: ${response.notificationId}'),
                  subtitle: Text('Action: ${response.actionId ?? "Tapped"}'),
                  trailing: const Icon(Icons.arrow_forward_ios, size: 16),
                  onTap: () => _showNotificationDialog(response),
                ),
              )),
            ],
          ],
        ),
      ),
    );
  }

  Widget _buildSection({required String title, required List<Widget> children}) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        ...children,
        const SizedBox(height: 24),
      ],
    );
  }

  Widget _buildButton(String text, VoidCallback onPressed, {Color? color}) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8.0),
      child: ElevatedButton(
        onPressed: onPressed,
        style: color != null ? ElevatedButton.styleFrom(backgroundColor: color) : null,
        child: Text(text),
      ),
    );
  }

  Future<void> _showSimpleNotification() async {
    try {
      await widget.notifications.showSimpleNotification(
        id: 'simple_${DateTime.now().millisecondsSinceEpoch}',
        title: 'Simple Notification',
        body: 'This is a simple text notification without any extras.',
      );
      _showSnackBar('Simple notification sent!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showNotificationWithPayload() async {
    try {
      await widget.notifications.showSimpleNotification(
        id: 'payload_${DateTime.now().millisecondsSinceEpoch}',
        title: 'Notification with Payload',
        body: 'Tap this notification to see the payload data.',
        payload: {
          'type': 'notification',
          'userId': '12345',
          'message': 'Hello from notification!',
          'timestamp': DateTime.now().toIso8601String(),
        },
      );
      _showSnackBar('Notification with payload sent! Tap it to test routing.');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }
  
  Future<void> _showRoutingNotification() async {
    try {
      await widget.notifications.showSimpleNotification(
        id: 'routing_${DateTime.now().millisecondsSinceEpoch}',
        title: 'Test Navigation',
        body: 'Tap to navigate to Message screen automatically!',
        payload: {
          'type': 'message',
          'messageId': 'msg_${DateTime.now().millisecondsSinceEpoch}',
          'senderId': 'user_123',
          'senderName': 'John Doe',
          'message': 'This is a test message for routing',
        },
      );
      _showSnackBar('Routing notification sent! Close app, tap notification to test auto-navigation.');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }
  
  Future<void> _showOrderRoutingNotification() async {
    try {
      await widget.notifications.showSimpleNotification(
        id: 'order_routing_${DateTime.now().millisecondsSinceEpoch}',
        title: 'Order Update',
        body: 'Tap to view order details!',
        payload: {
          'type': 'order',
          'orderId': 'order_${DateTime.now().millisecondsSinceEpoch}',
          'orderStatus': 'shipped',
          'orderTotal': '99.99',
        },
      );
      _showSnackBar('Order routing notification sent! Close app, tap to test auto-navigation.');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showColdStartNotification() async {
    try {
      await widget.notifications.showNotification(
        NotificationRequest(
          id: 'coldstart_${DateTime.now().millisecondsSinceEpoch}',
          title: 'Cold Start Test',
          body: 'Close the app completely, then tap this notification. The event will be stored and delivered when the app starts.',
          payload: {
            'type': 'cold_start_test',
            'message': 'This notification tests cold start event handling',
            'timestamp': DateTime.now().toIso8601String(),
          },
          buttonsEnabled: true,
          actions: [
            NotificationAction(
              id: 'view_message',
              title: 'View Message',
              payload: {'action': 'view', 'screen': '/message'},
            ),
            NotificationAction(
              id: 'view_order',
              title: 'View Order',
              payload: {'action': 'view', 'screen': '/order'},
            ),
          ],
        ),
      );
      _showSnackBar('Cold start test notification sent! Close the app and tap it.');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showImageNotification() async {
    try {
      await widget.notifications.showImageNotification(
        id: 'image_${DateTime.now().millisecondsSinceEpoch}',
        title: 'Image Notification',
        body: 'This notification includes an image!',
        imageUrl: 'https://picsum.photos/400/300?random=${DateTime.now().millisecondsSinceEpoch}',
      );
      _showSnackBar('Image notification sent!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showNotificationWithButtons() async {
    try {
      await widget.notifications.showNotification(
        NotificationRequest(
          id: 'buttons_${DateTime.now().millisecondsSinceEpoch}',
          title: 'Notification with Buttons',
          body: 'This notification has action buttons. Try tapping them!',
          buttonsEnabled: true,
          actions: [
            NotificationAction(
              id: 'action_view',
              title: 'View',
              payload: {'action': 'view'},
            ),
            NotificationAction(
              id: 'action_reply',
              title: 'Reply',
              payload: {'action': 'reply'},
            ),
            NotificationAction(
              id: 'action_delete',
              title: 'Delete',
              isDestructive: true,
              payload: {'action': 'delete'},
            ),
          ],
        ),
      );
      _showSnackBar('Notification with buttons sent!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showRichNotification() async {
    try {
      await widget.notifications.showNotification(
        NotificationRequest(
          id: 'rich_${DateTime.now().millisecondsSinceEpoch}',
          title: 'Rich Notification',
          body: 'This is a rich notification with both image and buttons!',
          imageEnabled: true,
          image: NotificationImage(
            url: 'https://picsum.photos/400/300?random=${DateTime.now().millisecondsSinceEpoch}',
          ),
          buttonsEnabled: true,
          actions: [
            NotificationAction(
              id: 'action_like',
              title: '👍 Like',
              payload: {'action': 'like'},
            ),
            NotificationAction(
              id: 'action_share',
              title: '📤 Share',
              payload: {'action': 'share'},
            ),
          ],
          payload: {
            'type': 'rich_notification',
            'timestamp': DateTime.now().toIso8601String(),
          },
        ),
      );
      _showSnackBar('Rich notification sent!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showNotificationWithCategory() async {
    try {
      await widget.notifications.showNotification(
        NotificationRequest(
          id: 'category_${DateTime.now().millisecondsSinceEpoch}',
          title: 'Categorized Notification',
          body: 'This notification uses a custom category.',
          categoryId: 'custom_category',
          payload: {'category': 'custom_category'},
        ),
      );
      _showSnackBar('Categorized notification sent!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _showNotificationWithPriority() async {
    try {
      await widget.notifications.showNotification(
        NotificationRequest(
          id: 'priority_${DateTime.now().millisecondsSinceEpoch}',
          title: 'High Priority Notification',
          body: 'This notification has high priority (Android).',
          priority: NotificationPriority.high,
          payload: {'priority': 'high'},
        ),
      );
      _showSnackBar('High priority notification sent!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _cancelAllNotifications() async {
    try {
      await widget.notifications.cancelAllNotifications();
      _showSnackBar('All notifications cancelled!');
    } catch (e) {
      _showSnackBar('Error: $e');
    }
  }

  Future<void> _getFCMToken() async {
    try {
      final token = await widget.notifications.getFCMToken();
      print("------------");
      print(token);
      print("------------");
      if (token != null) {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: const Text('FCM Token'),
            content: SelectableText(token),
            actions: [
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                  _showSnackBar('Token copied to clipboard');
                },
                child: const Text('Copy'),
              ),
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('Close'),
              ),
            ],
          ),
        );
      } else {
        _showSnackBar('Failed to get FCM token. Make sure Firebase is configured (google-services.json / GoogleService-Info.plist)');
      }
    } catch (e) {
      _showSnackBar('Error: $e. Make sure Firebase is configured.');
    }
  }

  void _showRemotePushInfo() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Remote Push Payload Format'),
        content: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text(
                'Send this JSON in Firebase data payload:',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              SelectableText(
                '''In Firebase Console → Custom Data:

Key: id
Value: notification_123

Key: title
Value: New Message

Key: body
Value: You have a new message

Key: image
Value: https://picsum.photos/400/300

Key: imageEnabled
Value: true

Key: buttons
Value: [{"id":"reply","title":"Reply"},{"id":"delete","title":"Delete","isDestructive":true}]

Key: buttonsEnabled
Value: true

Key: payload
Value: {"type":"message","userId":"123"}

Key: priority
Value: high''',
                style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
              ),
              const SizedBox(height: 16),
              const Text(
                'Important Notes:',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const Text('• In Firebase Console, enter buttons as plain JSON (no extra escaping)'),
              const Text('• Example buttons value: [{"id":"reply","title":"Reply"}]'),
              const Text('• Do NOT add extra backslashes or quotes'),
              const Text('• The plugin automatically handles parsing'),
              const Text('• Android: Works automatically'),
              const Text('• iOS: Requires NSE for images'),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }
}

// Example screens for routing
class MessageScreen extends StatelessWidget {
  const MessageScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // Get arguments passed from routing
    final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('Message Screen'),
        backgroundColor: Colors.green,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (args != null && args.isNotEmpty) ...[
              const Card(
                color: Colors.green,
                child: Padding(
                  padding: EdgeInsets.all(16),
                  child: Row(
                    children: [
                      Icon(Icons.check_circle, color: Colors.white),
                      SizedBox(width: 12),
                      Expanded(
                        child: Text(
                          'Navigated from Notification!',
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 24),
              const Text(
                'Message Data:',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 12),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      if (args['messageId'] != null)
                        _buildInfoRow('Message ID', args['messageId']?.toString()),
                      if (args['senderId'] != null)
                        _buildInfoRow('Sender ID', args['senderId']?.toString()),
                      if (args['senderName'] != null)
                        _buildInfoRow('Sender Name', args['senderName']?.toString()),
                      if (args['message'] != null)
                        _buildInfoRow('Message', args['message']?.toString()),
                    ],
                  ),
                ),
              ),
            ] else ...[
              const Center(
                child: Text(
                  'This is the message screen\n\nNavigate here from a notification to see message data.',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 16),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
  
  Widget _buildInfoRow(String label, String? value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            flex: 2,
            child: Text(
              '$label:',
              style: const TextStyle(
                fontWeight: FontWeight.bold,
                color: Colors.green,
              ),
            ),
          ),
          Expanded(
            flex: 3,
            child: Text(
              value ?? 'N/A',
              style: const TextStyle(fontFamily: 'monospace'),
            ),
          ),
        ],
      ),
    );
  }
}

class OrderScreen extends StatelessWidget {
  const OrderScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // Get arguments passed from routing
    final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('Order Screen'),
        backgroundColor: Colors.orange,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (args != null && args.isNotEmpty) ...[
              const Card(
                color: Colors.orange,
                child: Padding(
                  padding: EdgeInsets.all(16),
                  child: Row(
                    children: [
                      Icon(Icons.shopping_cart, color: Colors.white),
                      SizedBox(width: 12),
                      Expanded(
                        child: Text(
                          'Navigated from Notification!',
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 24),
              const Text(
                'Order Data:',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 12),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      if (args['orderId'] != null)
                        _buildInfoRow('Order ID', args['orderId']?.toString()),
                      if (args['orderStatus'] != null)
                        _buildInfoRow('Status', args['orderStatus']?.toString()),
                      if (args['orderTotal'] != null)
                        _buildInfoRow('Total', '\$${args['orderTotal']?.toString()}'),
                    ],
                  ),
                ),
              ),
            ] else ...[
              const Center(
                child: Text(
                  'This is the order screen\n\nNavigate here from a notification to see order data.',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 16),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
  
  Widget _buildInfoRow(String label, String? value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            flex: 2,
            child: Text(
              '$label:',
              style: const TextStyle(
                fontWeight: FontWeight.bold,
                color: Colors.orange,
              ),
            ),
          ),
          Expanded(
            flex: 3,
            child: Text(
              value ?? 'N/A',
              style: const TextStyle(fontFamily: 'monospace'),
            ),
          ),
        ],
      ),
    );
  }
}

class SettingsScreen extends StatefulWidget {
  final SwiftNotifications notifications;
  
  const SettingsScreen({super.key, required this.notifications});

  @override
  State<SettingsScreen> createState() => _SettingsScreenState();
}

class _SettingsScreenState extends State<SettingsScreen> {
  bool _verboseLogging = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Settings'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          SwitchListTile(
            title: const Text('Verbose Logging'),
            subtitle: const Text('Enable detailed logging for debugging'),
            value: _verboseLogging,
            onChanged: (value) {
              setState(() {
                _verboseLogging = value;
                if (value) {
                  SwiftNotifications.enableVerboseLogging();
                } else {
                  SwiftNotifications.disableVerboseLogging();
                }
              });
            },
          ),
          const Divider(),
          ListTile(
            title: const Text('About'),
            subtitle: const Text('Swift Notifications Example App'),
          ),
        ],
      ),
    );
  }
}

class ColdStartTestScreen extends StatelessWidget {
  final SwiftNotifications notifications;
  
  const ColdStartTestScreen({super.key, required this.notifications});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cold Start Test'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Card(
              color: Colors.blue,
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'How to Test Cold Start',
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
                    ),
                    SizedBox(height: 8),
                    Text(
                      '1. Send a notification using the button below\n'
                      '2. Close the app completely (swipe away from recent apps)\n'
                      '3. Tap the notification\n'
                      '4. The app will launch and the event will be delivered',
                      style: TextStyle(color: Colors.white),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () async {
                try {
                  await notifications.showNotification(
                    NotificationRequest(
                      id: 'coldstart_test_${DateTime.now().millisecondsSinceEpoch}',
                      title: 'Cold Start Test',
                      body: 'Close the app completely, then tap this notification to test cold start handling.',
                      payload: {
                        'type': 'cold_start_test',
                        'testId': DateTime.now().millisecondsSinceEpoch.toString(),
                        'message': 'This is a cold start test notification',
                      },
                      buttonsEnabled: true,
                      actions: [
                        NotificationAction(
                          id: 'test_action_1',
                          title: 'Test Action 1',
                          payload: {'action': 'test1', 'screen': '/message'},
                        ),
                        NotificationAction(
                          id: 'test_action_2',
                          title: 'Test Action 2',
                          payload: {'action': 'test2', 'screen': '/order'},
                        ),
                      ],
                    ),
                  );
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Notification sent! Close the app and tap it to test cold start.'),
                      duration: Duration(seconds: 5),
                    ),
                  );
                } catch (e) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Error: $e')),
                  );
                }
              },
              child: const Text('Send Cold Start Test Notification'),
            ),
            const SizedBox(height: 16),
            const Card(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'What Happens:',
                      style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                    ),
                    SizedBox(height: 8),
                    Text('• Notification event is stored in persistent storage'),
                    Text('• App launches from cold start'),
                    Text('• Plugin checks for pending events on initialization'),
                    Text('• Event is delivered to onNotificationResponse stream'),
                    Text('• You can handle routing based on the response'),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
140
points
162
downloads

Publisher

verified publisherswiftflutter.com

Weekly Downloads

A unified Flutter plugin for rich push and local notifications on Android, iOS, and macOS with no native code required.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

firebase_core, firebase_messaging, flutter, plugin_platform_interface

More

Packages that depend on swift_notifications

Packages that implement swift_notifications