mirror of
https://github.com/Iconica-Development/flutter_notification_center.git
synced 2025-05-18 16:43:44 +02:00
refactor: new component structure
This commit is contained in:
parent
ddf2a2bbb3
commit
367303aecf
51 changed files with 741 additions and 916 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -49,8 +49,10 @@ android/
|
|||
web/
|
||||
linux/
|
||||
macos/
|
||||
windows/
|
||||
|
||||
pubspec.lock
|
||||
.metadata
|
||||
flutter_notification_center.iml
|
||||
dotenv
|
||||
dotenv
|
||||
firebase_options.dart
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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/
|
1
packages/firebase_notification_center_repository/CHANGELOG.md
Symbolic link
1
packages/firebase_notification_center_repository/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../CHANGELOG.md
|
1
packages/firebase_notification_center_repository/LICENSE
Symbolic link
1
packages/firebase_notification_center_repository/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE
|
1
packages/firebase_notification_center_repository/README.md
Symbolic link
1
packages/firebase_notification_center_repository/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../README.md
|
|
@ -6,4 +6,4 @@ analyzer:
|
|||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
||||
rules:
|
|
@ -0,0 +1 @@
|
|||
export "src/firebase_notification_repository.dart";
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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:
|
20
packages/flutter_notification_center/.gitignore
vendored
20
packages/flutter_notification_center/.gitignore
vendored
|
@ -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/
|
||||
|
|
1
packages/flutter_notification_center/CHANGELOG.md
Symbolic link
1
packages/flutter_notification_center/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../CHANGELOG.md
|
1
packages/flutter_notification_center/LICENSE
Symbolic link
1
packages/flutter_notification_center/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE
|
1
packages/flutter_notification_center/README.md
Symbolic link
1
packages/flutter_notification_center/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../README.md
|
|
@ -6,4 +6,4 @@ analyzer:
|
|||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
||||
rules:
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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
|
1
packages/flutter_notification_center/firebase.json
Normal file
1
packages/flutter_notification_center/firebase.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -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 {
|
|
@ -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();
|
||||
}
|
|
@ -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({
|
|
@ -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({
|
|
@ -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({
|
|
@ -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/
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
export "src/services/firebase_notification_service.dart";
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
// });
|
||||
// }
|
29
packages/notification_center_repository_interface/.gitignore
vendored
Normal file
29
packages/notification_center_repository_interface/.gitignore
vendored
Normal 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/
|
1
packages/notification_center_repository_interface/CHANGELOG.md
Symbolic link
1
packages/notification_center_repository_interface/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../CHANGELOG.md
|
1
packages/notification_center_repository_interface/LICENSE
Symbolic link
1
packages/notification_center_repository_interface/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE
|
1
packages/notification_center_repository_interface/README.md
Symbolic link
1
packages/notification_center_repository_interface/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../README.md
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -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";
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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
|
Loading…
Reference in a new issue