Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend2/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
Expand All @@ -26,6 +27,8 @@ Future<void> main() async {

await getUserMessInfo();

NotificationNotifier.init();

runApp(
MultiProvider(
providers: [
Expand Down
86 changes: 44 additions & 42 deletions frontend2/lib/screens/notification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand All @@ -80,48 +80,50 @@ class _NotificationScreenState extends State<NotificationScreen> {

@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(
Expand Down
23 changes: 23 additions & 0 deletions frontend2/lib/utilities/Notifier.dart
Original file line number Diff line number Diff line change
@@ -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<List<String>> 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);
}
}
4 changes: 2 additions & 2 deletions server/modules/mess_change/messChangeScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
39 changes: 16 additions & 23 deletions server/modules/mess_change/messchangeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.`,
});
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -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)",
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand All @@ -674,5 +668,4 @@ module.exports = {
rejectAllMessChangeRequests,
getMessChangeScheduleInfo,
enableMessChangeAutomatic,
disableMessChangeAutomatic,
};
25 changes: 18 additions & 7 deletions server/modules/notification/notificationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@ 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) => {
try {
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 },
{ token: fcmToken },
{ upsert: true, new: true, setDefaultsOnInsert: true }
);



res.json({ message: "FCM token registered" });
} catch (err) {
console.error(err);
Expand All @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions server/modules/notification/notificationManager.js
Original file line number Diff line number Diff line change
@@ -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}
4 changes: 2 additions & 2 deletions server/modules/notification/notificationRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down