refactor: new component structure

This commit is contained in:
Niels Gorter 2024-09-24 13:50:11 +02:00
parent ddf2a2bbb3
commit 367303aecf
51 changed files with 741 additions and 916 deletions

4
.gitignore vendored
View file

@ -49,8 +49,10 @@ android/
web/
linux/
macos/
windows/
pubspec.lock
.metadata
flutter_notification_center.iml
dotenv
dotenv
firebase_options.dart

View file

@ -1,3 +1,7 @@
## [5.0.0] - 24 September 2024
* Refactor package with the new component structure
## [4.0.0] - 14 August 2024
* Fix overflow issue with long text in notification

View file

@ -15,12 +15,13 @@ A Flutter package for creating notification center displaying a list of notifica
To use this package, add `flutter_notification_center` as a dependency in your pubspec.yaml file.
- Provide a `NotificationService` to the userstory to define and alter behaviour, this service accepts and `NotificationRepositoryInterface` as repository (data source)
- For custom notification styling provide the optional notificationWidgetBuilder with your own implementation.
The `NotificationConfig` has its own parameters, as specified below:
| Parameter | Explanation |
|-----------|-------------|
| service | The notification service that will be used |
| seperateNotificationsWithDivider | If true notifications will be seperated with dividers within the notification center |
| translations | The translations that will be used |
| notificationWidgetBuilder | The widget that defines the styles and logic for every notification |

View file

@ -22,31 +22,8 @@ migrate_working_dir/
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
.dart_tools/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# iOS related
/ios/
lib/config/
pubspec.lock
dotenv
build/

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -0,0 +1 @@
export "src/firebase_notification_repository.dart";

View file

@ -0,0 +1,151 @@
import "package:cloud_firestore/cloud_firestore.dart";
import "package:firebase_core/firebase_core.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
class FirebaseNotificationRepository
implements NotificationRepositoryInterface {
FirebaseNotificationRepository({
FirebaseApp? firebaseApp,
this.activeNotificationsCollection = "active_notifications",
this.plannedNotificationsCollection = "planned_notifications",
}) : firebaseApp = firebaseApp ?? Firebase.app();
final FirebaseApp firebaseApp;
final String activeNotificationsCollection;
final String plannedNotificationsCollection;
@override
Future<NotificationModel> addNotification(
String userId,
NotificationModel notification,
List<String> recipientIds,
) async {
var newNotification = notification;
for (var recipientId in recipientIds) {
DocumentReference notifications;
if (notification.scheduledFor != null &&
notification.scheduledFor!.isAfter(DateTime.now())) {
notifications = FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(plannedNotificationsCollection)
.doc(recipientId)
.collection(plannedNotificationsCollection)
.doc(notification.id);
} else {
newNotification = notification.copyWith(
id: "${notification.id}-${DateTime.now().millisecondsSinceEpoch}",
);
notifications = FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(activeNotificationsCollection)
.doc(recipientId)
.collection(activeNotificationsCollection)
.doc(newNotification.id);
}
var currentDateTime = DateTime.now();
newNotification.dateTimePushed = currentDateTime;
var notificationMap = newNotification.toMap();
await notifications.set(notificationMap);
}
return newNotification;
}
@override
Future<void> deleteNotification(
String userId,
String id,
bool planned,
) async {
try {
if (planned) {
await FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(plannedNotificationsCollection)
.doc(userId)
.collection(plannedNotificationsCollection)
.doc(id)
.delete();
} else {
await FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(id)
.delete();
}
} catch (e) {
throw Exception("Failed to delete notification: $e");
}
}
@override
Stream<NotificationModel?> getNotification(String userId, String id) {
var notificationStream = FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(id)
.snapshots()
.map((snapshot) {
if (snapshot.exists) {
if (snapshot.data() == null) {
return null;
}
return NotificationModel.fromJson(snapshot.data()!);
} else {
return null;
}
});
return notificationStream;
}
@override
Stream<List<NotificationModel>> getNotifications(String userId) {
var notificationsStream = FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.snapshots()
.map(
(snapshot) => snapshot.docs
.map((doc) => NotificationModel.fromJson(doc.data()))
.toList(),
);
return notificationsStream;
}
@override
Future<NotificationModel> updateNotification(
String userId,
NotificationModel notification,
) async {
await FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(notification.id)
.update(notification.toMap());
return notification;
}
@override
Stream<List<NotificationModel>> getPlannedNotifications(String userId) {
var notificationsStream = FirebaseFirestore.instanceFor(app: firebaseApp)
.collection(plannedNotificationsCollection)
.doc(userId)
.collection(plannedNotificationsCollection)
.snapshots()
.map(
(snapshot) => snapshot.docs
.map((doc) => NotificationModel.fromJson(doc.data()))
.toList(),
);
return notificationsStream;
}
}

View file

@ -0,0 +1,31 @@
name: firebase_notification_center_repository
description: "A new Flutter package project."
version: 5.0.0
homepage:
publish_to: 'none'
environment:
sdk: ^3.5.3
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
# Firebase
cloud_firestore: ^5.4.2
firebase_core: ^3.5.0
notification_center_repository_interface:
git:
url: https://github.com/Iconica-Development/flutter_notification_center.git
path: packages/notification_center_repository_interface
ref: 5.0.0
dev_dependencies:
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
flutter:

