mirror of
https://github.com/Iconica-Development/flutter_notification_center.git
synced 2025-05-19 00:53:44 +02:00
feat: rework flutter_notification_center design
This commit is contained in:
parent
b57bc7ce46
commit
169228152e
21 changed files with 496 additions and 471 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
## [2.0.0] - 6 June 2024
|
||||||
|
|
||||||
|
* Rework design for notification center
|
||||||
|
* Added iconica linter
|
||||||
|
|
||||||
## [1.4.1] - 4 June 2024
|
## [1.4.1] - 4 June 2024
|
||||||
|
|
||||||
* Fix notification amount number to properly size and show plus icon when above certain amount
|
* Fix notification amount number to properly size and show plus icon when above certain amount
|
||||||
|
|
|
@ -1,28 +1,9 @@
|
||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||||
# 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,
|
# Possible to overwrite the rules from the package
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
|
||||||
linter:
|
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:
|
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
|
|
||||||
|
|
|
@ -23,6 +23,5 @@ linter:
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
|
@ -82,7 +82,6 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
|
||||||
notificationTranslations: const NotificationTranslations.empty(),
|
notificationTranslations: const NotificationTranslations.empty(),
|
||||||
context: context,
|
context: context,
|
||||||
),
|
),
|
||||||
seperateNotificationsWithDivider: true,
|
|
||||||
);
|
);
|
||||||
popupHandler = PopupHandler(context: context, config: config);
|
popupHandler = PopupHandler(context: context, config: config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@ name: example
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.2 <4.0.0'
|
sdk: ">=3.3.2 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
@ -17,16 +17,14 @@ dependencies:
|
||||||
flutter_notification_center:
|
flutter_notification_center:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||||
ref: 1.4.0
|
|
||||||
path: packages/flutter_notification_center
|
path: packages/flutter_notification_center
|
||||||
|
ref: 2.0.0
|
||||||
|
|
||||||
flutter_notification_center_firebase:
|
flutter_notification_center_firebase:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||||
ref: 1.4.0
|
|
||||||
path: packages/flutter_notification_center_firebase
|
path: packages/flutter_notification_center_firebase
|
||||||
|
ref: 2.0.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -39,4 +37,3 @@ dev_dependencies:
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,17 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
export "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
||||||
|
|
||||||
export "src/models/notification.dart";
|
export "src/models/notification.dart";
|
||||||
export "src/models/notification_config.dart";
|
export "src/models/notification_config.dart";
|
||||||
export "src/models/notification_theme.dart";
|
export "src/models/notification_theme.dart";
|
||||||
export "src/models/notification_translation.dart";
|
export "src/models/notification_translation.dart";
|
||||||
export "src/notification_bell.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_bell_story.dart";
|
||||||
export "src/notification_center.dart";
|
export "src/notification_center.dart";
|
||||||
|
export "src/notification_detail.dart";
|
||||||
|
export "src/notification_dialog.dart";
|
||||||
|
export "src/notification_snackbar.dart";
|
||||||
|
export "src/popup_handler.dart";
|
||||||
export "src/services/notification_service.dart";
|
export "src/services/notification_service.dart";
|
||||||
export "package:flutter_animated_widgets/flutter_animated_widgets.dart";
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
|
|
||||||
import '../../flutter_notification_center.dart';
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
|
||||||
/// Configuration class for notifications.
|
/// Configuration class for notifications.
|
||||||
class NotificationConfig {
|
class NotificationConfig {
|
||||||
|
@ -12,7 +12,6 @@ class NotificationConfig {
|
||||||
/// translations for notification messages.
|
/// translations for notification messages.
|
||||||
const NotificationConfig({
|
const NotificationConfig({
|
||||||
required this.service,
|
required this.service,
|
||||||
this.seperateNotificationsWithDivider = true,
|
|
||||||
this.translations = const NotificationTranslations.empty(),
|
this.translations = const NotificationTranslations.empty(),
|
||||||
this.notificationWidgetBuilder,
|
this.notificationWidgetBuilder,
|
||||||
this.showAsSnackBar = true,
|
this.showAsSnackBar = true,
|
||||||
|
@ -23,9 +22,6 @@ class NotificationConfig {
|
||||||
/// The notification service to use for delivering notifications.
|
/// The notification service to use for delivering notifications.
|
||||||
final NotificationService service;
|
final NotificationService service;
|
||||||
|
|
||||||
/// Whether to seperate notifications with a divider.
|
|
||||||
final bool seperateNotificationsWithDivider;
|
|
||||||
|
|
||||||
/// Translations for notification messages.
|
/// Translations for notification messages.
|
||||||
final NotificationTranslations translations;
|
final NotificationTranslations translations;
|
||||||
|
|
||||||
|
@ -33,7 +29,8 @@ class NotificationConfig {
|
||||||
final Widget Function(NotificationModel, BuildContext)?
|
final Widget Function(NotificationModel, BuildContext)?
|
||||||
notificationWidgetBuilder;
|
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;
|
final bool showAsSnackBar;
|
||||||
|
|
||||||
/// Whether to show notification popups.
|
/// Whether to show notification popups.
|
||||||
|
|
|
@ -14,7 +14,7 @@ class NotificationTranslations {
|
||||||
});
|
});
|
||||||
|
|
||||||
const NotificationTranslations.empty({
|
const NotificationTranslations.empty({
|
||||||
this.appBarTitle = "Notification Center",
|
this.appBarTitle = "Notifications",
|
||||||
this.noNotifications = "No unread notifications available.",
|
this.noNotifications = "No unread notifications available.",
|
||||||
this.notificationDismissed = "Notification dismissed.",
|
this.notificationDismissed = "Notification dismissed.",
|
||||||
this.notificationPinned = "Notification pinned.",
|
this.notificationPinned = "Notification pinned.",
|
||||||
|
@ -63,8 +63,8 @@ class NotificationTranslations {
|
||||||
String? datePrefix,
|
String? datePrefix,
|
||||||
String? notAvailable,
|
String? notAvailable,
|
||||||
String? dissmissDialog,
|
String? dissmissDialog,
|
||||||
}) {
|
}) =>
|
||||||
return NotificationTranslations(
|
NotificationTranslations(
|
||||||
appBarTitle: appBarTitle ?? this.appBarTitle,
|
appBarTitle: appBarTitle ?? this.appBarTitle,
|
||||||
noNotifications: noNotifications ?? this.noNotifications,
|
noNotifications: noNotifications ?? this.noNotifications,
|
||||||
notificationDismissed:
|
notificationDismissed:
|
||||||
|
@ -76,5 +76,4 @@ class NotificationTranslations {
|
||||||
notAvailable: notAvailable ?? this.notAvailable,
|
notAvailable: notAvailable ?? this.notAvailable,
|
||||||
dissmissDialog: dissmissDialog ?? this.dissmissDialog,
|
dissmissDialog: dissmissDialog ?? this.dissmissDialog,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "../flutter_notification_center.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
|
||||||
/// A bell icon widget that displays the number of active notifications.
|
/// A bell icon widget that displays the number of active notifications.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "../flutter_notification_center.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
|
||||||
/// A widget representing a notification bell.
|
/// A widget representing a notification bell.
|
||||||
class NotificationBellWidgetStory extends StatelessWidget {
|
class NotificationBellWidgetStory extends StatelessWidget {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import '../flutter_notification_center.dart';
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
import "package:intl/intl.dart";
|
||||||
|
|
||||||
/// Widget for displaying the notification center.
|
/// Widget for displaying the notification center.
|
||||||
class NotificationCenter extends StatefulWidget {
|
class NotificationCenter extends StatefulWidget {
|
||||||
|
@ -27,7 +28,7 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
super.initState();
|
super.initState();
|
||||||
// ignore: discarded_futures
|
// ignore: discarded_futures
|
||||||
_notificationsFuture = widget.config.service.getActiveNotifications();
|
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||||
widget.config.service.getActiveAmountStream().listen((amount) {
|
widget.config.service.getActiveAmountStream().listen((amount) async {
|
||||||
_notificationsFuture = widget.config.service.getActiveNotifications();
|
_notificationsFuture = widget.config.service.getActiveNotifications();
|
||||||
});
|
});
|
||||||
widget.config.service.addListener(_listener);
|
widget.config.service.addListener(_listener);
|
||||||
|
@ -44,17 +45,26 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.colorScheme.surface,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.config.translations.appBarTitle,
|
widget.config.translations.appBarTitle,
|
||||||
|
style: theme.appBarTheme.titleTextStyle,
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
leading: IconButton(
|
iconTheme: theme.appBarTheme.iconTheme ??
|
||||||
icon: const Icon(Icons.arrow_back),
|
const IconThemeData(color: Colors.white),
|
||||||
onPressed: () {
|
leading: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_back_ios,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: FutureBuilder<List<NotificationModel>>(
|
body: FutureBuilder<List<NotificationModel>>(
|
||||||
|
@ -64,47 +74,57 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
debugPrint("Error: ${snapshot.error}");
|
debugPrint("Error: ${snapshot.error}");
|
||||||
return Center(
|
return Center(child: Text(widget.config.translations.errorMessage));
|
||||||
child: Text(widget.config.translations.errorMessage));
|
|
||||||
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
|
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(widget.config.translations.noNotifications),
|
child: Text(widget.config.translations.noNotifications),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: snapshot.data!.length * 2 - 1,
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 20,
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
|
itemCount: snapshot.data!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index.isOdd) {
|
var notification = snapshot.data![index];
|
||||||
return Padding(
|
return notification.isPinned
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
? GestureDetector(
|
||||||
child: widget.config.seperateNotificationsWithDivider
|
onTap: () async => _navigateToNotificationDetail(
|
||||||
? const Divider(
|
context,
|
||||||
color: Colors.grey,
|
notification,
|
||||||
thickness: 1.0,
|
widget.config.service,
|
||||||
)
|
widget.config.translations,
|
||||||
: Container(),
|
const NotificationStyle(),
|
||||||
);
|
),
|
||||||
}
|
child: Dismissible(
|
||||||
var notification = snapshot.data![index ~/ 2];
|
key: Key("${notification.id}_pinned"),
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
||||||
child: widget.config.notificationWidgetBuilder != null
|
|
||||||
? widget.config.notificationWidgetBuilder!(
|
|
||||||
notification, context)
|
|
||||||
: notification.isPinned
|
|
||||||
//Pinned notification
|
|
||||||
? Dismissible(
|
|
||||||
key: Key('${notification.id}_pinned'),
|
|
||||||
onDismissed: (direction) async {
|
onDismissed: (direction) async {
|
||||||
|
if (direction == DismissDirection.endToStart) {
|
||||||
await unPinNotification(
|
await unPinNotification(
|
||||||
widget.config.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context);
|
context,
|
||||||
|
);
|
||||||
|
} else if (direction ==
|
||||||
|
DismissDirection.startToEnd) {
|
||||||
|
await unPinNotification(
|
||||||
|
widget.config.service,
|
||||||
|
notification,
|
||||||
|
widget.config.translations,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
background: Container(
|
background: Container(
|
||||||
color:
|
decoration: const BoxDecoration(
|
||||||
const Color.fromRGBO(59, 213, 111, 1),
|
color: Color.fromRGBO(59, 213, 111, 1),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(6),
|
||||||
|
bottomLeft: Radius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(left: 16.0),
|
padding: EdgeInsets.only(left: 16.0),
|
||||||
|
@ -115,85 +135,64 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
secondaryBackground: Container(
|
secondaryBackground: Container(
|
||||||
color:
|
decoration: const BoxDecoration(
|
||||||
const Color.fromRGBO(59, 213, 111, 1),
|
color: Color.fromRGBO(59, 213, 111, 1),
|
||||||
alignment: Alignment.centerLeft,
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(6),
|
||||||
|
bottomRight: Radius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(left: 16.0),
|
padding: EdgeInsets.only(right: 16.0),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.push_pin,
|
Icons.push_pin,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
child: _notificationItem(
|
||||||
onTap: () async =>
|
|
||||||
_navigateToNotificationDetail(
|
|
||||||
context,
|
context,
|
||||||
notification,
|
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
|
: GestureDetector(
|
||||||
: Dismissible(
|
onTap: () async => _navigateToNotificationDetail(
|
||||||
|
context,
|
||||||
|
notification,
|
||||||
|
widget.config.service,
|
||||||
|
widget.config.translations,
|
||||||
|
const NotificationStyle(),
|
||||||
|
),
|
||||||
|
child: Dismissible(
|
||||||
key: Key(notification.id),
|
key: Key(notification.id),
|
||||||
onDismissed: (direction) async {
|
onDismissed: (direction) async {
|
||||||
if (direction ==
|
if (direction == DismissDirection.endToStart) {
|
||||||
DismissDirection.endToStart) {
|
|
||||||
await dismissNotification(
|
await dismissNotification(
|
||||||
widget.config.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context);
|
context,
|
||||||
|
);
|
||||||
} else if (direction ==
|
} else if (direction ==
|
||||||
DismissDirection.startToEnd) {
|
DismissDirection.startToEnd) {
|
||||||
await pinNotification(
|
await pinNotification(
|
||||||
widget.config.service,
|
widget.config.service,
|
||||||
notification,
|
notification,
|
||||||
widget.config.translations,
|
widget.config.translations,
|
||||||
context);
|
context,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
background: Container(
|
background: Container(
|
||||||
color:
|
decoration: const BoxDecoration(
|
||||||
const Color.fromRGBO(59, 213, 111, 1),
|
color: Color.fromRGBO(59, 213, 111, 1),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(6),
|
||||||
|
bottomLeft: Radius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(left: 16.0),
|
padding: EdgeInsets.only(left: 16.0),
|
||||||
|
@ -203,9 +202,16 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
secondaryBackground: Container(
|
secondaryBackground: Padding(
|
||||||
color:
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
const Color.fromRGBO(255, 131, 131, 1),
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color.fromRGBO(255, 131, 131, 1),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(6),
|
||||||
|
bottomRight: Radius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(right: 16.0),
|
padding: EdgeInsets.only(right: 16.0),
|
||||||
|
@ -215,56 +221,93 @@ class NotificationCenterState extends State<NotificationCenter> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
),
|
||||||
onTap: () async =>
|
child: _notificationItem(
|
||||||
_navigateToNotificationDetail(
|
|
||||||
context,
|
context,
|
||||||
notification,
|
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _notificationItem(
|
||||||
|
BuildContext context,
|
||||||
|
NotificationModel notification,
|
||||||
|
) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
var dateTimePushed =
|
||||||
|
DateFormat("dd-MM-yyyy 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(
|
||||||
|
children: [
|
||||||
|
if (!notification.isPinned) ...[
|
||||||
|
if (!notification.isRead) ...[
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
Icons.circle_rounded,
|
||||||
|
color: Colors.black,
|
||||||
|
size: 10,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
] else ...[
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Transform.rotate(
|
||||||
|
angle: 0.5,
|
||||||
|
child: Icon(
|
||||||
|
notification.isRead
|
||||||
|
? Icons.push_pin_outlined
|
||||||
|
: Icons.push_pin,
|
||||||
|
color: Colors.black,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
notification.title,
|
||||||
|
style: notification.isRead
|
||||||
|
? theme.textTheme.bodyMedium
|
||||||
|
: theme.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
dateTimePushed,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _navigateToNotificationDetail(
|
Future<void> _navigateToNotificationDetail(
|
||||||
|
@ -274,7 +317,6 @@ Future<void> _navigateToNotificationDetail(
|
||||||
NotificationTranslations notificationTranslations,
|
NotificationTranslations notificationTranslations,
|
||||||
NotificationStyle style,
|
NotificationStyle style,
|
||||||
) async {
|
) async {
|
||||||
await markNotificationAsRead(notificationService, notification);
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -287,6 +329,7 @@ Future<void> _navigateToNotificationDetail(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await markNotificationAsRead(notificationService, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dismissNotification(
|
Future<void> dismissNotification(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "../flutter_notification_center.dart";
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
|
|
||||||
/// A page displaying the details of a notification.
|
/// A page displaying the details of a notification.
|
||||||
|
@ -27,13 +27,27 @@ class NotificationDetailPage extends StatelessWidget {
|
||||||
final NotificationTranslations translations;
|
final NotificationTranslations translations;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: theme.colorScheme.surface,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||||
title: Text(
|
title: Text(
|
||||||
translations.appBarTitle,
|
translations.appBarTitle,
|
||||||
style: notificationStyle.appTitleTextStyle,
|
style: theme.appBarTheme.titleTextStyle,
|
||||||
),
|
),
|
||||||
|
iconTheme: theme.appBarTheme.iconTheme ??
|
||||||
|
const IconThemeData(color: Colors.white),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
leading: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_back_ios,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -48,12 +62,12 @@ class NotificationDetailPage extends StatelessWidget {
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
notification.body,
|
notification.body,
|
||||||
style:
|
style: notificationStyle.subtitleTextStyle ?? const TextStyle(),
|
||||||
notificationStyle.subtitleTextStyle ?? const TextStyle(),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'${translations.datePrefix} ${DateFormat('yyyy-MM-dd HH:mm').format(
|
"${translations.datePrefix}"
|
||||||
|
' ${DateFormat('yyyy-MM-dd HH:mm').format(
|
||||||
notification.dateTimePushed ?? DateTime.now(),
|
notification.dateTimePushed ?? DateTime.now(),
|
||||||
)}',
|
)}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
|
@ -66,4 +80,5 @@ class NotificationDetailPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import 'package:intl/intl.dart';
|
import "package:intl/intl.dart";
|
||||||
|
|
||||||
class NotificationDialog extends StatelessWidget {
|
class NotificationDialog extends StatelessWidget {
|
||||||
|
const NotificationDialog({
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
required this.translations,
|
||||||
|
super.key,
|
||||||
|
this.datetimePublished,
|
||||||
|
});
|
||||||
final String title;
|
final String title;
|
||||||
final String body;
|
final String body;
|
||||||
final DateTime? datetimePublished;
|
final DateTime? datetimePublished;
|
||||||
final NotificationTranslations translations;
|
final NotificationTranslations translations;
|
||||||
|
|
||||||
const NotificationDialog({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.body,
|
|
||||||
required this.translations,
|
|
||||||
this.datetimePublished,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String formattedDateTime = datetimePublished != null
|
var formattedDateTime = datetimePublished != null
|
||||||
? DateFormat('dd MMM HH:mm').format(datetimePublished!)
|
? DateFormat("dd MMM HH:mm").format(datetimePublished!)
|
||||||
: translations.notAvailable;
|
: translations.notAvailable;
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
import 'package:intl/intl.dart';
|
import "package:intl/intl.dart";
|
||||||
|
|
||||||
class NotificationSnackbar extends SnackBar {
|
class NotificationSnackbar extends SnackBar {
|
||||||
NotificationSnackbar({
|
NotificationSnackbar({
|
||||||
super.key,
|
|
||||||
required String title,
|
required String title,
|
||||||
required String body,
|
required String body,
|
||||||
required NotificationTranslations translations,
|
required NotificationTranslations translations,
|
||||||
required VoidCallback onDismiss,
|
required VoidCallback onDismiss,
|
||||||
|
super.key,
|
||||||
DateTime? datetimePublished,
|
DateTime? datetimePublished,
|
||||||
}) : super(
|
}) : super(
|
||||||
content: Column(
|
content: Column(
|
||||||
|
@ -33,7 +33,7 @@ class NotificationSnackbar extends SnackBar {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
datetimePublished != null
|
datetimePublished != null
|
||||||
? DateFormat('dd MMM HH:mm').format(datetimePublished)
|
? DateFormat("dd-MM-yyyy HH:mm").format(datetimePublished)
|
||||||
: translations.notAvailable,
|
: translations.notAvailable,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
// Define a PopupHandler class to handle notification popups
|
// Define a PopupHandler class to handle notification popups
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
|
|
||||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
|
||||||
class PopupHandler {
|
class PopupHandler {
|
||||||
final BuildContext context;
|
|
||||||
final NotificationConfig config;
|
|
||||||
|
|
||||||
PopupHandler({
|
PopupHandler({
|
||||||
required this.context,
|
required this.context,
|
||||||
required this.config,
|
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.enableNotificationPopups) return;
|
||||||
|
|
||||||
if (config.showAsSnackBar) {
|
if (config.showAsSnackBar) {
|
||||||
|
@ -31,7 +30,7 @@ class PopupHandler {
|
||||||
} else {
|
} else {
|
||||||
if (ModalRoute.of(context)?.isCurrent != true) return;
|
if (ModalRoute.of(context)?.isCurrent != true) return;
|
||||||
|
|
||||||
showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => NotificationDialog(
|
builder: (context) => NotificationDialog(
|
||||||
translations: config.translations,
|
translations: config.translations,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "../models/notification.dart";
|
import "package:flutter_notification_center/src/models/notification.dart";
|
||||||
|
|
||||||
/// An abstract class representing a service for managing notifications.
|
/// An abstract class representing a service for managing notifications.
|
||||||
abstract class NotificationService with ChangeNotifier {
|
abstract class NotificationService with ChangeNotifier {
|
||||||
|
@ -26,8 +26,10 @@ abstract class NotificationService with ChangeNotifier {
|
||||||
List<NotificationModel> listOfPlannedNotifications;
|
List<NotificationModel> listOfPlannedNotifications;
|
||||||
|
|
||||||
/// Pushes a notification to the service.
|
/// Pushes a notification to the service.
|
||||||
Future pushNotification(NotificationModel notification,
|
Future pushNotification(
|
||||||
[Function(NotificationModel model)? onNewNotification]);
|
NotificationModel notification, [
|
||||||
|
Function(NotificationModel model)? onNewNotification,
|
||||||
|
]);
|
||||||
|
|
||||||
/// Retrieves the list of active notifications.
|
/// Retrieves the list of active notifications.
|
||||||
Future<List<NotificationModel>> getActiveNotifications();
|
Future<List<NotificationModel>> getActiveNotifications();
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
name: flutter_notification_center
|
name: flutter_notification_center
|
||||||
description: "A Flutter package for displaying notifications in a notification center."
|
description: "A Flutter package for displaying notifications in a notification center."
|
||||||
publish_to: 'none'
|
publish_to: "none"
|
||||||
|
version: 2.0.0
|
||||||
version: 1.4.1
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.2 <4.0.0'
|
sdk: ">=3.3.2 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
@ -15,7 +14,7 @@ dependencies:
|
||||||
flutter_animated_widgets:
|
flutter_animated_widgets:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_animated_widgets
|
url: https://github.com/Iconica-Development/flutter_animated_widgets
|
||||||
ref: 0.2.0
|
ref: 0.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import "package:flutter_test/flutter_test.dart";
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('test', () {
|
test("test", () {
|
||||||
expect(true, true);
|
expect(true, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,9 @@
|
||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||||
# 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,
|
# Possible to overwrite the rules from the package
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
|
||||||
linter:
|
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:
|
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,14 +1,25 @@
|
||||||
import 'dart:async';
|
import "dart:async";
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import "package:cloud_firestore/cloud_firestore.dart";
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import "package:firebase_auth/firebase_auth.dart";
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import "package:firebase_core/firebase_core.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_notification_center/flutter_notification_center.dart';
|
import "package:flutter_notification_center/flutter_notification_center.dart";
|
||||||
|
|
||||||
class FirebaseNotificationService
|
class FirebaseNotificationService
|
||||||
with ChangeNotifier
|
with ChangeNotifier
|
||||||
implements NotificationService {
|
implements NotificationService {
|
||||||
|
FirebaseNotificationService({
|
||||||
|
required this.newNotificationCallback,
|
||||||
|
this.firebaseApp,
|
||||||
|
this.activeNotificationsCollection = "active_notifications",
|
||||||
|
this.plannedNotificationsCollection = "planned_notifications",
|
||||||
|
this.listOfActiveNotifications = const [],
|
||||||
|
this.listOfPlannedNotifications = const [],
|
||||||
|
}) {
|
||||||
|
_firebaseApp = firebaseApp ?? Firebase.app();
|
||||||
|
unawaited(_startTimer());
|
||||||
|
}
|
||||||
final Function(NotificationModel) newNotificationCallback;
|
final Function(NotificationModel) newNotificationCallback;
|
||||||
final FirebaseApp? firebaseApp;
|
final FirebaseApp? firebaseApp;
|
||||||
final String activeNotificationsCollection;
|
final String activeNotificationsCollection;
|
||||||
|
@ -23,33 +34,23 @@ class FirebaseNotificationService
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
late Timer _timer;
|
late Timer _timer;
|
||||||
|
|
||||||
FirebaseNotificationService({
|
Future<void> _startTimer() async {
|
||||||
required this.newNotificationCallback,
|
_timer = Timer.periodic(const Duration(seconds: 15), (timer) async {
|
||||||
this.firebaseApp,
|
debugPrint("Checking for scheduled notifications");
|
||||||
this.activeNotificationsCollection = 'active_notifications',
|
await checkForScheduledNotifications();
|
||||||
this.plannedNotificationsCollection = 'planned_notifications',
|
|
||||||
this.listOfActiveNotifications = const [],
|
|
||||||
this.listOfPlannedNotifications = const [],
|
|
||||||
}) {
|
|
||||||
_firebaseApp = firebaseApp ?? Firebase.app();
|
|
||||||
_startTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startTimer() {
|
|
||||||
_timer = Timer.periodic(const Duration(seconds: 15), (timer) {
|
|
||||||
debugPrint('Checking for scheduled notifications');
|
|
||||||
checkForScheduledNotifications();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> pushNotification(NotificationModel notification,
|
Future<void> pushNotification(
|
||||||
[Function(NotificationModel model)? onNewNotification]) async {
|
NotificationModel notification, [
|
||||||
|
Function(NotificationModel model)? onNewNotification,
|
||||||
|
]) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,9 +60,9 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(activeNotificationsCollection);
|
.collection(activeNotificationsCollection);
|
||||||
|
|
||||||
DateTime currentDateTime = DateTime.now();
|
var currentDateTime = DateTime.now();
|
||||||
notification.dateTimePushed = currentDateTime;
|
notification.dateTimePushed = currentDateTime;
|
||||||
Map<String, dynamic> notificationMap = notification.toMap();
|
var notificationMap = notification.toMap();
|
||||||
await notifications.doc(notification.id).set(notificationMap);
|
await notifications.doc(notification.id).set(notificationMap);
|
||||||
|
|
||||||
listOfActiveNotifications = [...listOfActiveNotifications, notification];
|
listOfActiveNotifications = [...listOfActiveNotifications, notification];
|
||||||
|
@ -74,8 +75,8 @@ class FirebaseNotificationService
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error creating document: $e');
|
debugPrint("Error creating document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +86,7 @@ class FirebaseNotificationService
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,12 +96,11 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(activeNotificationsCollection);
|
.collection(activeNotificationsCollection);
|
||||||
|
|
||||||
QuerySnapshot querySnapshot = await activeNotificationsResult.get();
|
var querySnapshot = await activeNotificationsResult.get();
|
||||||
|
|
||||||
List<NotificationModel> activeNotifications =
|
var activeNotifications = querySnapshot.docs.map((doc) {
|
||||||
querySnapshot.docs.map((doc) {
|
var data = doc.data()! as Map<String, dynamic>;
|
||||||
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
data["id"] = doc.id;
|
||||||
data['id'] = doc.id;
|
|
||||||
return NotificationModel.fromJson(data);
|
return NotificationModel.fromJson(data);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
@ -114,19 +114,22 @@ class FirebaseNotificationService
|
||||||
.sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!));
|
.sort((a, b) => b.dateTimePushed!.compareTo(a.dateTimePushed!));
|
||||||
|
|
||||||
listOfActiveNotifications.insertAll(
|
listOfActiveNotifications.insertAll(
|
||||||
0, activeNotifications.where((element) => element.isPinned));
|
0,
|
||||||
|
activeNotifications.where((element) => element.isPinned),
|
||||||
|
);
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return listOfActiveNotifications;
|
return listOfActiveNotifications;
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error getting active notifications: $e');
|
debugPrint("Error getting active notifications: $e");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> createRecurringNotification(
|
Future<void> createRecurringNotification(
|
||||||
NotificationModel notification) async {
|
NotificationModel notification,
|
||||||
|
) async {
|
||||||
if (notification.recurring) {
|
if (notification.recurring) {
|
||||||
switch (notification.occuringInterval) {
|
switch (notification.occuringInterval) {
|
||||||
case OcurringInterval.daily:
|
case OcurringInterval.daily:
|
||||||
|
@ -147,18 +150,19 @@ class FirebaseNotificationService
|
||||||
break;
|
break;
|
||||||
case null:
|
case null:
|
||||||
}
|
}
|
||||||
createScheduledNotification(notification);
|
await createScheduledNotification(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> createScheduledNotification(
|
Future<void> createScheduledNotification(
|
||||||
NotificationModel notification) async {
|
NotificationModel notification,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,21 +172,22 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(plannedNotificationsCollection);
|
.collection(plannedNotificationsCollection);
|
||||||
|
|
||||||
Map<String, dynamic> notificationMap = notification.toMap();
|
var notificationMap = notification.toMap();
|
||||||
await plannedNotifications.doc(notification.id).set(notificationMap);
|
await plannedNotifications.doc(notification.id).set(notificationMap);
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error creating document: $e');
|
debugPrint("Error creating document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deletePlannedNotification(
|
Future<void> deletePlannedNotification(
|
||||||
NotificationModel notificationModel) async {
|
NotificationModel notificationModel,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,24 +207,26 @@ class FirebaseNotificationService
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
if (querySnapshot.docs.isEmpty) {
|
if (querySnapshot.docs.isEmpty) {
|
||||||
debugPrint('The collection is now empty');
|
debugPrint("The collection is now empty");
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'Deleted planned notification with title: ${notificationModel.title}');
|
"Deleted planned notification with title: ${notificationModel.title}",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error deleting document: $e');
|
debugPrint("Error deleting document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dismissActiveNotification(
|
Future<void> dismissActiveNotification(
|
||||||
NotificationModel notificationModel) async {
|
NotificationModel notificationModel,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,19 +240,20 @@ class FirebaseNotificationService
|
||||||
listOfActiveNotifications
|
listOfActiveNotifications
|
||||||
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
|
.removeAt(listOfActiveNotifications.indexOf(notificationModel));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error deleting document: $e');
|
debugPrint("Error deleting document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> pinActiveNotification(
|
Future<void> pinActiveNotification(
|
||||||
NotificationModel notificationModel) async {
|
NotificationModel notificationModel,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +263,7 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(activeNotificationsCollection)
|
.collection(activeNotificationsCollection)
|
||||||
.doc(notificationModel.id);
|
.doc(notificationModel.id);
|
||||||
await documentReference.update({'isPinned': true});
|
await documentReference.update({"isPinned": true});
|
||||||
notificationModel.isPinned = true;
|
notificationModel.isPinned = true;
|
||||||
|
|
||||||
listOfActiveNotifications
|
listOfActiveNotifications
|
||||||
|
@ -263,19 +271,20 @@ class FirebaseNotificationService
|
||||||
listOfActiveNotifications.insert(0, notificationModel);
|
listOfActiveNotifications.insert(0, notificationModel);
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error updating document: $e');
|
debugPrint("Error updating document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> unPinActiveNotification(
|
Future<void> unPinActiveNotification(
|
||||||
NotificationModel notificationModel) async {
|
NotificationModel notificationModel,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +294,7 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(activeNotificationsCollection)
|
.collection(activeNotificationsCollection)
|
||||||
.doc(notificationModel.id);
|
.doc(notificationModel.id);
|
||||||
await documentReference.update({'isPinned': false});
|
await documentReference.update({"isPinned": false});
|
||||||
notificationModel.isPinned = false;
|
notificationModel.isPinned = false;
|
||||||
|
|
||||||
listOfActiveNotifications
|
listOfActiveNotifications
|
||||||
|
@ -294,19 +303,20 @@ class FirebaseNotificationService
|
||||||
listOfActiveNotifications.add(notificationModel);
|
listOfActiveNotifications.add(notificationModel);
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error updating document: $e');
|
debugPrint("Error updating document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> markNotificationAsRead(
|
Future<void> markNotificationAsRead(
|
||||||
NotificationModel notificationModel) async {
|
NotificationModel notificationModel,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,22 +326,22 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(activeNotificationsCollection)
|
.collection(activeNotificationsCollection)
|
||||||
.doc(notificationModel.id);
|
.doc(notificationModel.id);
|
||||||
await documentReference.update({'isRead': true});
|
await documentReference.update({"isRead": true});
|
||||||
notificationModel.isRead = true;
|
notificationModel.isRead = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error updating document: $e');
|
debugPrint("Error updating document: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> checkForScheduledNotifications() async {
|
Future<void> checkForScheduledNotifications() async {
|
||||||
DateTime currentTime = DateTime.now();
|
var currentTime = DateTime.now();
|
||||||
try {
|
try {
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,20 +351,19 @@ class FirebaseNotificationService
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(plannedNotificationsCollection);
|
.collection(plannedNotificationsCollection);
|
||||||
|
|
||||||
QuerySnapshot querySnapshot = await plannedNotificationsResult.get();
|
var querySnapshot = await plannedNotificationsResult.get();
|
||||||
|
|
||||||
if (querySnapshot.docs.isEmpty) {
|
if (querySnapshot.docs.isEmpty) {
|
||||||
debugPrint('No scheduled notifications to be pushed');
|
debugPrint("No scheduled notifications to be pushed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NotificationModel> plannedNotifications =
|
var plannedNotifications = querySnapshot.docs.map((doc) {
|
||||||
querySnapshot.docs.map((doc) {
|
var data = doc.data()! as Map<String, dynamic>;
|
||||||
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
|
|
||||||
return NotificationModel.fromJson(data);
|
return NotificationModel.fromJson(data);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
for (NotificationModel notification in plannedNotifications) {
|
for (var notification in plannedNotifications) {
|
||||||
if (notification.scheduledFor!.isBefore(currentTime) ||
|
if (notification.scheduledFor!.isBefore(currentTime) ||
|
||||||
notification.scheduledFor!.isAtSameMomentAs(currentTime)) {
|
notification.scheduledFor!.isAtSameMomentAs(currentTime)) {
|
||||||
await pushNotification(notification, newNotificationCallback);
|
await pushNotification(notification, newNotificationCallback);
|
||||||
|
@ -363,7 +372,7 @@ class FirebaseNotificationService
|
||||||
|
|
||||||
//Plan new recurring notification instance
|
//Plan new recurring notification instance
|
||||||
if (notification.recurring) {
|
if (notification.recurring) {
|
||||||
NotificationModel newNotification = NotificationModel(
|
var newNotification = NotificationModel(
|
||||||
id: UniqueKey().toString(),
|
id: UniqueKey().toString(),
|
||||||
title: notification.title,
|
title: notification.title,
|
||||||
body: notification.body,
|
body: notification.body,
|
||||||
|
@ -375,8 +384,8 @@ class FirebaseNotificationService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Exception catch (e) {
|
||||||
debugPrint('Error getting planned notifications: $e');
|
debugPrint("Error getting planned notifications: $e");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,7 +395,7 @@ class FirebaseNotificationService
|
||||||
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
var userId = FirebaseAuth.instanceFor(app: _firebaseApp).currentUser?.uid;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
debugPrint('User is not authenticated');
|
debugPrint("User is not authenticated");
|
||||||
yield 0;
|
yield 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
name: flutter_notification_center_firebase
|
name: flutter_notification_center_firebase
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
version: 2.0.0
|
||||||
version: 1.4.1
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.18.0 <3.0.0"
|
sdk: ">=2.18.0 <3.0.0"
|
||||||
|
@ -22,15 +21,16 @@ dependencies:
|
||||||
flutter_notification_center:
|
flutter_notification_center:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_notification_center
|
url: https://github.com/Iconica-Development/flutter_notification_center
|
||||||
ref: 1.4.0
|
ref: 2.0.0
|
||||||
path: packages/flutter_notification_center
|
path: packages/flutter_notification_center
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_iconica_analysis:
|
||||||
flutter_lints: ^2.0.0
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||||
|
ref: 7.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue