flutter_notification_reader 1.0.0
flutter_notification_reader: ^1.0.0 copied to clipboard
a flutter plug-in to watch 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,
),
),
);
}
}