View file

@ -22,22 +22,8 @@ migrate_working_dir/
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
build/

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -6,4 +6,4 @@ analyzer:
exclude:
linter:
rules:
rules:

View file

@ -30,7 +30,6 @@ migrate_working_dir/
.pub-cache/
.pub/
/build/
.dart_tools/
# Symbolication related
app.*.symbols
@ -42,11 +41,3 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
# iOS related
/ios/
lib/config/
pubspec.lock
dotenv
firebase_options.dart

View file

@ -23,5 +23,6 @@ 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

View file

@ -0,0 +1 @@
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}

View file

@ -1,167 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_notification_center/flutter_notification_center.dart';
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
class CustomNotificationWidget extends StatelessWidget {
final NotificationModel notification;
final FirebaseNotificationService notificationService;
final NotificationTranslations notificationTranslations;
final BuildContext context;
const CustomNotificationWidget({
required this.notification,
required this.notificationTranslations,
required this.notificationService,
required this.context,
super.key,
});
@override
Widget build(BuildContext context) {
return notification.isPinned
//Pinned notification
? Dismissible(
key: Key('${notification.id}_pinned'),
onDismissed: (direction) async {
await unPinNotification(notificationService, notification,
notificationTranslations, 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),
child: ListTile(
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
notification.title,
),
),
],
),
trailing: IconButton(
icon: const Icon(Icons.push_pin),
onPressed: () async =>
_navigateToNotificationDetail(context, notification),
padding: const EdgeInsets.only(left: 60.0),
),
),
),
)
//Dismissable notification
: Dismissible(
key: Key(notification.id),
onDismissed: (direction) async {
if (direction == DismissDirection.endToStart) {
await dismissNotification(notificationService, notification,
notificationTranslations, context);
} else if (direction == DismissDirection.startToEnd) {
await pinNotification(notificationService, notification,
notificationTranslations, 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,
),
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,
),
)
: null,
),
),
);
}
Future<void> _navigateToNotificationDetail(
BuildContext context,
NotificationModel notification,
) async {
unawaited(markNotificationAsRead(notificationService, notification));
if (context.mounted) {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationDetailPage(
translations: notificationTranslations,
notification: notification,
),
),
);
}
}
}

View file

