refactor: change to monorepo and add notificationWidgetBuilder

This commit is contained in:
Vick Top 2024-04-11 14:48:26 +02:00
parent 13e950fc43
commit d48a42ccda
39 changed files with 618 additions and 873 deletions

View file

@ -1,45 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "68bfaea224880b488c617afe30ab12091ea8fa4e"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
- platform: android
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
- platform: ios
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
- platform: linux
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
- platform: macos
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
- platform: web
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
- platform: windows
create_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
base_revision: 68bfaea224880b488c617afe30ab12091ea8fa4e
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -1,4 +0,0 @@
mixin FirebaseCollectionNames {
static const String active_notifications = 'active_notifications';
static const String planned_notifications = 'planned_notifications';
}

View file

@ -1,136 +0,0 @@
// import 'dart:async';
// import 'package:flutter/material.dart';
// import 'package:flutter_notification_center/src/models/notification.dart';
// import 'package:flutter_notification_center/src/services/notification_service.dart';
// class LocalNotificationService implements NotificationService {
// @override
// List<NotificationModel> listOfActiveNotifications;
// @override
// List<NotificationModel> listOfPlannedNotifications;
// late Timer _timer;
// LocalNotificationService(
// {this.listOfActiveNotifications = const [],
// this.listOfPlannedNotifications = const []}) {
// _startTimer();
// }
// void _startTimer() {
// _timer = Timer.periodic(const Duration(seconds: 5), (timer) {
// debugPrint('Checking for scheduled notifications...');
// checkForScheduledNotifications();
// });
// }
// void _cancelTimer() {
// _timer.cancel();
// }
// @override
// Future pushNotification(NotificationModel notification) async {
// notification.dateTimePushed = DateTime.now();
// listOfActiveNotifications.add(notification);
// }
// @override
// Future<List<NotificationModel>> getActiveNotifications() async {
// print('Getting all active notifications...');
// return listOfActiveNotifications;
// }
// @override
// Future createScheduledNotification(NotificationModel notification) async {
// listOfPlannedNotifications = [...listOfPlannedNotifications, notification];
// print('Creating scheduled notification: $notification');
// }
// @override
// Future createRecurringNotification(NotificationModel notification) async {
// // If recurring, update the scheduled date for the next occurrence
// notification.title = notification.id.toString();
// await pushNotification(notification);
// if (notification.recurring) {
// switch (notification.occuringInterval) {
// case OcurringInterval.daily:
// notification.scheduledFor =
// notification.scheduledFor!.add(const Duration(days: 1));
// break;
// case OcurringInterval.weekly:
// notification.scheduledFor =
// notification.scheduledFor!.add(const Duration(days: 7));
// break;
// case OcurringInterval.monthly:
// // Add logic for monthly recurrence, e.g., adding 1 month to the scheduled date
// break;
// case OcurringInterval.debug:
// notification.scheduledFor =
// notification.scheduledFor!.add(const Duration(seconds: 5));
// break;
// case null:
// // TODO: Handle this case.
// }
// // Create the next recurring notification
// listOfPlannedNotifications = [
// ...listOfPlannedNotifications,
// notification
// ];
// print('Created recurring notification for: ${notification.scheduledFor}');
// }
// }
// @override
// Future deleteScheduledNotification(NotificationModel notification) async {
// listOfPlannedNotifications =
// listOfPlannedNotifications.where((n) => n != notification).toList();
// print('Notification deleted: $notification');
// }
// @override
// Future dismissActiveNotification(NotificationModel notification) async {
// String id = notification.id;
// listOfActiveNotifications.removeWhere((n) => n.id == id);
// print('Notification with ID $id dismissed');
// print('List of active notifications: $listOfActiveNotifications');
// }
// @override
// Future<void> checkForScheduledNotifications() async {
// DateTime currentTime = DateTime.now();
// if (listOfPlannedNotifications.isEmpty) {
// print('There are no scheduled notifications to be pushed');
// return;
// }
// for (NotificationModel notification
// in listOfPlannedNotifications.toList()) {
// // Check if scheduledFor is not null
// if (notification.scheduledFor != null) {
// // Check if the scheduled date and time is before or equal to the current date and time
// if (notification.scheduledFor!.isBefore(currentTime) ||
// notification.scheduledFor!.isAtSameMomentAs(currentTime)) {
// // Push the notification if it's due
// await pushNotification(notification);
// print('Scheduled notification pushed: $notification');
// // If recurring, update the scheduled date for the next occurrence
// if (notification.recurring) {
// // Increment the ID for recurring notifications
// notification.id += 1;
// notification.title = notification.id.toString();
// print('New RECURRING ID IS: ${notification.id}');
// // Create the next recurring notification
// await createRecurringNotification(notification);
// } else {
// // Delete the notification if not recurring
// print('Non-recurring notification removed: $notification');
// }
// }
// }
// }
// }
// }

