diff --git a/frontend2/lib/main.dart b/frontend2/lib/main.dart index f8c31c02..55a21802 100644 --- a/frontend2/lib/main.dart +++ b/frontend2/lib/main.dart @@ -15,6 +15,7 @@ import 'package:frontend2/utilities/startupitem.dart'; import 'package:provider/provider.dart'; import 'package:frontend2/utilities/notifications.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:frontend2/utilities/Notifier.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -26,6 +27,8 @@ Future main() async { await getUserMessInfo(); + NotificationNotifier.init(); + runApp( MultiProvider( providers: [ diff --git a/frontend2/lib/screens/notification.dart b/frontend2/lib/screens/notification.dart index 103d0c49..340cc3bc 100644 --- a/frontend2/lib/screens/notification.dart +++ b/frontend2/lib/screens/notification.dart @@ -63,7 +63,7 @@ import 'package:flutter/material.dart'; import 'package:frontend2/providers/notifications.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:frontend2/utilities/notification_card.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:frontend2/utilities/Notifier.dart'; class NotificationScreen extends StatefulWidget { const NotificationScreen({super.key}); @@ -80,48 +80,50 @@ class _NotificationScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.transparent, - body: DraggableScrollableSheet( - initialChildSize: 0.85, - minChildSize: 0.6, - maxChildSize: 0.95, - builder: (context, controller) { - return Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), - ), - child: Column( - children: [ - // Header - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - CircleAvatar( - radius: 20, - backgroundColor: Colors.blue[50], - child: const Icon(Icons.notifications_none, - color: Colors.blue, size: 27), - ), - const SizedBox(width: 8), - Text( - "Notifications", - style: Theme.of(context) - .textTheme - .headlineMedium - ?.copyWith(color: Colors.black, fontSize: 20), - ), - const Spacer(), - IconButton( - icon: const Icon(Icons.close, size: 26), - onPressed: () => Navigator.pop(context), - ), - ], + return ValueListenableBuilder( + valueListenable: NotificationNotifier.notifier, + builder: (context, storedNotifications, child) => Scaffold( + backgroundColor: Colors.transparent, + body: DraggableScrollableSheet( + initialChildSize: 0.85, + minChildSize: 0.6, + maxChildSize: 0.95, + builder: (context, controller) { + return Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + children: [ + // Header + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + children: [ + CircleAvatar( + radius: 20, + backgroundColor: Colors.blue[50], + child: const Icon(Icons.notifications_none, + color: Colors.blue, size: 27), + ), + const SizedBox(width: 8), + Text( + "Notifications", + style: Theme.of(context) + .textTheme + .headlineMedium + ?.copyWith(color: Colors.black, fontSize: 20), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.close, size: 26), + onPressed: () => Navigator.pop(context), + ), + ], + ), ), - ), // List of notifications ValueListenableBuilder( diff --git a/frontend2/lib/utilities/Notifier.dart b/frontend2/lib/utilities/Notifier.dart new file mode 100644 index 00000000..5e59d511 --- /dev/null +++ b/frontend2/lib/utilities/Notifier.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; + +class NotificationNotifier { + static ValueNotifier> notifier = ValueNotifier([]); + static SharedPreferences? prefs; + + static void init() async { + prefs = await SharedPreferences.getInstance(); + notifier.value = prefs?.getStringList("notifications") ?? []; + FirebaseMessaging.onMessage.listen((RemoteMessage message) async { + String? title = message.notification?.title ?? 'No Title'; + String? body = message.notification?.body ?? 'No Body'; + NotificationNotifier.addNewNotification(title, body); + }); + } + + static void addNewNotification(String title, String body) { + notifier.value.add('$title: $body'); + prefs?.setStringList('notifications', notifier.value); + } +} \ No newline at end of file diff --git a/server/modules/mess_change/messChangeScheduler.js b/server/modules/mess_change/messChangeScheduler.js index be4c6887..18e83bfe 100644 --- a/server/modules/mess_change/messChangeScheduler.js +++ b/server/modules/mess_change/messChangeScheduler.js @@ -17,7 +17,7 @@ const initializeMessChangeScheduler = () => { console.log("🌏 Current IST time:", istTime.toLocaleString("en-IN")); // TEST SCHEDULE - Enable at 2:15 AM IST on 7 Sept 2025 - const testEnableDate = new Date("2025-09-07T02:46:40+05:30"); + const testEnableDate = new Date("2025-09-07T03:22:00+05:30"); const enableJob = schedule.scheduleJob( "mess-change-enable", testEnableDate, @@ -31,7 +31,7 @@ const initializeMessChangeScheduler = () => { ); // TEST SCHEDULE - Disable at 2:30 AM IST on 7 Sept 2025 - const testDisableDate = new Date("2025-09-07T02:47:00+05:30"); + const testDisableDate = new Date("2025-09-07T03:23:00+05:30"); const disableJob = schedule.scheduleJob( "mess-change-disable", testDisableDate, diff --git a/server/modules/mess_change/messchangeController.js b/server/modules/mess_change/messchangeController.js index a392bc65..a60d3b02 100644 --- a/server/modules/mess_change/messchangeController.js +++ b/server/modules/mess_change/messchangeController.js @@ -2,6 +2,7 @@ const { User } = require("../user/userModel.js"); const { Hostel } = require("../hostel/hostelModel.js"); const { MessChange } = require("./messChangeModel.js"); const { MessChangeSettings } = require("./messChangeSettingsModel.js"); +const { sendCustomNotificationToAllUsers } = require("../notification/notificationManager.js"); const getAllMessChangeRequestsForAllHostels = async (req, res) => { try { @@ -233,6 +234,8 @@ const rejectAllMessChangeRequests = async (req, res) => { await updateLastProcessedTimestamp(); + sendCustomNotificationToAllUsers("Mess Change is Disabled", "Mess Change is Disabled"); + return res.status(200).json({ message: `Rejected ${users.length} pending requests. Mess change has been automatically disabled.`, }); @@ -288,6 +291,8 @@ const processAllMessChangeRequests = async (req, res) => { // Automatically disable mess change after processing and update timestamp await updateLastProcessedTimestamp(); + sendCustomNotificationToAllUsers("Mess Change is Disabled", "Mess Change is Disabled"); + return res.status(200).json({ message: `${acceptedUsers.length} requests accepted, ${rejectedUsers.length} rejected. Mess change has been automatically disabled.`, acceptedUsers, @@ -506,6 +511,9 @@ const enableMessChange = async (req, res) => { } await settings.save(); + + sendCustomNotificationToAllUsers("Mess Change is Enabled", "Mess Change is Enabled"); + return res.status(200).json({ message: "Mess change enabled successfully", @@ -532,6 +540,8 @@ const disableMessChange = async (req, res) => { await settings.save(); + sendCustomNotificationToAllUsers("Mess Change is Disabled", "Mess Change is Disabled"); + return res.status(200).json({ message: "Mess change disabled successfully", data: settings, @@ -584,8 +594,9 @@ const getMessChangeScheduleInfo = async (req, res) => { try { const settings = await MessChangeSettings.findOne(); - const testEnableDate = new Date("2025-09-07T02:15:00+05:30"); - const testDisableDate = new Date("2025-09-07T02:30:00+05:30"); + // FOR TESTING: Fixed test dates + const testEnableDate = new Date("2025-09-07T02:48:00+05:30"); + const testDisableDate = new Date("2025-09-07T04:30:00+05:30"); return res.status(200).json({ message: "Mess change schedule information (TEST MODE)", @@ -631,6 +642,9 @@ const enableMessChangeAutomatic = async () => { } await settings.save(); + + sendCustomNotificationToAllUsers("Enable", "Mess Change is Enabled"); + console.log("✅ Mess change enabled automatically"); return { success: true, settings }; } catch (error) { @@ -639,26 +653,6 @@ const enableMessChangeAutomatic = async () => { } }; -const disableMessChangeAutomatic = async () => { - try { - let settings = await MessChangeSettings.findOne(); - - if (!settings) { - console.log("⚠️ No mess change settings found - nothing to disable"); - return { success: false, message: "No settings found" }; - } - - settings.isEnabled = false; - settings.disabledAt = new Date(); - - await settings.save(); - console.log("✅ Mess change disabled automatically"); - return { success: true, settings }; - } catch (error) { - console.error("❌ Error disabling mess change automatically:", error); - return { success: false, error }; - } -}; module.exports = { getAllMessChangeRequestsForAllHostels, @@ -674,5 +668,4 @@ module.exports = { rejectAllMessChangeRequests, getMessChangeScheduleInfo, enableMessChangeAutomatic, - disableMessChangeAutomatic, }; diff --git a/server/modules/notification/notificationController.js b/server/modules/notification/notificationController.js index fa6ec527..7b250cbf 100644 --- a/server/modules/notification/notificationController.js +++ b/server/modules/notification/notificationController.js @@ -2,6 +2,7 @@ const admin = require("./firebase.js"); const Notification = require("./notificationModel.js"); const FCMToken = require("./FCMToken.js"); const User = require("../user/userModel.js"); +const { Hostel } = require("../hostel/hostelModel.js"); // Register (or update) FCM token for a user const registerToken = async (req, res) => { @@ -9,9 +10,17 @@ const registerToken = async (req, res) => { if (!req.user) return res.status(403).json({ error: "Only users can register tokens" }); + const curr_sub_mess_name = (await Hostel.findById((await req.user.curr_subscribed_mess)._id))['hostel_name'].replaceAll(' ', '_'); + + console.log(curr_sub_mess_name); + + const { fcmToken } = req.body; if (!fcmToken) return res.status(400).json({ error: "FCM token is required" }); + + admin.messaging().subscribeToTopic(fcmToken, "All_Hostels"); + admin.messaging().subscribeToTopic(fcmToken, curr_sub_mess_name); await FCMToken.findOneAndUpdate( { user: req.user._id }, @@ -19,6 +28,8 @@ const registerToken = async (req, res) => { { upsert: true, new: true, setDefaultsOnInsert: true } ); + + res.json({ message: "FCM token registered" }); } catch (err) { console.error(err); @@ -34,27 +45,27 @@ const sendNotification = async (req, res) => { // .status(403) // .json({ error: "Only hostel admins can send notifications" }); - const { title, body } = req.body; + const { title, body, topic } = req.body; // const users = await User.find({ hostel: req.hostel._id }); // const userIds = users.map((u) => u._id); // const tokens = await FCMToken.find({ user: { $in: userIds } }); - const fcmTokens = (await FCMToken.find({})).map((t) => t.token); + // const fcmTokens = (await FCMToken.find({})).map((t) => t.token); - console.log(fcmTokens); + // console.log(fcmTokens); // const fcmTokens = ["fKlOwXuIQlKoXNmz3Azir3:APA91bEgK2VmDUasiIJT0Rpmb9emp-aYmuO5w-arzpeaAs9QMK_B9AT8iQlNDPXBm0tLb2wVQHDogj3XmaTn76YORG0iVBZ4zgf1DUMSY7DTfaYLdYZL6LA"]; - if (fcmTokens.length === 0) - return res.status(400).json({ error: "No user tokens found" }); + // if (fcmTokens.length === 0) + // return res.status(400).json({ error: "No user tokens found" }); const message = { notification: { title, body }, - tokens: fcmTokens, + topic: topic }; console.log(message); - await admin.messaging().sendEachForMulticast(message); + await admin.messaging().send(message); // await Notification.create({ // title, diff --git a/server/modules/notification/notificationManager.js b/server/modules/notification/notificationManager.js new file mode 100644 index 00000000..06b29aa3 --- /dev/null +++ b/server/modules/notification/notificationManager.js @@ -0,0 +1,10 @@ +const admin = require("./firebase.js"); + +const sendCustomNotificationToAllUsers = (title, body) => { + admin.messaging().send({ + notification: { title, body }, + topic: "All_Hostels" + }); +}; + +module.exports = {sendCustomNotificationToAllUsers} \ No newline at end of file diff --git a/server/modules/notification/notificationRoute.js b/server/modules/notification/notificationRoute.js index 2154fb6e..bfb019bc 100644 --- a/server/modules/notification/notificationRoute.js +++ b/server/modules/notification/notificationRoute.js @@ -12,8 +12,8 @@ const { markAsRead, } = require("./notificationController.js"); -// router.post("/send", sendNotification); -router.post("/send", authenticateAdminJWT, sendNotification); +router.post("/send", sendNotification); +// router.post("/send", authenticateAdminJWT, sendNotification); router.post("/register-token", authenticateJWT, registerToken); router.get("/", authenticateJWT, getUserNotifications); router.post("/:id/mark-read", authenticateJWT, markAsRead);