@ -1,10 +1,10 @@
import 'package:example/custom_notification.dart';
// import 'package:example/firebase_options.dart';
// import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_notification_center_repository/firebase_notification_center_repository.dart';
// import 'package:example/firebase_options.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:flutter_notification_center/flutter_notification_center.dart';
@ -22,6 +22,7 @@ void main() async {
}
Future<void> _configureApp() async {
// Generate a FirebaseOptions and uncomment the following lines to initialize Firebase.
// await Firebase.initializeApp(
// options: DefaultFirebaseOptions.currentPlatform,
// );
@ -34,7 +35,8 @@ Future<void> _configureApp() async {
}
Future<void> _signInUser() async {
/// Implement your own sign in logic here
// Sign in, you could use the line below or implement your own sign in method.
// await FirebaseAuth.instance.signInAnonymously();
}
class NotificationCenterDemo extends StatefulWidget {
@ -45,30 +47,39 @@ class NotificationCenterDemo extends StatefulWidget {
}
class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
late NotificationConfig config;
late PopupHandler popupHandler;
late NotificationService service;
// Provide a user ID here. For Firebase you can use the commented line below.
String userId = ""; //FirebaseAuth.instance.currentUser!.uid;
@override
void initState() {
super.initState();
var service = FirebaseNotificationService(
newNotificationCallback: (notification) {
popupHandler.handleNotificationPopup(notification);
},
service = NotificationService(
userId: userId,
repository: FirebaseNotificationRepository(),
);
config = NotificationConfig(
service: service,
enableNotificationPopups: true,
showAsSnackBar: true,
notificationWidgetBuilder: (notification, context) =>
CustomNotificationWidget(
notification: notification,
notificationService: service,
notificationTranslations: const NotificationTranslations.empty(),
context: context,
// Uncomment the line below to send a test notification.
// Provide a user ID in the list to send the notification to.
_sendTestNotification([userId]);
}
_sendTestNotification(List<String> recipientIds) async {
await service.pushNotification(
NotificationModel(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: 'Test Notification',
body: 'This is a test notification.',
// For a scheduled message provide a scheduledFor date.
// For a recurring message provide a scheduledFor date, set recurring to true and provide an occuringInterval.
//
// scheduledFor: DateTime.now().add(const Duration(seconds: 5)),
// recurring: true,
// occuringInterval: OcurringInterval.debug,
),
recipientIds,
);
popupHandler = PopupHandler(context: context, config: config);
}
@override
@ -79,7 +90,8 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
centerTitle: true,
actions: [
NotificationBellWidgetStory(
config: config,
userId: userId,
service: service,
),
],
),

View file

@ -2,38 +2,47 @@ 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
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ">=3.3.2 <4.0.0"
sdk: ^3.5.3
dependencies:
flutter:
sdk: flutter
intl: ^0.17.0
cupertino_icons: ^1.0.8
flutter_notification_center:
git:
url: https://github.com/Iconica-Development/flutter_notification_center
path: packages/flutter_notification_center
ref: 3.0.0
path: ../
flutter_notification_center_firebase:
git:
url: https://github.com/Iconica-Development/flutter_notification_center
path: packages/flutter_notification_center_firebase
ref: 3.0.0
firebase_notification_center_repository:
path: ../../firebase_notification_center_repository
intl: ^0.19.0
firebase_auth: any
firebase_core: any
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
# The following section is specific to Flutter packages.
flutter_lints: ^4.0.0
flutter:
uses-material-design: true
uses-material-design: true

View file

@ -0,0 +1 @@
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}

View file

@ -1,17 +1,13 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
export "package:notification_center_repository_interface/notification_center_repository_interface.dart";
export "package:flutter_animated_widgets/flutter_animated_widgets.dart";
export "src/models/notification.dart";
export "src/models/notification_config.dart";
export "src/models/notification_translation.dart";
export "src/notification_bell.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";
// Screens
export "src/screens/notification_bell.dart";
export "src/screens/notification_detail.dart";
// Widgets
export "src/widgets/notification_dialog.dart";
export "src/widgets/notification_snackbar.dart";
export "src/widgets/popup_handler.dart";

View file

@ -1,27 +1,64 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
import "package:flutter_notification_center/src/notification_center.dart";
import "package:flutter_notification_center/src/screens/notification_bell.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
/// A widget representing a notification bell.
class NotificationBellWidgetStory extends StatelessWidget {
class NotificationBellWidgetStory extends StatefulWidget {
/// Creates a new [NotificationBellWidgetStory] instance.
///
/// The [config] parameter specifies the notification configuration.
const NotificationBellWidgetStory({
required this.config,
required this.userId,
this.config,
this.service,
this.animatedIconStyle,
super.key,
});
/// The user ID.
final String userId;
/// The notification configuration.
final NotificationConfig config;
final NotificationConfig? config;
/// The notification service.
final NotificationService? service;
final AnimatedNotificationBellStyle? animatedIconStyle;
@override
State<NotificationBellWidgetStory> createState() =>
_NotificationBellWidgetStoryState();
}
class _NotificationBellWidgetStoryState
extends State<NotificationBellWidgetStory> {
late NotificationConfig config;
late NotificationService service;
@override
void initState() {
config = widget.config ?? const NotificationConfig();
service = widget.service ??
NotificationService(
userId: widget.userId,
);
super.initState();
}
@override
Widget build(BuildContext context) => NotificationBell(
config: config,
service: service,
animatedIconStyle: widget.animatedIconStyle,
onTap: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => NotificationCenter(
config: config,
service: service,
),
),
);

View file

@ -1,7 +1,8 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:flutter_notification_center/src/screens/notification_detail.dart";
import "package:flutter_svg/svg.dart";
import "package:intl/intl.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
/// Widget for displaying the notification center.
class NotificationCenter extends StatefulWidget {
@ -10,39 +11,31 @@ class NotificationCenter extends StatefulWidget {
/// [config]: Configuration for the notification center.
const NotificationCenter({
required this.config,
required this.service,
super.key,
});
/// Configuration for the notification center.
final NotificationConfig config;
final NotificationService service;
@override
NotificationCenterState createState() => NotificationCenterState();
}
/// State for the notification center.
class NotificationCenterState extends State<NotificationCenter> {
late Future<List<NotificationModel>> _notificationsFuture;
late Stream<List<NotificationModel>> _notificationsStream;
@override
void initState() {
super.initState();
// ignore: discarded_futures
_notificationsFuture = widget.config.service.getActiveNotifications();
widget.config.service.getActiveAmountStream().listen((amount) async {
_notificationsFuture = widget.config.service.getActiveNotifications();
_notificationsStream = widget.service.getActiveNotifications();
widget.service.getActiveAmountStream().listen((data) {
setState(() {});
});
widget.config.service.addListener(_listener);
}
@override
void dispose() {
widget.config.service.removeListener(_listener);
super.dispose();
}
void _listener() {
setState(() {});
}
@override
@ -67,8 +60,8 @@ class NotificationCenterState extends State<NotificationCenter> {
),
),
),
body: FutureBuilder<List<NotificationModel>>(
future: _notificationsFuture,
body: StreamBuilder<List<NotificationModel>>(
stream: _notificationsStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
@ -101,7 +94,7 @@ class NotificationCenterState extends State<NotificationCenter> {
await _navigateToNotificationDetail(
context,
notification,
widget.config.service,
widget.service,
widget.config.translations,
);
}
@ -111,7 +104,7 @@ class NotificationCenterState extends State<NotificationCenter> {
onDismissed: (direction) async {
if (direction == DismissDirection.endToStart) {
await unPinNotification(
widget.config.service,
widget.service,
notification,
widget.config.translations,
context,
@ -119,7 +112,7 @@ class NotificationCenterState extends State<NotificationCenter> {
} else if (direction ==
DismissDirection.startToEnd) {
await unPinNotification(
widget.config.service,
widget.service,
notification,
widget.config.translations,
context,
@ -183,7 +176,7 @@ class NotificationCenterState extends State<NotificationCenter> {
await _navigateToNotificationDetail(
context,
notification,
widget.config.service,
widget.service,
widget.config.translations,
);
}
@ -193,7 +186,7 @@ class NotificationCenterState extends State<NotificationCenter> {
onDismissed: (direction) async {
if (direction == DismissDirection.endToStart) {
await dismissNotification(
widget.config.service,
widget.service,
notification,
widget.config.translations,
context,
@ -201,7 +194,7 @@ class NotificationCenterState extends State<NotificationCenter> {
} else if (direction ==
DismissDirection.startToEnd) {
await pinNotification(
widget.config.service,
widget.service,
notification,
widget.config.translations,
context,
@ -270,8 +263,11 @@ Widget _notificationItem(
NotificationConfig config,
) {
var theme = Theme.of(context);
var dateTimePushed =
DateFormat("dd/MM/yyyy 'at' HH:mm").format(notification.dateTimePushed!);
String? dateTimePushed;
if (notification.dateTimePushed != null) {
dateTimePushed = DateFormat("dd/MM/yyyy 'at' HH:mm")
.format(notification.dateTimePushed!);
}
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Container(
@ -332,7 +328,7 @@ Widget _notificationItem(
],
),
Text(
dateTimePushed,
dateTimePushed ?? "",
style: theme.textTheme.labelSmall,
),
],

View file

@ -1,5 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
/// A bell icon widget that displays the number of active notifications.
///
@ -15,6 +16,8 @@ class NotificationBell extends StatefulWidget {
/// [onTap]: Callback function to be invoked when the bell icon is tapped.
const NotificationBell({
required this.config,
required this.service,
this.animatedIconStyle,
this.onTap,
super.key,
});
@ -23,6 +26,12 @@ class NotificationBell extends StatefulWidget {
/// the notification service.
final NotificationConfig config;
/// The notification service used to fetch active notifications.
final NotificationService service;
/// The style of the animated bell icon.
final AnimatedNotificationBellStyle? animatedIconStyle;
/// Callback function to be invoked when the bell icon is tapped.
final VoidCallback? onTap;
@ -37,7 +46,7 @@ class _NotificationBellState extends State<NotificationBell> {
@override
void initState() {
super.initState();
widget.config.service.getActiveAmountStream().listen((amount) {
widget.service.getActiveAmountStream().listen((amount) {
setState(() {
notificationAmount = amount;
});
@ -51,7 +60,8 @@ class _NotificationBellState extends State<NotificationBell> {
icon: AnimatedNotificationBell(
duration: const Duration(seconds: 1),
notificationCount: notificationAmount,
style: widget.config.bellStyle,
style:
widget.animatedIconStyle ?? const AnimatedNotificationBellStyle(),
),
);
}

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:intl/intl.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
/// A page displaying the details of a notification.
class NotificationDetailPage extends StatelessWidget {

View file

@ -1,64 +0,0 @@
import "dart:async";
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 with ChangeNotifier {
/// Creates a new [NotificationService] instance.
///
/// 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,
/// with a default value of an empty list.
NotificationService({
this.listOfActiveNotifications = const [],
this.listOfPlannedNotifications = const [],
});
/// A list of active notifications.
List<NotificationModel> listOfActiveNotifications;
/// A list of planned notifications.
List<NotificationModel> listOfPlannedNotifications;
/// Pushes a notification to the service.
Future pushNotification(
NotificationModel notification,
List<String> recipientIds, [
Function(NotificationModel model)? onNewNotification,
]);
/// Retrieves the list of active notifications.
Future<List<NotificationModel>> getActiveNotifications();
/// Creates a scheduled notification.
Future createScheduledNotification(NotificationModel notification);
/// Creates a recurring notification.
Future createRecurringNotification(NotificationModel notification);
/// Deletes a scheduled notification.
Future deletePlannedNotification(NotificationModel notification);
/// Dismisses an active notification.
Future dismissActiveNotification(NotificationModel notification);
/// Pin an active notification.
Future pinActiveNotification(NotificationModel notification);
/// Unpin an active notification.
Future unPinActiveNotification(NotificationModel notification);
/// Marks a notification as read.
Future markNotificationAsRead(NotificationModel notification);
/// Checks for scheduled notifications.
Future checkForScheduledNotifications();
/// Returns a stream of the number of active notifications.
Stream<int> getActiveAmountStream();
}

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:intl/intl.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
class NotificationDialog extends StatelessWidget {
const NotificationDialog({

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:intl/intl.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
class NotificationSnackbar extends SnackBar {
NotificationSnackbar({

View file

@ -1,7 +1,8 @@
// Define a PopupHandler class to handle notification popups
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:flutter_notification_center/src/widgets/notification_dialog.dart";
import "package:flutter_notification_center/src/widgets/notification_snackbar.dart";
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
class PopupHandler {
PopupHandler({

View file

@ -1,32 +1,37 @@
name: flutter_notification_center
description: "A Flutter package for displaying notifications in a notification center."
publish_to: "none"
version: 4.0.0
description: "A new Flutter package project."
version: 5.0.0
homepage:
publish_to: 'none'
environment:
sdk: ">=3.3.2 <4.0.0"
sdk: ^3.5.3
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
intl: any
intl: ^0.19.0
flutter_svg: ^2.0.10+1
flutter_animated_widgets:
git:
url: https://github.com/Iconica-Development/flutter_animated_widgets
ref: 0.3.1
flutter_svg: ^2.0.10+1
notification_center_repository_interface:
git:
url: https://github.com/Iconica-Development/flutter_notification_center.git
path: packages/notification_center_repository_interface
ref: 5.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
assets:
- assets/
- assets/

View file

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: GPL-3.0-or-later
import "package:flutter_test/flutter_test.dart";
void main() {
test("test", () {
expect(true, true);
});
}

View file

@ -1,5 +0,0 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
export "src/services/firebase_notification_service.dart";

View file

@ -1,420 +0,0 @@
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";
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;
final String plannedNotificationsCollection;
late FirebaseApp _firebaseApp;
@override
List<NotificationModel> listOfActiveNotifications;
@override
List<NotificationModel> listOfPlannedNotifications;
// ignore: unused_field
late Timer _timer;
Future<void> _startTimer() async {
_timer = Timer.periodic(const Duration(seconds: 15), (timer) async {
await checkForScheduledNotifications();
});
}
@override
Future<void> pushNotification(
NotificationModel notification,
List<String> recipientIds, [
Function(NotificationModel model)? onNewNotification,
]) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
for (var recipientId in recipientIds) {
CollectionReference notifications =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(recipientId)
.collection(activeNotificationsCollection);
var currentDateTime = DateTime.now();
notification.dateTimePushed = currentDateTime;
var notificationMap = notification.toMap();
await notifications.doc(notification.id).set(notificationMap);
}
if (recipientIds.contains(userId)) {
listOfActiveNotifications = [
...listOfActiveNotifications,
notification,
];
//Show popup with notification conte
if (onNewNotification != null) {
onNewNotification(notification);
} else {
newNotificationCallback(notification);
}
}
notifyListeners();
} on Exception catch (e) {
debugPrint("Error creating document: $e");
}
}
@override
Future<List<NotificationModel>> getActiveNotifications() async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return [];
}
CollectionReference activeNotificationsResult =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection);
var querySnapshot = await activeNotificationsResult.get();
var activeNotifications = querySnapshot.docs.map((doc) {
var data = doc.data()! as Map<String, dynamic>;
data["id"] = doc.id;
return NotificationModel.fromJson(data);
}).toList();
listOfActiveNotifications = List.from(activeNotifications);
listOfActiveNotifications.removeWhere((element) => element.isPinned);
activeNotifications
.sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!));
listOfActiveNotifications
.sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!));
listOfActiveNotifications.insertAll(
0,
activeNotifications.where((element) => element.isPinned),
);
notifyListeners();
return listOfActiveNotifications;
} on Exception catch (e) {
debugPrint("Error getting active notifications: $e");
return [];
}
}
@override
Future<void> createRecurringNotification(
NotificationModel notification,
) async {
if (notification.recurring) {
switch (notification.occuringInterval) {
case OcurringInterval.daily:
notification.scheduledFor =
DateTime.now().add(const Duration(days: 1));
break;
case OcurringInterval.weekly:
notification.scheduledFor =
DateTime.now().add(const Duration(days: 7));
break;
case OcurringInterval.monthly:
notification.scheduledFor =
DateTime.now().add(const Duration(days: 30));
break;
case OcurringInterval.debug:
notification.scheduledFor =
DateTime.now().add(const Duration(seconds: 10));
break;
case null:
}
await createScheduledNotification(notification);
}
}
@override
Future<void> createScheduledNotification(
NotificationModel notification,
) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
CollectionReference plannedNotifications =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(plannedNotificationsCollection)
.doc(userId)
.collection(plannedNotificationsCollection);
var notificationMap = notification.toMap();
await plannedNotifications.doc(notification.id).set(notificationMap);
} on Exception catch (e) {
debugPrint("Error creating document: $e");
}
}
@override
Future<void> deletePlannedNotification(
NotificationModel notificationModel,
) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
DocumentReference documentReference =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(plannedNotificationsCollection)
.doc(userId)
.collection(plannedNotificationsCollection)
.doc(notificationModel.id);
await documentReference.delete();
QuerySnapshot querySnapshot =
await FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(plannedNotificationsCollection)
.doc(userId)
.collection(plannedNotificationsCollection)
.get();
if (querySnapshot.docs.isEmpty) {
debugPrint("The collection is now empty");
} else {
debugPrint(
"Deleted planned notification with title: ${notificationModel.title}",
);
}
} on Exception catch (e) {
debugPrint("Error deleting document: $e");
}
}
@override
Future<void> dismissActiveNotification(
NotificationModel notificationModel,
) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
DocumentReference documentReference =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(notificationModel.id);
await documentReference.delete();
listOfActiveNotifications
.removeWhere((element) => element.id == notificationModel.id);
notifyListeners();
} on Exception catch (e) {
debugPrint("Error deleting document: $e");
}
}
@override
Future<void> pinActiveNotification(
NotificationModel notificationModel,
) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
DocumentReference documentReference =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(notificationModel.id);
await documentReference.update({"isPinned": true});
notificationModel.isPinned = true;
listOfActiveNotifications
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
listOfActiveNotifications.insert(0, notificationModel);
notifyListeners();
} on Exception catch (e) {
debugPrint("Error updating document: $e");
}
}
@override
Future<void> unPinActiveNotification(
NotificationModel notificationModel,
) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
DocumentReference documentReference =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(notificationModel.id);
await documentReference.update({"isPinned": false});
notificationModel.isPinned = false;
listOfActiveNotifications
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
listOfActiveNotifications.add(notificationModel);
notifyListeners();
} on Exception catch (e) {
debugPrint("Error updating document: $e");
}
}
@override
Future<void> markNotificationAsRead(
NotificationModel notificationModel,
) async {
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
DocumentReference documentReference =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.doc(notificationModel.id);
await documentReference.update({"isRead": true});
notificationModel.isRead = true;
notifyListeners();
} on Exception catch (e) {
debugPrint("Error updating document: $e");
}
}
@override
Future<void> checkForScheduledNotifications() async {
var currentTime = DateTime.now();
try {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
return;
}
CollectionReference plannedNotificationsResult =
FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(plannedNotificationsCollection)
.doc(userId)
.collection(plannedNotificationsCollection);
var querySnapshot = await plannedNotificationsResult.get();
if (querySnapshot.docs.isEmpty) {
return;
}
var plannedNotifications = querySnapshot.docs.map((doc) {
var data = doc.data()! as Map<String, dynamic>;
return NotificationModel.fromJson(data);
}).toList();
for (var notification in plannedNotifications) {
if (notification.scheduledFor!.isBefore(currentTime) ||
notification.scheduledFor!.isAtSameMomentAs(currentTime)) {
await pushNotification(
notification,
[userId],
newNotificationCallback,
);
await deletePlannedNotification(notification);
//Plan new recurring notification instance
if (notification.recurring) {
var newNotification = NotificationModel(
id: UniqueKey().toString(),
title: notification.title,
body: notification.body,
recurring: true,
occuringInterval: notification.occuringInterval,
scheduledFor: DateTime.now().add(const Duration(seconds: 10)),
);
await createScheduledNotification(newNotification);
}
}
}
} on Exception catch (e) {
debugPrint("Error getting planned notifications: $e");
return;
}
}
@override
Stream<int> getActiveAmountStream() async* {
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
if (userId == null) {
debugPrint("User is not authenticated");
yield 0;
}
var amount = FirebaseFirestore.instanceFor(app: _firebaseApp)
.collection(activeNotificationsCollection)
.doc(userId)
.collection(activeNotificationsCollection)
.where("isRead", isEqualTo: false)
.snapshots()
.map((e) => e.docs.length);
yield* amount;
}
}

View file

@ -1,36 +0,0 @@
name: flutter_notification_center_firebase
description: "A new Flutter project."
publish_to: "none"
version: 4.0.0
environment:
sdk: ">=2.18.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
intl: any
# Firebase
cloud_firestore: ^4.16.0
firebase_auth: ^4.2.6
firebase_core: ^2.5.0
cupertino_icons: ^1.0.2
flutter_notification_center:
git:
url: https://github.com/Iconica-Development/flutter_notification_center
ref: 4.0.0
path: packages/flutter_notification_center
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
flutter:
uses-material-design: true

View file

@ -1,30 +0,0 @@
// // This is a basic Flutter widget test.
// //
// // To perform an interaction with a widget in your test, use the WidgetTester
// // utility in the flutter_test package. For example, you can send tap and scroll
// // gestures. You can also use WidgetTester to find child widgets in the widget
// // tree, read text, and verify that the values of widget properties are correct.
// import 'package:flutter/material.dart';
// import 'package:flutter_test/flutter_test.dart';
// import 'package:example/main.dart';
// void main() {
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(const MyApp());
// // Verify that our counter starts at 0.
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);
// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();
// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
// });
// }

View file

@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/

View file

@ -0,0 +1 @@
../../CHANGELOG.md

View file

@ -0,0 +1 @@
../../LICENSE

View file

@ -0,0 +1 @@
../../README.md

View file

@ -0,0 +1,9 @@
include: package:flutter_iconica_analysis/analysis_options.yaml
# Possible to overwrite the rules from the package
analyzer:
exclude:
linter:
rules:

View file

@ -0,0 +1,13 @@
// Interfaces
export "src/interfaces/notification_repository_interface.dart";
// Local
export "src/local/local_notification_repository.dart";
// Models
export "src/models/notification.dart";
export "src/models/notification_config.dart";
export "src/models/notification_translation.dart";
// Services
export "src/services/notification_service.dart";

View file

@ -0,0 +1,27 @@
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
abstract class NotificationRepositoryInterface {
Future<NotificationModel> addNotification(
String userId,
NotificationModel notification,
List<String> recipientIds,
);
Stream<NotificationModel?> getNotification(String userId, String id);
Stream<List<NotificationModel>> getNotifications(String userId);
Stream<List<NotificationModel>> getPlannedNotifications(String userId);
Future<NotificationModel> updateNotification(
String userId,
NotificationModel notification,
);
Future<void> deleteNotification(
String userId,
String id,
// ignore: avoid_positional_boolean_parameters
bool planned,
);
}

View file

@ -0,0 +1,92 @@
import "dart:async";
import "package:notification_center_repository_interface/src/interfaces/notification_repository_interface.dart";
import "package:notification_center_repository_interface/src/models/notification.dart";
import "package:rxdart/rxdart.dart";
class LocalNotificationRepository implements NotificationRepositoryInterface {
final List<NotificationModel> _activeNotifications = [];
final List<NotificationModel> _plannedNotifications = [];
final StreamController<List<NotificationModel>> _notificationsController =
BehaviorSubject<List<NotificationModel>>();
final StreamController<List<NotificationModel>>
_plannedNotificationsController =
BehaviorSubject<List<NotificationModel>>();
final StreamController<NotificationModel> _notificationController =
BehaviorSubject<NotificationModel>();
@override
Future<NotificationModel> addNotification(
String userId,
NotificationModel notification,
List<String> recipientIds,
) async {
if (notification.scheduledFor != null &&
notification.scheduledFor!.isAfter(DateTime.now())) {
_plannedNotifications.add(notification);
} else {
_activeNotifications.add(notification);
}
getNotifications(userId);
return notification;
}
@override
Future<void> deleteNotification(
String userId,
String id,
bool planned,
) async {
if (planned) {
_plannedNotifications.removeWhere((element) => element.id == id);
} else {
_activeNotifications.removeWhere((element) => element.id == id);
}
getNotifications(userId);
}
@override
Stream<NotificationModel?> getNotification(String userId, String id) {
var notification = _activeNotifications.firstWhere(
(element) => element.id == id,
);
_notificationController.add(notification);
return _notificationController.stream;
}
@override
Stream<List<NotificationModel>> getNotifications(String userId) {
_notificationsController.add(_activeNotifications);
return _notificationsController.stream;
}
@override
Stream<List<NotificationModel>> getPlannedNotifications(String userId) {
_plannedNotificationsController.add(_plannedNotifications);
return _plannedNotificationsController.stream;
}
@override
Future<NotificationModel> updateNotification(
String userId,
NotificationModel notification,
) async {
_activeNotifications
.removeWhere((element) => element.id == notification.id);
_activeNotifications.add(notification);
getNotifications(userId);
return notification;
}
}

View file

@ -1,5 +1,3 @@
import "package:flutter/material.dart";
/// Enum representing the interval at which notifications occur.
enum OcurringInterval {
/// Notifications occur daily.
@ -39,7 +37,7 @@ class NotificationModel {
this.occuringInterval,
this.isPinned = false,
this.isRead = false,
this.icon = Icons.notifications,
this.icon,
});
/// Method to create a NotificationModel object from JSON data
@ -59,9 +57,7 @@ class NotificationModel {
: null,
isPinned = json["isPinned"] ?? false,
isRead = json["isRead"] ?? false,
icon = json["icon"] != null
? IconData(json["icon"], fontFamily: Icons.notifications.fontFamily)
: Icons.notifications;
icon = json["icon"] ?? 0xe44f;
/// Unique identifier for the notification.
final String id;
@ -91,14 +87,16 @@ class NotificationModel {
bool isRead;
/// Icon to be displayed with the notification.
final IconData icon;
final int? icon;
/// Override toString() to provide custom string representation
@override
String toString() => "NotificationModel{id: $id, title: $title, body: $body, "
String toString() => """
NotificationModel{id: $id, title: $title, body: $body, "
"dateTimePushed: $dateTimePushed, scheduledFor: $scheduledFor, "
"recurring: $recurring, occuringInterval: $occuringInterval, "
"isPinned: $isPinned, icon: $icon}";
"isPinned: $isPinned, icon: $icon
}""";
/// Convert the NotificationModel object to a Map.
Map<String, dynamic> toMap() => {
@ -111,7 +109,7 @@ class NotificationModel {
"occuringInterval": occuringInterval?.index,
"isPinned": isPinned,
"isRead": isRead,
"icon": icon.codePoint,
"icon": icon,
};
/// Create a copy of the NotificationModel with some fields replaced.
@ -125,7 +123,7 @@ class NotificationModel {
OcurringInterval? occuringInterval,
bool? isPinned,
bool? isRead,
IconData? icon,
int? icon,
}) =>
NotificationModel(
id: id ?? this.id,

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:notification_center_repository_interface/src/models/notification.dart";
import "package:notification_center_repository_interface/src/models/notification_translation.dart";
/// Configuration class for notifications.
class NotificationConfig {
@ -11,20 +11,15 @@ class NotificationConfig {
/// notification. The [translations] parameter is also optional and provides
/// translations for notification messages.
const NotificationConfig({
required this.service,
this.translations = const NotificationTranslations.empty(),
this.notificationWidgetBuilder,
this.showAsSnackBar = true,
this.enableNotificationPopups = true,
this.bellStyle = const AnimatedNotificationBellStyle(),
this.pinnedIconColor = Colors.black,
this.emptyNotificationsBuilder,
this.onNotificationTap,
});
/// The notification service to use for delivering notifications.
final NotificationService service;
/// Translations for notification messages.
final NotificationTranslations translations;
@ -39,9 +34,6 @@ class NotificationConfig {
/// Whether to show notification popups.
final bool enableNotificationPopups;
/// The style of the notification bell.
final AnimatedNotificationBellStyle bellStyle;
/// The color of the trailing icon (if any) in the notification.
final Color? pinnedIconColor;

View file

@ -0,0 +1,157 @@
import "dart:async";
import "package:notification_center_repository_interface/src/interfaces/notification_repository_interface.dart";
import "package:notification_center_repository_interface/src/local/local_notification_repository.dart";
import "package:notification_center_repository_interface/src/models/notification.dart";
class NotificationService {
NotificationService({
required this.userId,
this.pollingInterval = const Duration(seconds: 15),
NotificationRepositoryInterface? repository,
this.onNewNotification,
}) : repository = repository ?? LocalNotificationRepository() {
unawaited(_startTimer());
}
final NotificationRepositoryInterface repository;
final Function(NotificationModel)? onNewNotification;
final String userId;
final Duration pollingInterval;
Timer? timer;
Future<void> _startTimer() async {
timer = Timer.periodic(pollingInterval, (timer) async {
await checkForScheduledNotifications();
});
}
/// Pushes a notification to the service.
Future<void> pushNotification(
NotificationModel notification,
List<String> recipientIds,
) {
var result = repository.addNotification(
userId,
notification,
recipientIds,
);
if (recipientIds.contains(userId)) {
getActiveNotifications();
//Show popup with notification conte
if (onNewNotification != null) {
onNewNotification!.call(notification);
}
}
return result;
}
/// Retrieves the list of active notifications.
Stream<List<NotificationModel>> getActiveNotifications() =>
repository.getNotifications(userId);
/// Retrieves the list of planned notifications.
Stream<List<NotificationModel>> getPlannedNotifications() =>
repository.getPlannedNotifications(userId);
/// Creates a scheduled notification.
Future<void> createScheduledNotification(
NotificationModel notification,
List<String> recipientIds,
) async =>
pushNotification(
notification,
recipientIds,
);
/// Creates a recurring notification.
Future<void> createRecurringNotification(
NotificationModel notification,
List<String> recipientIds,
) async {
if (notification.recurring) {
switch (notification.occuringInterval) {
case OcurringInterval.daily:
notification.scheduledFor =
DateTime.now().add(const Duration(days: 1));
case OcurringInterval.weekly:
notification.scheduledFor =
DateTime.now().add(const Duration(days: 7));
case OcurringInterval.monthly:
notification.scheduledFor =
DateTime.now().add(const Duration(days: 30));
case OcurringInterval.debug:
notification.scheduledFor =
DateTime.now().add(const Duration(seconds: 10));
case null:
}
await createScheduledNotification(notification, recipientIds);
}
}
/// Deletes a scheduled notification.
Future<void> deletePlannedNotification(NotificationModel notification) =>
repository.deleteNotification(userId, notification.id, true);
/// Dismisses an active notification.
Future<void> dismissActiveNotification(NotificationModel notification) =>
repository.deleteNotification(userId, notification.id, false);
/// Pin an active notification.
Future<void> pinActiveNotification(NotificationModel notification) =>
repository.updateNotification(
userId,
notification.copyWith(isPinned: true),
);
/// Unpin an active notification.
Future<void> unPinActiveNotification(NotificationModel notification) =>
repository.updateNotification(
userId,
notification.copyWith(isPinned: false),
);
/// Marks a notification as read.
Future<void> markNotificationAsRead(NotificationModel notification) =>
repository.updateNotification(
userId,
notification.copyWith(isRead: true),
);
/// Checks for scheduled notifications.
Future<void> checkForScheduledNotifications() async {
var notifications = await repository.getPlannedNotifications(userId).first;
for (var notification in notifications) {
if (notification.scheduledFor != null &&
notification.scheduledFor!.isBefore(DateTime.now()) ||
notification.scheduledFor!.isAtSameMomentAs(DateTime.now())) {
await pushNotification(
notification,
[userId],
);
await deletePlannedNotification(notification);
if (notification.recurring) {
await createRecurringNotification(
notification,
[userId],
);
}
}
}
}
/// Returns a stream of the number of active notifications.
Stream<int> getActiveAmountStream() => repository
.getNotifications(userId)
.map((notifications) => notifications.length);
}

View file

@ -0,0 +1,20 @@
name: notification_center_repository_interface
description: "A new Flutter package project."
version: 5.0.0
homepage:
environment:
sdk: ^3.5.3
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
rxdart: any
dev_dependencies:
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0