View file

@ -1,410 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "79b6452b4066fcbdd74c2aac354e80c591a727e0364bedccecdb5a5321784fa2"
url: "https://pub.dev"
source: hosted
version: "1.3.28"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
cloud_firestore:
dependency: "direct main"
description:
name: cloud_firestore
sha256: "1368754eb153676e7f188aadcc01b3d280f1e2fbe125cceb7fd319670e9846ca"
url: "https://pub.dev"
source: hosted
version: "4.16.0"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
sha256: "8f0f25ca02a850d909c06c098d49a09b616133ac2d2acb12129e14c5310b7ebe"
url: "https://pub.dev"
source: hosted
version: "6.1.12"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
sha256: b6f05eb405517ddda1be982ed0c707d0977a45e2fcfa2086d1125ce0916ad823
url: "https://pub.dev"
source: hosted
version: "3.11.0"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.6"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: "4a958a32c8f0ed7573153edc3b09e0552fe0aba93be9028e8dbc5148d6f49f15"
url: "https://pub.dev"
source: hosted
version: "4.19.0"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: "5b26659a47fcaf6fcb36658c57d56f99e31d8393b72337ccde726bd8292baa25"
url: "https://pub.dev"
source: hosted
version: "7.2.1"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: a9c9faea77db8b7215f642d7a9a715347f1c46093f025813226596e3a25879b0
url: "https://pub.dev"
source: hosted
version: "5.11.0"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "4b45655ec1b21a1783681f72f840a2e74d298046c2b7c286ab0e4f0efbf93d0a"
url: "https://pub.dev"
source: hosted
version: "2.28.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
url: "https://pub.dev"
source: hosted
version: "5.0.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "28e30e00748497b9a70db2025942a42c5d752534eb678e9b9b98db056cf404ba"
url: "https://pub.dev"
source: hosted
version: "2.14.0"
firebase_storage:
dependency: "direct main"
description:
name: firebase_storage
sha256: d140fa336d29bc9e128448bb2f582407c6dc01c8d7d4b3160e1ab198e5f7f74c
url: "https://pub.dev"
source: hosted
version: "11.7.0"
firebase_storage_platform_interface:
dependency: transitive
description:
name: firebase_storage_platform_interface
sha256: b14e6a872da7303a45e081ff55160fd772eb15b7a00bdca12c740546fb67c18f
url: "https://pub.dev"
source: hosted
version: "5.1.15"
firebase_storage_web:
dependency: transitive
description:
name: firebase_storage_web
sha256: a1ad5da5983ac91c85ca7b5b4735b580cc7e1646aa502d7fa4dff7ab899e2159
url: "https://pub.dev"
source: hosted
version: "3.9.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_animated_widgets:
dependency: transitive
description:
path: "."
ref: "0.0.1"
resolved-ref: "0eb6ea4c2e64b757b468e23ee2055e27e8302397"
url: "https://github.com/Iconica-Development/flutter_animated_widgets"
source: git
version: "0.0.1"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_notification_center:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0+1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
url: "https://pub.dev"
source: hosted
version: "0.13.6"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
url: "https://pub.dev"
source: hosted
version: "0.17.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.11.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: "direct main"
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
sdks:
dart: ">=3.3.2 <4.0.0"
flutter: ">=3.3.0"

View file

