mirror of
https://github.com/Iconica-Development/flutter_notification_center.git
synced 2025-05-19 00:53:44 +02:00
Compare commits
21 commits
Author | SHA1 | Date | |
---|---|---|---|
|
463a745053 | ||
|
006a83dcde | ||
|
17350efe91 | ||
|
5e3e22d870 | ||
|
c246abdbc9 | ||
|
dce9ac0b4f | ||
|
406589194f | ||
367303aecf | |||
|
ddf2a2bbb3 | ||
|
90738db549 | ||
|
43a7f44e88 | ||
|
dbf83cc8d6 | ||
|
0b14e46833 | ||
|
2acb617512 | ||
|
b929ff5af9 | ||
|
91621bde96 | ||
|
169228152e | ||
|
b57bc7ce46 | ||
|
22287a503d | ||
|
03d43052ef | ||
a46a1d92d9 |
69 changed files with 1690 additions and 1594 deletions
17
.github/dependabot.yml
vendored
Normal file
17
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/flutter_notification_center"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/flutter_notification_center_firebase"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "pub"
|
||||
directory: "/packages/flutter_notification_center/example"
|
||||
schedule:
|
||||
interval: "weekly"
|
14
.github/workflows/document-component.yml
vendored
Normal file
14
.github/workflows/document-component.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Iconica Standard Component Documentation Workflow
|
||||
# Workflow Caller version: 1.0.0
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-iconica-component-documentation-workflow:
|
||||
uses: Iconica-Development/.github/.github/workflows/component-documentation.yml@master
|
||||
secrets: inherit
|
||||
permissions: write-all
|
14
.github/workflows/melos-ci.yml
vendored
Normal file
14
.github/workflows/melos-ci.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Iconica Standard Melos CI Workflow
|
||||
# Workflow Caller version: 1.0.0
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-global-iconica-workflow:
|
||||
uses: Iconica-Development/.github/.github/workflows/melos-ci.yml@master
|
||||
secrets: inherit
|
||||
permissions: write-all
|
||||
with:
|
||||
subfolder: '.' # add optional subfolder to run workflow in
|
14
.github/workflows/release.yml
vendored
Normal file
14
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
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
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -49,8 +49,11 @@ android/
|
|||
web/
|
||||
linux/
|
||||
macos/
|
||||
windows/
|
||||
|
||||
pubspec.lock
|
||||
.metadata
|
||||
flutter_notification_center.iml
|
||||
dotenv
|
||||
dotenv
|
||||
firebase_options.dart
|
||||
pubspec_overrides.yaml
|
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -1,3 +1,38 @@
|
|||
## [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
|
||||
|
||||
* Rework design for notification center
|
||||
* Added iconica linter
|
||||
|
||||
## [1.4.1] - 4 June 2024
|
||||
|
||||
* Fix notification amount number to properly size and show plus icon when above certain amount
|
||||
|
||||
|
||||
## [1.4.0] - 29 May 2024
|
||||
|
||||
* Make notifications user bound and not app bound (https://github.com/Iconica-Development/flutter_notification_center/issues/8)
|
||||
* Fix indicator amount of bell icon (https://github.com/Iconica-Development/flutter_notification_center/issues/10)
|
||||
* Add unpin functionality (https://github.com/Iconica-Development/flutter_notification_center/issues/15)
|
||||
* Change color of bin icon (https://github.com/Iconica-Development/flutter_notification_center/issues/19)
|
||||
* Fix multiple dialog and snackbars stacking on top
|
||||
* Fix notification center updating when new notifications come in and the screen is open
|
||||
* Fix sorting of all notifications
|
||||
* Fix pinned notifications should remain on top
|
||||
|
||||
## [1.3.1] - 30 April 2024
|
||||
|
||||
* Fix Animationcontroller not disposing correctly.
|
||||
|
|
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
|||
Copyright (c) 2024 Iconica, All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
323
README.md
323
README.md
|
@ -1,59 +1,300 @@
|
|||
# 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
|
||||
|
||||
- Notification center: A page containing a list of 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.
|
||||
- Pinned notifications: A pinned notification can't be dismissed by the user from the notification center.
|
||||
- Dismissable notifications: A dismissable notification can be dismissed by the user from the notification center.
|
||||
- Notification detail page: Clicking on a notification takes the user to a notification detail page.
|
||||
- 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.
|
||||
- **Real-time Notifications**: Fetch and display active notifications in real-time.
|
||||
- **Scheduled and Recurring Notifications**: Set notifications for future delivery or create recurring notifications.
|
||||
- **Interactive Notification Center**: Display notifications with interactive options to dismiss, pin, or mark them as read.
|
||||
- **Customizable UI**: Adjust UI elements like icons, colors, and layout of the notification center.
|
||||
- **Localization**: Easily translate notification messages for multiple languages.
|
||||
- **Modular Architecture**: Compatible with Firebase and local storage solutions, allowing flexibility in backend configuration.
|
||||
|
||||
## Setup
|
||||
---
|
||||
|
||||
To use this package, add `flutter_notification_center` as a dependency in your pubspec.yaml file.
|
||||
## Getting Started
|
||||
|
||||
- For custom notification styling provide the optional notificationWidgetBuilder with your own implementation.
|
||||
### Core Components
|
||||
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.
|
||||
|
||||
The `NotificationConfig` has its own parameters, as specified below:
|
||||
| Parameter | Explanation |
|
||||
|-----------|-------------|
|
||||
| service | The notification service that will be used |
|
||||
| seperateNotificationsWithDivider | If true notifications will be seperated with dividers within the notification center |
|
||||
| translations | The translations that will be used |
|
||||
| notificationWidgetBuilder | The widget that defines the styles and logic for every notification |
|
||||
| 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|
|
||||
### Requirements
|
||||
1. **Firebase**: Firebase should be initialized for storing notifications in Firestore.
|
||||
2. **User Identification**: Each user requires a unique `userId` for personalized notifications.
|
||||
|
||||
If you set `enableNotificationPopups` to true, you can use `PopupHandler` in the `newNotificationCallback` to display popups in case a new notification is pushed.
|
||||
---
|
||||
|
||||
The `notificationWidgetBuilder` expects the following parameters, as specified below:
|
||||
| Parameter | Explanation |
|
||||
|-----------|-------------|
|
||||
| notification | The notification that is being defined |
|
||||
| style | The styling that will be used for the notification |
|
||||
| notificationService | The notification service that will be used |
|
||||
| notificationTranslations | The translations that will be used for the notification|
|
||||
| context | The provided context |
|
||||
## Installation
|
||||
|
||||
1. **Add Dependencies**: Add required dependencies in `pubspec.yaml`.
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
firebase_notification_center_repository:
|
||||
git:
|
||||
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
|
||||
Create instant notification:
|
||||
- Make a call to pushNotification() and provide a NotificationModel with the required attributes.
|
||||
|
||||
Create scheduled notification:
|
||||
- Make a call to createScheduledNotification() and provide a NotificationModel with the required attributes + scheduledFor.
|
||||
### Setting Up Firebase
|
||||
|
||||
Create recurring notification:
|
||||
- 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)
|
||||
1. **Initialize FirebaseNotificationRepository**:
|
||||
- This repository interacts with Firebase Firestore for managing notifications.
|
||||
|
||||
To create a pinned notification, set isPinned = true when creating the notification.
|
||||
```dart
|
||||
final notificationRepository = FirebaseNotificationRepository(
|
||||
activeNotificationsCollection: "active_notifications",
|
||||
plannedNotificationsCollection: "planned_notifications",
|
||||
);
|
||||
```
|
||||
|
||||
### Example
|
||||
2. **Firestore Configuration**:
|
||||
- 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.
|
||||
|
||||
|
@ -61,7 +302,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).
|
||||
|
||||
## Want to contribute
|
||||
## 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).
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<?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>
|
39
melos.yaml
Normal file
39
melos.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
name: flutter_notification_center
|
||||
|
||||
packages:
|
||||
- packages/**
|
||||
|
||||
command:
|
||||
version:
|
||||
branch: master
|
||||
|
||||
scripts:
|
||||
lint:all:
|
||||
run: dart run melos run analyze && dart run melos run format-check
|
||||
description: Run all static analysis checks.
|
||||
|
||||
get:
|
||||
run: |
|
||||
melos exec -c 1 -- "flutter pub get"
|
||||
melos exec --scope="*example*" -c 1 -- "flutter pub get"
|
||||
|
||||
upgrade:
|
||||
run: melos exec -c 1 -- "flutter pub upgrade"
|
||||
|
||||
create:
|
||||
# run create in the example folder of flutter_chat_view
|
||||
run: melos exec --scope="*example*" -c 1 -- "flutter create ."
|
||||
|
||||
analyze:
|
||||
run: |
|
||||
dart run melos exec -c 1 -- \
|
||||
flutter analyze --fatal-infos
|
||||
description: Run `flutter analyze` for all packages.
|
||||
|
||||
format:
|
||||
run: dart run melos exec dart format .
|
||||
description: Run `dart format` for all packages.
|
||||
|
||||
format-check:
|
||||
run: dart run melos exec dart format . --set-exit-if-changed
|
||||
description: Run `dart format` checks for all packages.
|
|
@ -22,31 +22,8 @@ migrate_working_dir/
|
|||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
.dart_tools/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# iOS related
|
||||
/ios/
|
||||
|
||||
lib/config/
|
||||
pubspec.lock
|
||||
dotenv
|
||||
|
||||
build/
|
1
packages/firebase_notification_center_repository/CHANGELOG.md
Symbolic link
1
packages/firebase_notification_center_repository/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../CHANGELOG.md
|
1
packages/firebase_notification_center_repository/LICENSE
Symbolic link
1
packages/firebase_notification_center_repository/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE
|
1
packages/firebase_notification_center_repository/README.md
Symbolic link
1
packages/firebase_notification_center_repository/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../README.md
|
|
@ -1,4 +1,4 @@
|
|||
include: package:flutter_iconica_analysis/components_options.yaml
|
||||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
|
@ -0,0 +1 @@
|
|||
export "src/firebase_notification_repository.dart";
|
|
@ -0,0 +1,151 @@
|
|||
import "package:cloud_firestore/cloud_firestore.dart";
|
||||
import "package:firebase_core/firebase_core.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
class FirebaseNotificationRepository
|
||||
implements NotificationRepositoryInterface {
|
||||
FirebaseNotificationRepository({
|
||||
FirebaseApp? firebaseApp,
|
||||
this.activeNotificationsCollection = "active_notifications",
|
||||
this.plannedNotificationsCollection = "planned_notifications",
|
||||
}) : firebaseApp = firebaseApp ?? Firebase.app();
|
||||
|
||||
final FirebaseApp firebaseApp;
|
||||
final String activeNotificationsCollection;
|
||||
final String plannedNotificationsCollection;
|
||||
|
||||
@override
|
||||
Future<NotificationModel> addNotification(
|
||||
String userId,
|
||||
NotificationModel notification,
|
||||
List<String> recipientIds,
|
||||
) async {
|
||||
var newNotification = notification;
|
||||
|
||||
for (var recipientId in recipientIds) {
|
||||
DocumentReference notifications;
|
||||
if (notification.scheduledFor != null &&
|
||||
notification.scheduledFor!.isAfter(DateTime.now())) {
|
||||
notifications = FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(recipientId)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(notification.id);
|
||||
} else {
|
||||
newNotification = notification.copyWith(
|
||||
id: "${notification.id}-${DateTime.now().millisecondsSinceEpoch}",
|
||||
);
|
||||
|
||||
notifications = FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(recipientId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(newNotification.id);
|
||||
}
|
||||
|
||||
var currentDateTime = DateTime.now();
|
||||
newNotification.dateTimePushed = currentDateTime;
|
||||
var notificationMap = newNotification.toMap();
|
||||
await notifications.set(notificationMap);
|
||||
}
|
||||
|
||||
return newNotification;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteNotification(
|
||||
String userId,
|
||||
String id,
|
||||
bool planned,
|
||||
) async {
|
||||
try {
|
||||
if (planned) {
|
||||
await FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(id)
|
||||
.delete();
|
||||
} else {
|
||||
await FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(id)
|
||||
.delete();
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception("Failed to delete notification: $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<NotificationModel?> getNotification(String userId, String id) {
|
||||
var notificationStream = FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(id)
|
||||
.snapshots()
|
||||
.map((snapshot) {
|
||||
if (snapshot.exists) {
|
||||
if (snapshot.data() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return NotificationModel.fromJson(snapshot.data()!);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return notificationStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<NotificationModel>> getNotifications(String userId) {
|
||||
var notificationsStream = FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.snapshots()
|
||||
.map(
|
||||
(snapshot) => snapshot.docs
|
||||
.map((doc) => NotificationModel.fromJson(doc.data()))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
return notificationsStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<NotificationModel> updateNotification(
|
||||
String userId,
|
||||
NotificationModel notification,
|
||||
) async {
|
||||
await FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(notification.id)
|
||||
.update(notification.toMap());
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<NotificationModel>> getPlannedNotifications(String userId) {
|
||||
var notificationsStream = FirebaseFirestore.instanceFor(app: firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.snapshots()
|
||||
.map(
|
||||
(snapshot) => snapshot.docs
|
||||
.map((doc) => NotificationModel.fromJson(doc.data()))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
return notificationsStream;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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,22 +22,8 @@ migrate_working_dir/
|
|||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
build/
|
||||
|
|
1
packages/flutter_notification_center/CHANGELOG.md
Symbolic link
1
packages/flutter_notification_center/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../CHANGELOG.md
|
1
packages/flutter_notification_center/LICENSE
Symbolic link
1
packages/flutter_notification_center/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE
|
1
packages/flutter_notification_center/README.md
Symbolic link
1
packages/flutter_notification_center/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../README.md
|
|
@ -1,28 +1,9 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
rules:
|
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 319 B |
|
@ -30,7 +30,6 @@ migrate_working_dir/
|
|||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
.dart_tools/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
@ -42,11 +41,3 @@ app.*.map.json
|
|||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# iOS related
|
||||
/ios/
|
||||
|
||||
lib/config/
|
||||
pubspec.lock
|
||||
dotenv
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}
|
|
@ -1,168 +0,0 @@
|
|||
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 NotificationStyle style;
|
||||
final FirebaseNotificationService notificationService;
|
||||
final NotificationTranslations notificationTranslations;
|
||||
final BuildContext context;
|
||||
|
||||
const CustomNotificationWidget({
|
||||
required this.notification,
|
||||
required this.style,
|
||||
required this.notificationTranslations,
|
||||
required this.notificationService,
|
||||
required this.context,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return notification.isPinned
|
||||
//Pinned notification
|
||||
? GestureDetector(
|
||||
onTap: () async =>
|
||||
_navigateToNotificationDetail(context, notification),
|
||||
child: ListTile(
|
||||
leading: style.showNotificationIcon != null
|
||||
? Icon(
|
||||
notification.icon,
|
||||
color: style.leadingIconColor,
|
||||
)
|
||||
: null,
|
||||
title: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
notification.title,
|
||||
style: style.titleTextStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.push_pin),
|
||||
color: style.pinnedIconColor,
|
||||
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);
|
||||
} else if (direction == DismissDirection.startToEnd) {
|
||||
await pinNotification(
|
||||
notificationService, notification, 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.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
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: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: style.isReadDotColor,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _navigateToNotificationDetail(
|
||||
BuildContext context,
|
||||
NotificationModel notification,
|
||||
) async {
|
||||
await markNotificationAsRead(notificationService, notification);
|
||||
if (context.mounted) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NotificationDetailPage(
|
||||
translations: notificationTranslations,
|
||||
notification: notification,
|
||||
notificationStyle: style,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dismissNotification(
|
||||
FirebaseNotificationService notificationService,
|
||||
NotificationModel notification,
|
||||
) async {
|
||||
await notificationService.dismissActiveNotification(notification);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Notification dismissed"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markNotificationAsRead(
|
||||
FirebaseNotificationService notificationService,
|
||||
NotificationModel notification,
|
||||
) async {
|
||||
await notificationService.markNotificationAsRead(notification);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:example/custom_notification.dart';
|
||||
// import 'package:example/firebase_options.dart';
|
||||
// import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_notification_center_repository/firebase_notification_center_repository.dart';
|
||||
// import 'package:example/firebase_options.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
|
||||
// import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
|
||||
|
@ -21,14 +22,10 @@ void main() async {
|
|||
}
|
||||
|
||||
Future<void> _configureApp() async {
|
||||
try {
|
||||
await dotenv.load(fileName: 'dotenv');
|
||||
} catch (e) {
|
||||
debugPrint('Failed to load dotenv file: $e');
|
||||
}
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
// Generate a FirebaseOptions and uncomment the following lines to initialize Firebase.
|
||||
// await Firebase.initializeApp(
|
||||
// options: DefaultFirebaseOptions.currentPlatform,
|
||||
// );
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[
|
||||
DeviceOrientation.portraitDown,
|
||||
|
@ -38,7 +35,8 @@ Future<void> _configureApp() async {
|
|||
}
|
||||
|
||||
Future<void> _signInUser() async {
|
||||
/// Implement your own sign in logic here
|
||||
// Sign in, you could use the line below or implement your own sign in method.
|
||||
// await FirebaseAuth.instance.signInAnonymously();
|
||||
}
|
||||
|
||||
class NotificationCenterDemo extends StatefulWidget {
|
||||
|
@ -49,45 +47,39 @@ class NotificationCenterDemo extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
||||
late NotificationConfig config;
|
||||
late PopupHandler popupHandler;
|
||||
late NotificationService service;
|
||||
// Provide a user ID here. For Firebase you can use the commented line below.
|
||||
String userId = ""; //FirebaseAuth.instance.currentUser!.uid;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var service =
|
||||
FirebaseNotificationService(newNotificationCallback: (notification) {
|
||||
popupHandler.handleNotificationPopup(notification);
|
||||
});
|
||||
config = NotificationConfig(
|
||||
service: service,
|
||||
enableNotificationPopups: true,
|
||||
showAsSnackBar: false,
|
||||
notificationWidgetBuilder: (notification, context) =>
|
||||
CustomNotificationWidget(
|
||||
notification: notification,
|
||||
style: const NotificationStyle(
|
||||
appTitleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 20,
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 16,
|
||||
),
|
||||
leadingIconColor: Colors.grey,
|
||||
pinnedIconColor: Colors.grey,
|
||||
isReadDotColor: Colors.red,
|
||||
showNotificationIcon: true,
|
||||
),
|
||||
notificationService: service,
|
||||
notificationTranslations: const NotificationTranslations(),
|
||||
context: context,
|
||||
),
|
||||
seperateNotificationsWithDivider: true,
|
||||
|
||||
service = NotificationService(
|
||||
userId: userId,
|
||||
repository: FirebaseNotificationRepository(),
|
||||
);
|
||||
|
||||
// Uncomment the line below to send a test notification.
|
||||
// Provide a user ID in the list to send the notification to.
|
||||
_sendTestNotification([userId]);
|
||||
}
|
||||
|
||||
_sendTestNotification(List<String> recipientIds) async {
|
||||
await service.pushNotification(
|
||||
NotificationModel(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
title: 'Test Notification',
|
||||
body: 'This is a test notification.',
|
||||
// For a scheduled message provide a scheduledFor date.
|
||||
// For a recurring message provide a scheduledFor date, set recurring to true and provide an occuringInterval.
|
||||
//
|
||||
// scheduledFor: DateTime.now().add(const Duration(seconds: 5)),
|
||||
// recurring: true,
|
||||
// occuringInterval: OcurringInterval.debug,
|
||||
),
|
||||
recipientIds,
|
||||
);
|
||||
popupHandler = PopupHandler(context: context, config: config);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -98,7 +90,8 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
|||
centerTitle: true,
|
||||
actions: [
|
||||
NotificationBellWidgetStory(
|
||||
config: config,
|
||||
userId: userId,
|
||||
service: service,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -4,48 +4,45 @@ description: "A new Flutter project."
|
|||
# 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
|
||||
|
||||
version: 1.0.0
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
# followed by an optional build number separated by a +.
|
||||
# Both the version and the builder number may be overridden in flutter
|
||||
# build by specifying --build-name and --build-number, respectively.
|
||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.2 <4.0.0'
|
||||
sdk: ^3.5.3
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
intl: ^0.17.0
|
||||
|
||||
flutter_animated_widgets:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_animated_widgets
|
||||
ref: 0.0.1
|
||||
cloud_firestore: ^4.16.1
|
||||
|
||||
flutter_dotenv: ^5.0.2
|
||||
firebase_auth: ^4.2.6
|
||||
firebase_core: ^2.5.0
|
||||
firebase_storage: ^11.0.14
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
flutter_notification_center:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||
ref: 1.3.0
|
||||
path: packages/flutter_notification_center
|
||||
flutter_notification_center_firebase:
|
||||
path: ../../flutter_notification_center_firebase
|
||||
path: ../
|
||||
|
||||
firebase_notification_center_repository:
|
||||
path: ../../firebase_notification_center_repository
|
||||
|
||||
intl: ^0.20.1
|
||||
|
||||
firebase_auth: any
|
||||
firebase_core: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_iconica_analysis:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||
ref: 7.0.0
|
||||
sdk: flutter
|
||||
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- dotenv
|
||||
|
||||
uses-material-design: true
|
1
packages/flutter_notification_center/firebase.json
Normal file
1
packages/flutter_notification_center/firebase.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"appshell-demo","configurations":{"web":"1:431820621472:web:f4b27eea24be24fd1babc5"}}}}}}
|
|
@ -1,17 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2024 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
export "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
export "src/models/notification.dart";
|
||||
export "src/models/notification_config.dart";
|
||||
export "src/models/notification_theme.dart";
|
||||
export "src/models/notification_translation.dart";
|
||||
export "src/notification_bell.dart";
|
||||
export "src/notification_dialog.dart";
|
||||
export "src/popup_handler.dart";
|
||||
export "src/notification_snackbar.dart";
|
||||
export "src/notification_detail.dart";
|
||||
export "src/notification_bell_story.dart";
|
||||
export "src/notification_center.dart";
|
||||
export "src/services/notification_service.dart";
|
||||
export "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
||||
|
||||
// Screens
|
||||
export "src/screens/notification_bell.dart";
|
||||
export "src/screens/notification_detail.dart";
|
||||
|
||||
// Widgets
|
||||
export "src/widgets/notification_dialog.dart";
|
||||
export "src/widgets/notification_snackbar.dart";
|
||||
export "src/widgets/popup_handler.dart";
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
/// Defines the appearance customization for notifications.
|
||||
class NotificationStyle {
|
||||
/// Creates a new [NotificationStyle] instance.
|
||||
///
|
||||
/// Each parameter is optional and allows customizing various aspects
|
||||
/// of the notification appearance.
|
||||
const NotificationStyle({
|
||||
this.titleTextStyle,
|
||||
this.subtitleTextStyle,
|
||||
this.backgroundColor,
|
||||
this.leadingIconColor,
|
||||
this.pinnedIconColor,
|
||||
this.contentPadding,
|
||||
this.titleTextAlign,
|
||||
this.subtitleTextAlign,
|
||||
this.dense,
|
||||
this.tileDecoration,
|
||||
this.emptyNotificationsBuilder,
|
||||
this.appTitleTextStyle,
|
||||
this.dividerColor,
|
||||
this.isReadDotColor,
|
||||
this.showNotificationIcon,
|
||||
});
|
||||
|
||||
/// The text style for the title of the notification.
|
||||
final TextStyle? titleTextStyle;
|
||||
|
||||
/// The text style for the subtitle of the notification.
|
||||
final TextStyle? subtitleTextStyle;
|
||||
|
||||
/// The background color of the notification.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// The color of the leading icon (if any) in the notification.
|
||||
final Color? leadingIconColor;
|
||||
|
||||
/// The color of the trailing icon (if any) in the notification.
|
||||
final Color? pinnedIconColor;
|
||||
|
||||
/// The padding around the content of the notification.
|
||||
final EdgeInsets? contentPadding;
|
||||
|
||||
/// The alignment of the title text within the notification.
|
||||
final TextAlign? titleTextAlign;
|
||||
|
||||
/// The alignment of the subtitle text within the notification.
|
||||
final TextAlign? subtitleTextAlign;
|
||||
|
||||
/// Whether the notification should be dense or not.
|
||||
final bool? dense;
|
||||
|
||||
/// The decoration to apply to the tile of the notification.
|
||||
final BoxDecoration? tileDecoration;
|
||||
|
||||
/// A builder function to display when there are no notifications.
|
||||
final Widget Function()? emptyNotificationsBuilder;
|
||||
|
||||
/// The text style for the application title.
|
||||
final TextStyle? appTitleTextStyle;
|
||||
|
||||
/// The color of the divider.
|
||||
final Color? dividerColor;
|
||||
|
||||
/// The color of the dot that shows that anotification has not been read.
|
||||
final Color? isReadDotColor;
|
||||
|
||||
/// Whether to show the notification icon.
|
||||
final bool? showNotificationIcon;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/// Defines translations for notification messages.
|
||||
class NotificationTranslations {
|
||||
/// Creates a new [NotificationTranslations] instance.
|
||||
///
|
||||
/// The [appBarTitle] parameter specifies the title to be displayed in the
|
||||
/// app bar of the notification center. The default value
|
||||
/// is 'Notification Center'.
|
||||
///
|
||||
/// The [noNotifications] parameter specifies the message to be displayed when
|
||||
/// there are no unread notifications available. The default value is
|
||||
/// 'No unread notifications available.'.
|
||||
const NotificationTranslations({
|
||||
this.appBarTitle = "Notification Center",
|
||||
this.noNotifications = "No unread notifications available.",
|
||||
});
|
||||
|
||||
/// The title to be displayed in the app bar of the notification center.
|
||||
final String appBarTitle;
|
||||
|
||||
/// The message to be displayed when there are no unread
|
||||
/// notifications available.
|
||||
final String noNotifications;
|
||||
}
|
|
@ -1,27 +1,64 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "../flutter_notification_center.dart";
|
||||
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
||||
import "package:flutter_notification_center/src/notification_center.dart";
|
||||
import "package:flutter_notification_center/src/screens/notification_bell.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
/// A widget representing a notification bell.
|
||||
class NotificationBellWidgetStory extends StatelessWidget {
|
||||
class NotificationBellWidgetStory extends StatefulWidget {
|
||||
/// Creates a new [NotificationBellWidgetStory] instance.
|
||||
///
|
||||
/// The [config] parameter specifies the notification configuration.
|
||||
const NotificationBellWidgetStory({
|
||||
required this.config,
|
||||
required this.userId,
|
||||
this.config,
|
||||
this.service,
|
||||
this.animatedIconStyle,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID.
|
||||
final String userId;
|
||||
|
||||
/// The notification configuration.
|
||||
final NotificationConfig config;
|
||||
final NotificationConfig? config;
|
||||
|
||||
/// The notification service.
|
||||
final NotificationService? service;
|
||||
|
||||
final AnimatedNotificationBellStyle? animatedIconStyle;
|
||||
|
||||
@override
|
||||
State<NotificationBellWidgetStory> createState() =>
|
||||
_NotificationBellWidgetStoryState();
|
||||
}
|
||||
|
||||
class _NotificationBellWidgetStoryState
|
||||
extends State<NotificationBellWidgetStory> {
|
||||
late NotificationConfig config;
|
||||
late NotificationService service;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
config = widget.config ?? const NotificationConfig();
|
||||
service = widget.service ??
|
||||
NotificationService(
|
||||
userId: widget.userId,
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NotificationBell(
|
||||
config: config,
|
||||
service: service,
|
||||
animatedIconStyle: widget.animatedIconStyle,
|
||||
onTap: () async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NotificationCenter(
|
||||
config: config,
|
||||
service: service,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../flutter_notification_center.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_notification_center/src/screens/notification_detail.dart";
|
||||
import "package:flutter_svg/svg.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
/// Widget for displaying the notification center.
|
||||
class NotificationCenter extends StatefulWidget {
|
||||
|
@ -8,222 +11,331 @@ class NotificationCenter extends StatefulWidget {
|
|||
/// [config]: Configuration for the notification center.
|
||||
const NotificationCenter({
|
||||
required this.config,
|
||||
required this.service,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Configuration for the notification center.
|
||||
final NotificationConfig config;
|
||||
|
||||
final NotificationService service;
|
||||
|
||||
@override
|
||||
NotificationCenterState createState() => NotificationCenterState();
|
||||
}
|
||||
|
||||
/// State for the notification center.
|
||||
class NotificationCenterState extends State<NotificationCenter> {
|
||||
late Future<List<NotificationModel>> _notificationsFuture;
|
||||
late Stream<List<NotificationModel>> _notificationsStream;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// ignore: discarded_futures
|
||||
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||
widget.config.service.addListener(_listener);
|
||||
_notificationsStream = widget.service.getActiveNotifications();
|
||||
|
||||
widget.service.getActiveAmountStream().listen((data) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.config.service.removeListener(_listener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _listener() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
widget.config.translations.appBarTitle,
|
||||
),
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||
title: Text(
|
||||
widget.config.translations.appBarTitle,
|
||||
style: theme.textTheme.headlineLarge,
|
||||
),
|
||||
body: FutureBuilder<List<NotificationModel>>(
|
||||
future: _notificationsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text("Error: ${snapshot.error}"));
|
||||
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
|
||||
return const Center(
|
||||
child: Text("No unread notifications available."),
|
||||
);
|
||||
} else {
|
||||
return ListView.builder(
|
||||
itemCount: snapshot.data!.length * 2 - 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index.isOdd) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: widget.config.seperateNotificationsWithDivider
|
||||
? const Divider(
|
||||
color: Colors.grey,
|
||||
thickness: 1.0,
|
||||
)
|
||||
: Container(),
|
||||
);
|
||||
}
|
||||
var notification = snapshot.data![index ~/ 2];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: widget.config.notificationWidgetBuilder != null
|
||||
? widget.config.notificationWidgetBuilder!(
|
||||
notification, context)
|
||||
: notification.isPinned
|
||||
//Pinned notification
|
||||
? GestureDetector(
|
||||
onTap: () async =>
|
||||
_navigateToNotificationDetail(
|
||||
context,
|
||||
notification,
|
||||
widget.config.service,
|
||||
widget.config.translations,
|
||||
const NotificationStyle()),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
notification.icon,
|
||||
color: Colors.grey,
|
||||
),
|
||||
title: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
notification.title,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.push_pin),
|
||||
color: Colors.grey,
|
||||
onPressed: () async =>
|
||||
_navigateToNotificationDetail(
|
||||
context,
|
||||
notification,
|
||||
widget.config.service,
|
||||
widget.config.translations,
|
||||
const NotificationStyle()),
|
||||
padding:
|
||||
const EdgeInsets.only(left: 60.0),
|
||||
),
|
||||
),
|
||||
)
|
||||
//Dismissable notification
|
||||
: Dismissible(
|
||||
key: Key(notification.id),
|
||||
onDismissed: (direction) async {
|
||||
if (direction ==
|
||||
DismissDirection.endToStart) {
|
||||
await dismissNotification(
|
||||
widget.config.service,
|
||||
notification,
|
||||
context);
|
||||
} else if (direction ==
|
||||
DismissDirection.startToEnd) {
|
||||
await pinNotification(
|
||||
widget.config.service,
|
||||
notification,
|
||||
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.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () async =>
|
||||
_navigateToNotificationDetail(
|
||||
context,
|
||||
notification,
|
||||
widget.config.service,
|
||||
widget.config.translations,
|
||||
const NotificationStyle()),
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
notification.icon,
|
||||
color: Colors.grey,
|
||||
),
|
||||
title: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
notification.title,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: !notification.isRead
|
||||
? Container(
|
||||
margin: const EdgeInsets.only(
|
||||
right: 8.0),
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.red,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
centerTitle: true,
|
||||
iconTheme: theme.appBarTheme.iconTheme ??
|
||||
const IconThemeData(color: Colors.white),
|
||||
leading: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
body: StreamBuilder<List<NotificationModel>>(
|
||||
stream: _notificationsStream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
debugPrint("Error: ${snapshot.error}");
|
||||
return Center(child: Text(widget.config.translations.errorMessage));
|
||||
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
|
||||
return widget.config.emptyNotificationsBuilder?.call() ??
|
||||
Center(
|
||||
child: Text(
|
||||
widget.config.translations.noNotifications,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 20,
|
||||
horizontal: 20,
|
||||
),
|
||||
itemCount: snapshot.data!.length,
|
||||
itemBuilder: (context, index) {
|
||||
var notification = snapshot.data![index];
|
||||
return notification.isPinned
|
||||
? GestureDetector(
|
||||
onTap: () async {
|
||||
if (widget.config.onNotificationTap != null) {
|
||||
widget.config.onNotificationTap!.call(notification);
|
||||
} else {
|
||||
await _navigateToNotificationDetail(
|
||||
context,
|
||||
notification,
|
||||
widget.service,
|
||||
widget.config.translations,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Dismissible(
|
||||
key: Key("${notification.id}_pinned"),
|
||||
onDismissed: (direction) async {
|
||||
if (direction == DismissDirection.endToStart) {
|
||||
await unPinNotification(
|
||||
widget.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context,
|
||||
);
|
||||
} else if (direction ==
|
||||
DismissDirection.startToEnd) {
|
||||
await unPinNotification(
|
||||
widget.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context,
|
||||
);
|
||||
}
|
||||
},
|
||||
background: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF88CB33),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(6),
|
||||
bottomLeft: Radius.circular(6),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: SvgPicture.asset(
|
||||
"assets/unpin_icon.svg",
|
||||
package: "flutter_notification_center",
|
||||
theme: const SvgTheme(
|
||||
currentColor: Colors.white,
|
||||
),
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
secondaryBackground: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF88CB33),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(6),
|
||||
bottomRight: Radius.circular(6),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: SvgPicture.asset(
|
||||
"assets/unpin_icon.svg",
|
||||
package: "flutter_notification_center",
|
||||
theme: const SvgTheme(
|
||||
currentColor: Colors.white,
|
||||
),
|
||||
height: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: _notificationItem(
|
||||
context,
|
||||
notification,
|
||||
widget.config,
|
||||
),
|
||||
),
|
||||
)
|
||||
: GestureDetector(
|
||||
onTap: () async {
|
||||
if (widget.config.onNotificationTap != null) {
|
||||
widget.config.onNotificationTap!.call(notification);
|
||||
} else {
|
||||
await _navigateToNotificationDetail(
|
||||
context,
|
||||
notification,
|
||||
widget.service,
|
||||
widget.config.translations,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Dismissible(
|
||||
key: Key(notification.id),
|
||||
onDismissed: (direction) async {
|
||||
if (direction == DismissDirection.endToStart) {
|
||||
await dismissNotification(
|
||||
widget.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context,
|
||||
);
|
||||
} else if (direction ==
|
||||
DismissDirection.startToEnd) {
|
||||
await pinNotification(
|
||||
widget.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context,
|
||||
);
|
||||
}
|
||||
},
|
||||
background: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF88CB33),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(6),
|
||||
bottomLeft: Radius.circular(6),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Transform.rotate(
|
||||
angle: 0.5,
|
||||
child: const Icon(
|
||||
Icons.push_pin_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
secondaryBackground: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFFF6161),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(6),
|
||||
bottomRight: Radius.circular(6),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerRight,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(right: 16.0),
|
||||
child: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: _notificationItem(
|
||||
context,
|
||||
notification,
|
||||
widget.config,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _notificationItem(
|
||||
BuildContext context,
|
||||
NotificationModel notification,
|
||||
NotificationConfig config,
|
||||
) {
|
||||
var theme = Theme.of(context);
|
||||
String? dateTimePushed;
|
||||
if (notification.dateTimePushed != null) {
|
||||
dateTimePushed = DateFormat("dd/MM/yyyy 'at' HH:mm")
|
||||
.format(notification.dateTimePushed!);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!notification.isPinned) ...[
|
||||
if (!notification.isRead) ...[
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Icon(
|
||||
Icons.circle_rounded,
|
||||
color: Colors.black,
|
||||
size: 8,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
],
|
||||
] else ...[
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Transform.rotate(
|
||||
angle: 0.5,
|
||||
child: Icon(
|
||||
Icons.push_pin_outlined,
|
||||
color: config.pinnedIconColor,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
child: Text(
|
||||
notification.title,
|
||||
style: notification.isRead && !notification.isPinned
|
||||
? theme.textTheme.bodyMedium
|
||||
: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
dateTimePushed ?? "",
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _navigateToNotificationDetail(
|
||||
|
@ -231,9 +343,7 @@ Future<void> _navigateToNotificationDetail(
|
|||
NotificationModel notification,
|
||||
NotificationService notificationService,
|
||||
NotificationTranslations notificationTranslations,
|
||||
NotificationStyle style,
|
||||
) async {
|
||||
await markNotificationAsRead(notificationService, notification);
|
||||
if (context.mounted) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
|
@ -241,23 +351,25 @@ Future<void> _navigateToNotificationDetail(
|
|||
builder: (context) => NotificationDetailPage(
|
||||
translations: notificationTranslations,
|
||||
notification: notification,
|
||||
notificationStyle: style,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await markNotificationAsRead(notificationService, notification);
|
||||
}
|
||||
|
||||
Future<void> dismissNotification(
|
||||
NotificationService notificationService,
|
||||
NotificationModel notification,
|
||||
NotificationTranslations notificationTranslations,
|
||||
BuildContext context,
|
||||
) async {
|
||||
await notificationService.dismissActiveNotification(notification);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Notification dismissed"),
|
||||
SnackBar(
|
||||
content: Text(notificationTranslations.notificationDismissed),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -266,13 +378,32 @@ Future<void> dismissNotification(
|
|||
Future<void> pinNotification(
|
||||
NotificationService notificationService,
|
||||
NotificationModel notification,
|
||||
NotificationTranslations notificationTranslations,
|
||||
BuildContext context,
|
||||
) async {
|
||||
await notificationService.pinActiveNotification(notification);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("Notification pinned"),
|
||||
SnackBar(
|
||||
content: Text(notificationTranslations.notificationPinned),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> unPinNotification(
|
||||
NotificationService notificationService,
|
||||
NotificationModel notification,
|
||||
NotificationTranslations notificationTranslations,
|
||||
BuildContext context,
|
||||
) async {
|
||||
await notificationService.unPinActiveNotification(notification);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(notificationTranslations.notificationUnpinned),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "../flutter_notification_center.dart";
|
||||
import "package:intl/intl.dart";
|
||||
|
||||
/// A page displaying the details of a notification.
|
||||
class NotificationDetailPage extends StatelessWidget {
|
||||
/// Creates a new [NotificationDetailPage] instance.
|
||||
///
|
||||
/// The [notificationStyle] parameter specifies the notification style.
|
||||
///
|
||||
/// The [notification] parameter specifies the notification
|
||||
/// to display details for.
|
||||
const NotificationDetailPage({
|
||||
required this.notificationStyle,
|
||||
required this.notification,
|
||||
required this.translations,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The notification style.
|
||||
final NotificationStyle notificationStyle;
|
||||
|
||||
/// The notification to display details for.
|
||||
final NotificationModel notification;
|
||||
|
||||
/// The translations for the notification detail page.
|
||||
final NotificationTranslations translations;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
translations.appBarTitle,
|
||||
style: notificationStyle.appTitleTextStyle,
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
notification.title,
|
||||
style: notificationStyle.titleTextStyle ?? const TextStyle(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
notification.body,
|
||||
style:
|
||||
notificationStyle.subtitleTextStyle ?? const TextStyle(),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Date: ${DateFormat('yyyy-MM-dd HH:mm').format(
|
||||
notification.dateTimePushed ?? DateTime.now(),
|
||||
)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "../flutter_notification_center.dart";
|
||||
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
/// A bell icon widget that displays the number of active notifications.
|
||||
///
|
||||
|
@ -15,6 +16,8 @@ class NotificationBell extends StatefulWidget {
|
|||
/// [onTap]: Callback function to be invoked when the bell icon is tapped.
|
||||
const NotificationBell({
|
||||
required this.config,
|
||||
required this.service,
|
||||
this.animatedIconStyle,
|
||||
this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
@ -23,6 +26,12 @@ class NotificationBell extends StatefulWidget {
|
|||
/// the notification service.
|
||||
final NotificationConfig config;
|
||||
|
||||
/// The notification service used to fetch active notifications.
|
||||
final NotificationService service;
|
||||
|
||||
/// The style of the animated bell icon.
|
||||
final AnimatedNotificationBellStyle? animatedIconStyle;
|
||||
|
||||
/// Callback function to be invoked when the bell icon is tapped.
|
||||
final VoidCallback? onTap;
|
||||
|
||||
|
@ -37,24 +46,22 @@ class _NotificationBellState extends State<NotificationBell> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Fetch active notifications and update the notification count
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
var amount = await widget.config.service.getActiveNotifications();
|
||||
|
||||
widget.service.getActiveAmountStream().listen((amount) {
|
||||
setState(() {
|
||||
notificationAmount = amount.length;
|
||||
notificationAmount = amount;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: widget.onTap,
|
||||
icon: AnimatedNotificationBell(
|
||||
duration: const Duration(seconds: 1),
|
||||
notificationCount: notificationAmount,
|
||||
style: widget.config.bellStyle,
|
||||
style:
|
||||
widget.animatedIconStyle ?? const AnimatedNotificationBellStyle(),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
/// A page displaying the details of a notification.
|
||||
class NotificationDetailPage extends StatelessWidget {
|
||||
/// Creates a new [NotificationDetailPage] instance.
|
||||
///
|
||||
/// The [notificationStyle] parameter specifies the notification style.
|
||||
///
|
||||
/// The [notification] parameter specifies the notification
|
||||
/// to display details for.
|
||||
const NotificationDetailPage({
|
||||
required this.notification,
|
||||
required this.translations,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The notification to display details for.
|
||||
final NotificationModel notification;
|
||||
|
||||
/// The translations for the notification detail page.
|
||||
final NotificationTranslations translations;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||
title: Text(
|
||||
translations.appBarTitle,
|
||||
style: theme.textTheme.headlineLarge,
|
||||
),
|
||||
iconTheme: theme.appBarTheme.iconTheme ??
|
||||
const IconThemeData(color: Colors.white),
|
||||
centerTitle: true,
|
||||
leading: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
notification.title,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
notification.body,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
"${translations.datePrefix}"
|
||||
' ${DateFormat('yyyy-MM-dd HH:mm').format(
|
||||
notification.dateTimePushed ?? DateTime.now(),
|
||||
)}',
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "../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);
|
||||
|
||||
/// Marks a notification as read.
|
||||
Future markNotificationAsRead(NotificationModel notification);
|
||||
|
||||
/// Checks for scheduled notifications.
|
||||
Future checkForScheduledNotifications();
|
||||
}
|
|
@ -1,23 +1,25 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
class NotificationDialog extends StatelessWidget {
|
||||
const NotificationDialog({
|
||||
required this.title,
|
||||
required this.body,
|
||||
required this.translations,
|
||||
super.key,
|
||||
this.datetimePublished,
|
||||
});
|
||||
final String title;
|
||||
final String body;
|
||||
final DateTime? datetimePublished;
|
||||
|
||||
const NotificationDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.body,
|
||||
this.datetimePublished,
|
||||
});
|
||||
final NotificationTranslations translations;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String formattedDateTime = datetimePublished != null
|
||||
? DateFormat('dd MMM HH:mm').format(datetimePublished!)
|
||||
: 'N/A';
|
||||
var formattedDateTime = datetimePublished != null
|
||||
? DateFormat("dd MMM HH:mm").format(datetimePublished!)
|
||||
: translations.notAvailable;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
|
@ -55,9 +57,9 @@ class NotificationDialog extends StatelessWidget {
|
|||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Dismiss',
|
||||
style: TextStyle(
|
||||
child: Text(
|
||||
translations.dissmissDialog,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
|
@ -1,11 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
class NotificationSnackbar extends SnackBar {
|
||||
NotificationSnackbar({
|
||||
super.key,
|
||||
required String title,
|
||||
required String body,
|
||||
required NotificationTranslations translations,
|
||||
required VoidCallback onDismiss,
|
||||
super.key,
|
||||
DateTime? datetimePublished,
|
||||
}) : super(
|
||||
content: Column(
|
||||
|
@ -30,8 +33,8 @@ class NotificationSnackbar extends SnackBar {
|
|||
const SizedBox(height: 4),
|
||||
Text(
|
||||
datetimePublished != null
|
||||
? DateFormat('dd MMM HH:mm').format(datetimePublished)
|
||||
: 'N/A',
|
||||
? DateFormat("dd-MM-yyyy HH:mm").format(datetimePublished)
|
||||
: translations.notAvailable,
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.white,
|
||||
|
@ -39,10 +42,10 @@ class NotificationSnackbar extends SnackBar {
|
|||
),
|
||||
],
|
||||
),
|
||||
duration: const Duration(seconds: 8),
|
||||
duration: const Duration(seconds: 50),
|
||||
action: SnackBarAction(
|
||||
label: 'Dismiss',
|
||||
onPressed: () {},
|
||||
label: translations.dissmissDialog,
|
||||
onPressed: onDismiss,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
);
|
|
@ -1,32 +1,40 @@
|
|||
// Define a PopupHandler class to handle notification popups
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_notification_center/src/widgets/notification_dialog.dart";
|
||||
import "package:flutter_notification_center/src/widgets/notification_snackbar.dart";
|
||||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
class PopupHandler {
|
||||
final BuildContext context;
|
||||
final NotificationConfig config;
|
||||
|
||||
PopupHandler({
|
||||
required this.context,
|
||||
required this.config,
|
||||
});
|
||||
final BuildContext context;
|
||||
final NotificationConfig config;
|
||||
|
||||
void handleNotificationPopup(NotificationModel notification) {
|
||||
Future<void> handleNotificationPopup(NotificationModel notification) async {
|
||||
if (!config.enableNotificationPopups) return;
|
||||
|
||||
if (config.showAsSnackBar) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
NotificationSnackbar(
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
translations: config.translations,
|
||||
onDismiss: () {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
},
|
||||
datetimePublished: DateTime.now(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
showDialog(
|
||||
if (ModalRoute.of(context)?.isCurrent != true) return;
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => NotificationDialog(
|
||||
translations: config.translations,
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
datetimePublished: notification.dateTimePushed,
|
|
@ -1,30 +1,33 @@
|
|||
name: flutter_notification_center
|
||||
description: "A Flutter package for displaying notifications in a notification center."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.3.1
|
||||
description: "A new Flutter package project."
|
||||
version: 5.1.0
|
||||
homepage:
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.2 <4.0.0'
|
||||
sdk: ^3.5.3
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
|
||||
intl: ^0.20.1
|
||||
flutter_svg: ^2.0.10+1
|
||||
|
||||
flutter_animated_widgets:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_animated_widgets
|
||||
ref: 0.1.1
|
||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
version: ^0.3.1
|
||||
|
||||
notification_center_repository_interface:
|
||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
version: ^5.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_iconica_analysis:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||
ref: 7.0.0
|
||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
version: ^7.0.0
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/
|
|
@ -1,12 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('test', () {
|
||||
expect(true, true);
|
||||
});
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
|
@ -1,9 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
export "src/services/firebase_notification_service.dart";
|
||||
export "src/config/firebase_collections.dart";
|
||||
export "src/config/environment.dart";
|
||||
export "src/config/firebase_options.dart";
|
||||
export "src/config/firebase.dart";
|
|
@ -1,25 +0,0 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
const _errorMessage = 'Unable to fetch dotenv, did you make sure to generate '
|
||||
'your build environment config?\nUse the command: '
|
||||
'dart pub run environment_config:generate\n'
|
||||
'For more information, look at the readme\n'
|
||||
'Using default now...';
|
||||
|
||||
/// This environment config is used for the features inside the package
|
||||
/// The project that uses this package should have their own environment config
|
||||
/// the values in the dotenv should atleast include the following:
|
||||
mixin SharedEnvironmentConfig {}
|
||||
|
||||
/// This environment config is used only for the firebase configuration
|
||||
mixin SharedFirebaseEnvironmentConfig {
|
||||
static String get firebaseAppName {
|
||||
var firebaseAppName = dotenv.env['FIREBASE_APP_NAME'];
|
||||
if (firebaseAppName == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseAppName;
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
|
||||
const _errorMessage = 'Unable to fetch dotenv, did you make sure to generate '
|
||||
'your build environment config?\nUse the command: '
|
||||
'flutter pub run environment_config:generate\n'
|
||||
'For more information, look at the readme\n'
|
||||
'Using default now...';
|
||||
|
||||
class EnvironmentConfig {
|
||||
String get firebaseProjectId {
|
||||
var firebaseProjectId = dotenv.env['FIREBASE_PROJECT_ID'];
|
||||
if (firebaseProjectId == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseProjectId;
|
||||
}
|
||||
|
||||
String get firebaseMessageId {
|
||||
var firebaseMessageId = dotenv.env['FIREBASE_MESSAGE_ID'];
|
||||
if (firebaseMessageId == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseMessageId;
|
||||
}
|
||||
|
||||
String get firebaseAuthDomain {
|
||||
var firebaseAuthDomain = dotenv.env['FIREBASE_AUTH_DOMAIN'];
|
||||
if (firebaseAuthDomain == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseAuthDomain;
|
||||
}
|
||||
|
||||
String get firebaseStorageUrl {
|
||||
var firebaseStorageUrl = dotenv.env['FIREBASE_STORAGE_URL'];
|
||||
if (firebaseStorageUrl == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseStorageUrl;
|
||||
}
|
||||
|
||||
String get firebaseDatabaseUrl {
|
||||
var firebaseDatabaseUrl = dotenv.env['FIREBASE_DATABASE_URL'];
|
||||
if (firebaseDatabaseUrl == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseDatabaseUrl;
|
||||
}
|
||||
|
||||
String get firebaseWebApiKey {
|
||||
var firebaseKey = dotenv.env['FIREBASE_WEB_API_KEY'];
|
||||
if (firebaseKey == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseKey;
|
||||
}
|
||||
|
||||
String get firebaseIosApiKey {
|
||||
var firebaseKey = dotenv.env['FIREBASE_IOS_API_KEY'];
|
||||
if (firebaseKey == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseKey;
|
||||
}
|
||||
|
||||
String get firebaseAndroidApiKey {
|
||||
var firebaseKey = dotenv.env['FIREBASE_ANDROID_API_KEY'];
|
||||
if (firebaseKey == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseKey;
|
||||
}
|
||||
|
||||
String get firebaseAppIdAndroid {
|
||||
var firebaseAppIdAndroid = dotenv.env['FIREBASE_APP_ID_ANDROID'];
|
||||
if (firebaseAppIdAndroid == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseAppIdAndroid;
|
||||
}
|
||||
|
||||
String get firebaseAppIdIos {
|
||||
var firebaseAppIdIos = dotenv.env['FIREBASE_APP_ID_IOS'];
|
||||
if (firebaseAppIdIos == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseAppIdIos;
|
||||
}
|
||||
|
||||
String get firebaseAppIdMacos {
|
||||
var firebaseAppIdMacos = dotenv.env['FIREBASE_APP_ID_MACOS'];
|
||||
if (firebaseAppIdMacos == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseAppIdMacos;
|
||||
}
|
||||
|
||||
String get firebaseAppIdWeb {
|
||||
var firebaseAppIdWeb = dotenv.env['FIREBASE_APP_ID_WEB'];
|
||||
if (firebaseAppIdWeb == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseAppIdWeb;
|
||||
}
|
||||
|
||||
String get firebaseClientIdIos {
|
||||
var firebaseClientIdIos = dotenv.env['FIREBASE_CLIENT_ID_IOS'];
|
||||
if (firebaseClientIdIos == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseClientIdIos;
|
||||
}
|
||||
|
||||
String get firebaseClientIdMacos {
|
||||
var firebaseClientIdMacos = dotenv.env['FIREBASE_CLIENT_ID_MACOS'];
|
||||
if (firebaseClientIdMacos == null) {
|
||||
debugPrint(_errorMessage);
|
||||
throw Exception(_errorMessage);
|
||||
}
|
||||
return firebaseClientIdMacos;
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_storage/firebase_storage.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
mixin FirebaseInstance {
|
||||
static FirebaseApp instance() =>
|
||||
SharedFirebaseEnvironmentConfig.firebaseAppName.isEmpty
|
||||
? Firebase.app()
|
||||
: Firebase.app(SharedFirebaseEnvironmentConfig.firebaseAppName);
|
||||
}
|
||||
|
||||
mixin Database {
|
||||
static FirebaseFirestore ref() => FirebaseFirestore.instanceFor(
|
||||
app: FirebaseInstance.instance(),
|
||||
);
|
||||
}
|
||||
|
||||
mixin Storage {
|
||||
static Reference ref({bool prefixed = true}) =>
|
||||
FirebaseStorage.instanceFor(app: FirebaseInstance.instance()).ref();
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
mixin FirebaseCollectionNames {
|
||||
static const String activeNotifications = 'active_notifications';
|
||||
static const String plannedNotifications = 'planned_notifications';
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
||||
|
||||
import 'environment_config.dart';
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show TargetPlatform, defaultTargetPlatform, kIsWeb;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
return macos;
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: EnvironmentConfig().firebaseWebApiKey,
|
||||
appId: EnvironmentConfig().firebaseAppIdWeb,
|
||||
messagingSenderId: EnvironmentConfig().firebaseMessageId,
|
||||
projectId: EnvironmentConfig().firebaseProjectId,
|
||||
authDomain: EnvironmentConfig().firebaseAuthDomain,
|
||||
storageBucket: EnvironmentConfig().firebaseStorageUrl,
|
||||
);
|
||||
|
||||
static FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: EnvironmentConfig().firebaseAndroidApiKey,
|
||||
appId: EnvironmentConfig().firebaseAppIdAndroid,
|
||||
messagingSenderId: EnvironmentConfig().firebaseMessageId,
|
||||
projectId: EnvironmentConfig().firebaseProjectId,
|
||||
storageBucket: EnvironmentConfig().firebaseStorageUrl,
|
||||
);
|
||||
|
||||
static FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: EnvironmentConfig().firebaseIosApiKey,
|
||||
appId: EnvironmentConfig().firebaseAppIdIos,
|
||||
messagingSenderId: EnvironmentConfig().firebaseMessageId,
|
||||
projectId: EnvironmentConfig().firebaseProjectId,
|
||||
storageBucket: EnvironmentConfig().firebaseStorageUrl,
|
||||
iosClientId: EnvironmentConfig().firebaseClientIdIos,
|
||||
iosBundleId: 'nl.iconica.appshellDemo',
|
||||
);
|
||||
|
||||
static FirebaseOptions macos = FirebaseOptions(
|
||||
apiKey: EnvironmentConfig().firebaseIosApiKey,
|
||||
appId: EnvironmentConfig().firebaseAppIdMacos,
|
||||
messagingSenderId: EnvironmentConfig().firebaseMessageId,
|
||||
projectId: EnvironmentConfig().firebaseProjectId,
|
||||
storageBucket: EnvironmentConfig().firebaseStorageUrl,
|
||||
iosClientId: EnvironmentConfig().firebaseClientIdMacos,
|
||||
iosBundleId: 'nl.iconica.appshellDemo.RunnerTests',
|
||||
);
|
||||
}
|
|
@ -1,244 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
import '../config/firebase_collections.dart';
|
||||
|
||||
class FirebaseNotificationService
|
||||
with ChangeNotifier
|
||||
implements NotificationService {
|
||||
final Function(NotificationModel) newNotificationCallback;
|
||||
|
||||
@override
|
||||
List<NotificationModel> listOfActiveNotifications;
|
||||
@override
|
||||
List<NotificationModel> listOfPlannedNotifications;
|
||||
|
||||
// ignore: unused_field
|
||||
late Timer _timer;
|
||||
|
||||
FirebaseNotificationService(
|
||||
{required this.newNotificationCallback,
|
||||
this.listOfActiveNotifications = const [],
|
||||
this.listOfPlannedNotifications = const []}) {
|
||||
_startTimer();
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
_timer = Timer.periodic(const Duration(seconds: 15), (timer) {
|
||||
debugPrint('Checking for scheduled notifications');
|
||||
checkForScheduledNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pushNotification(NotificationModel notification,
|
||||
[Function(NotificationModel model)? onNewNotification]) async {
|
||||
try {
|
||||
CollectionReference notifications = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications);
|
||||
|
||||
DateTime currentDateTime = DateTime.now();
|
||||
notification.dateTimePushed = currentDateTime;
|
||||
Map<String, dynamic> notificationMap = notification.toMap();
|
||||
await notifications.doc(notification.id).set(notificationMap);
|
||||
|
||||
listOfActiveNotifications.add(notification);
|
||||
|
||||
//Show popup with notification conte
|
||||
if (onNewNotification != null) {
|
||||
onNewNotification(notification);
|
||||
} else {
|
||||
newNotificationCallback(notification);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error creating document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<NotificationModel>> getActiveNotifications() async {
|
||||
try {
|
||||
CollectionReference activeNotificationsCollection = FirebaseFirestore
|
||||
.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications);
|
||||
|
||||
QuerySnapshot querySnapshot = await activeNotificationsCollection.get();
|
||||
|
||||
List<NotificationModel> activeNotifications =
|
||||
querySnapshot.docs.map((doc) {
|
||||
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
||||
data['id'] = doc.id;
|
||||
return NotificationModel.fromJson(data);
|
||||
}).toList();
|
||||
|
||||
listOfActiveNotifications = activeNotifications;
|
||||
notifyListeners();
|
||||
return listOfActiveNotifications;
|
||||
} 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:
|
||||
}
|
||||
createScheduledNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> createScheduledNotification(
|
||||
NotificationModel notification) async {
|
||||
try {
|
||||
CollectionReference plannedNotifications = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications);
|
||||
Map<String, dynamic> notificationMap = notification.toMap();
|
||||
await plannedNotifications.doc(notification.id).set(notificationMap);
|
||||
} catch (e) {
|
||||
debugPrint('Error creating document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deletePlannedNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.delete();
|
||||
|
||||
QuerySnapshot querySnapshot = await FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications)
|
||||
.get();
|
||||
|
||||
if (querySnapshot.docs.isEmpty) {
|
||||
debugPrint('The collection is now empty');
|
||||
} else {
|
||||
debugPrint(
|
||||
'Deleted planned notification with title: ${notificationModel.title}');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error deleting document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dismissActiveNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.delete();
|
||||
listOfActiveNotifications
|
||||
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error deleting document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pinActiveNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.update({'isPinned': true});
|
||||
notificationModel.isPinned = true;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error updating document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> markNotificationAsRead(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.update({'isRead': true});
|
||||
notificationModel.isRead = true;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error updating document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkForScheduledNotifications() async {
|
||||
DateTime currentTime = DateTime.now();
|
||||
try {
|
||||
CollectionReference plannedNotificationsCollection = FirebaseFirestore
|
||||
.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications);
|
||||
|
||||
QuerySnapshot querySnapshot = await plannedNotificationsCollection.get();
|
||||
|
||||
if (querySnapshot.docs.isEmpty) {
|
||||
debugPrint('No scheduled notifications to be pushed');
|
||||
return;
|
||||
}
|
||||
|
||||
List<NotificationModel> plannedNotifications =
|
||||
querySnapshot.docs.map((doc) {
|
||||
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
||||
return NotificationModel.fromJson(data);
|
||||
}).toList();
|
||||
|
||||
for (NotificationModel 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) {
|
||||
NotificationModel 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error getting planned notifications: $e');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
name: flutter_notification_center_firebase
|
||||
description: "A new Flutter project."
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.3.1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_dotenv: ^5.0.2
|
||||
intl: any
|
||||
|
||||
# Firebase
|
||||
cloud_firestore: ^4.16.0
|
||||
firebase_auth: ^4.2.6
|
||||
firebase_core: ^2.5.0
|
||||
firebase_storage: ^11.0.14
|
||||
|
||||
cupertino_icons: ^1.0.2
|
||||
|
||||
flutter_notification_center:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||
ref: 1.3.1
|
||||
path: packages/flutter_notification_center
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// // This is a basic Flutter widget test.
|
||||
// //
|
||||
// // To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// // utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// // gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// // tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// import 'package:example/main.dart';
|
||||
|
||||
// void main() {
|
||||
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// // Build our app and trigger a frame.
|
||||
// await tester.pumpWidget(const MyApp());
|
||||
|
||||
// // Verify that our counter starts at 0.
|
||||
// expect(find.text('0'), findsOneWidget);
|
||||
// expect(find.text('1'), findsNothing);
|
||||
|
||||
// // Tap the '+' icon and trigger a frame.
|
||||
// await tester.tap(find.byIcon(Icons.add));
|
||||
// await tester.pump();
|
||||
|
||||
// // Verify that our counter has incremented.
|
||||
// expect(find.text('0'), findsNothing);
|
||||
// expect(find.text('1'), findsOneWidget);
|
||||
// });
|
||||
// }
|
29
packages/notification_center_repository_interface/.gitignore
vendored
Normal file
29
packages/notification_center_repository_interface/.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
build/
|
1
packages/notification_center_repository_interface/CHANGELOG.md
Symbolic link
1
packages/notification_center_repository_interface/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../CHANGELOG.md
|
1
packages/notification_center_repository_interface/LICENSE
Symbolic link
1
packages/notification_center_repository_interface/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE
|
1
packages/notification_center_repository_interface/README.md
Symbolic link
1
packages/notification_center_repository_interface/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../README.md
|
|
@ -0,0 +1,9 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -0,0 +1,13 @@
|
|||
// Interfaces
|
||||
export "src/interfaces/notification_repository_interface.dart";
|
||||
|
||||
// Local
|
||||
export "src/local/local_notification_repository.dart";
|
||||
|
||||
// Models
|
||||
export "src/models/notification.dart";
|
||||
export "src/models/notification_config.dart";
|
||||
export "src/models/notification_translation.dart";
|
||||
|
||||
// Services
|
||||
export "src/services/notification_service.dart";
|
|
@ -0,0 +1,27 @@
|
|||
import "package:notification_center_repository_interface/notification_center_repository_interface.dart";
|
||||
|
||||
abstract class NotificationRepositoryInterface {
|
||||
Future<NotificationModel> addNotification(
|
||||
String userId,
|
||||
NotificationModel notification,
|
||||
List<String> recipientIds,
|
||||
);
|
||||
|
||||
Stream<NotificationModel?> getNotification(String userId, String id);
|
||||
|
||||
Stream<List<NotificationModel>> getNotifications(String userId);
|
||||
|
||||
Stream<List<NotificationModel>> getPlannedNotifications(String userId);
|
||||
|
||||
Future<NotificationModel> updateNotification(
|
||||
String userId,
|
||||
NotificationModel notification,
|
||||
);
|
||||
|
||||
Future<void> deleteNotification(
|
||||
String userId,
|
||||
String id,
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
bool planned,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:notification_center_repository_interface/src/interfaces/notification_repository_interface.dart";
|
||||
import "package:notification_center_repository_interface/src/models/notification.dart";
|
||||
import "package:rxdart/rxdart.dart";
|
||||
|
||||
class LocalNotificationRepository implements NotificationRepositoryInterface {
|
||||
final List<NotificationModel> _activeNotifications = [];
|
||||
final List<NotificationModel> _plannedNotifications = [];
|
||||
|
||||
final StreamController<List<NotificationModel>> _notificationsController =
|
||||
BehaviorSubject<List<NotificationModel>>();
|
||||
|
||||
final StreamController<List<NotificationModel>>
|
||||
_plannedNotificationsController =
|
||||
BehaviorSubject<List<NotificationModel>>();
|
||||
|
||||
final StreamController<NotificationModel> _notificationController =
|
||||
BehaviorSubject<NotificationModel>();
|
||||
|
||||
@override
|
||||
Future<NotificationModel> addNotification(
|
||||
String userId,
|
||||
NotificationModel notification,
|
||||
List<String> recipientIds,
|
||||
) async {
|
||||
if (notification.scheduledFor != null &&
|
||||
notification.scheduledFor!.isAfter(DateTime.now())) {
|
||||
_plannedNotifications.add(notification);
|
||||
} else {
|
||||
_activeNotifications.add(notification);
|
||||
}
|
||||
|
||||
getNotifications(userId);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteNotification(
|
||||
String userId,
|
||||
String id,
|
||||
bool planned,
|
||||
) async {
|
||||
if (planned) {
|
||||
_plannedNotifications.removeWhere((element) => element.id == id);
|
||||
} else {
|
||||
_activeNotifications.removeWhere((element) => element.id == id);
|
||||
}
|
||||
|
||||
getNotifications(userId);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<NotificationModel?> getNotification(String userId, String id) {
|
||||
var notification = _activeNotifications.firstWhere(
|
||||
(element) => element.id == id,
|
||||
);
|
||||
|
||||
_notificationController.add(notification);
|
||||
|
||||
return _notificationController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<NotificationModel>> getNotifications(String userId) {
|
||||
_notificationsController.add(_activeNotifications);
|
||||
|
||||
return _notificationsController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<NotificationModel>> getPlannedNotifications(String userId) {
|
||||
_plannedNotificationsController.add(_plannedNotifications);
|
||||
|
||||
return _plannedNotificationsController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<NotificationModel> updateNotification(
|
||||
String userId,
|
||||
NotificationModel notification,
|
||||
) async {
|
||||
_activeNotifications
|
||||
.removeWhere((element) => element.id == notification.id);
|
||||
|
||||
_activeNotifications.add(notification);
|
||||
getNotifications(userId);
|
||||
|
||||
return notification;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
/// Enum representing the interval at which notifications occur.
|
||||
enum OcurringInterval {
|
||||
/// Notifications occur daily.
|
||||
|
@ -39,7 +37,7 @@ class NotificationModel {
|
|||
this.occuringInterval,
|
||||
this.isPinned = false,
|
||||
this.isRead = false,
|
||||
this.icon = Icons.notifications,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
/// Method to create a NotificationModel object from JSON data
|
||||
|
@ -59,9 +57,7 @@ class NotificationModel {
|
|||
: null,
|
||||
isPinned = json["isPinned"] ?? false,
|
||||
isRead = json["isRead"] ?? false,
|
||||
icon = json["icon"] != null
|
||||
? IconData(json["icon"], fontFamily: Icons.notifications.fontFamily)
|
||||
: Icons.notifications;
|
||||
icon = json["icon"] ?? 0xe44f;
|
||||
|
||||
/// Unique identifier for the notification.
|
||||
final String id;
|
||||
|
@ -91,14 +87,16 @@ class NotificationModel {
|
|||
bool isRead;
|
||||
|
||||
/// Icon to be displayed with the notification.
|
||||
final IconData icon;
|
||||
final int? icon;
|
||||
|
||||
/// Override toString() to provide custom string representation
|
||||
@override
|
||||
String toString() => "NotificationModel{id: $id, title: $title, body: $body, "
|
||||
String toString() => """
|
||||
NotificationModel{id: $id, title: $title, body: $body, "
|
||||
"dateTimePushed: $dateTimePushed, scheduledFor: $scheduledFor, "
|
||||
"recurring: $recurring, occuringInterval: $occuringInterval, "
|
||||
"isPinned: $isPinned, icon: $icon}";
|
||||
"isPinned: $isPinned, icon: $icon
|
||||
}""";
|
||||
|
||||
/// Convert the NotificationModel object to a Map.
|
||||
Map<String, dynamic> toMap() => {
|
||||
|
@ -111,7 +109,7 @@ class NotificationModel {
|
|||
"occuringInterval": occuringInterval?.index,
|
||||
"isPinned": isPinned,
|
||||
"isRead": isRead,
|
||||
"icon": icon.codePoint,
|
||||
"icon": icon,
|
||||
};
|
||||
|
||||
/// Create a copy of the NotificationModel with some fields replaced.
|
||||
|
@ -125,7 +123,7 @@ class NotificationModel {
|
|||
OcurringInterval? occuringInterval,
|
||||
bool? isPinned,
|
||||
bool? isRead,
|
||||
IconData? icon,
|
||||
int? icon,
|
||||
}) =>
|
||||
NotificationModel(
|
||||
id: id ?? this.id,
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../flutter_notification_center.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";
|
||||
|
||||
/// Configuration class for notifications.
|
||||
class NotificationConfig {
|
||||
|
@ -11,21 +11,15 @@ class NotificationConfig {
|
|||
/// notification. The [translations] parameter is also optional and provides
|
||||
/// translations for notification messages.
|
||||
const NotificationConfig({
|
||||
required this.service,
|
||||
this.seperateNotificationsWithDivider = true,
|
||||
this.translations = const NotificationTranslations(),
|
||||
this.translations = const NotificationTranslations.empty(),
|
||||
this.notificationWidgetBuilder,
|
||||
this.showAsSnackBar = true,
|
||||
this.enableNotificationPopups = true,
|
||||
this.bellStyle = const AnimatedNotificationBellStyle(),
|
||||
this.pinnedIconColor = Colors.black,
|
||||
this.emptyNotificationsBuilder,
|
||||
this.onNotificationTap,
|
||||
});
|
||||
|
||||
/// The notification service to use for delivering notifications.
|
||||
final NotificationService service;
|
||||
|
||||
/// Whether to seperate notifications with a divider.
|
||||
final bool seperateNotificationsWithDivider;
|
||||
|
||||
/// Translations for notification messages.
|
||||
final NotificationTranslations translations;
|
||||
|
||||
|
@ -33,12 +27,18 @@ class NotificationConfig {
|
|||
final Widget Function(NotificationModel, BuildContext)?
|
||||
notificationWidgetBuilder;
|
||||
|
||||
/// Whether to show notifications as snackbars. If false show notifications as a dialog.
|
||||
/// Whether to show notifications as snackbars.
|
||||
/// If false show notifications as a dialog.
|
||||
final bool showAsSnackBar;
|
||||
|
||||
/// Whether to show notification popups.
|
||||
final bool enableNotificationPopups;
|
||||
|
||||
/// The style of the notification bell.
|
||||
final AnimatedNotificationBellStyle bellStyle;
|
||||
/// The color of the trailing icon (if any) in the notification.
|
||||
final Color? pinnedIconColor;
|
||||
|
||||
/// A builder function to display when there are no notifications.
|
||||
final Widget Function()? emptyNotificationsBuilder;
|
||||
|
||||
final Function(NotificationModel)? onNotificationTap;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/// Defines translations for notification messages.
|
||||
class NotificationTranslations {
|
||||
/// Creates a new [NotificationTranslations] instance.
|
||||
const NotificationTranslations({
|
||||
required this.appBarTitle,
|
||||
required this.noNotifications,
|
||||
required this.notificationDismissed,
|
||||
required this.notificationPinned,
|
||||
required this.notificationUnpinned,
|
||||
required this.errorMessage,
|
||||
required this.datePrefix,
|
||||
required this.notAvailable,
|
||||
required this.dissmissDialog,
|
||||
});
|
||||
|
||||
const NotificationTranslations.empty({
|
||||
this.appBarTitle = "notifications",
|
||||
this.noNotifications = "No unread notifications available.",
|
||||
this.notificationDismissed = "Notification dismissed.",
|
||||
this.notificationPinned = "Notification pinned.",
|
||||
this.notificationUnpinned = "Notification unpinned.",
|
||||
this.errorMessage = "An error occurred. Please try again later.",
|
||||
this.datePrefix = "Date:",
|
||||
this.notAvailable = "N/A",
|
||||
this.dissmissDialog = "Dismiss",
|
||||
});
|
||||
|
||||
/// The title to be displayed in the app bar of the notification center.
|
||||
final String appBarTitle;
|
||||
|
||||
/// The message to be displayed when there are no unread
|
||||
/// notifications available.
|
||||
final String noNotifications;
|
||||
|
||||
/// The message to be displayed when a notification is dismissed.
|
||||
final String notificationDismissed;
|
||||
|
||||
/// The message to be displayed when a notification is pinned.
|
||||
final String notificationPinned;
|
||||
|
||||
/// The message to be displayed when a notification is unpinned.
|
||||
final String notificationUnpinned;
|
||||
|
||||
/// The message to be displayed when an error occurs.
|
||||
final String errorMessage;
|
||||
|
||||
/// The message to be displayed before the date of a notification.
|
||||
final String datePrefix;
|
||||
|
||||
/// The message to be displayed when parsing of the date fails
|
||||
final String notAvailable;
|
||||
|
||||
/// The message to be displayed on the dismiss dialog / snackbar
|
||||
final String dissmissDialog;
|
||||
|
||||
NotificationTranslations copyWith({
|
||||
String? appBarTitle,
|
||||
String? noNotifications,
|
||||
String? notificationDismissed,
|
||||
String? notificationPinned,
|
||||
String? notificationUnpinned,
|
||||
String? errorMessage,
|
||||
String? datePrefix,
|
||||
String? notAvailable,
|
||||
String? dissmissDialog,
|
||||
}) =>
|
||||
NotificationTranslations(
|
||||
appBarTitle: appBarTitle ?? this.appBarTitle,
|
||||
noNotifications: noNotifications ?? this.noNotifications,
|
||||
notificationDismissed:
|
||||
notificationDismissed ?? this.notificationDismissed,
|
||||
notificationPinned: notificationPinned ?? this.notificationPinned,
|
||||
notificationUnpinned: notificationUnpinned ?? this.notificationUnpinned,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
datePrefix: datePrefix ?? this.datePrefix,
|
||||
notAvailable: notAvailable ?? this.notAvailable,
|
||||
dissmissDialog: dissmissDialog ?? this.dissmissDialog,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:notification_center_repository_interface/src/interfaces/notification_repository_interface.dart";
|
||||
import "package:notification_center_repository_interface/src/local/local_notification_repository.dart";
|
||||
import "package:notification_center_repository_interface/src/models/notification.dart";
|
||||
|
||||
class NotificationService {
|
||||
NotificationService({
|
||||
required this.userId,
|
||||
this.pollingInterval = const Duration(seconds: 15),
|
||||
NotificationRepositoryInterface? repository,
|
||||
this.onNewNotification,
|
||||
}) : repository = repository ?? LocalNotificationRepository() {
|
||||
unawaited(_startTimer());
|
||||
}
|
||||
|
||||
final NotificationRepositoryInterface repository;
|
||||
final Function(NotificationModel)? onNewNotification;
|
||||
final String userId;
|
||||
final Duration pollingInterval;
|
||||
|
||||
Timer? timer;
|
||||
|
||||
Future<void> _startTimer() async {
|
||||
timer = Timer.periodic(pollingInterval, (timer) async {
|
||||
await checkForScheduledNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
/// Pushes a notification to the service.
|
||||
Future<void> pushNotification(
|
||||
NotificationModel notification,
|
||||
List<String> recipientIds,
|
||||
) {
|
||||
var result = repository.addNotification(
|
||||
userId,
|
||||
notification,
|
||||
recipientIds,
|
||||
);
|
||||
|
||||
if (recipientIds.contains(userId)) {
|
||||
getActiveNotifications();
|
||||
|
||||
//Show popup with notification conte
|
||||
if (onNewNotification != null) {
|
||||
onNewNotification!.call(notification);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Retrieves the list of active notifications.
|
||||
Stream<List<NotificationModel>> getActiveNotifications() =>
|
||||
repository.getNotifications(userId);
|
||||
|
||||
/// Retrieves the list of planned notifications.
|
||||
Stream<List<NotificationModel>> getPlannedNotifications() =>
|
||||
repository.getPlannedNotifications(userId);
|
||||
|
||||
/// Creates a scheduled notification.
|
||||
Future<void> createScheduledNotification(
|
||||
NotificationModel notification,
|
||||
List<String> recipientIds,
|
||||
) async =>
|
||||
pushNotification(
|
||||
notification,
|
||||
recipientIds,
|
||||
);
|
||||
|
||||
/// Creates a recurring notification.
|
||||
Future<void> createRecurringNotification(
|
||||
NotificationModel notification,
|
||||
List<String> recipientIds,
|
||||
) async {
|
||||
if (notification.recurring) {
|
||||
switch (notification.occuringInterval) {
|
||||
case OcurringInterval.daily:
|
||||
notification.scheduledFor =
|
||||
DateTime.now().add(const Duration(days: 1));
|
||||
|
||||
case OcurringInterval.weekly:
|
||||
notification.scheduledFor =
|
||||
DateTime.now().add(const Duration(days: 7));
|
||||
|
||||
case OcurringInterval.monthly:
|
||||
notification.scheduledFor =
|
||||
DateTime.now().add(const Duration(days: 30));
|
||||
|
||||
case OcurringInterval.debug:
|
||||
notification.scheduledFor =
|
||||
DateTime.now().add(const Duration(seconds: 10));
|
||||
|
||||
case null:
|
||||
}
|
||||
await createScheduledNotification(notification, recipientIds);
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes a scheduled notification.
|
||||
Future<void> deletePlannedNotification(NotificationModel notification) =>
|
||||
repository.deleteNotification(userId, notification.id, true);
|
||||
|
||||
/// Dismisses an active notification.
|
||||
Future<void> dismissActiveNotification(NotificationModel notification) =>
|
||||
repository.deleteNotification(userId, notification.id, false);
|
||||
|
||||
/// Pin an active notification.
|
||||
Future<void> pinActiveNotification(NotificationModel notification) =>
|
||||
repository.updateNotification(
|
||||
userId,
|
||||
notification.copyWith(isPinned: true),
|
||||
);
|
||||
|
||||
/// Unpin an active notification.
|
||||
Future<void> unPinActiveNotification(NotificationModel notification) =>
|
||||
repository.updateNotification(
|
||||
userId,
|
||||
notification.copyWith(isPinned: false),
|
||||
);
|
||||
|
||||
/// Marks a notification as read.
|
||||
Future<void> markNotificationAsRead(NotificationModel notification) =>
|
||||
repository.updateNotification(
|
||||
userId,
|
||||
notification.copyWith(isRead: true),
|
||||
);
|
||||
|
||||
/// Checks for scheduled notifications.
|
||||
Future<void> checkForScheduledNotifications() async {
|
||||
var notifications = await repository.getPlannedNotifications(userId).first;
|
||||
|
||||
for (var notification in notifications) {
|
||||
if (notification.scheduledFor != null &&
|
||||
notification.scheduledFor!.isBefore(DateTime.now()) ||
|
||||
notification.scheduledFor!.isAtSameMomentAs(DateTime.now())) {
|
||||
await pushNotification(
|
||||
notification,
|
||||
[userId],
|
||||
);
|
||||
|
||||
await deletePlannedNotification(notification);
|
||||
if (notification.recurring) {
|
||||
await createRecurringNotification(
|
||||
notification,
|
||||
[userId],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a stream of the number of active notifications.
|
||||
Stream<int> getActiveAmountStream() => repository
|
||||
.getNotifications(userId)
|
||||
.map((notifications) => notifications.length);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
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
|
32
pubspec.yaml
32
pubspec.yaml
|
@ -1,32 +1,6 @@
|
|||
name: flutter_notification_center
|
||||
description: "A new Flutter project."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
version: 1.2.0
|
||||
name: flutter_nofication_center_workspace
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.2 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
|
||||
flutter_animated_widgets:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_animated_widgets
|
||||
ref: 0.1.1
|
||||
|
||||
sdk: ">=3.1.0 <4.0.0"
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_iconica_analysis:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||
ref: 7.0.0
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
melos: ^3.0.1
|
|
@ -1,30 +0,0 @@
|
|||
// // This is a basic Flutter widget test.
|
||||
// //
|
||||
// // To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// // utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// // gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// // tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// import 'package:flutter_notification_center/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);
|
||||
// });
|
||||
// }
|
Loading…
Reference in a new issue