mirror of
https://github.com/Iconica-Development/flutter_notification_center.git
synced 2025-05-19 00:53:44 +02:00
fix: resolve several issues
This commit is contained in:
parent
8aa34cfa41
commit
a46a1d92d9
31 changed files with 589 additions and 536 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
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
|||
## [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.
|
|
@ -1,9 +0,0 @@
|
|||
include: package:flutter_iconica_analysis/components_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
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.
|
|
@ -49,4 +49,4 @@ app.*.map.json
|
|||
lib/config/
|
||||
pubspec.lock
|
||||
dotenv
|
||||
|
||||
firebase_options.dart
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
|
||||
|
@ -22,7 +24,35 @@ class CustomNotificationWidget extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return notification.isPinned
|
||||
//Pinned notification
|
||||
? GestureDetector(
|
||||
? Dismissible(
|
||||
key: Key('${notification.id}_pinned'),
|
||||
onDismissed: (direction) async {
|
||||
await unPinNotification(notificationService, notification,
|
||||
notificationTranslations, context);
|
||||
},
|
||||
background: Container(
|
||||
color: const Color.fromRGBO(59, 213, 111, 1),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 16.0),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
secondaryBackground: Container(
|
||||
color: const Color.fromRGBO(59, 213, 111, 1),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 16.0),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () async =>
|
||||
_navigateToNotificationDetail(context, notification),
|
||||
child: ListTile(
|
||||
|
@ -51,16 +81,18 @@ class CustomNotificationWidget extends StatelessWidget {
|
|||
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);
|
||||
await dismissNotification(notificationService, notification,
|
||||
notificationTranslations, context);
|
||||
} else if (direction == DismissDirection.startToEnd) {
|
||||
await pinNotification(
|
||||
notificationService, notification, context);
|
||||
await pinNotification(notificationService, notification,
|
||||
notificationTranslations, context);
|
||||
}
|
||||
},
|
||||
background: Container(
|
||||
|
@ -81,7 +113,7 @@ class CustomNotificationWidget extends StatelessWidget {
|
|||
padding: EdgeInsets.only(right: 16.0),
|
||||
child: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.white,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -130,7 +162,7 @@ class CustomNotificationWidget extends StatelessWidget {
|
|||
BuildContext context,
|
||||
NotificationModel notification,
|
||||
) async {
|
||||
await markNotificationAsRead(notificationService, notification);
|
||||
unawaited(markNotificationAsRead(notificationService, notification));
|
||||
if (context.mounted) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
|
@ -144,25 +176,4 @@ class CustomNotificationWidget extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,8 +1,9 @@
|
|||
import 'package:example/custom_notification.dart';
|
||||
// import 'package:firebase_auth/firebase_auth.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:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
|
@ -21,14 +22,9 @@ 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,
|
||||
);
|
||||
// await Firebase.initializeApp(
|
||||
// options: DefaultFirebaseOptions.currentPlatform,
|
||||
// );
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[
|
||||
DeviceOrientation.portraitDown,
|
||||
|
@ -55,14 +51,15 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var service =
|
||||
FirebaseNotificationService(newNotificationCallback: (notification) {
|
||||
var service = FirebaseNotificationService(
|
||||
newNotificationCallback: (notification) {
|
||||
popupHandler.handleNotificationPopup(notification);
|
||||
});
|
||||
},
|
||||
);
|
||||
config = NotificationConfig(
|
||||
service: service,
|
||||
enableNotificationPopups: true,
|
||||
showAsSnackBar: false,
|
||||
showAsSnackBar: true,
|
||||
notificationWidgetBuilder: (notification, context) =>
|
||||
CustomNotificationWidget(
|
||||
notification: notification,
|
||||
|
@ -82,7 +79,7 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
|||
showNotificationIcon: true,
|
||||
),
|
||||
notificationService: service,
|
||||
notificationTranslations: const NotificationTranslations(),
|
||||
notificationTranslations: const NotificationTranslations.empty(),
|
||||
context: context,
|
||||
),
|
||||
seperateNotificationsWithDivider: true,
|
||||
|
|
|
@ -14,24 +14,17 @@ dependencies:
|
|||
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
|
||||
|
||||
flutter_notification_center:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||
ref: 1.3.0
|
||||
ref: 1.4.0
|
||||
path: packages/flutter_notification_center
|
||||
|
||||
flutter_notification_center_firebase:
|
||||
path: ../../flutter_notification_center_firebase
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||
ref: 1.4.0
|
||||
path: packages/flutter_notification_center_firebase
|
||||
|
||||
|
||||
|
||||
|
@ -46,6 +39,4 @@ dev_dependencies:
|
|||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- dotenv
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class NotificationConfig {
|
|||
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,
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
/// 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({
|
||||
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 = "Notification Center",
|
||||
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.
|
||||
|
@ -20,4 +31,50 @@ class NotificationTranslations {
|
|||
/// 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,
|
||||
}) {
|
||||
return NotificationTranslations(
|
||||
appBarTitle: appBarTitle ?? this.appBarTitle,
|
||||
noNotifications: noNotifications ?? this.noNotifications,
|
||||
notificationDismissed:
|
||||
notificationDismissed ?? this.notificationDismissed,
|
||||
notificationPinned: notificationPinned ?? this.notificationPinned,
|
||||
notificationUnpinned: notificationUnpinned ?? this.notificationUnpinned,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
datePrefix: datePrefix ?? this.datePrefix,
|
||||
notAvailable: notAvailable ?? this.notAvailable,
|
||||
dissmissDialog: dissmissDialog ?? this.dissmissDialog,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,13 +37,9 @@ 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.config.service.getActiveAmountStream().listen((amount) {
|
||||
setState(() {
|
||||
notificationAmount = amount.length;
|
||||
notificationAmount = amount;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ class NotificationCenterState extends State<NotificationCenter> {
|
|||
super.initState();
|
||||
// ignore: discarded_futures
|
||||
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||
widget.config.service.getActiveAmountStream().listen((amount) {
|
||||
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||
});
|
||||
widget.config.service.addListener(_listener);
|
||||
}
|
||||
|
||||
|
@ -60,10 +63,12 @@ class NotificationCenterState extends State<NotificationCenter> {
|
|||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text("Error: ${snapshot.error}"));
|
||||
debugPrint("Error: ${snapshot.error}");
|
||||
return Center(
|
||||
child: Text(widget.config.translations.errorMessage));
|
||||
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
|
||||
return const Center(
|
||||
child: Text("No unread notifications available."),
|
||||
return Center(
|
||||
child: Text(widget.config.translations.noNotifications),
|
||||
);
|
||||
} else {
|
||||
return ListView.builder(
|
||||
|
@ -88,7 +93,40 @@ class NotificationCenterState extends State<NotificationCenter> {
|
|||
notification, context)
|
||||
: notification.isPinned
|
||||
//Pinned notification
|
||||
? GestureDetector(
|
||||
? Dismissible(
|
||||
key: Key('${notification.id}_pinned'),
|
||||
onDismissed: (direction) async {
|
||||
await unPinNotification(
|
||||
widget.config.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context);
|
||||
},
|
||||
background: Container(
|
||||
color:
|
||||
const Color.fromRGBO(59, 213, 111, 1),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 16.0),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
secondaryBackground: Container(
|
||||
color:
|
||||
const Color.fromRGBO(59, 213, 111, 1),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 16.0),
|
||||
child: Icon(
|
||||
Icons.push_pin,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () async =>
|
||||
_navigateToNotificationDetail(
|
||||
context,
|
||||
|
@ -131,6 +169,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
|||
const EdgeInsets.only(left: 60.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
//Dismissable notification
|
||||
: Dismissible(
|
||||
|
@ -141,12 +180,14 @@ class NotificationCenterState extends State<NotificationCenter> {
|
|||
await dismissNotification(
|
||||
widget.config.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context);
|
||||
} else if (direction ==
|
||||
DismissDirection.startToEnd) {
|
||||
await pinNotification(
|
||||
widget.config.service,
|
||||
notification,
|
||||
widget.config.translations,
|
||||
context);
|
||||
}
|
||||
},
|
||||
|
@ -170,7 +211,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
|||
padding: EdgeInsets.only(right: 16.0),
|
||||
child: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.white,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -251,13 +292,15 @@ Future<void> _navigateToNotificationDetail(
|
|||
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 +309,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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class NotificationDetailPage extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Date: ${DateFormat('yyyy-MM-dd HH:mm').format(
|
||||
'${translations.datePrefix} ${DateFormat('yyyy-MM-dd HH:mm').format(
|
||||
notification.dateTimePushed ?? DateTime.now(),
|
||||
)}',
|
||||
style: const TextStyle(
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class NotificationDialog extends StatelessWidget {
|
||||
final String title;
|
||||
final String body;
|
||||
final DateTime? datetimePublished;
|
||||
final NotificationTranslations translations;
|
||||
|
||||
const NotificationDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.body,
|
||||
required this.translations,
|
||||
this.datetimePublished,
|
||||
});
|
||||
|
||||
|
@ -17,7 +20,7 @@ class NotificationDialog extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
String formattedDateTime = datetimePublished != null
|
||||
? DateFormat('dd MMM HH:mm').format(datetimePublished!)
|
||||
: 'N/A';
|
||||
: translations.notAvailable;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
|
@ -55,9 +58,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,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class NotificationSnackbar extends SnackBar {
|
||||
|
@ -6,6 +7,8 @@ class NotificationSnackbar extends SnackBar {
|
|||
super.key,
|
||||
required String title,
|
||||
required String body,
|
||||
required NotificationTranslations translations,
|
||||
required VoidCallback onDismiss,
|
||||
DateTime? datetimePublished,
|
||||
}) : super(
|
||||
content: Column(
|
||||
|
@ -31,7 +34,7 @@ class NotificationSnackbar extends SnackBar {
|
|||
Text(
|
||||
datetimePublished != null
|
||||
? DateFormat('dd MMM HH:mm').format(datetimePublished)
|
||||
: 'N/A',
|
||||
: 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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -16,17 +16,25 @@ class PopupHandler {
|
|||
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 {
|
||||
if (ModalRoute.of(context)?.isCurrent != true) return;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => NotificationDialog(
|
||||
translations: config.translations,
|
||||
title: notification.title,
|
||||
body: notification.body,
|
||||
datetimePublished: notification.dateTimePushed,
|
||||
|
|
|
@ -47,9 +47,15 @@ abstract class NotificationService with ChangeNotifier {
|
|||
/// Pin an active notification.
|
||||
Future pinActiveNotification(NotificationModel notification);
|
||||
|
||||
/// Unpin an active notification.
|
||||
Future unPinActiveNotification(NotificationModel notification);
|
||||
|
||||
/// Marks a notification as read.
|
||||
Future markNotificationAsRead(NotificationModel notification);
|
||||
|
||||
/// Checks for scheduled notifications.
|
||||
Future checkForScheduledNotifications();
|
||||
|
||||
/// Returns a stream of the number of active notifications.
|
||||
Stream<int> getActiveAmountStream();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ name: flutter_notification_center
|
|||
description: "A Flutter package for displaying notifications in a notification center."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.3.1
|
||||
version: 1.4.0
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.2 <4.0.0'
|
||||
|
|
|
@ -3,7 +3,3 @@
|
|||
// 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,14 +1,19 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
||||
import '../config/firebase_collections.dart';
|
||||
|
||||
class FirebaseNotificationService
|
||||
with ChangeNotifier
|
||||
implements NotificationService {
|
||||
final Function(NotificationModel) newNotificationCallback;
|
||||
final FirebaseApp? firebaseApp;
|
||||
final String activeNotificationsCollection;
|
||||
final String plannedNotificationsCollection;
|
||||
late FirebaseApp _firebaseApp;
|
||||
|
||||
@override
|
||||
List<NotificationModel> listOfActiveNotifications;
|
||||
|
@ -18,10 +23,15 @@ class FirebaseNotificationService
|
|||
// ignore: unused_field
|
||||
late Timer _timer;
|
||||
|
||||
FirebaseNotificationService(
|
||||
{required this.newNotificationCallback,
|
||||
FirebaseNotificationService({
|
||||
required this.newNotificationCallback,
|
||||
this.firebaseApp,
|
||||
this.activeNotificationsCollection = 'active_notifications',
|
||||
this.plannedNotificationsCollection = 'planned_notifications',
|
||||
this.listOfActiveNotifications = const [],
|
||||
this.listOfPlannedNotifications = const []}) {
|
||||
this.listOfPlannedNotifications = const [],
|
||||
}) {
|
||||
_firebaseApp = firebaseApp ?? Firebase.app();
|
||||
_startTimer();
|
||||
}
|
||||
|
||||
|
@ -36,15 +46,25 @@ class FirebaseNotificationService
|
|||
Future<void> pushNotification(NotificationModel notification,
|
||||
[Function(NotificationModel model)? onNewNotification]) async {
|
||||
try {
|
||||
CollectionReference notifications = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications);
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
CollectionReference notifications =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection);
|
||||
|
||||
DateTime currentDateTime = DateTime.now();
|
||||
notification.dateTimePushed = currentDateTime;
|
||||
Map<String, dynamic> notificationMap = notification.toMap();
|
||||
await notifications.doc(notification.id).set(notificationMap);
|
||||
|
||||
listOfActiveNotifications.add(notification);
|
||||
listOfActiveNotifications = [...listOfActiveNotifications, notification];
|
||||
|
||||
//Show popup with notification conte
|
||||
if (onNewNotification != null) {
|
||||
|
@ -62,11 +82,20 @@ class FirebaseNotificationService
|
|||
@override
|
||||
Future<List<NotificationModel>> getActiveNotifications() async {
|
||||
try {
|
||||
CollectionReference activeNotificationsCollection = FirebaseFirestore
|
||||
.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications);
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
QuerySnapshot querySnapshot = await activeNotificationsCollection.get();
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return [];
|
||||
}
|
||||
|
||||
CollectionReference activeNotificationsResult =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection);
|
||||
|
||||
QuerySnapshot querySnapshot = await activeNotificationsResult.get();
|
||||
|
||||
List<NotificationModel> activeNotifications =
|
||||
querySnapshot.docs.map((doc) {
|
||||
|
@ -75,7 +104,18 @@ class FirebaseNotificationService
|
|||
return NotificationModel.fromJson(data);
|
||||
}).toList();
|
||||
|
||||
listOfActiveNotifications = activeNotifications;
|
||||
listOfActiveNotifications = List.from(activeNotifications);
|
||||
|
||||
listOfActiveNotifications.removeWhere((element) => element.isPinned);
|
||||
activeNotifications
|
||||
.sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!));
|
||||
|
||||
listOfActiveNotifications
|
||||
.sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!));
|
||||
|
||||
listOfActiveNotifications.insertAll(
|
||||
0, activeNotifications.where((element) => element.isPinned));
|
||||
|
||||
notifyListeners();
|
||||
return listOfActiveNotifications;
|
||||
} catch (e) {
|
||||
|
@ -115,8 +155,19 @@ class FirebaseNotificationService
|
|||
Future<void> createScheduledNotification(
|
||||
NotificationModel notification) async {
|
||||
try {
|
||||
CollectionReference plannedNotifications = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications);
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
CollectionReference plannedNotifications =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(plannedNotificationsCollection);
|
||||
|
||||
Map<String, dynamic> notificationMap = notification.toMap();
|
||||
await plannedNotifications.doc(notification.id).set(notificationMap);
|
||||
} catch (e) {
|
||||
|
@ -128,13 +179,26 @@ class FirebaseNotificationService
|
|||
Future<void> deletePlannedNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications)
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentReference documentReference =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.delete();
|
||||
|
||||
QuerySnapshot querySnapshot = await FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications)
|
||||
QuerySnapshot querySnapshot =
|
||||
await FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.get();
|
||||
|
||||
if (querySnapshot.docs.isEmpty) {
|
||||
|
@ -152,8 +216,18 @@ class FirebaseNotificationService
|
|||
Future<void> dismissActiveNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications)
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentReference documentReference =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.delete();
|
||||
listOfActiveNotifications
|
||||
|
@ -168,11 +242,57 @@ class FirebaseNotificationService
|
|||
Future<void> pinActiveNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications)
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentReference documentReference =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.update({'isPinned': true});
|
||||
notificationModel.isPinned = true;
|
||||
|
||||
listOfActiveNotifications
|
||||
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
|
||||
listOfActiveNotifications.insert(0, notificationModel);
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error updating document: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> unPinActiveNotification(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentReference documentReference =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.update({'isPinned': false});
|
||||
notificationModel.isPinned = false;
|
||||
|
||||
listOfActiveNotifications
|
||||
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
|
||||
|
||||
listOfActiveNotifications.add(notificationModel);
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error updating document: $e');
|
||||
|
@ -183,8 +303,18 @@ class FirebaseNotificationService
|
|||
Future<void> markNotificationAsRead(
|
||||
NotificationModel notificationModel) async {
|
||||
try {
|
||||
DocumentReference documentReference = FirebaseFirestore.instance
|
||||
.collection(FirebaseCollectionNames.activeNotifications)
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentReference documentReference =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(notificationModel.id);
|
||||
await documentReference.update({'isRead': true});
|
||||
notificationModel.isRead = true;
|
||||
|
@ -198,11 +328,20 @@ class FirebaseNotificationService
|
|||
Future<void> checkForScheduledNotifications() async {
|
||||
DateTime currentTime = DateTime.now();
|
||||
try {
|
||||
CollectionReference plannedNotificationsCollection = FirebaseFirestore
|
||||
.instance
|
||||
.collection(FirebaseCollectionNames.plannedNotifications);
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
QuerySnapshot querySnapshot = await plannedNotificationsCollection.get();
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
CollectionReference plannedNotificationsResult =
|
||||
FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(plannedNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(plannedNotificationsCollection);
|
||||
|
||||
QuerySnapshot querySnapshot = await plannedNotificationsResult.get();
|
||||
|
||||
if (querySnapshot.docs.isEmpty) {
|
||||
debugPrint('No scheduled notifications to be pushed');
|
||||
|
@ -241,4 +380,22 @@ class FirebaseNotificationService
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<int> getActiveAmountStream() async* {
|
||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||
|
||||
if (userId == null) {
|
||||
debugPrint('User is not authenticated');
|
||||
yield 0;
|
||||
}
|
||||
|
||||
var amount = FirebaseFirestore.instanceFor(app: _firebaseApp)
|
||||
.collection(activeNotificationsCollection)
|
||||
.doc(userId)
|
||||
.collection(activeNotificationsCollection)
|
||||
.snapshots()
|
||||
.map((e) => e.docs.length);
|
||||
yield* amount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ name: flutter_notification_center_firebase
|
|||
description: "A new Flutter project."
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.3.1
|
||||
version: 1.4.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
|
@ -10,21 +10,19 @@ environment:
|
|||
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
|
||||
ref: 1.4.0
|
||||
path: packages/flutter_notification_center
|
||||
|
||||
dev_dependencies:
|
||||
|
|
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