@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
export "package:flutter_notification_center/src/models/notification.dart";
export "package:flutter_notification_center/src/models/notification_config.dart";
export "package:flutter_notification_center/src/models/notification_theme.dart";
export "package:flutter_notification_center/src/models/notification_translation.dart";
export "package:flutter_notification_center/src/notification_bell.dart";
export "package:flutter_notification_center/src/notification_bell_story.dart";
export "package:flutter_notification_center/src/notification_center.dart";
export "package:flutter_notification_center/src/services/notification_service.dart";

View file

@ -1,211 +0,0 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "package:flutter_notification_center/src/notification_detail.dart";
/// Widget for displaying the notification center.
class NotificationCenter extends StatefulWidget {
/// Constructs a new NotificationCenter instance.
///
/// [config]: Configuration for the notification center.
const NotificationCenter({
required this.config,
super.key,
});
/// Configuration for the notification center.
final NotificationConfig config;
@override
NotificationCenterState createState() => NotificationCenterState();
}
/// State for the notification center.
class NotificationCenterState extends State<NotificationCenter> {
late Future<List<NotificationModel>> _notificationsFuture;
@override
void initState() {
super.initState();
// ignore: discarded_futures
_notificationsFuture = widget.config.service.getActiveNotifications();
widget.config.service.addListener(_listener);
}
@override
void dispose() {
widget.config.service.removeListener(_listener);
super.dispose();
}
void _listener() {
setState(() {});
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(
widget.config.translations.appBarTitle,
style: widget.config.style.appTitleTextStyle,
),
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
body: FutureBuilder<List<NotificationModel>>(
future: _notificationsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text("Error: ${snapshot.error}"));
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
return const Center(
child: Text("No unread notifications available."),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.length * 2 -
1, // Double the itemCount to include dividers
itemBuilder: (context, index) {
if (index.isOdd) {
// If index is odd, return a Divider with padding
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Divider(
color: Colors.grey, // Customize as needed
thickness: 1.0, // Customize thickness as needed
),
);
}
var notification = snapshot.data![index ~/ 2];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: notification.isPinned
? GestureDetector(
onTap: () async =>
_navigateToNotificationDetail(notification),
child: ListTile(
leading: Icon(
notification.icon,
color: widget.config.style.leadingIconColor,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
notification.title,
style: widget.config.style.titleTextStyle,
),
),
],
),
trailing: IconButton(
icon: const Icon(Icons.push_pin),
color: widget.config.style.trailingIconColor,
onPressed: () async =>
_navigateToNotificationDetail(notification),
),
),
)
: Dismissible(
key: Key(notification.id),
onDismissed: (direction) async {
await widget.config.service
.dismissActiveNotification(notification);
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Notification dismissed"),
),
);
},
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
child: GestureDetector(
onTap: () async =>
_navigateToNotificationDetail(notification),
child: ListTile(
leading: Icon(
Icons.notification_important,
color: widget.config.style.leadingIconColor,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
notification.title,
style:
widget.config.style.titleTextStyle,
),
),
],
),
trailing: !notification.isRead
? Container(
margin:
const EdgeInsets.only(left: 4.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.config.style
.isReadDotColor ??
Colors.red,
),
)
: null,
),
),
),
);
},
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: _addNewNotification,
child: const Icon(Icons.add),
),
);
Future<void> _navigateToNotificationDetail(
NotificationModel notification,
) async {
await widget.config.service.markNotificationAsRead(notification);
await Navigator.push(
// ignore: use_build_context_synchronously
context,
MaterialPageRoute(
builder: (context) => NotificationDetailPage(
config: widget.config,
notification: notification,
),
),
);
}
Future<void> _addNewNotification() async {
await widget.config.service.pushNotification(
NotificationModel(
id: UniqueKey().toString(),
title: UniqueKey().toString(),
icon: Icons.notifications_active,
body: "This is a new notification",
),
);
}
}

View file

@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View file

