diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d54bc..b23aafd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [2.0.0] - 6 June 2024 + +* Rework design for notification center +* Added iconica linter + ## [1.4.1] - 4 June 2024 * Fix notification amount number to properly size and show plus icon when above certain amount diff --git a/packages/flutter_notification_center/analysis_options.yaml b/packages/flutter_notification_center/analysis_options.yaml index 0d29021..31b4b51 100644 --- a/packages/flutter_notification_center/analysis_options.yaml +++ b/packages/flutter_notification_center/analysis_options.yaml @@ -1,28 +1,9 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. +include: package:flutter_iconica_analysis/analysis_options.yaml -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +# Possible to overwrite the rules from the package + +analyzer: + exclude: linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_notification_center/example/analysis_options.yaml b/packages/flutter_notification_center/example/analysis_options.yaml index 0d29021..0439848 100644 --- a/packages/flutter_notification_center/example/analysis_options.yaml +++ b/packages/flutter_notification_center/example/analysis_options.yaml @@ -23,6 +23,5 @@ linter: rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_notification_center/example/lib/main.dart b/packages/flutter_notification_center/example/lib/main.dart index 7c904d0..39ac1cf 100644 --- a/packages/flutter_notification_center/example/lib/main.dart +++ b/packages/flutter_notification_center/example/lib/main.dart @@ -82,7 +82,6 @@ class _NotificationCenterDemoState extends State { notificationTranslations: const NotificationTranslations.empty(), context: context, ), - seperateNotificationsWithDivider: true, ); popupHandler = PopupHandler(context: context, config: config); } diff --git a/packages/flutter_notification_center/example/pubspec.yaml b/packages/flutter_notification_center/example/pubspec.yaml index 8344608..610a6c8 100644 --- a/packages/flutter_notification_center/example/pubspec.yaml +++ b/packages/flutter_notification_center/example/pubspec.yaml @@ -2,12 +2,12 @@ name: example description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0 environment: - sdk: '>=3.3.2 <4.0.0' + sdk: ">=3.3.2 <4.0.0" dependencies: flutter: @@ -17,20 +17,18 @@ dependencies: flutter_notification_center: git: url: https://github.com/Iconica-Development/flutter_notification_center - ref: 1.4.0 path: packages/flutter_notification_center + ref: 2.0.0 flutter_notification_center_firebase: git: url: https://github.com/Iconica-Development/flutter_notification_center - ref: 1.4.0 path: packages/flutter_notification_center_firebase - - + ref: 2.0.0 dev_dependencies: flutter_test: - sdk: flutter + sdk: flutter flutter_iconica_analysis: git: url: https://github.com/Iconica-Development/flutter_iconica_analysis @@ -39,4 +37,3 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: uses-material-design: true - diff --git a/packages/flutter_notification_center/lib/flutter_notification_center.dart b/packages/flutter_notification_center/lib/flutter_notification_center.dart index 2b24131..12bb3cf 100644 --- a/packages/flutter_notification_center/lib/flutter_notification_center.dart +++ b/packages/flutter_notification_center/lib/flutter_notification_center.dart @@ -2,16 +2,17 @@ // // SPDX-License-Identifier: BSD-3-Clause +export "package:flutter_animated_widgets/flutter_animated_widgets.dart"; + export "src/models/notification.dart"; export "src/models/notification_config.dart"; export "src/models/notification_theme.dart"; export "src/models/notification_translation.dart"; export "src/notification_bell.dart"; -export "src/notification_dialog.dart"; -export "src/popup_handler.dart"; -export "src/notification_snackbar.dart"; -export "src/notification_detail.dart"; export "src/notification_bell_story.dart"; export "src/notification_center.dart"; +export "src/notification_detail.dart"; +export "src/notification_dialog.dart"; +export "src/notification_snackbar.dart"; +export "src/popup_handler.dart"; export "src/services/notification_service.dart"; -export "package:flutter_animated_widgets/flutter_animated_widgets.dart"; diff --git a/packages/flutter_notification_center/lib/src/models/notification_config.dart b/packages/flutter_notification_center/lib/src/models/notification_config.dart index e09070d..9dd970a 100644 --- a/packages/flutter_notification_center/lib/src/models/notification_config.dart +++ b/packages/flutter_notification_center/lib/src/models/notification_config.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; +import "package:flutter/material.dart"; -import '../../flutter_notification_center.dart'; +import "package:flutter_notification_center/flutter_notification_center.dart"; /// Configuration class for notifications. class NotificationConfig { @@ -12,7 +12,6 @@ class NotificationConfig { /// translations for notification messages. const NotificationConfig({ required this.service, - this.seperateNotificationsWithDivider = true, this.translations = const NotificationTranslations.empty(), this.notificationWidgetBuilder, this.showAsSnackBar = true, @@ -23,9 +22,6 @@ class NotificationConfig { /// The notification service to use for delivering notifications. final NotificationService service; - /// Whether to seperate notifications with a divider. - final bool seperateNotificationsWithDivider; - /// Translations for notification messages. final NotificationTranslations translations; @@ -33,7 +29,8 @@ class NotificationConfig { final Widget Function(NotificationModel, BuildContext)? notificationWidgetBuilder; - /// Whether to show notifications as snackbars. If false show notifications as a dialog. + /// Whether to show notifications as snackbars. + /// If false show notifications as a dialog. final bool showAsSnackBar; /// Whether to show notification popups. diff --git a/packages/flutter_notification_center/lib/src/models/notification_translation.dart b/packages/flutter_notification_center/lib/src/models/notification_translation.dart index dc7602d..5290f33 100644 --- a/packages/flutter_notification_center/lib/src/models/notification_translation.dart +++ b/packages/flutter_notification_center/lib/src/models/notification_translation.dart @@ -14,7 +14,7 @@ class NotificationTranslations { }); const NotificationTranslations.empty({ - this.appBarTitle = "Notification Center", + this.appBarTitle = "Notifications", this.noNotifications = "No unread notifications available.", this.notificationDismissed = "Notification dismissed.", this.notificationPinned = "Notification pinned.", @@ -63,18 +63,17 @@ class NotificationTranslations { String? datePrefix, String? notAvailable, String? dissmissDialog, - }) { - return NotificationTranslations( - appBarTitle: appBarTitle ?? this.appBarTitle, - noNotifications: noNotifications ?? this.noNotifications, - notificationDismissed: - notificationDismissed ?? this.notificationDismissed, - notificationPinned: notificationPinned ?? this.notificationPinned, - notificationUnpinned: notificationUnpinned ?? this.notificationUnpinned, - errorMessage: errorMessage ?? this.errorMessage, - datePrefix: datePrefix ?? this.datePrefix, - notAvailable: notAvailable ?? this.notAvailable, - dissmissDialog: dissmissDialog ?? this.dissmissDialog, - ); - } + }) => + NotificationTranslations( + appBarTitle: appBarTitle ?? this.appBarTitle, + noNotifications: noNotifications ?? this.noNotifications, + notificationDismissed: + notificationDismissed ?? this.notificationDismissed, + notificationPinned: notificationPinned ?? this.notificationPinned, + notificationUnpinned: notificationUnpinned ?? this.notificationUnpinned, + errorMessage: errorMessage ?? this.errorMessage, + datePrefix: datePrefix ?? this.datePrefix, + notAvailable: notAvailable ?? this.notAvailable, + dissmissDialog: dissmissDialog ?? this.dissmissDialog, + ); } diff --git a/packages/flutter_notification_center/lib/src/notification_bell.dart b/packages/flutter_notification_center/lib/src/notification_bell.dart index ebaf229..356286e 100644 --- a/packages/flutter_notification_center/lib/src/notification_bell.dart +++ b/packages/flutter_notification_center/lib/src/notification_bell.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "../flutter_notification_center.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; /// A bell icon widget that displays the number of active notifications. /// diff --git a/packages/flutter_notification_center/lib/src/notification_bell_story.dart b/packages/flutter_notification_center/lib/src/notification_bell_story.dart index f2aeab7..a279144 100644 --- a/packages/flutter_notification_center/lib/src/notification_bell_story.dart +++ b/packages/flutter_notification_center/lib/src/notification_bell_story.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "../flutter_notification_center.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; /// A widget representing a notification bell. class NotificationBellWidgetStory extends StatelessWidget { diff --git a/packages/flutter_notification_center/lib/src/notification_center.dart b/packages/flutter_notification_center/lib/src/notification_center.dart index 2378155..b0ffb70 100644 --- a/packages/flutter_notification_center/lib/src/notification_center.dart +++ b/packages/flutter_notification_center/lib/src/notification_center.dart @@ -1,5 +1,6 @@ -import 'package:flutter/material.dart'; -import '../flutter_notification_center.dart'; +import "package:flutter/material.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; +import "package:intl/intl.dart"; /// Widget for displaying the notification center. class NotificationCenter extends StatefulWidget { @@ -27,7 +28,7 @@ class NotificationCenterState extends State { super.initState(); // ignore: discarded_futures _notificationsFuture = widget.config.service.getActiveNotifications(); - widget.config.service.getActiveAmountStream().listen((amount) { + widget.config.service.getActiveAmountStream().listen((amount) async { _notificationsFuture = widget.config.service.getActiveNotifications(); }); widget.config.service.addListener(_listener); @@ -44,227 +45,269 @@ class NotificationCenterState extends State { } @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text( - widget.config.translations.appBarTitle, - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.pop(context); - }, - ), + Widget build(BuildContext context) { + var theme = Theme.of(context); + return Scaffold( + backgroundColor: theme.colorScheme.surface, + appBar: AppBar( + backgroundColor: theme.appBarTheme.backgroundColor, + title: Text( + widget.config.translations.appBarTitle, + style: theme.appBarTheme.titleTextStyle, ), - body: FutureBuilder>( - future: _notificationsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - debugPrint("Error: ${snapshot.error}"); - return Center( - child: Text(widget.config.translations.errorMessage)); - } else if (snapshot.data == null || snapshot.data!.isEmpty) { - return Center( - child: Text(widget.config.translations.noNotifications), - ); - } else { - return ListView.builder( - itemCount: snapshot.data!.length * 2 - 1, - itemBuilder: (context, index) { - if (index.isOdd) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: widget.config.seperateNotificationsWithDivider - ? const Divider( - color: Colors.grey, - thickness: 1.0, - ) - : Container(), - ); - } - var notification = snapshot.data![index ~/ 2]; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: widget.config.notificationWidgetBuilder != null - ? widget.config.notificationWidgetBuilder!( - notification, context) - : notification.isPinned - //Pinned notification - ? Dismissible( - key: Key('${notification.id}_pinned'), - onDismissed: (direction) async { - await unPinNotification( - widget.config.service, - notification, - widget.config.translations, - context); - }, - background: Container( - color: - const Color.fromRGBO(59, 213, 111, 1), - alignment: Alignment.centerLeft, - child: const Padding( - padding: EdgeInsets.only(left: 16.0), - child: Icon( - Icons.push_pin, - color: Colors.white, - ), - ), - ), - secondaryBackground: Container( - color: - const Color.fromRGBO(59, 213, 111, 1), - alignment: Alignment.centerLeft, - child: const Padding( - padding: EdgeInsets.only(left: 16.0), - child: Icon( - Icons.push_pin, - color: Colors.white, - ), - ), - ), - child: GestureDetector( - onTap: () async => - _navigateToNotificationDetail( - context, - notification, - widget.config.service, - widget.config.translations, - const NotificationStyle()), - child: ListTile( - leading: Icon( - notification.icon, - color: Colors.grey, - ), - title: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - notification.title, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.w400, - fontSize: 16, - ), - ), - ), - ], - ), - trailing: IconButton( - icon: const Icon(Icons.push_pin), - color: Colors.grey, - onPressed: () async => - _navigateToNotificationDetail( - context, - notification, - widget.config.service, - widget.config.translations, - const NotificationStyle()), - padding: - const EdgeInsets.only(left: 60.0), - ), - ), - ), - ) - //Dismissable notification - : Dismissible( - key: Key(notification.id), - onDismissed: (direction) async { - if (direction == - DismissDirection.endToStart) { - await dismissNotification( - widget.config.service, - notification, - widget.config.translations, - context); - } else if (direction == - DismissDirection.startToEnd) { - await pinNotification( - widget.config.service, - notification, - widget.config.translations, - context); - } - }, - background: Container( - color: - const Color.fromRGBO(59, 213, 111, 1), - alignment: Alignment.centerLeft, - child: const Padding( - padding: EdgeInsets.only(left: 16.0), - child: Icon( - Icons.push_pin, - color: Colors.white, - ), - ), - ), - secondaryBackground: Container( - color: - const Color.fromRGBO(255, 131, 131, 1), - alignment: Alignment.centerRight, - child: const Padding( - padding: EdgeInsets.only(right: 16.0), - child: Icon( - Icons.delete, - color: Colors.black, - ), - ), - ), - child: GestureDetector( - onTap: () async => - _navigateToNotificationDetail( - context, - notification, - widget.config.service, - widget.config.translations, - const NotificationStyle()), - child: ListTile( - leading: Icon( - notification.icon, - color: Colors.grey, - ), - title: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - notification.title, - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.w400, - fontSize: 16, - ), - ), - ), - ], - ), - trailing: !notification.isRead - ? Container( - margin: const EdgeInsets.only( - right: 8.0), - width: 12.0, - height: 12.0, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.red, - ), - ) - : null, - ), - ), - )); - }, - ); - } + centerTitle: true, + iconTheme: theme.appBarTheme.iconTheme ?? + const IconThemeData(color: Colors.white), + leading: GestureDetector( + onTap: () { + Navigator.pop(context); }, + child: const Icon( + Icons.arrow_back_ios, + ), ), - ); + ), + body: FutureBuilder>( + future: _notificationsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + debugPrint("Error: ${snapshot.error}"); + return Center(child: Text(widget.config.translations.errorMessage)); + } else if (snapshot.data == null || snapshot.data!.isEmpty) { + return Center( + child: Text(widget.config.translations.noNotifications), + ); + } else { + return ListView.builder( + padding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 20, + ), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var notification = snapshot.data![index]; + return notification.isPinned + ? GestureDetector( + onTap: () async => _navigateToNotificationDetail( + context, + notification, + widget.config.service, + widget.config.translations, + const NotificationStyle(), + ), + child: Dismissible( + key: Key("${notification.id}_pinned"), + onDismissed: (direction) async { + if (direction == DismissDirection.endToStart) { + await unPinNotification( + widget.config.service, + notification, + widget.config.translations, + context, + ); + } else if (direction == + DismissDirection.startToEnd) { + await unPinNotification( + widget.config.service, + notification, + widget.config.translations, + context, + ); + } + }, + background: Container( + decoration: const BoxDecoration( + color: Color.fromRGBO(59, 213, 111, 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(6), + bottomLeft: Radius.circular(6), + ), + ), + alignment: Alignment.centerLeft, + child: const Padding( + padding: EdgeInsets.only(left: 16.0), + child: Icon( + Icons.push_pin, + color: Colors.white, + ), + ), + ), + secondaryBackground: Container( + decoration: const BoxDecoration( + color: Color.fromRGBO(59, 213, 111, 1), + borderRadius: BorderRadius.only( + topRight: Radius.circular(6), + bottomRight: Radius.circular(6), + ), + ), + alignment: Alignment.centerRight, + child: const Padding( + padding: EdgeInsets.only(right: 16.0), + child: Icon( + Icons.push_pin, + color: Colors.white, + ), + ), + ), + child: _notificationItem( + context, + notification, + ), + ), + ) + : GestureDetector( + onTap: () async => _navigateToNotificationDetail( + context, + notification, + widget.config.service, + widget.config.translations, + const NotificationStyle(), + ), + child: Dismissible( + key: Key(notification.id), + onDismissed: (direction) async { + if (direction == DismissDirection.endToStart) { + await dismissNotification( + widget.config.service, + notification, + widget.config.translations, + context, + ); + } else if (direction == + DismissDirection.startToEnd) { + await pinNotification( + widget.config.service, + notification, + widget.config.translations, + context, + ); + } + }, + background: Container( + decoration: const BoxDecoration( + color: Color.fromRGBO(59, 213, 111, 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(6), + bottomLeft: Radius.circular(6), + ), + ), + alignment: Alignment.centerLeft, + child: const Padding( + padding: EdgeInsets.only(left: 16.0), + child: Icon( + Icons.push_pin, + color: Colors.white, + ), + ), + ), + secondaryBackground: Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Container( + decoration: const BoxDecoration( + color: Color.fromRGBO(255, 131, 131, 1), + borderRadius: BorderRadius.only( + topRight: Radius.circular(6), + bottomRight: Radius.circular(6), + ), + ), + alignment: Alignment.centerRight, + child: const Padding( + padding: EdgeInsets.only(right: 16.0), + child: Icon( + Icons.delete, + color: Colors.black, + ), + ), + ), + ), + child: _notificationItem( + context, + notification, + ), + ), + ); + }, + ); + } + }, + ), + ); + } +} + +Widget _notificationItem( + BuildContext context, + NotificationModel notification, +) { + var theme = Theme.of(context); + var dateTimePushed = + DateFormat("dd-MM-yyyy HH:mm").format(notification.dateTimePushed!); + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(6), + ), + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (!notification.isPinned) ...[ + if (!notification.isRead) ...[ + const SizedBox( + width: 8, + ), + const Icon( + Icons.circle_rounded, + color: Colors.black, + size: 10, + ), + const SizedBox( + width: 8, + ), + ], + ] else ...[ + const SizedBox( + width: 8, + ), + Transform.rotate( + angle: 0.5, + child: Icon( + notification.isRead + ? Icons.push_pin_outlined + : Icons.push_pin, + color: Colors.black, + size: 30, + ), + ), + const SizedBox( + width: 8, + ), + ], + Text( + notification.title, + style: notification.isRead + ? theme.textTheme.bodyMedium + : theme.textTheme.bodyLarge, + ), + ], + ), + Text( + dateTimePushed, + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + ), + ); } Future _navigateToNotificationDetail( @@ -274,7 +317,6 @@ Future _navigateToNotificationDetail( NotificationTranslations notificationTranslations, NotificationStyle style, ) async { - await markNotificationAsRead(notificationService, notification); if (context.mounted) { await Navigator.push( context, @@ -287,6 +329,7 @@ Future _navigateToNotificationDetail( ), ); } + await markNotificationAsRead(notificationService, notification); } Future dismissNotification( diff --git a/packages/flutter_notification_center/lib/src/notification_detail.dart b/packages/flutter_notification_center/lib/src/notification_detail.dart index 433a9e5..8e3d456 100644 --- a/packages/flutter_notification_center/lib/src/notification_detail.dart +++ b/packages/flutter_notification_center/lib/src/notification_detail.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "../flutter_notification_center.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; import "package:intl/intl.dart"; /// A page displaying the details of a notification. @@ -27,43 +27,58 @@ class NotificationDetailPage extends StatelessWidget { final NotificationTranslations translations; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text( - translations.appBarTitle, - style: notificationStyle.appTitleTextStyle, - ), - centerTitle: true, + Widget build(BuildContext context) { + var theme = Theme.of(context); + return Scaffold( + backgroundColor: theme.colorScheme.surface, + appBar: AppBar( + backgroundColor: theme.appBarTheme.backgroundColor, + title: Text( + translations.appBarTitle, + style: theme.appBarTheme.titleTextStyle, ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - notification.title, - style: notificationStyle.titleTextStyle ?? const TextStyle(), - ), - const SizedBox(height: 10), - Text( - notification.body, - style: - notificationStyle.subtitleTextStyle ?? const TextStyle(), - ), - const SizedBox(height: 10), - Text( - '${translations.datePrefix} ${DateFormat('yyyy-MM-dd HH:mm').format( - notification.dateTimePushed ?? DateTime.now(), - )}', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), + iconTheme: theme.appBarTheme.iconTheme ?? + const IconThemeData(color: Colors.white), + centerTitle: true, + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const Icon( + Icons.arrow_back_ios, ), ), - ); + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + notification.title, + style: notificationStyle.titleTextStyle ?? const TextStyle(), + ), + const SizedBox(height: 10), + Text( + notification.body, + style: notificationStyle.subtitleTextStyle ?? const TextStyle(), + ), + const SizedBox(height: 10), + Text( + "${translations.datePrefix}" + ' ${DateFormat('yyyy-MM-dd HH:mm').format( + notification.dateTimePushed ?? DateTime.now(), + )}', + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), + ), + ), + ); + } } diff --git a/packages/flutter_notification_center/lib/src/notification_dialog.dart b/packages/flutter_notification_center/lib/src/notification_dialog.dart index fcdffba..caf3314 100644 --- a/packages/flutter_notification_center/lib/src/notification_dialog.dart +++ b/packages/flutter_notification_center/lib/src/notification_dialog.dart @@ -1,25 +1,24 @@ -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"; class NotificationDialog extends StatelessWidget { + const NotificationDialog({ + required this.title, + required this.body, + required this.translations, + super.key, + this.datetimePublished, + }); final String title; final String body; final DateTime? datetimePublished; final NotificationTranslations translations; - const NotificationDialog({ - super.key, - required this.title, - required this.body, - required this.translations, - this.datetimePublished, - }); - @override Widget build(BuildContext context) { - String formattedDateTime = datetimePublished != null - ? DateFormat('dd MMM HH:mm').format(datetimePublished!) + var formattedDateTime = datetimePublished != null + ? DateFormat("dd MMM HH:mm").format(datetimePublished!) : translations.notAvailable; return AlertDialog( diff --git a/packages/flutter_notification_center/lib/src/notification_snackbar.dart b/packages/flutter_notification_center/lib/src/notification_snackbar.dart index db8515f..5469ba8 100644 --- a/packages/flutter_notification_center/lib/src/notification_snackbar.dart +++ b/packages/flutter_notification_center/lib/src/notification_snackbar.dart @@ -1,14 +1,14 @@ -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"; class NotificationSnackbar extends SnackBar { NotificationSnackbar({ - super.key, required String title, required String body, required NotificationTranslations translations, required VoidCallback onDismiss, + super.key, DateTime? datetimePublished, }) : super( content: Column( @@ -33,7 +33,7 @@ class NotificationSnackbar extends SnackBar { const SizedBox(height: 4), Text( datetimePublished != null - ? DateFormat('dd MMM HH:mm').format(datetimePublished) + ? DateFormat("dd-MM-yyyy HH:mm").format(datetimePublished) : translations.notAvailable, style: const TextStyle( fontSize: 12.0, diff --git a/packages/flutter_notification_center/lib/src/popup_handler.dart b/packages/flutter_notification_center/lib/src/popup_handler.dart index d755b00..a6dc6ae 100644 --- a/packages/flutter_notification_center/lib/src/popup_handler.dart +++ b/packages/flutter_notification_center/lib/src/popup_handler.dart @@ -1,18 +1,17 @@ // Define a PopupHandler class to handle notification popups -import 'package:flutter/material.dart'; +import "package:flutter/material.dart"; -import 'package:flutter_notification_center/flutter_notification_center.dart'; +import "package:flutter_notification_center/flutter_notification_center.dart"; class PopupHandler { - final BuildContext context; - final NotificationConfig config; - PopupHandler({ required this.context, required this.config, }); + final BuildContext context; + final NotificationConfig config; - void handleNotificationPopup(NotificationModel notification) { + Future handleNotificationPopup(NotificationModel notification) async { if (!config.enableNotificationPopups) return; if (config.showAsSnackBar) { @@ -31,7 +30,7 @@ class PopupHandler { } else { if (ModalRoute.of(context)?.isCurrent != true) return; - showDialog( + await showDialog( context: context, builder: (context) => NotificationDialog( translations: config.translations, diff --git a/packages/flutter_notification_center/lib/src/services/notification_service.dart b/packages/flutter_notification_center/lib/src/services/notification_service.dart index 473a0dc..140057c 100644 --- a/packages/flutter_notification_center/lib/src/services/notification_service.dart +++ b/packages/flutter_notification_center/lib/src/services/notification_service.dart @@ -1,7 +1,7 @@ import "dart:async"; import "package:flutter/material.dart"; -import "../models/notification.dart"; +import "package:flutter_notification_center/src/models/notification.dart"; /// An abstract class representing a service for managing notifications. abstract class NotificationService with ChangeNotifier { @@ -26,8 +26,10 @@ abstract class NotificationService with ChangeNotifier { List listOfPlannedNotifications; /// Pushes a notification to the service. - Future pushNotification(NotificationModel notification, - [Function(NotificationModel model)? onNewNotification]); + Future pushNotification( + NotificationModel notification, [ + Function(NotificationModel model)? onNewNotification, + ]); /// Retrieves the list of active notifications. Future> getActiveNotifications(); diff --git a/packages/flutter_notification_center/pubspec.yaml b/packages/flutter_notification_center/pubspec.yaml index 62832c9..0bade56 100644 --- a/packages/flutter_notification_center/pubspec.yaml +++ b/packages/flutter_notification_center/pubspec.yaml @@ -1,11 +1,10 @@ name: flutter_notification_center description: "A Flutter package for displaying notifications in a notification center." -publish_to: 'none' - -version: 1.4.1 +publish_to: "none" +version: 2.0.0 environment: - sdk: '>=3.3.2 <4.0.0' + sdk: ">=3.3.2 <4.0.0" dependencies: flutter: @@ -15,11 +14,11 @@ dependencies: flutter_animated_widgets: git: url: https://github.com/Iconica-Development/flutter_animated_widgets - ref: 0.2.0 + ref: 0.3.0 dev_dependencies: flutter_test: - sdk: flutter + sdk: flutter flutter_iconica_analysis: git: url: https://github.com/Iconica-Development/flutter_iconica_analysis diff --git a/packages/flutter_notification_center/test/widget_test.dart b/packages/flutter_notification_center/test/widget_test.dart index 0a17bd7..d327631 100644 --- a/packages/flutter_notification_center/test/widget_test.dart +++ b/packages/flutter_notification_center/test/widget_test.dart @@ -3,10 +3,10 @@ // SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: GPL-3.0-or-later -import 'package:flutter_test/flutter_test.dart'; +import "package:flutter_test/flutter_test.dart"; void main() { - test('test', () { + test("test", () { expect(true, true); }); } diff --git a/packages/flutter_notification_center_firebase/analysis_options.yaml b/packages/flutter_notification_center_firebase/analysis_options.yaml index 0d29021..31b4b51 100644 --- a/packages/flutter_notification_center_firebase/analysis_options.yaml +++ b/packages/flutter_notification_center_firebase/analysis_options.yaml @@ -1,28 +1,9 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. +include: package:flutter_iconica_analysis/analysis_options.yaml -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +# Possible to overwrite the rules from the package + +analyzer: + exclude: linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_notification_center_firebase/lib/src/services/firebase_notification_service.dart b/packages/flutter_notification_center_firebase/lib/src/services/firebase_notification_service.dart index 5da6914..751d9b5 100644 --- a/packages/flutter_notification_center_firebase/lib/src/services/firebase_notification_service.dart +++ b/packages/flutter_notification_center_firebase/lib/src/services/firebase_notification_service.dart @@ -1,14 +1,25 @@ -import 'dart:async'; +import "dart:async"; -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_notification_center/flutter_notification_center.dart'; +import "package:cloud_firestore/cloud_firestore.dart"; +import "package:firebase_auth/firebase_auth.dart"; +import "package:firebase_core/firebase_core.dart"; +import "package:flutter/material.dart"; +import "package:flutter_notification_center/flutter_notification_center.dart"; class FirebaseNotificationService with ChangeNotifier implements NotificationService { + FirebaseNotificationService({ + required this.newNotificationCallback, + this.firebaseApp, + this.activeNotificationsCollection = "active_notifications", + this.plannedNotificationsCollection = "planned_notifications", + this.listOfActiveNotifications = const [], + this.listOfPlannedNotifications = const [], + }) { + _firebaseApp = firebaseApp ?? Firebase.app(); + unawaited(_startTimer()); + } final Function(NotificationModel) newNotificationCallback; final FirebaseApp? firebaseApp; final String activeNotificationsCollection; @@ -23,33 +34,23 @@ class FirebaseNotificationService // ignore: unused_field late Timer _timer; - FirebaseNotificationService({ - required this.newNotificationCallback, - this.firebaseApp, - this.activeNotificationsCollection = 'active_notifications', - this.plannedNotificationsCollection = 'planned_notifications', - this.listOfActiveNotifications = const [], - this.listOfPlannedNotifications = const [], - }) { - _firebaseApp = firebaseApp ?? Firebase.app(); - _startTimer(); - } - - void _startTimer() { - _timer = Timer.periodic(const Duration(seconds: 15), (timer) { - debugPrint('Checking for scheduled notifications'); - checkForScheduledNotifications(); + Future _startTimer() async { + _timer = Timer.periodic(const Duration(seconds: 15), (timer) async { + debugPrint("Checking for scheduled notifications"); + await checkForScheduledNotifications(); }); } @override - Future pushNotification(NotificationModel notification, - [Function(NotificationModel model)? onNewNotification]) async { + Future pushNotification( + NotificationModel notification, [ + Function(NotificationModel model)? onNewNotification, + ]) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -59,9 +60,9 @@ class FirebaseNotificationService .doc(userId) .collection(activeNotificationsCollection); - DateTime currentDateTime = DateTime.now(); + var currentDateTime = DateTime.now(); notification.dateTimePushed = currentDateTime; - Map notificationMap = notification.toMap(); + var notificationMap = notification.toMap(); await notifications.doc(notification.id).set(notificationMap); listOfActiveNotifications = [...listOfActiveNotifications, notification]; @@ -74,8 +75,8 @@ class FirebaseNotificationService } notifyListeners(); - } catch (e) { - debugPrint('Error creating document: $e'); + } on Exception catch (e) { + debugPrint("Error creating document: $e"); } } @@ -85,7 +86,7 @@ class FirebaseNotificationService var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return []; } @@ -95,12 +96,11 @@ class FirebaseNotificationService .doc(userId) .collection(activeNotificationsCollection); - QuerySnapshot querySnapshot = await activeNotificationsResult.get(); + var querySnapshot = await activeNotificationsResult.get(); - List activeNotifications = - querySnapshot.docs.map((doc) { - Map data = doc.data() as Map; - data['id'] = doc.id; + var activeNotifications = querySnapshot.docs.map((doc) { + var data = doc.data()! as Map; + data["id"] = doc.id; return NotificationModel.fromJson(data); }).toList(); @@ -114,19 +114,22 @@ class FirebaseNotificationService .sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!)); listOfActiveNotifications.insertAll( - 0, activeNotifications.where((element) => element.isPinned)); + 0, + activeNotifications.where((element) => element.isPinned), + ); notifyListeners(); return listOfActiveNotifications; - } catch (e) { - debugPrint('Error getting active notifications: $e'); + } on Exception catch (e) { + debugPrint("Error getting active notifications: $e"); return []; } } @override Future createRecurringNotification( - NotificationModel notification) async { + NotificationModel notification, + ) async { if (notification.recurring) { switch (notification.occuringInterval) { case OcurringInterval.daily: @@ -147,18 +150,19 @@ class FirebaseNotificationService break; case null: } - createScheduledNotification(notification); + await createScheduledNotification(notification); } } @override Future createScheduledNotification( - NotificationModel notification) async { + NotificationModel notification, + ) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -168,21 +172,22 @@ class FirebaseNotificationService .doc(userId) .collection(plannedNotificationsCollection); - Map notificationMap = notification.toMap(); + var notificationMap = notification.toMap(); await plannedNotifications.doc(notification.id).set(notificationMap); - } catch (e) { - debugPrint('Error creating document: $e'); + } on Exception catch (e) { + debugPrint("Error creating document: $e"); } } @override Future deletePlannedNotification( - NotificationModel notificationModel) async { + NotificationModel notificationModel, + ) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -202,24 +207,26 @@ class FirebaseNotificationService .get(); if (querySnapshot.docs.isEmpty) { - debugPrint('The collection is now empty'); + debugPrint("The collection is now empty"); } else { debugPrint( - 'Deleted planned notification with title: ${notificationModel.title}'); + "Deleted planned notification with title: ${notificationModel.title}", + ); } - } catch (e) { - debugPrint('Error deleting document: $e'); + } on Exception catch (e) { + debugPrint("Error deleting document: $e"); } } @override Future dismissActiveNotification( - NotificationModel notificationModel) async { + NotificationModel notificationModel, + ) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -233,19 +240,20 @@ class FirebaseNotificationService listOfActiveNotifications .removeAt(listOfActiveNotifications.indexOf(notificationModel)); notifyListeners(); - } catch (e) { - debugPrint('Error deleting document: $e'); + } on Exception catch (e) { + debugPrint("Error deleting document: $e"); } } @override Future pinActiveNotification( - NotificationModel notificationModel) async { + NotificationModel notificationModel, + ) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -255,7 +263,7 @@ class FirebaseNotificationService .doc(userId) .collection(activeNotificationsCollection) .doc(notificationModel.id); - await documentReference.update({'isPinned': true}); + await documentReference.update({"isPinned": true}); notificationModel.isPinned = true; listOfActiveNotifications @@ -263,19 +271,20 @@ class FirebaseNotificationService listOfActiveNotifications.insert(0, notificationModel); notifyListeners(); - } catch (e) { - debugPrint('Error updating document: $e'); + } on Exception catch (e) { + debugPrint("Error updating document: $e"); } } @override Future unPinActiveNotification( - NotificationModel notificationModel) async { + NotificationModel notificationModel, + ) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -285,7 +294,7 @@ class FirebaseNotificationService .doc(userId) .collection(activeNotificationsCollection) .doc(notificationModel.id); - await documentReference.update({'isPinned': false}); + await documentReference.update({"isPinned": false}); notificationModel.isPinned = false; listOfActiveNotifications @@ -294,19 +303,20 @@ class FirebaseNotificationService listOfActiveNotifications.add(notificationModel); notifyListeners(); - } catch (e) { - debugPrint('Error updating document: $e'); + } on Exception catch (e) { + debugPrint("Error updating document: $e"); } } @override Future markNotificationAsRead( - NotificationModel notificationModel) async { + NotificationModel notificationModel, + ) async { try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -316,22 +326,22 @@ class FirebaseNotificationService .doc(userId) .collection(activeNotificationsCollection) .doc(notificationModel.id); - await documentReference.update({'isRead': true}); + await documentReference.update({"isRead": true}); notificationModel.isRead = true; notifyListeners(); - } catch (e) { - debugPrint('Error updating document: $e'); + } on Exception catch (e) { + debugPrint("Error updating document: $e"); } } @override Future checkForScheduledNotifications() async { - DateTime currentTime = DateTime.now(); + var currentTime = DateTime.now(); try { var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); return; } @@ -341,20 +351,19 @@ class FirebaseNotificationService .doc(userId) .collection(plannedNotificationsCollection); - QuerySnapshot querySnapshot = await plannedNotificationsResult.get(); + var querySnapshot = await plannedNotificationsResult.get(); if (querySnapshot.docs.isEmpty) { - debugPrint('No scheduled notifications to be pushed'); + debugPrint("No scheduled notifications to be pushed"); return; } - List plannedNotifications = - querySnapshot.docs.map((doc) { - Map data = doc.data() as Map; + var plannedNotifications = querySnapshot.docs.map((doc) { + var data = doc.data()! as Map; return NotificationModel.fromJson(data); }).toList(); - for (NotificationModel notification in plannedNotifications) { + for (var notification in plannedNotifications) { if (notification.scheduledFor!.isBefore(currentTime) || notification.scheduledFor!.isAtSameMomentAs(currentTime)) { await pushNotification(notification, newNotificationCallback); @@ -363,7 +372,7 @@ class FirebaseNotificationService //Plan new recurring notification instance if (notification.recurring) { - NotificationModel newNotification = NotificationModel( + var newNotification = NotificationModel( id: UniqueKey().toString(), title: notification.title, body: notification.body, @@ -375,8 +384,8 @@ class FirebaseNotificationService } } } - } catch (e) { - debugPrint('Error getting planned notifications: $e'); + } on Exception catch (e) { + debugPrint("Error getting planned notifications: $e"); return; } } @@ -386,7 +395,7 @@ class FirebaseNotificationService var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid; if (userId == null) { - debugPrint('User is not authenticated'); + debugPrint("User is not authenticated"); yield 0; } diff --git a/packages/flutter_notification_center_firebase/pubspec.yaml b/packages/flutter_notification_center_firebase/pubspec.yaml index 9071acb..8af27fc 100644 --- a/packages/flutter_notification_center_firebase/pubspec.yaml +++ b/packages/flutter_notification_center_firebase/pubspec.yaml @@ -1,8 +1,7 @@ name: flutter_notification_center_firebase description: "A new Flutter project." publish_to: "none" - -version: 1.4.1 +version: 2.0.0 environment: sdk: ">=2.18.0 <3.0.0" @@ -10,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - intl: any + intl: any # Firebase cloud_firestore: ^4.16.0 @@ -22,15 +21,16 @@ dependencies: flutter_notification_center: git: url: https://github.com/Iconica-Development/flutter_notification_center - ref: 1.4.0 + ref: 2.0.0 path: packages/flutter_notification_center dev_dependencies: flutter_test: sdk: flutter - - flutter_lints: ^2.0.0 + flutter_iconica_analysis: + git: + url: https://github.com/Iconica-Development/flutter_iconica_analysis + ref: 7.0.0 flutter: uses-material-design: true -