flutter_notification_reader 1.0.1 copy "flutter_notification_reader: ^1.0.1" to clipboard
flutter_notification_reader: ^1.0.1 copied to clipboard

PlatformAndroid

a flutter plug-in to watch live notifications

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_notification_reader/flutter_notification_reader.dart';

class NotificationEvent {
  final DateTime timestamp;
  final String package, title, content;

  NotificationEvent({
    required this.timestamp,
    required this.package,
    required this.title,
    required this.content,
  });
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final List<NotificationEvent> _notificationsLog = [];
  bool _filterEnabled = true;

  final _targetPackages = [
    {"name": "WhatsApp", "package": "com.whatsapp"},
    {"name": "Telegram", "package": "org.telegram.messenger"},
  ];

  @override
  void initState() {
    super.initState();
    _initializeListener();
  }

  Future<void> _initializeListener() async {
    await _updateFilter();

    FlutterNotificationReader.notificationStream.listen((event) {
      setState(() {
        _notificationsLog.insert(
          0,
          NotificationEvent(
            title: event["title"],
            content: event["content"],
            package: event["package"],
            timestamp: DateTime.fromMillisecondsSinceEpoch(event["timestamp"]),
          ),
        );
      });
    });
  }

  Future<void> _updateFilter() async {
    final packages =
    _filterEnabled
        ? _targetPackages.map((e) => e["package"] as String).toList()
        : null;
    await FlutterNotificationReader.setTargetPackages(packages: packages);
  }

  Future<void> _toggleFilter(bool value) async {
    setState(() => _filterEnabled = value);
    await _updateFilter();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Notification Listener',
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('📩 Notification Monitor'),
          centerTitle: true,
          actions: [
            IconButton(
              tooltip: "Open Notification Settings",
              icon: const Icon(Icons.settings),
              onPressed:
              FlutterNotificationReader.openNotificationAccessSettings,
            ),
          ],
        ),
        body: Column(
          children: [
            _FilterSection(
              enabled: _filterEnabled,
              targets: _targetPackages,
              onToggle: _toggleFilter,
            ),
            Expanded(
              child:
              _notificationsLog.isEmpty
                  ? const _EmptyState()
                  : _NotificationsList(notifications: _notificationsLog),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton.extended(
          onPressed: () => setState(_notificationsLog.clear),
          icon: const Icon(Icons.delete_sweep),
          label: const Text("Clear Logs"),
        ),
      ),
    );
  }
}

class _FilterSection extends StatelessWidget {
  final bool enabled;
  final List<Map<String, String>> targets;
  final ValueChanged<bool> onToggle;

  const _FilterSection({
    required this.enabled,
    required this.targets,
    required this.onToggle,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(16),
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      elevation: 1,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  "Filter by Package",
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                Switch.adaptive(value: enabled, onChanged: onToggle),
              ],
            ),
            AnimatedCrossFade(
              crossFadeState:
              enabled
                  ? CrossFadeState.showFirst
                  : CrossFadeState.showSecond,
              duration: const Duration(milliseconds: 300),
              firstChild: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const SizedBox(height: 8),
                  for (final pkg in targets)
                    Row(
                      children: [
                        const Icon(Icons.check_circle, size: 18),
                        const SizedBox(width: 6),
                        Expanded(
                          child: Text(
                            "${pkg["name"]} (${pkg["package"]})",
                            style: const TextStyle(fontSize: 14),
                          ),
                        ),
                      ],
                    ),
                ],
              ),
              secondChild: const Padding(
                padding: EdgeInsets.only(top: 8),
                child: Text(
                  "📢 Listening to all apps",
                  style: TextStyle(fontSize: 14, fontStyle: FontStyle.italic),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _NotificationsList extends StatelessWidget {
  final List<NotificationEvent> notifications;
  const _NotificationsList({required this.notifications});

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      itemCount: notifications.length,
      separatorBuilder: (_, __) => const SizedBox(height: 8),
      itemBuilder: (_, index) => _NotificationTile(notifications[index]),
    );
  }
}

class _NotificationTile extends StatelessWidget {
  final NotificationEvent notification;
  const _NotificationTile(this.notification);

  @override
  Widget build(BuildContext context) {
    final time = TimeOfDay.fromDateTime(notification.timestamp);
    return Card(
      elevation: 1,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Padding(
        padding: const EdgeInsets.all(14),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                const Icon(Icons.notifications, size: 18),
                const SizedBox(width: 6),
                Expanded(
                  child: Text(
                    notification.title.isEmpty
                        ? "(No Title)"
                        : notification.title,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                Text(
                  time.format(context),
                  style: const TextStyle(fontSize: 12, color: Colors.grey),
                ),
              ],
            ),
            const SizedBox(height: 6),
            Text(
              notification.content.isEmpty
                  ? "(No message content)"
                  : notification.content,
              style: const TextStyle(fontSize: 14, height: 1.4),
            ),
            const SizedBox(height: 6),
            Text(
              notification.package,
              style: const TextStyle(
                fontSize: 12,
                color: Colors.teal,
                fontStyle: FontStyle.italic,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _EmptyState extends StatelessWidget {
  const _EmptyState();

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        "No notifications yet...\nAllow access and wait for incoming messages.",
        textAlign: TextAlign.center,
        style: TextStyle(
          fontSize: 14,
          color: Colors.grey,
          fontStyle: FontStyle.italic,
        ),
      ),
    );
  }
}