@ -0,0 +1,16 @@
# flutter_notification_center
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:flutter_notification_center/flutter_notification_center.dart';
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
class CustomNotificationWidget extends StatelessWidget {
final NotificationModel notification;
final NotificationStyle style;
final FirebaseNotificationService notificationService;
final NotificationTranslations notificationTranslations;
final BuildContext context;
const CustomNotificationWidget({
required this.notification,
required this.style,
required this.notificationTranslations,
required this.notificationService,
required this.context,
super.key,
});
@override
Widget build(BuildContext context) {
return notification.isPinned
//Pinned notification
? GestureDetector(
onTap: () async =>
_navigateToNotificationDetail(context, notification),
child: ListTile(
leading: style.showNotificationIcon != null
? Icon(
notification.icon,
color: style.leadingIconColor,
)
: null,
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
notification.title,
style: style.titleTextStyle,
),
),
],
),
trailing: IconButton(
icon: const Icon(Icons.push_pin),
color: style.pinnedIconColor,
onPressed: () async =>
_navigateToNotificationDetail(context, notification),
padding: const EdgeInsets.only(left: 60.0),
),
),
)
//Dismissable notification
: Dismissible(
key: Key(notification.id),
onDismissed: (direction) async {
await dismissNotification(notificationService, notification);
},
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
child: GestureDetector(
onTap: () async =>
_navigateToNotificationDetail(context, notification),
child: ListTile(
leading: Icon(
notification.icon,
color: style.leadingIconColor,
),
title: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
notification.title,
style: style.titleTextStyle,
),
),
],
),
trailing: !notification.isRead
? Container(
margin: const EdgeInsets.only(right: 8.0),
width: 12.0,
height: 12.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: style.isReadDotColor ?? Colors.red,
),
)
: null,
),
),
);
}
Future<void> _navigateToNotificationDetail(
BuildContext context,
NotificationModel notification,
) async {
await markNotificationAsRead(notificationService, notification);
if (context.mounted) {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationDetailPage(
translations: notificationTranslations,
notification: notification,
notificationStyle: style,
),
),
);
}
}
Future<void> dismissNotification(
FirebaseNotificationService notificationService,
NotificationModel notification,
) async {
await notificationService.dismissActiveNotification(notification);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Notification dismissed"),
),
);
}
}
Future<void> markNotificationAsRead(
FirebaseNotificationService notificationService,
NotificationModel notification,
) async {
await notificationService.markNotificationAsRead(notification);
}
}

View file

