mirror of
https://github.com/Iconica-Development/flutter_notification_center.git
synced 2025-05-19 17:13:46 +02:00
Compare commits
No commits in common. "master" and "3.0.0" have entirely different histories.
54 changed files with 1009 additions and 1117 deletions
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
@ -1,14 +0,0 @@
|
||||||
name: Iconica Standard Component Release Workflow
|
|
||||||
# Workflow Caller version: 1.0.0
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
call-global-iconica-workflow:
|
|
||||||
uses: Iconica-Development/.github/.github/workflows/component-release.yml@master
|
|
||||||
secrets: inherit
|
|
||||||
permissions: write-all
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -49,11 +49,8 @@ android/
|
||||||
web/
|
web/
|
||||||
linux/
|
linux/
|
||||||
macos/
|
macos/
|
||||||
windows/
|
|
||||||
|
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
.metadata
|
.metadata
|
||||||
flutter_notification_center.iml
|
flutter_notification_center.iml
|
||||||
dotenv
|
dotenv
|
||||||
firebase_options.dart
|
|
||||||
pubspec_overrides.yaml
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,17 +1,3 @@
|
||||||
## [5.1.0] - 13 February 2025
|
|
||||||
|
|
||||||
* Update intl to 0.20.1
|
|
||||||
|
|
||||||
## [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
|
|
||||||
* Fix new notification only sending to the person that triggered the notification
|
|
||||||
* Added `onNotificationTap` to a notification
|
|
||||||
|
|
||||||
## [2.0.0] - 6 June 2024
|
## [2.0.0] - 6 June 2024
|
||||||
|
|
||||||
* Rework design for notification center
|
* Rework design for notification center
|
||||||
|
|
323
README.md
323
README.md
|
@ -1,300 +1,59 @@
|
||||||
# flutter_notification_center
|
# flutter_notification_center
|
||||||
|
A Flutter package for creating notification center displaying a list of notifications.
|
||||||
`flutter_notification_center` is a comprehensive and flexible notification management package for Flutter applications. It allows developers to integrate real-time notifications, schedule messages, and provide an interactive notification center, with support for Firebase and local data storage. The package is highly customizable, enabling developers to adjust the UI, translate notification messages, and configure notifications to enhance user engagement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
- [Features](#features)
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Setting Up Firebase](#setting-up-firebase)
|
|
||||||
- [Configuring the Notification Center](#configuring-the-notification-center)
|
|
||||||
- [Displaying Notifications](#displaying-notifications)
|
|
||||||
- [Customization](#customization)
|
|
||||||
- [UI Customization](#ui-customization)
|
|
||||||
- [Translations](#translations)
|
|
||||||
- [API Reference](#api-reference)
|
|
||||||
- [Examples](#examples)
|
|
||||||
- [Issues](#issues)
|
|
||||||
- [Contributing](#contribute)
|
|
||||||
- [Author](#author)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Real-time Notifications**: Fetch and display active notifications in real-time.
|
- Notification center: A page containing a list of notifications.
|
||||||
- **Scheduled and Recurring Notifications**: Set notifications for future delivery or create recurring notifications.
|
- Notification bell: A clickable bell icon that can be placed anywere showing the amound of unread notifications. Clicking the bell takes the user to the notification center.
|
||||||
- **Interactive Notification Center**: Display notifications with interactive options to dismiss, pin, or mark them as read.
|
- Pinned notifications: A pinned notification can't be dismissed by the user from the notification center.
|
||||||
- **Customizable UI**: Adjust UI elements like icons, colors, and layout of the notification center.
|
- Dismissable notifications: A dismissable notification can be dismissed by the user from the notification center.
|
||||||
- **Localization**: Easily translate notification messages for multiple languages.
|
- Notification detail page: Clicking on a notification takes the user to a notification detail page.
|
||||||
- **Modular Architecture**: Compatible with Firebase and local storage solutions, allowing flexibility in backend configuration.
|
- Notification types: Notification types that can be created are: instant, scheduled, recurring.
|
||||||
|
- Notification popups: If a notification is pushed a popup can appear in form of a dialog or snackbar.
|
||||||
|
|
||||||
---
|
## Setup
|
||||||
|
|
||||||
## Getting Started
|
To use this package, add `flutter_notification_center` as a dependency in your pubspec.yaml file.
|
||||||
|
|
||||||
### Core Components
|
- For custom notification styling provide the optional notificationWidgetBuilder with your own implementation.
|
||||||
1. **Notification Center**: Provides a UI for displaying notifications.
|
|
||||||
2. **Notification Bell**: Displays the count of active notifications with animation support.
|
|
||||||
3. **Notification Service**: Manages notifications, including adding, deleting, and retrieving notifications.
|
|
||||||
|
|
||||||
### Requirements
|
The `NotificationConfig` has its own parameters, as specified below:
|
||||||
1. **Firebase**: Firebase should be initialized for storing notifications in Firestore.
|
| Parameter | Explanation |
|
||||||
2. **User Identification**: Each user requires a unique `userId` for personalized notifications.
|
|-----------|-------------|
|
||||||
|
| 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 |
|
||||||
|
| enableNotificationPopups | If set to false no popups will be shown if a new notification is pushed |
|
||||||
|
| showAsSnackBar | If true notifications popups will show as snackbar. If false shown as dialog|
|
||||||
|
|
||||||
---
|
If you set `enableNotificationPopups` to true, you can use `PopupHandler` in the `newNotificationCallback` to display popups in case a new notification is pushed.
|
||||||
|
|
||||||
## Installation
|
The `notificationWidgetBuilder` expects the following parameters, as specified below:
|
||||||
|
| Parameter | Explanation |
|
||||||
1. **Add Dependencies**: Add required dependencies in `pubspec.yaml`.
|
|-----------|-------------|
|
||||||
|
| notification | The notification that is being defined |
|
||||||
```yaml
|
| style | The styling that will be used for the notification |
|
||||||
dependencies:
|
| notificationService | The notification service that will be used |
|
||||||
firebase_notification_center_repository:
|
| notificationTranslations | The translations that will be used for the notification|
|
||||||
git:
|
| context | The provided context |
|
||||||
url: https://github.com/Iconica-Development/flutter_notification_center.git
|
|
||||||
path: packages/firebase_notification_center_repository
|
|
||||||
ref: 5.0.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
|
|
||||||
flutter_notification_center:
|
|
||||||
git:
|
|
||||||
url: https://github.com/Iconica-Development/flutter_notification_center.git
|
|
||||||
path: packages/flutter_notification_center
|
|
||||||
ref: 5.0.0
|
|
||||||
firebase_core: ^latest_version
|
|
||||||
cloud_firestore: ^latest_version
|
|
||||||
intl: ^latest_version
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Firebase Setup**:
|
|
||||||
- Follow [Firebase setup instructions](https://firebase.google.com/docs/flutter/setup) for both Android and iOS.
|
|
||||||
- Ensure Firestore is configured with `active_notifications` and `planned_notifications` collections for immediate and scheduled notifications, respectively.
|
|
||||||
|
|
||||||
3. **Asset Configuration**:
|
|
||||||
- Some icons, like `unpin_icon.svg`, may need to be referenced in `pubspec.yaml`.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
flutter:
|
|
||||||
assets:
|
|
||||||
- assets/unpin_icon.svg
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Initialize Firebase**:
|
|
||||||
- Initialize Firebase in the `main.dart` file.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
|
||||||
await Firebase.initializeApp();
|
|
||||||
runApp(MyApp());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
Create instant notification:
|
||||||
|
- Make a call to pushNotification() and provide a NotificationModel with the required attributes.
|
||||||
|
|
||||||
### Setting Up Firebase
|
Create scheduled notification:
|
||||||
|
- Make a call to createScheduledNotification() and provide a NotificationModel with the required attributes + scheduledFor.
|
||||||
|
|
||||||
1. **Initialize FirebaseNotificationRepository**:
|
Create recurring notification:
|
||||||
- This repository interacts with Firebase Firestore for managing notifications.
|
- Make a call to createRecurringNotification() and provide a NotificationModel with the required attributes and the following additional attributes:
|
||||||
|
- scheduledFor
|
||||||
|
- recurring = true
|
||||||
|
- occuringInterval with the pre defined interval (daily, weekly, monthly)
|
||||||
|
|
||||||
```dart
|
To create a pinned notification, set isPinned = true when creating the notification.
|
||||||
final notificationRepository = FirebaseNotificationRepository(
|
|
||||||
activeNotificationsCollection: "active_notifications",
|
|
||||||
plannedNotificationsCollection: "planned_notifications",
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Firestore Configuration**:
|
### Example
|
||||||
- Ensure Firestore is correctly set up with appropriate collections.
|
|
||||||
|
|
||||||
### Configuring the Notification Center
|
|
||||||
|
|
||||||
1. **NotificationBell Widget**:
|
|
||||||
- Displays the count of active notifications.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final notificationBell = NotificationBell(
|
|
||||||
config: NotificationConfig(),
|
|
||||||
service: notificationService, // An instance of NotificationService
|
|
||||||
onTap: () {
|
|
||||||
// Define behavior when tapped, e.g., navigate to NotificationCenter
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **NotificationCenter Widget**:
|
|
||||||
- Displays a list of notifications, allowing users to interact with each one.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final notificationCenter = NotificationCenter(
|
|
||||||
config: NotificationConfig(
|
|
||||||
translations: NotificationTranslations(appBarTitle: "Notifications"),
|
|
||||||
showAsSnackBar: true,
|
|
||||||
enableNotificationPopups: true,
|
|
||||||
),
|
|
||||||
service: notificationService,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Displaying Notifications
|
|
||||||
|
|
||||||
1. **Adding Notifications**:
|
|
||||||
- Use `addNotification` to add new notifications to the repository.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final newNotification = NotificationModel(
|
|
||||||
id: "123",
|
|
||||||
title: "New Alert",
|
|
||||||
body: "You have a new notification",
|
|
||||||
scheduledFor: DateTime.now().add(Duration(days: 1)),
|
|
||||||
);
|
|
||||||
|
|
||||||
notificationRepository.addNotification("user_id", newNotification, ["recipient_id"]);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Streaming Notifications**:
|
|
||||||
- Use `getNotifications` to listen to real-time updates of active notifications.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
notificationRepository.getNotifications("user_id").listen((notifications) {
|
|
||||||
for (var notification in notifications) {
|
|
||||||
print(notification.title);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Deleting Notifications**:
|
|
||||||
- Remove a notification by calling `deleteNotification`.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
notificationRepository.deleteNotification("user_id", "notification_id", false);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
### UI Customization
|
|
||||||
|
|
||||||
1. **Notification Bell Styling**:
|
|
||||||
- Customize the `NotificationBell` appearance with `AnimatedNotificationBellStyle`.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final notificationBell = NotificationBell(
|
|
||||||
animatedIconStyle: AnimatedNotificationBellStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
onTap: () { /* Navigate to NotificationCenter */ },
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Notification Display Layout**:
|
|
||||||
- Customize the `NotificationCenter` layout with `NotificationConfig`, adjusting elements like the app bar title, icon color, and UI interactions.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final notificationConfig = NotificationConfig(
|
|
||||||
showAsSnackBar: true,
|
|
||||||
enableNotificationPopups: true,
|
|
||||||
pinnedIconColor: Colors.green,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Translations
|
|
||||||
|
|
||||||
1. **Localized Translations**:
|
|
||||||
- `NotificationTranslations` provides customizable text for various notification messages.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final translations = NotificationTranslations(
|
|
||||||
appBarTitle: "Mis Notificaciones",
|
|
||||||
notificationDismissed: "Notificación descartada",
|
|
||||||
notificationPinned: "Notificación fijada",
|
|
||||||
notificationUnpinned: "Notificación desmarcada",
|
|
||||||
);
|
|
||||||
|
|
||||||
final notificationConfig = NotificationConfig(translations: translations);
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Dialog Customization**:
|
|
||||||
- Adjust dialog text within `NotificationDialog` using custom translations, ideal for supporting multiple languages.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### Classes
|
|
||||||
|
|
||||||
- **NotificationService**: Manages notifications, with methods for adding, updating, deleting, and scheduling.
|
|
||||||
- **NotificationModel**: Represents a notification structure with fields like `id`, `title`, `body`, and `scheduledFor`.
|
|
||||||
- **NotificationConfig**: Customization class for notifications, allowing display options like snackbar or dialog.
|
|
||||||
- **NotificationRepositoryInterface**: Defines the required methods for a notification repository, enabling various backend implementations.
|
|
||||||
- **LocalNotificationRepository**: In-memory implementation, useful for testing.
|
|
||||||
|
|
||||||
### Key Methods
|
|
||||||
|
|
||||||
- `addNotification(userId, notification, recipientIds)`: Adds a new notification.
|
|
||||||
- `deleteNotification(userId, id, planned)`: Deletes a notification.
|
|
||||||
- `getNotifications(userId)`: Streams active notifications.
|
|
||||||
- `getPlannedNotifications(userId)`: Streams scheduled notifications.
|
|
||||||
- `updateNotification(userId, notification)`: Updates an existing notification.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final firebaseRepo = FirebaseNotificationRepository();
|
|
||||||
|
|
||||||
final notification = NotificationModel(
|
|
||||||
id: "notif_01",
|
|
||||||
title: "Welcome!",
|
|
||||||
body: "Thank you for signing up.",
|
|
||||||
scheduledFor: DateTime.now().add(Duration(hours: 1)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Adding a notification
|
|
||||||
await firebaseRepo.addNotification("user_123", notification, ["user_123"]);
|
|
||||||
|
|
||||||
// Listening to active notifications
|
|
||||||
firebaseRepo.getNotifications("user_123").listen((notifications) {
|
|
||||||
notifications.forEach((notif) => print(notif.title));
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
### Advanced Usage With Custom Translations
|
|
||||||
```dart
|
|
||||||
final translations = NotificationTranslations(
|
|
||||||
appBarTitle: "My Custom Notifications",
|
|
||||||
noNotifications: "No notifications at this time.",
|
|
||||||
);
|
|
||||||
|
|
||||||
final notificationConfig = NotificationConfig(
|
|
||||||
translations: translations,
|
|
||||||
showAsSnackBar: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Using the config in a custom notification center setup
|
|
||||||
final notificationCenter = NotificationCenter(
|
|
||||||
config: notificationConfig,
|
|
||||||
service: firebaseRepo,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
See [Example Code](example/lib/main.dart) for more info.
|
See [Example Code](example/lib/main.dart) for more info.
|
||||||
|
|
||||||
|
@ -302,7 +61,7 @@ See [Example Code](example/lib/main.dart) for more info.
|
||||||
|
|
||||||
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_notification_center/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
|
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/flutter_notification_center/pulls) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
|
||||||
|
|
||||||
## Contribute
|
## Want to contribute
|
||||||
|
|
||||||
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_notification_center/pulls).
|
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/flutter_notification_center/pulls).
|
||||||
|
|
||||||
|
|
17
flutter_notification_center.iml
Normal file
17
flutter_notification_center.iml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Flutter Plugins" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -1 +0,0 @@
|
||||||
../../CHANGELOG.md
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE
|
|
|
@ -1 +0,0 @@
|
||||||
../../README.md
|
|
|
@ -1 +0,0 @@
|
||||||
export "src/firebase_notification_repository.dart";
|
|
|
@ -1,151 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
name: firebase_notification_center_repository
|
|
||||||
description: "A new Flutter package project."
|
|
||||||
version: 5.1.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:
|
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
|
||||||
version: ^5.1.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
flutter_iconica_analysis:
|
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
|
||||||
version: ^7.0.0
|
|
20
packages/flutter_notification_center/.gitignore
vendored
20
packages/flutter_notification_center/.gitignore
vendored
|
@ -22,8 +22,22 @@ migrate_working_dir/
|
||||||
#.vscode/
|
#.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
|
||||||
/pubspec.lock
|
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
build/
|
.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
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../CHANGELOG.md
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE
|
|
|
@ -1 +0,0 @@
|
||||||
../../README.md
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M680-840v80h-40v327l-80-80v-247H400v87l-87-87-33-33v-47h400ZM480-40l-40-40v-240H240v-80l80-80v-46L56-792l56-56 736 736-58 56-264-264h-6v240l-40 40ZM354-400h92l-44-44-2-2-46 46Zm126-193Zm-78 149Z"/></svg>
|
|
Before Width: | Height: | Size: 319 B |
|
@ -30,6 +30,7 @@ migrate_working_dir/
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
|
.dart_tools/
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
|
@ -41,3 +42,11 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
# iOS related
|
||||||
|
/ios/
|
||||||
|
|
||||||
|
lib/config/
|
||||||
|
pubspec.lock
|
||||||
|
dotenv
|
||||||
|
firebase_options.dart
|
||||||
|
|
|
@ -23,6 +23,5 @@ linter:
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
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/firebase_options.dart';
|
import 'package:example/custom_notification.dart';
|
||||||
// import 'package:firebase_auth/firebase_auth.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:example/firebase_options.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
// import 'package:firebase_core/firebase_core.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:intl/date_symbol_data_local.dart';
|
||||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ void main() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _configureApp() async {
|
Future<void> _configureApp() async {
|
||||||
// Generate a FirebaseOptions and uncomment the following lines to initialize Firebase.
|
|
||||||
// await Firebase.initializeApp(
|
// await Firebase.initializeApp(
|
||||||
// options: DefaultFirebaseOptions.currentPlatform,
|
// options: DefaultFirebaseOptions.currentPlatform,
|
||||||
// );
|
// );
|
||||||
|
@ -35,8 +34,7 @@ Future<void> _configureApp() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _signInUser() async {
|
Future<void> _signInUser() async {
|
||||||
// Sign in, you could use the line below or implement your own sign in method.
|
/// Implement your own sign in logic here
|
||||||
// await FirebaseAuth.instance.signInAnonymously();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationCenterDemo extends StatefulWidget {
|
class NotificationCenterDemo extends StatefulWidget {
|
||||||
|
@ -47,39 +45,30 @@ class NotificationCenterDemo extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
||||||
late NotificationService service;
|
late NotificationConfig config;
|
||||||
// Provide a user ID here. For Firebase you can use the commented line below.
|
late PopupHandler popupHandler;
|
||||||
String userId = ""; //FirebaseAuth.instance.currentUser!.uid;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
var service = FirebaseNotificationService(
|
||||||
service = NotificationService(
|
newNotificationCallback: (notification) {
|
||||||
userId: userId,
|
popupHandler.handleNotificationPopup(notification);
|
||||||
repository: FirebaseNotificationRepository(),
|
},
|
||||||
);
|
);
|
||||||
|
config = NotificationConfig(
|
||||||
// Uncomment the line below to send a test notification.
|
service: service,
|
||||||
// Provide a user ID in the list to send the notification to.
|
enableNotificationPopups: true,
|
||||||
_sendTestNotification([userId]);
|
showAsSnackBar: true,
|
||||||
}
|
notificationWidgetBuilder: (notification, context) =>
|
||||||
|
CustomNotificationWidget(
|
||||||
_sendTestNotification(List<String> recipientIds) async {
|
notification: notification,
|
||||||
await service.pushNotification(
|
notificationService: service,
|
||||||
NotificationModel(
|
notificationTranslations: const NotificationTranslations.empty(),
|
||||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
context: context,
|
||||||
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
|
@override
|
||||||
|
@ -90,8 +79,7 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
actions: [
|
actions: [
|
||||||
NotificationBellWidgetStory(
|
NotificationBellWidgetStory(
|
||||||
userId: userId,
|
config: config,
|
||||||
service: service,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,47 +2,38 @@ name: example
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# 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
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
version: 1.0.0
|
||||||
# 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:
|
environment:
|
||||||
sdk: ^3.5.3
|
sdk: ">=3.3.2 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
intl: ^0.17.0
|
||||||
cupertino_icons: ^1.0.8
|
|
||||||
|
|
||||||
flutter_notification_center:
|
flutter_notification_center:
|
||||||
path: ../
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||||
|
path: packages/flutter_notification_center
|
||||||
|
ref: 3.0.0
|
||||||
|
|
||||||
firebase_notification_center_repository:
|
flutter_notification_center_firebase:
|
||||||
path: ../../firebase_notification_center_repository
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||||
intl: ^0.20.1
|
path: packages/flutter_notification_center_firebase
|
||||||
|
ref: 3.0.0
|
||||||
firebase_auth: any
|
|
||||||
firebase_core: any
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_iconica_analysis:
|
||||||
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||||
|
ref: 7.0.0
|
||||||
|
|
||||||
flutter_lints: ^5.0.0
|
# The following section is specific to Flutter packages.
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
|
@ -1 +0,0 @@
|
||||||
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}
|
|
|
@ -1,13 +1,17 @@
|
||||||
export "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
// SPDX-FileCopyrightText: 2024 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
export "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
||||||
|
|
||||||
|
export "src/models/notification.dart";
|
||||||
|
export "src/models/notification_config.dart";
|
||||||
|
export "src/models/notification_translation.dart";
|
||||||
|
export "src/notification_bell.dart";
|
||||||
export "src/notification_bell_story.dart";
|
export "src/notification_bell_story.dart";
|
||||||
export "src/notification_center.dart";
|
export "src/notification_center.dart";
|
||||||
|
export "src/notification_detail.dart";
|
||||||
// Screens
|
export "src/notification_dialog.dart";
|
||||||
export "src/screens/notification_bell.dart";
|
export "src/notification_snackbar.dart";
|
||||||
export "src/screens/notification_detail.dart";
|
export "src/popup_handler.dart";
|
||||||
|
export "src/services/notification_service.dart";
|
||||||
// Widgets
|
|
||||||
export "src/widgets/notification_dialog.dart";
|
|
||||||
export "src/widgets/notification_snackbar.dart";
|
|
||||||
export "src/widgets/popup_handler.dart";
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
|
||||||
/// Enum representing the interval at which notifications occur.
|
/// Enum representing the interval at which notifications occur.
|
||||||
enum OcurringInterval {
|
enum OcurringInterval {
|
||||||
/// Notifications occur daily.
|
/// Notifications occur daily.
|
||||||
|
@ -37,7 +39,7 @@ class NotificationModel {
|
||||||
this.occuringInterval,
|
this.occuringInterval,
|
||||||
this.isPinned = false,
|
this.isPinned = false,
|
||||||
this.isRead = false,
|
this.isRead = false,
|
||||||
this.icon,
|
this.icon = Icons.notifications,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Method to create a NotificationModel object from JSON data
|
/// Method to create a NotificationModel object from JSON data
|
||||||
|
@ -57,7 +59,9 @@ class NotificationModel {
|
||||||
: null,
|
: null,
|
||||||
isPinned = json["isPinned"] ?? false,
|
isPinned = json["isPinned"] ?? false,
|
||||||
isRead = json["isRead"] ?? false,
|
isRead = json["isRead"] ?? false,
|
||||||
icon = json["icon"] ?? 0xe44f;
|
icon = json["icon"] != null
|
||||||
|
? IconData(json["icon"], fontFamily: Icons.notifications.fontFamily)
|
||||||
|
: Icons.notifications;
|
||||||
|
|
||||||
/// Unique identifier for the notification.
|
/// Unique identifier for the notification.
|
||||||
final String id;
|
final String id;
|
||||||
|
@ -87,16 +91,14 @@ class NotificationModel {
|
||||||
bool isRead;
|
bool isRead;
|
||||||
|
|
||||||
/// Icon to be displayed with the notification.
|
/// Icon to be displayed with the notification.
|
||||||
final int? icon;
|
final IconData icon;
|
||||||
|
|
||||||
/// Override toString() to provide custom string representation
|
/// Override toString() to provide custom string representation
|
||||||
@override
|
@override
|
||||||
String toString() => """
|
String toString() => "NotificationModel{id: $id, title: $title, body: $body, "
|
||||||
NotificationModel{id: $id, title: $title, body: $body, "
|
|
||||||
"dateTimePushed: $dateTimePushed, scheduledFor: $scheduledFor, "
|
"dateTimePushed: $dateTimePushed, scheduledFor: $scheduledFor, "
|
||||||
"recurring: $recurring, occuringInterval: $occuringInterval, "
|
"recurring: $recurring, occuringInterval: $occuringInterval, "
|
||||||
"isPinned: $isPinned, icon: $icon
|
"isPinned: $isPinned, icon: $icon}";
|
||||||
}""";
|
|
||||||
|
|
||||||
/// Convert the NotificationModel object to a Map.
|
/// Convert the NotificationModel object to a Map.
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
|
@ -109,7 +111,7 @@ NotificationModel{id: $id, title: $title, body: $body, "
|
||||||
"occuringInterval": occuringInterval?.index,
|
"occuringInterval": occuringInterval?.index,
|
||||||
"isPinned": isPinned,
|
"isPinned": isPinned,
|
||||||
"isRead": isRead,
|
"isRead": isRead,
|
||||||
"icon": icon,
|
"icon": icon.codePoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a copy of the NotificationModel with some fields replaced.
|
/// Create a copy of the NotificationModel with some fields replaced.
|
||||||
|
@ -123,7 +125,7 @@ NotificationModel{id: $id, title: $title, body: $body, "
|
||||||
OcurringInterval? occuringInterval,
|
OcurringInterval? occuringInterval,
|
||||||
bool? isPinned,
|
bool? isPinned,
|
||||||
bool? isRead,
|
bool? isRead,
|
||||||
int? icon,
|
IconData? icon,
|
||||||
}) =>
|
}) =>
|
||||||
NotificationModel(
|
NotificationModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
|
@ -1,6 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:notification_center_repository_interface/src/models/notification.dart";
|
|
||||||
import "package:notification_center_repository_interface/src/models/notification_translation.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
|
||||||
/// Configuration class for notifications.
|
/// Configuration class for notifications.
|
||||||
class NotificationConfig {
|
class NotificationConfig {
|
||||||
|
@ -11,15 +11,19 @@ class NotificationConfig {
|
||||||
/// notification. The [translations] parameter is also optional and provides
|
/// notification. The [translations] parameter is also optional and provides
|
||||||
/// translations for notification messages.
|
/// translations for notification messages.
|
||||||
const NotificationConfig({
|
const NotificationConfig({
|
||||||
|
required this.service,
|
||||||
this.translations = const NotificationTranslations.empty(),
|
this.translations = const NotificationTranslations.empty(),
|
||||||
this.notificationWidgetBuilder,
|
this.notificationWidgetBuilder,
|
||||||
this.showAsSnackBar = true,
|
this.showAsSnackBar = true,
|
||||||
this.enableNotificationPopups = true,
|
this.enableNotificationPopups = true,
|
||||||
|
this.bellStyle = const AnimatedNotificationBellStyle(),
|
||||||
this.pinnedIconColor = Colors.black,
|
this.pinnedIconColor = Colors.black,
|
||||||
this.emptyNotificationsBuilder,
|
this.emptyNotificationsBuilder,
|
||||||
this.onNotificationTap,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The notification service to use for delivering notifications.
|
||||||
|
final NotificationService service;
|
||||||
|
|
||||||
/// Translations for notification messages.
|
/// Translations for notification messages.
|
||||||
final NotificationTranslations translations;
|
final NotificationTranslations translations;
|
||||||
|
|
||||||
|
@ -34,11 +38,12 @@ class NotificationConfig {
|
||||||
/// Whether to show notification popups.
|
/// Whether to show notification popups.
|
||||||
final bool enableNotificationPopups;
|
final bool enableNotificationPopups;
|
||||||
|
|
||||||
|
/// The style of the notification bell.
|
||||||
|
final AnimatedNotificationBellStyle bellStyle;
|
||||||
|
|
||||||
/// The color of the trailing icon (if any) in the notification.
|
/// The color of the trailing icon (if any) in the notification.
|
||||||
final Color? pinnedIconColor;
|
final Color? pinnedIconColor;
|
||||||
|
|
||||||
/// A builder function to display when there are no notifications.
|
/// A builder function to display when there are no notifications.
|
||||||
final Widget Function()? emptyNotificationsBuilder;
|
final Widget Function()? emptyNotificationsBuilder;
|
||||||
|
|
||||||
final Function(NotificationModel)? onNotificationTap;
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
|
||||||
|
|
||||||
/// A bell icon widget that displays the number of active notifications.
|
/// A bell icon widget that displays the number of active notifications.
|
||||||
///
|
///
|
||||||
|
@ -16,8 +15,6 @@ class NotificationBell extends StatefulWidget {
|
||||||
/// [onTap]: Callback function to be invoked when the bell icon is tapped.
|
/// [onTap]: Callback function to be invoked when the bell icon is tapped.
|
||||||
const NotificationBell({
|
const NotificationBell({
|
||||||
required this.config,
|
required this.config,
|
||||||
required this.service,
|
|
||||||
this.animatedIconStyle,
|
|
||||||
this.onTap,
|
this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -26,12 +23,6 @@ class NotificationBell extends StatefulWidget {
|
||||||
/// the notification service.
|
/// the notification service.
|
||||||
final NotificationConfig config;
|
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.
|
/// Callback function to be invoked when the bell icon is tapped.
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
@ -46,7 +37,7 @@ class _NotificationBellState extends State<NotificationBell> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
widget.service.getActiveAmountStream().listen((amount) {
|
widget.config.service.getActiveAmountStream().listen((amount) {
|
||||||
setState(() {
|
setState(() {
|
||||||
notificationAmount = amount;
|
notificationAmount = amount;
|
||||||
});
|
});
|
||||||
|
@ -60,8 +51,7 @@ class _NotificationBellState extends State<NotificationBell> {
|
||||||
icon: AnimatedNotificationBell(
|
icon: AnimatedNotificationBell(
|
||||||
duration: const Duration(seconds: 1),
|
duration: const Duration(seconds: 1),
|
||||||
notificationCount: notificationAmount,
|
notificationCount: notificationAmount,
|
||||||
style:
|
style: widget.config.bellStyle,
|
||||||
widget.animatedIconStyle ?? const AnimatedNotificationBellStyle(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,64 +1,27 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:flutter_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.
|
/// A widget representing a notification bell.
|
||||||
class NotificationBellWidgetStory extends StatefulWidget {
|
class NotificationBellWidgetStory extends StatelessWidget {
|
||||||
/// Creates a new [NotificationBellWidgetStory] instance.
|
/// Creates a new [NotificationBellWidgetStory] instance.
|
||||||
///
|
///
|
||||||
/// The [config] parameter specifies the notification configuration.
|
/// The [config] parameter specifies the notification configuration.
|
||||||
const NotificationBellWidgetStory({
|
const NotificationBellWidgetStory({
|
||||||
required this.userId,
|
required this.config,
|
||||||
this.config,
|
|
||||||
this.service,
|
|
||||||
this.animatedIconStyle,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The user ID.
|
|
||||||
final String userId;
|
|
||||||
|
|
||||||
/// The notification configuration.
|
/// 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
|
@override
|
||||||
Widget build(BuildContext context) => NotificationBell(
|
Widget build(BuildContext context) => NotificationBell(
|
||||||
config: config,
|
config: config,
|
||||||
service: service,
|
|
||||||
animatedIconStyle: widget.animatedIconStyle,
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => NotificationCenter(
|
builder: (context) => NotificationCenter(
|
||||||
config: config,
|
config: config,
|
||||||
service: service,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_notification_center/src/screens/notification_detail.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:flutter_svg/svg.dart";
|
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
|
||||||
|
|
||||||
/// Widget for displaying the notification center.
|
/// Widget for displaying the notification center.
|
||||||
class NotificationCenter extends StatefulWidget {
|
class NotificationCenter extends StatefulWidget {
|
||||||
|
@ -11,31 +9,39 @@ class NotificationCenter extends StatefulWidget {
|
||||||
/// [config]: Configuration for the notification center.
|
/// [config]: Configuration for the notification center.
|
||||||
const NotificationCenter({
|
const NotificationCenter({
|
||||||
required this.config,
|
required this.config,
|
||||||
required this.service,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Configuration for the notification center.
|
/// Configuration for the notification center.
|
||||||
final NotificationConfig config;
|
final NotificationConfig config;
|
||||||
|
|
||||||
final NotificationService service;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NotificationCenterState createState() => NotificationCenterState();
|
NotificationCenterState createState() => NotificationCenterState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State for the notification center.
|
/// State for the notification center.
|
||||||
class NotificationCenterState extends State<NotificationCenter> {
|
class NotificationCenterState extends State<NotificationCenter> {
|
||||||
late Stream<List<NotificationModel>> _notificationsStream;
|
late Future<List<NotificationModel>> _notificationsFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_notificationsStream = widget.service.getActiveNotifications();
|
// ignore: discarded_futures
|
||||||
|
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||||
widget.service.getActiveAmountStream().listen((data) {
|
widget.config.service.getActiveAmountStream().listen((amount) async {
|
||||||
setState(() {});
|
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||||
});
|
});
|
||||||
|
widget.config.service.addListener(_listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.config.service.removeListener(_listener);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listener() {
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -60,8 +66,8 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: StreamBuilder<List<NotificationModel>>(
|
body: FutureBuilder<List<NotificationModel>>(
|
||||||
stream: _notificationsStream,
|
future: _notificationsFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
@ -87,24 +93,18 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
var notification = snapshot.data![index];
|
var notification = snapshot.data![index];
|
||||||
return notification.isPinned
|
return notification.isPinned
|
||||||
? GestureDetector(
|
? GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async => _navigateToNotificationDetail(
|
||||||
if (widget.config.onNotificationTap != null) {
|
context,
|
||||||
widget.config.onNotificationTap!.call(notification);
|
notification,
|
||||||
} else {
|
widget.config.service,
|
||||||
await _navigateToNotificationDetail(
|
widget.config.translations,
|
||||||
context,
|
),
|
||||||
notification,
|
|
||||||
widget.service,
|
|
||||||
widget.config.translations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Dismissible(
|
child: Dismissible(
|
||||||
key: Key("${notification.id}_pinned"),
|
key: Key("${notification.id}_pinned"),
|
||||||
onDismissed: (direction) async {
|
onDismissed: (direction) async {
|
||||||
if (direction == DismissDirection.endToStart) {
|
if (direction == DismissDirection.endToStart) {
|
||||||
await unPinNotification(
|
await unPinNotification(
|
||||||
widget.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context,
|
context,
|
||||||
|
@ -112,7 +112,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
} else if (direction ==
|
} else if (direction ==
|
||||||
DismissDirection.startToEnd) {
|
DismissDirection.startToEnd) {
|
||||||
await unPinNotification(
|
await unPinNotification(
|
||||||
widget.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context,
|
context,
|
||||||
|
@ -121,43 +121,35 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
},
|
},
|
||||||
background: Container(
|
background: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFF88CB33),
|
color: Color.fromRGBO(59, 213, 111, 1),
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(6),
|
topLeft: Radius.circular(6),
|
||||||
bottomLeft: Radius.circular(6),
|
bottomLeft: Radius.circular(6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: const Padding(
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
padding: EdgeInsets.only(left: 16.0),
|
||||||
child: SvgPicture.asset(
|
child: Icon(
|
||||||
"assets/unpin_icon.svg",
|
Icons.push_pin,
|
||||||
package: "flutter_notification_center",
|
color: Colors.white,
|
||||||
theme: const SvgTheme(
|
|
||||||
currentColor: Colors.white,
|
|
||||||
),
|
|
||||||
height: 24,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
secondaryBackground: Container(
|
secondaryBackground: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFF88CB33),
|
color: Color.fromRGBO(59, 213, 111, 1),
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(6),
|
topRight: Radius.circular(6),
|
||||||
bottomRight: Radius.circular(6),
|
bottomRight: Radius.circular(6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: const Padding(
|
||||||
padding: const EdgeInsets.only(right: 16.0),
|
padding: EdgeInsets.only(right: 16.0),
|
||||||
child: SvgPicture.asset(
|
child: Icon(
|
||||||
"assets/unpin_icon.svg",
|
Icons.push_pin,
|
||||||
package: "flutter_notification_center",
|
color: Colors.white,
|
||||||
theme: const SvgTheme(
|
|
||||||
currentColor: Colors.white,
|
|
||||||
),
|
|
||||||
height: 24,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -169,24 +161,18 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: GestureDetector(
|
: GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async => _navigateToNotificationDetail(
|
||||||
if (widget.config.onNotificationTap != null) {
|
context,
|
||||||
widget.config.onNotificationTap!.call(notification);
|
notification,
|
||||||
} else {
|
widget.config.service,
|
||||||
await _navigateToNotificationDetail(
|
widget.config.translations,
|
||||||
context,
|
),
|
||||||
notification,
|
|
||||||
widget.service,
|
|
||||||
widget.config.translations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Dismissible(
|
child: Dismissible(
|
||||||
key: Key(notification.id),
|
key: Key(notification.id),
|
||||||
onDismissed: (direction) async {
|
onDismissed: (direction) async {
|
||||||
if (direction == DismissDirection.endToStart) {
|
if (direction == DismissDirection.endToStart) {
|
||||||
await dismissNotification(
|
await dismissNotification(
|
||||||
widget.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context,
|
context,
|
||||||
|
@ -194,7 +180,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
} else if (direction ==
|
} else if (direction ==
|
||||||
DismissDirection.startToEnd) {
|
DismissDirection.startToEnd) {
|
||||||
await pinNotification(
|
await pinNotification(
|
||||||
widget.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context,
|
context,
|
||||||
|
@ -203,21 +189,18 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
},
|
},
|
||||||
background: Container(
|
background: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFF88CB33),
|
color: Color.fromRGBO(59, 213, 111, 1),
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(6),
|
topLeft: Radius.circular(6),
|
||||||
bottomLeft: Radius.circular(6),
|
bottomLeft: Radius.circular(6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: const Padding(
|
||||||
padding: const EdgeInsets.only(left: 16.0),
|
padding: EdgeInsets.only(left: 16.0),
|
||||||
child: Transform.rotate(
|
child: Icon(
|
||||||
angle: 0.5,
|
Icons.push_pin,
|
||||||
child: const Icon(
|
color: Colors.white,
|
||||||
Icons.push_pin_outlined,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -225,7 +208,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Color(0xFFFF6161),
|
color: Color.fromRGBO(255, 131, 131, 1),
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(6),
|
topRight: Radius.circular(6),
|
||||||
bottomRight: Radius.circular(6),
|
bottomRight: Radius.circular(6),
|
||||||
|
@ -236,7 +219,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
padding: EdgeInsets.only(right: 16.0),
|
padding: EdgeInsets.only(right: 16.0),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.delete,
|
Icons.delete,
|
||||||
color: Colors.white,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -263,11 +246,8 @@ Widget _notificationItem(
|
||||||
NotificationConfig config,
|
NotificationConfig config,
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
String? dateTimePushed;
|
var dateTimePushed =
|
||||||
if (notification.dateTimePushed != null) {
|
DateFormat("dd-MM-yyyy HH:mm").format(notification.dateTimePushed!);
|
||||||
dateTimePushed = DateFormat("dd/MM/yyyy 'at' HH:mm")
|
|
||||||
.format(notification.dateTimePushed!);
|
|
||||||
}
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -282,20 +262,16 @@ Widget _notificationItem(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
if (!notification.isPinned) ...[
|
if (!notification.isPinned) ...[
|
||||||
if (!notification.isRead) ...[
|
if (!notification.isRead) ...[
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
const Padding(
|
const Icon(
|
||||||
padding: EdgeInsets.only(top: 8),
|
Icons.circle_rounded,
|
||||||
child: Icon(
|
color: Colors.black,
|
||||||
Icons.circle_rounded,
|
size: 8,
|
||||||
color: Colors.black,
|
|
||||||
size: 8,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 8,
|
width: 8,
|
||||||
|
@ -308,7 +284,9 @@ Widget _notificationItem(
|
||||||
Transform.rotate(
|
Transform.rotate(
|
||||||
angle: 0.5,
|
angle: 0.5,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.push_pin_outlined,
|
notification.isRead
|
||||||
|
? Icons.push_pin_outlined
|
||||||
|
: Icons.push_pin,
|
||||||
color: config.pinnedIconColor,
|
color: config.pinnedIconColor,
|
||||||
size: 30,
|
size: 30,
|
||||||
),
|
),
|
||||||
|
@ -317,18 +295,16 @@ Widget _notificationItem(
|
||||||
width: 8,
|
width: 8,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Flexible(
|
Text(
|
||||||
child: Text(
|
notification.title,
|
||||||
notification.title,
|
style: notification.isRead && !notification.isPinned
|
||||||
style: notification.isRead && !notification.isPinned
|
? theme.textTheme.bodyMedium
|
||||||
? theme.textTheme.bodyMedium
|
: theme.textTheme.titleMedium,
|
||||||
: theme.textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
dateTimePushed ?? "",
|
dateTimePushed,
|
||||||
style: theme.textTheme.labelSmall,
|
style: theme.textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:intl/intl.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.
|
/// A page displaying the details of a notification.
|
||||||
class NotificationDetailPage extends StatelessWidget {
|
class NotificationDetailPage extends StatelessWidget {
|
|
@ -1,6 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
|
||||||
|
|
||||||
class NotificationDialog extends StatelessWidget {
|
class NotificationDialog extends StatelessWidget {
|
||||||
const NotificationDialog({
|
const NotificationDialog({
|
|
@ -1,6 +1,6 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
|
||||||
|
|
||||||
class NotificationSnackbar extends SnackBar {
|
class NotificationSnackbar extends SnackBar {
|
||||||
NotificationSnackbar({
|
NotificationSnackbar({
|
|
@ -1,8 +1,7 @@
|
||||||
// Define a PopupHandler class to handle notification popups
|
// Define a PopupHandler class to handle notification popups
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_notification_center/src/widgets/notification_dialog.dart";
|
|
||||||
import "package:flutter_notification_center/src/widgets/notification_snackbar.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
|
||||||
|
|
||||||
class PopupHandler {
|
class PopupHandler {
|
||||||
PopupHandler({
|
PopupHandler({
|
|
@ -0,0 +1,63 @@
|
||||||
|
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, [
|
||||||
|
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,33 +1,29 @@
|
||||||
name: flutter_notification_center
|
name: flutter_notification_center
|
||||||
description: "A new Flutter package project."
|
description: "A Flutter package for displaying notifications in a notification center."
|
||||||
version: 5.1.0
|
publish_to: "none"
|
||||||
homepage:
|
version: 3.0.0
|
||||||
publish_to: 'none'
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.3
|
sdk: ">=3.3.2 <4.0.0"
|
||||||
flutter: ">=1.17.0"
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
intl: any
|
||||||
intl: ^0.20.1
|
|
||||||
flutter_svg: ^2.0.10+1
|
|
||||||
|
|
||||||
flutter_animated_widgets:
|
flutter_animated_widgets:
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
git:
|
||||||
version: ^0.3.1
|
url: https://github.com/Iconica-Development/flutter_animated_widgets
|
||||||
|
ref: 0.3.1
|
||||||
notification_center_repository_interface:
|
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
|
||||||
version: ^5.1.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
flutter_iconica_analysis:
|
flutter_iconica_analysis:
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
git:
|
||||||
version: ^7.0.0
|
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||||
|
ref: 7.0.0
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
assets:
|
uses-material-design: true
|
||||||
- assets/
|
|
||||||
|
|
12
packages/flutter_notification_center/test/widget_test.dart
Normal file
12
packages/flutter_notification_center/test/widget_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
}
|
|
@ -22,8 +22,31 @@ migrate_working_dir/
|
||||||
#.vscode/
|
#.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
|
||||||
/pubspec.lock
|
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
build/
|
.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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
export "src/services/firebase_notification_service.dart";
|
|
@ -0,0 +1,411 @@
|
||||||
|
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 {
|
||||||
|
debugPrint("Checking for scheduled notifications");
|
||||||
|
await checkForScheduledNotifications();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> pushNotification(
|
||||||
|
NotificationModel notification, [
|
||||||
|
Function(NotificationModel model)? onNewNotification,
|
||||||
|
]) async {
|
||||||
|
try {
|
||||||
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
|
if (userId == null) {
|
||||||
|
debugPrint("User is not authenticated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionReference notifications =
|
||||||
|
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||||
|
.collection(activeNotificationsCollection)
|
||||||
|
.doc(userId)
|
||||||
|
.collection(activeNotificationsCollection);
|
||||||
|
|
||||||
|
var currentDateTime = DateTime.now();
|
||||||
|
notification.dateTimePushed = currentDateTime;
|
||||||
|
var notificationMap = notification.toMap();
|
||||||
|
await notifications.doc(notification.id).set(notificationMap);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
debugPrint("No scheduled notifications to be pushed");
|
||||||
|
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, 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;
|
||||||
|
}
|
||||||
|
}
|
36
packages/flutter_notification_center_firebase/pubspec.yaml
Normal file
36
packages/flutter_notification_center_firebase/pubspec.yaml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
name: flutter_notification_center_firebase
|
||||||
|
description: "A new Flutter project."
|
||||||
|
publish_to: "none"
|
||||||
|
version: 3.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: 3.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
|
|
@ -0,0 +1,30 @@
|
||||||
|
// // 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);
|
||||||
|
// });
|
||||||
|
// }
|
|
@ -1,29 +0,0 @@
|
||||||
# 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 +0,0 @@
|
||||||
../../CHANGELOG.md
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE
|
|
|
@ -1 +0,0 @@
|
||||||
../../README.md
|
|
|
@ -1,9 +0,0 @@
|
||||||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
|
||||||
|
|
||||||
# Possible to overwrite the rules from the package
|
|
||||||
|
|
||||||
analyzer:
|
|
||||||
exclude:
|
|
||||||
|
|
||||||
linter:
|
|
||||||
rules:
|
|
|
@ -1,13 +0,0 @@
|
||||||
// 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";
|
|
|
@ -1,27 +0,0 @@
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
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,157 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
name: notification_center_repository_interface
|
|
||||||
description: "A new Flutter package project."
|
|
||||||
version: 5.1.0
|
|
||||||
homepage:
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ^3.5.3
|
|
||||||
flutter: ">=1.17.0"
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
rxdart: any
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
flutter_iconica_analysis:
|
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
|
||||||
version: ^7.0.0
|
|
Loading…
Reference in a new issue