diff --git a/.gitignore b/.gitignore index 8393c0e..f81af0c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ app.*.map.json /android/app/profile /android/app/release -# iOS related +# Platform-specific files ios/ android/ web/ diff --git a/example/.gitignore b/example/.gitignore index 26d1ffb..46d0fec 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -45,3 +45,8 @@ app.*.map.json # iOS related /ios/ + +lib/config/ +pubspec.lock +dotenv + diff --git a/example/lib/main.dart b/example/lib/main.dart index 5b37acf..b11d591 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -68,8 +68,7 @@ class _NotificationCenterDemoState extends State { @override Widget build(BuildContext context) { var config = NotificationConfig( - service: Provider.of( - context), // Use the same instance of FirebaseNotificationService + service: Provider.of(context), style: const NotificationStyle( appTitleTextStyle: TextStyle( color: Colors.black, @@ -77,8 +76,8 @@ class _NotificationCenterDemoState extends State { ), titleTextStyle: TextStyle( color: Colors.black, - fontWeight: FontWeight.w500, - fontSize: 20, + fontWeight: FontWeight.w400, + fontSize: 16, ), ), ); diff --git a/example/lib/services/firebase_notification_service.dart b/example/lib/services/firebase_notification_service.dart index 0696ea3..32b04c8 100644 --- a/example/lib/services/firebase_notification_service.dart +++ b/example/lib/services/firebase_notification_service.dart @@ -6,7 +6,8 @@ import '../config/firebase_collections.dart'; import 'package:flutter_notification_center/src/models/notification.dart'; import 'package:flutter_notification_center/src/services/notification_service.dart'; -class FirebaseNotificationService with ChangeNotifier +class FirebaseNotificationService + with ChangeNotifier implements NotificationService { @override List listOfActiveNotifications; @@ -39,11 +40,7 @@ class FirebaseNotificationService with ChangeNotifier Map notificationMap = notification.toMap(); await notifications.doc(notification.id).set(notificationMap); - print('--- Trying to notify listeners ---'); listOfActiveNotifications.add(notification); - listOfActiveNotifications.forEach((notification) { - print('Notification ID: ${notification.id}'); - }); notifyListeners(); } catch (e) { debugPrint('Error creating document: $e'); @@ -67,12 +64,7 @@ class FirebaseNotificationService with ChangeNotifier }).toList(); listOfActiveNotifications = activeNotifications; - print('--- Trying to notify listeners ---'); - listOfActiveNotifications.forEach((notification) { - print('Notification ID: ${notification.id}'); - }); notifyListeners(); - return listOfActiveNotifications; } catch (e) { debugPrint('Error getting active notifications: $e'); @@ -151,6 +143,9 @@ class FirebaseNotificationService with ChangeNotifier .collection(FirebaseCollectionNames.active_notifications) .doc(notificationModel.id); await documentReference.delete(); + listOfActiveNotifications + .removeAt(listOfActiveNotifications.indexOf(notificationModel)); + notifyListeners(); } catch (e) { debugPrint('Error deleting document: $e'); } @@ -164,6 +159,8 @@ class FirebaseNotificationService with ChangeNotifier .collection(FirebaseCollectionNames.active_notifications) .doc(notificationModel.id); await documentReference.update({'isRead': true}); + notificationModel.isRead = true; + notifyListeners(); } catch (e) { debugPrint('Error updating document: $e'); } diff --git a/lib/flutter_notification_center.dart b/lib/flutter_notification_center.dart index 32902ff..bc844f4 100644 --- a/lib/flutter_notification_center.dart +++ b/lib/flutter_notification_center.dart @@ -2,13 +2,11 @@ // // SPDX-License-Identifier: BSD-3-Clause -library notification_center; - -export 'package:flutter_notification_center/src/services/notification_service.dart'; -export 'package:flutter_notification_center/src/notification_center.dart'; -export 'package:flutter_notification_center/src/models/notification.dart'; -export 'package:flutter_notification_center/src/models/notification_theme.dart'; -export 'package:flutter_notification_center/src/models/notification_config.dart'; -export 'package:flutter_notification_center/src/models/notification_translation.dart'; -export 'package:flutter_notification_center/src/notification_bell.dart'; -export 'package:flutter_notification_center/src/notification_bell_story.dart'; +export "package:flutter_notification_center/src/models/notification.dart"; +export "package:flutter_notification_center/src/models/notification_config.dart"; +export "package:flutter_notification_center/src/models/notification_theme.dart"; +export "package:flutter_notification_center/src/models/notification_translation.dart"; +export "package:flutter_notification_center/src/notification_bell.dart"; +export "package:flutter_notification_center/src/notification_bell_story.dart"; +export "package:flutter_notification_center/src/notification_center.dart"; +export "package:flutter_notification_center/src/services/notification_service.dart"; diff --git a/lib/src/models/notification.dart b/lib/src/models/notification.dart index 350f204..2461781 100644 --- a/lib/src/models/notification.dart +++ b/lib/src/models/notification.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import "package:flutter/material.dart"; /// Enum representing the interval at which notifications occur. enum OcurringInterval { @@ -17,6 +17,52 @@ enum OcurringInterval { /// Model class representing a notification. class NotificationModel { + /// Constructs a new NotificationModel instance. + /// + /// [id]: Unique identifier for the notification. + /// [title]: Title of the notification. + /// [body]: Body content of the notification. + /// [dateTimePushed]: Date and time when the notification was pushed. + /// [scheduledFor]: Date and time when the notification is scheduled for. + /// [recurring]: Indicates if the notification is recurring. + /// [occuringInterval]: Interval at which the notification occurs, + /// applicable if it's recurring. + /// [isPinned]: Indicates if the notification is pinned. + /// [isRead]: Indicates if the notification has been read. + NotificationModel({ + required this.id, + required this.title, + required this.body, + this.dateTimePushed, + this.scheduledFor, + this.recurring = false, + this.occuringInterval, + this.isPinned = false, + this.isRead = false, + this.icon = Icons.notifications, + }); + + /// Method to create a NotificationModel object from JSON data + NotificationModel.fromJson(Map json) + : id = json["id"], + title = json["title"], + body = json["body"], + dateTimePushed = json["dateTimePushed"] != null + ? DateTime.parse(json["dateTimePushed"]) + : null, + scheduledFor = json["scheduledFor"] != null + ? DateTime.parse(json["scheduledFor"]) + : null, + recurring = json["recurring"] ?? false, + occuringInterval = json["occuringInterval"] != null + ? OcurringInterval.values[json["occuringInterval"]] + : null, + isPinned = json["isPinned"] ?? false, + isRead = json["isRead"] ?? false, + icon = json["icon"] != null + ? IconData(json["icon"], fontFamily: Icons.notifications.fontFamily) + : Icons.notifications; + /// Unique identifier for the notification. final String id; @@ -42,80 +88,31 @@ class NotificationModel { final bool isPinned; /// Indicates if the notification has been read. - final bool isRead; + bool isRead; /// Icon to be displayed with the notification. final IconData icon; - /// Constructs a new NotificationModel instance. - /// - /// [id]: Unique identifier for the notification. - /// [title]: Title of the notification. - /// [body]: Body content of the notification. - /// [dateTimePushed]: Date and time when the notification was pushed. - /// [scheduledFor]: Date and time when the notification is scheduled for. - /// [recurring]: Indicates if the notification is recurring. - /// [occuringInterval]: Interval at which the notification occurs, applicable if it's recurring. - /// [isPinned]: Indicates if the notification is pinned. - /// [isRead]: Indicates if the notification has been read. - NotificationModel({ - required this.id, - required this.title, - required this.body, - this.dateTimePushed, - this.scheduledFor, - this.recurring = false, - this.occuringInterval, - this.isPinned = false, - this.isRead = false, - this.icon = Icons.notifications, - }); - /// Override toString() to provide custom string representation @override - String toString() { - return 'NotificationModel{id: $id, title: $title, body: $body, dateTimePushed: $dateTimePushed, scheduledFor: $scheduledFor, recurring: $recurring, occuringInterval: $occuringInterval, isPinned: $isPinned, icon: $icon}'; - } - - /// Method to create a NotificationModel object from JSON data - static NotificationModel fromJson(Map json) { - return NotificationModel( - id: json['id'], - title: json['title'], - body: json['body'], - dateTimePushed: json['dateTimePushed'] != null - ? DateTime.parse(json['dateTimePushed']) - : null, - scheduledFor: json['scheduledFor'] != null - ? DateTime.parse(json['scheduledFor']) - : null, - recurring: json['recurring'] ?? false, - occuringInterval: json['occuringInterval'] != null - ? OcurringInterval.values[json['occuringInterval']] - : null, - isPinned: json['isPinned'] ?? false, - isRead: json['isRead'] ?? false, - icon: json['icon'] != null - ? IconData(json['icon'], fontFamily: Icons.notifications.fontFamily) - : Icons.notifications, - ); - } + String toString() => "NotificationModel{id: $id, title: $title, body: $body, " + "dateTimePushed: $dateTimePushed, scheduledFor: $scheduledFor, " + "recurring: $recurring, occuringInterval: $occuringInterval, " + "isPinned: $isPinned, icon: $icon}"; /// Convert the NotificationModel object to a Map. - Map toMap() { - return { - 'id': id, - 'title': title, - 'body': body, - 'dateTimePushed': dateTimePushed?.toIso8601String(), - 'scheduledFor': scheduledFor?.toIso8601String(), - 'recurring': recurring, - 'occuringInterval': occuringInterval?.index, - 'isPinned': isPinned, - 'isRead': isRead, - 'icon': icon.codePoint, - }; - } + Map toMap() => { + "id": id, + "title": title, + "body": body, + "dateTimePushed": dateTimePushed?.toIso8601String(), + "scheduledFor": scheduledFor?.toIso8601String(), + "recurring": recurring, + "occuringInterval": occuringInterval?.index, + "isPinned": isPinned, + "isRead": isRead, + "icon": icon.codePoint, + }; /// Create a copy of the NotificationModel with some fields replaced. NotificationModel copyWith({ @@ -129,18 +126,17 @@ class NotificationModel { bool? isPinned, bool? isRead, IconData? icon, - }) { - return NotificationModel( - id: id ?? this.id, - title: title ?? this.title, - body: body ?? this.body, - dateTimePushed: dateTimePushed ?? this.dateTimePushed, - scheduledFor: scheduledFor ?? this.scheduledFor, - recurring: recurring ?? this.recurring, - occuringInterval: occuringInterval ?? this.occuringInterval, - isPinned: isPinned ?? this.isPinned, - isRead: isRead ?? this.isRead, - icon: icon ?? this.icon, - ); - } + }) => + NotificationModel( + id: id ?? this.id, + title: title ?? this.title, + body: body ?? this.body, + dateTimePushed: dateTimePushed ?? this.dateTimePushed, + scheduledFor: scheduledFor ?? this.scheduledFor, + recurring: recurring ?? this.recurring, + occuringInterval: occuringInterval ?? this.occuringInterval, + isPinned: isPinned ?? this.isPinned, + isRead: isRead ?? this.isRead, + icon: icon ?? this.icon, + ); } diff --git a/lib/src/models/notification_config.dart b/lib/src/models/notification_config.dart index 30bb78d..1b95d48 100644 --- a/lib/src/models/notification_config.dart +++ b/lib/src/models/notification_config.dart @@ -1,17 +1,7 @@ -import 'package:flutter_notification_center/flutter_notification_center.dart'; -import 'package:flutter_notification_center/src/models/notification_translation.dart'; +import "package:flutter_notification_center/flutter_notification_center.dart"; /// Configuration class for notifications. class NotificationConfig { - /// The notification service to use for delivering notifications. - final NotificationService service; - - /// The style of the notification. - final NotificationStyle style; - - /// Translations for notification messages. - final NotificationTranslations translations; - /// Creates a new [NotificationConfig] instance. /// /// The [service] parameter is required and specifies the notification service @@ -23,4 +13,13 @@ class NotificationConfig { this.style = const NotificationStyle(), this.translations = const NotificationTranslations(), }); + + /// The notification service to use for delivering notifications. + final NotificationService service; + + /// The style of the notification. + final NotificationStyle style; + + /// Translations for notification messages. + final NotificationTranslations translations; } diff --git a/lib/src/models/notification_theme.dart b/lib/src/models/notification_theme.dart index 9db994b..a954952 100644 --- a/lib/src/models/notification_theme.dart +++ b/lib/src/models/notification_theme.dart @@ -1,7 +1,28 @@ -import 'package:flutter/material.dart'; +import "package:flutter/material.dart"; /// Defines the appearance customization for notifications. class NotificationStyle { + /// Creates a new [NotificationStyle] instance. + /// + /// Each parameter is optional and allows customizing various aspects + /// of the notification appearance. + const NotificationStyle({ + this.titleTextStyle, + this.subtitleTextStyle, + this.backgroundColor, + this.leadingIconColor, + this.trailingIconColor, + this.contentPadding, + this.titleTextAlign, + this.subtitleTextAlign, + this.dense, + this.tileDecoration, + this.emptyNotificationsBuilder, + this.appTitleTextStyle, + this.dividerColor, + this.isReadDotColor, + }); + /// The text style for the title of the notification. final TextStyle? titleTextStyle; @@ -38,22 +59,9 @@ class NotificationStyle { /// The text style for the application title. final TextStyle? appTitleTextStyle; - /// Creates a new [NotificationStyle] instance. - /// - /// Each parameter is optional and allows customizing various aspects - /// of the notification appearance. - const NotificationStyle({ - this.titleTextStyle, - this.subtitleTextStyle, - this.backgroundColor, - this.leadingIconColor, - this.trailingIconColor, - this.contentPadding, - this.titleTextAlign, - this.subtitleTextAlign, - this.dense, - this.tileDecoration, - this.emptyNotificationsBuilder, - this.appTitleTextStyle, - }); + /// The color of the divider. + final Color? dividerColor; + + /// The color of the dot indicating if the notification is read. + final Color? isReadDotColor; } diff --git a/lib/src/models/notification_translation.dart b/lib/src/models/notification_translation.dart index eb1af68..cc7ef2d 100644 --- a/lib/src/models/notification_translation.dart +++ b/lib/src/models/notification_translation.dart @@ -1,21 +1,23 @@ /// Defines translations for notification messages. class NotificationTranslations { - /// The title to be displayed in the app bar of the notification center. - final String appBarTitle; - - /// The message to be displayed when there are no unread notifications available. - final String noNotifications; - /// Creates a new [NotificationTranslations] instance. /// /// The [appBarTitle] parameter specifies the title to be displayed in the - /// app bar of the notification center. The default value is 'Notification Center'. + /// app bar of the notification center. The default value + /// is 'Notification Center'. /// /// The [noNotifications] parameter specifies the message to be displayed when /// there are no unread notifications available. The default value is /// 'No unread notifications available.'. const NotificationTranslations({ - this.appBarTitle = 'Notification Center', - this.noNotifications = 'No unread notifications available.', + this.appBarTitle = "Notification Center", + this.noNotifications = "No unread notifications available.", }); + + /// The title to be displayed in the app bar of the notification center. + final String appBarTitle; + + /// The message to be displayed when there are no unread + /// notifications available. + final String noNotifications; } diff --git a/lib/src/notification_bell.dart b/lib/src/notification_bell.dart index 31fb0ba..b45c2ea 100644 --- a/lib/src/notification_bell.dart +++ b/lib/src/notification_bell.dart @@ -1,15 +1,30 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_animated_widgets/flutter_animated_widgets.dart'; -import 'package:flutter_notification_center/flutter_notification_center.dart'; +import "package:flutter/material.dart"; +import "package:flutter_animated_widgets/flutter_animated_widgets.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; +/// A bell icon widget that displays the number of active notifications. +/// +/// This widget displays a bell icon with an animation indicating the number +/// of active notifications. It interacts with the notification service provided +/// in the [config] to fetch the active notifications and update its display +/// accordingly. class NotificationBell extends StatefulWidget { + /// Constructs a NotificationBell widget. + /// + /// [config]: The notification configuration used to interact with the + /// notification service. + /// [onTap]: Callback function to be invoked when the bell icon is tapped. const NotificationBell({ required this.config, this.onTap, super.key, }); + /// The notification configuration used to interact with + /// the notification service. final NotificationConfig config; + + /// Callback function to be invoked when the bell icon is tapped. final VoidCallback? onTap; @override @@ -17,12 +32,14 @@ class NotificationBell extends StatefulWidget { } class _NotificationBellState extends State { - var notificationAmount = 0; + /// The number of active notifications. + int notificationAmount = 0; @override void initState() { super.initState(); + // Fetch active notifications and update the notification count WidgetsBinding.instance.addPostFrameCallback((_) async { var amount = await widget.config.service.getActiveNotifications(); @@ -33,14 +50,12 @@ class _NotificationBellState extends State { } @override - Widget build(BuildContext context) { - return IconButton( - onPressed: widget.onTap, - icon: AnimatedNotificationBell( - duration: const Duration(seconds: 1), - notificationCount: notificationAmount, - notificationIconSize: 45, - ), - ); - } + Widget build(BuildContext context) => IconButton( + onPressed: widget.onTap, + icon: AnimatedNotificationBell( + duration: const Duration(seconds: 1), + notificationCount: notificationAmount, + notificationIconSize: 45, + ), + ); } diff --git a/lib/src/notification_bell_story.dart b/lib/src/notification_bell_story.dart index 1c8b2c9..a279144 100644 --- a/lib/src/notification_bell_story.dart +++ b/lib/src/notification_bell_story.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_notification_center/flutter_notification_center.dart'; +import "package:flutter/material.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; /// A widget representing a notification bell. class NotificationBellWidgetStory extends StatelessWidget { @@ -8,25 +8,23 @@ class NotificationBellWidgetStory extends StatelessWidget { /// The [config] parameter specifies the notification configuration. const NotificationBellWidgetStory({ required this.config, - Key? key, - }) : super(key: key); + super.key, + }); /// The notification configuration. final NotificationConfig config; @override - Widget build(BuildContext context) { - return NotificationBell( - config: config, - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => NotificationCenter( - config: config, + Widget build(BuildContext context) => NotificationBell( + config: config, + onTap: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => NotificationCenter( + config: config, + ), ), - ), - ); - }, - ); - } + ); + }, + ); } diff --git a/lib/src/notification_center.dart b/lib/src/notification_center.dart index 12e42eb..b713e22 100644 --- a/lib/src/notification_center.dart +++ b/lib/src/notification_center.dart @@ -1,199 +1,193 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_notification_center/flutter_notification_center.dart'; -import 'package:flutter_notification_center/src/notification_detail.dart'; -import 'package:intl/intl.dart'; +import "package:flutter/material.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; +import "package:flutter_notification_center/src/notification_detail.dart"; /// Widget for displaying the notification center. class NotificationCenter extends StatefulWidget { - /// Configuration for the notification center. - final NotificationConfig config; - /// Constructs a new NotificationCenter instance. /// /// [config]: Configuration for the notification center. const NotificationCenter({ required this.config, - Key? key, - }) : super(key: key); + super.key, + }); + + /// Configuration for the notification center. + final NotificationConfig config; @override - _NotificationCenterState createState() => _NotificationCenterState(); + NotificationCenterState createState() => NotificationCenterState(); } -class _NotificationCenterState extends State { +/// State for the notification center. +class NotificationCenterState extends State { late Future> _notificationsFuture; @override void initState() { super.initState(); + // ignore: discarded_futures _notificationsFuture = widget.config.service.getActiveNotifications(); + widget.config.service.addListener(_listener); } @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text( - widget.config.translations.appBarTitle, - style: widget.config.style.appTitleTextStyle, + void dispose() { + widget.config.service.removeListener(_listener); + super.dispose(); + } + + void _listener() { + setState(() {}); + } + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: Text( + widget.config.translations.appBarTitle, + style: widget.config.style.appTitleTextStyle, + ), + centerTitle: true, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.pop(context); + }, + ), ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.pop(context); - }, - ), - ), - body: FutureBuilder>( - future: _notificationsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } else if (snapshot.data == null || snapshot.data!.isEmpty) { - return const Center( - child: Text('No unread notifications available.')); - } else { - return ListView.builder( - itemCount: snapshot.data!.length, + body: FutureBuilder>( + future: _notificationsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text("Error: ${snapshot.error}")); + } else if (snapshot.data == null || snapshot.data!.isEmpty) { + return const Center( + child: Text("No unread notifications available."), + ); + } else { + return ListView.builder( + itemCount: snapshot.data!.length * 2 - + 1, // Double the itemCount to include dividers itemBuilder: (context, index) { - final notification = snapshot.data![index]; - final formattedDateTime = notification.dateTimePushed != null - ? DateFormat('yyyy-MM-dd HH:mm') - .format(notification.dateTimePushed!) - : 'Pending'; - return notification.isPinned - ? GestureDetector( - onTap: () => - _navigateToNotificationDetail(notification), - child: Container( + if (index.isOdd) { + // If index is odd, return a Divider with padding + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 24.0), + child: Divider( + color: Colors.grey, // Customize as needed + thickness: 1.0, // Customize thickness as needed + ), + ); + } + var notification = snapshot.data![index ~/ 2]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: notification.isPinned + ? GestureDetector( + onTap: () async => + _navigateToNotificationDetail(notification), child: ListTile( - leading: Icon(notification.icon, - color: widget.config.style.leadingIconColor), - title: _buildNotificationTitle(notification.title, - widget.config.style.titleTextStyle), - subtitle: _buildNotificationSubtitle( - notification.body, - formattedDateTime, - notification.isRead), + leading: Icon( + notification.icon, + color: widget.config.style.leadingIconColor, + ), + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + notification.title, + style: widget.config.style.titleTextStyle, + ), + ), + ], + ), trailing: IconButton( icon: const Icon(Icons.push_pin), color: widget.config.style.trailingIconColor, - onPressed: () => + onPressed: () async => _navigateToNotificationDetail(notification), ), ), - ), - ) - : Dismissible( - key: Key(notification.id), - onDismissed: (direction) { - setState(() { - widget.config.service + ) + : Dismissible( + key: Key(notification.id), + onDismissed: (direction) async { + await widget.config.service .dismissActiveNotification(notification); - _refreshNotifications(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Notification dismissed'), + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Notification dismissed"), + ), + ); + }, + background: Container( + color: Colors.red, + alignment: Alignment.centerRight, + child: const Icon( + Icons.delete, + color: Colors.white, ), - ); - }, - background: Container( - color: Colors.red, - alignment: Alignment.centerRight, - child: Icon( - Icons.delete, - color: Colors.white, ), - ), - child: GestureDetector( - onTap: () => - _navigateToNotificationDetail(notification), - child: Container( + child: GestureDetector( + onTap: () async => + _navigateToNotificationDetail(notification), child: ListTile( leading: Icon( Icons.notification_important, color: widget.config.style.leadingIconColor, ), - title: _buildNotificationTitle( - notification.title, - widget.config.style.titleTextStyle), - subtitle: _buildNotificationSubtitle( - notification.body, - formattedDateTime, - notification.isRead), + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + notification.title, + style: + widget.config.style.titleTextStyle, + ), + ), + ], + ), + trailing: !notification.isRead + ? Container( + margin: + const EdgeInsets.only(left: 4.0), + width: 12.0, + height: 12.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.config.style + .isReadDotColor ?? + Colors.red, + ), + ) + : null, ), ), ), - ); - }); - } - }, - ), - floatingActionButton: FloatingActionButton( - onPressed: _addNewNotification, - child: const Icon(Icons.add), - ), - ); - } - - Widget _buildNotificationTitle(String title, TextStyle? textStyle) { - return Text( - title, - style: textStyle ?? const TextStyle(), - ); - } - - Widget _buildNotificationSubtitle( - String body, String formattedDateTime, bool isRead) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - body, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - Text( - formattedDateTime, - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), + ); + }, + ); + } + }, ), - if (!isRead) - Container( - margin: const EdgeInsets.only(left: 4.0), - width: 12.0, - height: 12.0, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.red, - ), - ), - ], - ); - } + floatingActionButton: FloatingActionButton( + onPressed: _addNewNotification, + child: const Icon(Icons.add), + ), + ); - void _refreshNotifications() { - setState(() { - _notificationsFuture = widget.config.service.getActiveNotifications(); - }); - } - - void _navigateToNotificationDetail(NotificationModel notification) { - widget.config.service.markNotificationAsRead(notification); - Navigator.push( + Future _navigateToNotificationDetail( + NotificationModel notification, + ) async { + await widget.config.service.markNotificationAsRead(notification); + await Navigator.push( + // ignore: use_build_context_synchronously context, MaterialPageRoute( builder: (context) => NotificationDetailPage( @@ -204,16 +198,14 @@ class _NotificationCenterState extends State { ); } - void _addNewNotification() { - widget.config.service.pushNotification( + Future _addNewNotification() async { + await widget.config.service.pushNotification( NotificationModel( id: UniqueKey().toString(), title: UniqueKey().toString(), - isPinned: true, icon: Icons.notifications_active, - body: 'This is a new notification', + body: "This is a new notification", ), ); - _refreshNotifications(); } } diff --git a/lib/src/notification_detail.dart b/lib/src/notification_detail.dart index 5b03f6c..78dc671 100644 --- a/lib/src/notification_detail.dart +++ b/lib/src/notification_detail.dart @@ -1,63 +1,64 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_notification_center/flutter_notification_center.dart'; -import 'package:intl/intl.dart'; +import "package:flutter/material.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; +import "package:intl/intl.dart"; /// A page displaying the details of a notification. class NotificationDetailPage extends StatelessWidget { + /// Creates a new [NotificationDetailPage] instance. + /// + /// The [config] parameter specifies the notification configuration. + /// + /// The [notification] parameter specifies the notification + /// to display details for. + const NotificationDetailPage({ + required this.config, + required this.notification, + super.key, + }); + /// The notification configuration. final NotificationConfig config; /// The notification to display details for. final NotificationModel notification; - /// Creates a new [NotificationDetailPage] instance. - /// - /// The [config] parameter specifies the notification configuration. - /// - /// The [notification] parameter specifies the notification to display details for. - const NotificationDetailPage({ - required this.config, - required this.notification, - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text( - config.translations.appBarTitle, - style: config.style.appTitleTextStyle, + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + title: Text( + config.translations.appBarTitle, + style: config.style.appTitleTextStyle, + ), + centerTitle: true, ), - centerTitle: true, - ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - notification.title, - style: config.style.titleTextStyle ?? const TextStyle(), - ), - const SizedBox(height: 10), - Text( - notification.body, - style: config.style.subtitleTextStyle ?? const TextStyle(), - ), - const SizedBox(height: 10), - Text( - 'Date: ${DateFormat('yyyy-MM-dd HH:mm').format(notification.dateTimePushed ?? DateTime.now())}', - style: TextStyle( - fontSize: 12, - color: Colors.grey, + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + notification.title, + style: config.style.titleTextStyle ?? const TextStyle(), ), - ), - ], + const SizedBox(height: 10), + Text( + notification.body, + style: config.style.subtitleTextStyle ?? const TextStyle(), + ), + const SizedBox(height: 10), + Text( + 'Date: ${DateFormat('yyyy-MM-dd HH:mm').format( + notification.dateTimePushed ?? DateTime.now(), + )}', + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), ), ), - ), - ); - } + ); } diff --git a/lib/src/services/notification_service.dart b/lib/src/services/notification_service.dart index ee39b21..4464606 100644 --- a/lib/src/services/notification_service.dart +++ b/lib/src/services/notification_service.dart @@ -1,27 +1,30 @@ -import 'dart:async'; +import "dart:async"; -import 'package:flutter_notification_center/src/models/notification.dart'; +import "package:flutter/material.dart"; +import "package:flutter_notification_center/src/models/notification.dart"; /// An abstract class representing a service for managing notifications. -abstract class NotificationService { - /// A list of active notifications. - List listOfActiveNotifications; - - /// A list of planned notifications. - List listOfPlannedNotifications; - +abstract class NotificationService with ChangeNotifier { /// Creates a new [NotificationService] instance. /// - /// The [listOfActiveNotifications] parameter specifies the list of active notifications, + /// The [listOfActiveNotifications] parameter specifies the + /// list of active notifications, /// with a default value of an empty list. /// - /// The [listOfPlannedNotifications] parameter specifies the list of planned notifications, + /// The [listOfPlannedNotifications] parameter specifies the + /// list of planned notifications, /// with a default value of an empty list. NotificationService({ this.listOfActiveNotifications = const [], this.listOfPlannedNotifications = const [], }); + /// A list of active notifications. + List listOfActiveNotifications; + + /// A list of planned notifications. + List listOfPlannedNotifications; + /// Pushes a notification to the service. Future pushNotification(NotificationModel notification);