@ -1,9 +1,9 @@
import 'package:example/config/firebase_options.dart';
import 'package:example/services/firebase_notification_service.dart';
import 'package:example/customer_notification.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_notification_center_firebase/flutter_notification_center_firebase.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_notification_center/flutter_notification_center.dart';
@ -47,7 +47,7 @@ Future<void> _signInUser() async {
User? user = auth.currentUser;
if (user == null) {
try {
UserCredential userCredential = await auth.signInWithEmailAndPassword(
await auth.signInWithEmailAndPassword(
email: 'freek@iconica.nl',
password: 'wachtwoord',
);
@ -58,7 +58,7 @@ Future<void> _signInUser() async {
}
class NotificationCenterDemo extends StatefulWidget {
const NotificationCenterDemo({Key? key}) : super(key: key);
const NotificationCenterDemo({super.key});
@override
State<NotificationCenterDemo> createState() => _NotificationCenterDemoState();
@ -69,17 +69,29 @@ class _NotificationCenterDemoState extends State<NotificationCenterDemo> {
Widget build(BuildContext context) {
var config = NotificationConfig(
service: Provider.of<FirebaseNotificationService>(context),
style: const NotificationStyle(
appTitleTextStyle: TextStyle(
color: Colors.black,
fontSize: 20,
),
titleTextStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
fontSize: 16,
notificationWidgetBuilder: (notification, context) =>
CustomNotificationWidget(
notification: notification,
style: const NotificationStyle(
appTitleTextStyle: TextStyle(
color: Colors.black,
fontSize: 20,
),
titleTextStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
fontSize: 16,
),
leadingIconColor: Colors.grey,
pinnedIconColor: Colors.grey,
isReadDotColor: Colors.red,
showNotificationIcon: true,
),
notificationService: Provider.of<FirebaseNotificationService>(context),
notificationTranslations: const NotificationTranslations(),
context: context,
),
seperateNotificationsWithDivider: true,
);
return Scaffold(

View file

@ -0,0 +1,49 @@
name: example
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0
environment:
sdk: '>=3.3.2 <4.0.0'
dependencies:
flutter:
sdk: flutter
intl: ^0.17.0
flutter_animated_widgets:
git:
url: https://github.com/Iconica-Development/flutter_animated_widgets
ref: 0.0.1
cloud_firestore: ^4.16.1
flutter_dotenv: ^5.0.2
firebase_auth: ^4.2.6
firebase_core: ^2.5.0
firebase_storage: ^11.0.14
provider: ^6.1.2
flutter_notification_center:
path: ../
flutter_notification_center_firebase:
path: ../../flutter_notification_center_firebase
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
assets:
- dotenv

View file

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
export "src/models/notification.dart";
export "src/models/notification_config.dart";
export "src/models/notification_theme.dart";
export "src/models/notification_translation.dart";
export "src/notification_bell.dart";
export "src/notification_detail.dart";
export "src/notification_bell_story.dart";
export "src/notification_center.dart";
export "src/services/notification_service.dart";

View file

@ -1,4 +1,6 @@
import "package:flutter_notification_center/flutter_notification_center.dart";
import 'package:flutter/material.dart';
import '../../flutter_notification_center.dart';
/// Configuration class for notifications.
class NotificationConfig {
@ -10,16 +12,21 @@ class NotificationConfig {
/// translations for notification messages.
const NotificationConfig({
required this.service,
this.style = const NotificationStyle(),
required this.seperateNotificationsWithDivider,
this.translations = const NotificationTranslations(),
required this.notificationWidgetBuilder,
});
/// The notification service to use for delivering notifications.
final NotificationService service;
/// The style of the notification.
final NotificationStyle style;
/// Whether to seperate notifications with a divider.
final bool seperateNotificationsWithDivider;
/// Translations for notification messages.
final NotificationTranslations translations;
/// Widget for building each notification item.
final Widget Function(NotificationModel, BuildContext)
notificationWidgetBuilder;
}

View file

@ -11,7 +11,7 @@ class NotificationStyle {
this.subtitleTextStyle,
this.backgroundColor,
this.leadingIconColor,
this.trailingIconColor,
this.pinnedIconColor,
this.contentPadding,
this.titleTextAlign,
this.subtitleTextAlign,
@ -21,6 +21,7 @@ class NotificationStyle {
this.appTitleTextStyle,
this.dividerColor,
this.isReadDotColor,
this.showNotificationIcon,
});
/// The text style for the title of the notification.
@ -36,7 +37,7 @@ class NotificationStyle {
final Color? leadingIconColor;
/// The color of the trailing icon (if any) in the notification.
final Color? trailingIconColor;
final Color? pinnedIconColor;
/// The padding around the content of the notification.
final EdgeInsets? contentPadding;
@ -62,6 +63,9 @@ class NotificationStyle {
/// The color of the divider.
final Color? dividerColor;
/// The color of the dot indicating if the notification is read.
/// The color of the dot that shows that anotification has not been read.
final Color? isReadDotColor;
/// Whether to show the notification icon.
final bool? showNotificationIcon;
}

View file

@ -1,6 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_animated_widgets/flutter_animated_widgets.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "../flutter_notification_center.dart";
/// A bell icon widget that displays the number of active notifications.
///

View file

@ -1,5 +1,5 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "../flutter_notification_center.dart";
/// A widget representing a notification bell.
class NotificationBellWidgetStory extends StatelessWidget {

View file

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import '../flutter_notification_center.dart';
/// Widget for displaying the notification center.
class NotificationCenter extends StatefulWidget {
/// Constructs a new NotificationCenter instance.
///
/// [config]: Configuration for the notification center.
const NotificationCenter({
required this.config,
super.key,
});
/// Configuration for the notification center.
final NotificationConfig config;
@override
NotificationCenterState createState() => NotificationCenterState();
}
/// State for the notification center.
class NotificationCenterState extends State<NotificationCenter> {
late Future<List<NotificationModel>> _notificationsFuture;
@override
void initState() {
super.initState();
// ignore: discarded_futures
_notificationsFuture = widget.config.service.getActiveNotifications();
widget.config.service.addListener(_listener);
}
@override
void dispose() {
widget.config.service.removeListener(_listener);
super.dispose();
}
void _listener() {
setState(() {});
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(
widget.config.translations.appBarTitle,
),
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
body: FutureBuilder<List<NotificationModel>>(
future: _notificationsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text("Error: ${snapshot.error}"));
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
return const Center(
child: Text("No unread notifications available."),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.length * 2 - 1,
itemBuilder: (context, index) {
if (index.isOdd) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: widget.config.seperateNotificationsWithDivider
? const Divider(
color: Colors.grey,
thickness: 1.0,
)
: Container(),
);
}
var notification = snapshot.data![index ~/ 2];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: widget.config
.notificationWidgetBuilder(notification, context),
);
},
);
}
},
),
);
}

View file

@ -1,33 +1,37 @@
import "package:flutter/material.dart";
import "package:flutter_notification_center/flutter_notification_center.dart";
import "../flutter_notification_center.dart";
import "package:intl/intl.dart";
/// A page displaying the details of a notification.
class NotificationDetailPage extends StatelessWidget {
/// Creates a new [NotificationDetailPage] instance.
///
/// The [config] parameter specifies the notification configuration.
/// The [notificationStyle] parameter specifies the notification style.
///
/// The [notification] parameter specifies the notification
/// to display details for.
const NotificationDetailPage({
required this.config,
required this.notificationStyle,
required this.notification,
required this.translations,
super.key,
});
/// The notification configuration.
final NotificationConfig config;
/// The notification style.
final NotificationStyle notificationStyle;
/// The notification to display details for.
final NotificationModel notification;
/// The translations for the notification detail page.
final NotificationTranslations translations;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(
config.translations.appBarTitle,
style: config.style.appTitleTextStyle,
translations.appBarTitle,
style: notificationStyle.appTitleTextStyle,
),
centerTitle: true,
),
@ -39,12 +43,13 @@ class NotificationDetailPage extends StatelessWidget {
children: [
Text(
notification.title,
style: config.style.titleTextStyle ?? const TextStyle(),
style: notificationStyle.titleTextStyle ?? const TextStyle(),
),
const SizedBox(height: 10),
Text(
notification.body,
style: config.style.subtitleTextStyle ?? const TextStyle(),
style:
notificationStyle.subtitleTextStyle ?? const TextStyle(),
),
const SizedBox(height: 10),
Text(

View file

@ -1,7 +1,7 @@
import "dart:async";
import "package:flutter/material.dart";
import "package:flutter_notification_center/src/models/notification.dart";
import "../models/notification.dart";
/// An abstract class representing a service for managing notifications.
abstract class NotificationService with ChangeNotifier {

View file

@ -0,0 +1,32 @@
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.0.0
environment:
sdk: '>=3.3.2 <4.0.0'
dependencies:
flutter:
sdk: flutter
intl: ^0.17.0
flutter_animated_widgets:
git:
url: https://github.com/Iconica-Development/flutter_animated_widgets
ref: 0.0.1
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

View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: GPL-3.0-or-later
import 'package:flutter_test/flutter_test.dart';
void main() {
test('test', () {
expect(true, true);
});
}

View file

@ -0,0 +1,52 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
.dart_tools/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# iOS related
/ios/
lib/config/
pubspec.lock
dotenv

View file

@ -0,0 +1,16 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
export "src/services/firebase_notification_service.dart";
export "src/config/firebase_collections.dart";
export "src/config/environment.dart";
export "src/config/firebase_options.dart";
export "src/config/firebase.dart";

View file

@ -0,0 +1,4 @@
mixin FirebaseCollectionNames {
static const String activeNotifications = 'active_notifications';
static const String plannedNotifications = 'planned_notifications';
}

View file

@ -1,7 +1,7 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:example/config/environment_config.dart';
import 'environment_config.dart';
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show TargetPlatform, defaultTargetPlatform, kIsWeb;

View file

@ -2,9 +2,8 @@ import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_notification_center/flutter_notification_center.dart';
import '../config/firebase_collections.dart';
import 'package:flutter_notification_center/src/models/notification.dart';
import 'package:flutter_notification_center/src/services/notification_service.dart';
class FirebaseNotificationService
with ChangeNotifier
@ -14,6 +13,7 @@ class FirebaseNotificationService
@override
List<NotificationModel> listOfPlannedNotifications;
// ignore: unused_field
late Timer _timer;
FirebaseNotificationService(
@ -33,7 +33,7 @@ class FirebaseNotificationService
Future<void> pushNotification(NotificationModel notification) async {
try {
CollectionReference notifications = FirebaseFirestore.instance
.collection(FirebaseCollectionNames.active_notifications);
.collection(FirebaseCollectionNames.activeNotifications);
DateTime currentDateTime = DateTime.now();
notification.dateTimePushed = currentDateTime;
@ -52,7 +52,7 @@ class FirebaseNotificationService
try {
CollectionReference activeNotificationsCollection = FirebaseFirestore
.instance
.collection(FirebaseCollectionNames.active_notifications);
.collection(FirebaseCollectionNames.activeNotifications);
QuerySnapshot querySnapshot = await activeNotificationsCollection.get();
@ -104,7 +104,7 @@ class FirebaseNotificationService
NotificationModel notification) async {
try {
CollectionReference plannedNotifications = FirebaseFirestore.instance
.collection(FirebaseCollectionNames.planned_notifications);
.collection(FirebaseCollectionNames.plannedNotifications);
Map<String, dynamic> notificationMap = notification.toMap();
await plannedNotifications.doc(notification.id).set(notificationMap);
} catch (e) {
@ -113,16 +113,16 @@ class FirebaseNotificationService
}
@override
Future deleteScheduledNotification(
Future<void> deleteScheduledNotification(
NotificationModel notificationModel) async {
try {
DocumentReference documentReference = FirebaseFirestore.instance
.collection(FirebaseCollectionNames.planned_notifications)
.collection(FirebaseCollectionNames.plannedNotifications)
.doc(notificationModel.id);
await documentReference.delete();
QuerySnapshot querySnapshot = await FirebaseFirestore.instance
.collection(FirebaseCollectionNames.planned_notifications)
.collection(FirebaseCollectionNames.plannedNotifications)
.get();
if (querySnapshot.docs.isEmpty) {
@ -137,10 +137,11 @@ class FirebaseNotificationService
}
@override
Future dismissActiveNotification(NotificationModel notificationModel) async {
Future<void> dismissActiveNotification(
NotificationModel notificationModel) async {
try {
DocumentReference documentReference = FirebaseFirestore.instance
.collection(FirebaseCollectionNames.active_notifications)
.collection(FirebaseCollectionNames.activeNotifications)
.doc(notificationModel.id);
await documentReference.delete();
listOfActiveNotifications
@ -156,7 +157,7 @@ class FirebaseNotificationService
NotificationModel notificationModel) async {
try {
DocumentReference documentReference = FirebaseFirestore.instance
.collection(FirebaseCollectionNames.active_notifications)
.collection(FirebaseCollectionNames.activeNotifications)
.doc(notificationModel.id);
await documentReference.update({'isRead': true});
notificationModel.isRead = true;
@ -172,7 +173,7 @@ class FirebaseNotificationService
try {
CollectionReference plannedNotificationsCollection = FirebaseFirestore
.instance
.collection(FirebaseCollectionNames.planned_notifications);
.collection(FirebaseCollectionNames.plannedNotifications);
QuerySnapshot querySnapshot = await plannedNotificationsCollection.get();
@ -207,13 +208,6 @@ class FirebaseNotificationService
}
}
}
plannedNotifications.forEach((notification) async {
if (notification.scheduledFor!.isBefore(currentTime) ||
notification.scheduledFor!.isAtSameMomentAs(currentTime)) {
await deleteScheduledNotification(notification);
}
});
} catch (e) {
debugPrint('Error getting planned notifications: $e');
return;

View file

@ -1,4 +1,4 @@
name: example
name: flutter_notification_center_firebase
description: "A new Flutter project."
publish_to: "none"
@ -21,10 +21,11 @@ dependencies:
firebase_storage: ^11.0.14
cupertino_icons: ^1.0.2
flutter_notification_center:
path: ../
provider: ^6.1.2
flutter_notification_center:
path: ../flutter_notification_center
dev_dependencies:
flutter_test:
sdk: flutter