diff --git a/.gitignore b/.gitignore
index b8fe4cc..7dc495a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,11 +4,11 @@
backend/.DS_Store
node_modules/
backend/.env
-
-## Frontend ignored file (Root level)
+backend/.idea
## Frontend ignored file (Root level)
+frontend/.env
# Miscellaneous
frontend/*.class
diff --git a/backend/app.js b/backend/app.js
index e8b54e7..4bb9c28 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -10,6 +10,10 @@ import cors from "cors";
import tokenRequired from "./middlewares/tokenRequired.js";
import adminResource from "./resources/adminResource.js";
+import roomListResource from "./resources/rooms/roomListResource.js";
+import roomResource from "./resources/rooms/roomResource.js";
+import lostAndFoundListResource from "./resources/lostAndFound/lostAndFoundListResource.js";
+
const PORT = `${process.env.PORT || 3000}`;
const app = express();
@@ -26,6 +30,9 @@ app.use("/admin-auth", adminAuthResource);
app.use(authResource);
app.use("otp", otpResource);
app.use("/", testResource);
+app.use("/rooms", roomListResource);
+app.use("/room", roomResource);
+app.use("/lost-and-found", lostAndFoundListResource);
app.get("/protected", tokenRequired, (req, res) => {
res.json({ message: "Access granted" });
diff --git a/backend/constants/errorMessages.js b/backend/constants/errorMessages.js
index 82c0512..ba0efea 100644
--- a/backend/constants/errorMessages.js
+++ b/backend/constants/errorMessages.js
@@ -2,26 +2,29 @@
export const userAlreadyExists = "User with this email already exists!";
export const userNotFound = "User with this email does not exist!";
export const incorrectPassword = "Incorrect password.";
-export const internalServerError = "Internal Server Error. Please try again later.";
+export const internalServerError =
+ "Internal Server Error. Please try again later.";
export const userCreated = "User created successfully";
//Database Connection
export const databaseConnected = "Database connected successfully";
export const databaseDisconnected = "Database disconnected";
-export const databaseConnectionError = "Error while connecting with the database";
+export const databaseConnectionError =
+ "Error while connecting with the database";
-// OTP
-export const emailIsRequired = 'Email is required';
-export const failedToSendOTPEmail = 'Failed to send OTP to email';
-export const emailAndOTPRequired = 'Email and OTP are required';
-export const noOTPFoundForEmail = 'No OTP found for the email';
-export const incorrectOTP = 'Incorrect OTP';
-export const otpVerfied = 'OTP verified successfully';
-export const otpSent = 'OTP sent successfully';
+// OTP
+export const emailIsRequired = "Email is required";
+export const failedToSendOTPEmail = "Failed to send OTP to to email";
+export const emailAndOTPRequired = "Email and OTP are required";
+export const noOTPFoundForEmail = "No OTP found for the email";
+export const incorrectOTP = "Incorrect OTP";
+export const otpVerfied = "OTP verified successfully";
+export const otpSent = "OTP sent successfully";
// Auth Middleware
-export const noAuthToken = 'No auth token, access denied';
-export const invalidAuthToken = 'Invalid token';
-export const tokenVerificationFailed = 'Token verification failed, authorization denied.';
-export const invalidUserType = 'Invalid user type';
-export const tokenUpdateError= 'Error updating token';
\ No newline at end of file
+export const noAuthToken = "No auth token, access denied";
+export const invalidAuthToken = "Invalid token";
+export const tokenVerificationFailed =
+ "Token verification failed, authorization denied.";
+export const invalidUserType = "Invalid user type";
+export const tokenUpdateError = "Error updating token";
diff --git a/backend/middlewares/multerConfig.js b/backend/middlewares/multerConfig.js
new file mode 100644
index 0000000..384f487
--- /dev/null
+++ b/backend/middlewares/multerConfig.js
@@ -0,0 +1,15 @@
+import multer from "multer";
+
+// Define storage configuration
+const storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ cb(null, "uploads/");
+ },
+ filename: function (req, file, cb) {
+ cb(null, file.originalname);
+ },
+});
+
+const uploader = multer({ storage: storage });
+
+export default uploader;
diff --git a/backend/models/lost_and_found.js b/backend/models/lost_and_found.js
index 2ff415d..a191c73 100644
--- a/backend/models/lost_and_found.js
+++ b/backend/models/lost_and_found.js
@@ -1,30 +1,33 @@
-import mongoose from 'mongoose';
+import mongoose from "mongoose";
const lostAndFoundItemSchema = new mongoose.Schema({
- name: {
- type: String,
- required: true
- },
- lastSeenLocation: {
- type: String,
- },
- imagePath: {
- type: String,
- },
- description: {
- type: String,
- required: true
- },
- contactNumber: {
- type: String,
- required: true
- },
- isLost: {
- type: Boolean,
- required: true
- }
+ name: {
+ type: String,
+ required: true,
+ },
+ lastSeenLocation: {
+ type: String,
+ },
+ imagePath: {
+ type: String,
+ nullable: true,
+ },
+ description: {
+ type: String,
+ },
+ contactNumber: {
+ type: String,
+ required: true,
+ },
+ isLost: {
+ type: Boolean,
+ required: true,
+ },
});
-const LostAndFoundItem = mongoose.model('LostAndFoundItem', lostAndFoundItemSchema);
+const LostAndFoundItem = mongoose.model(
+ "LostAndFoundItem",
+ lostAndFoundItemSchema
+);
-export default LostAndFoundItem;
\ No newline at end of file
+export default LostAndFoundItem;
diff --git a/backend/models/room.js b/backend/models/room.js
index 2af0ea3..a2ca4c1 100644
--- a/backend/models/room.js
+++ b/backend/models/room.js
@@ -1,26 +1,20 @@
-
-const mongoose = require('mongoose');
+import mongoose from "mongoose";
const roomSchema = new mongoose.Schema({
- id: {
- type: String,
- required: true,
- },
- name: {
- type: String,
- required: true,
- },
- vacant: {
- type: Boolean,
- default: true,
- },
- occupantId: {
- type: String,
- default: null,
- },
+ name: {
+ type: String,
+ required: true,
+ },
+ vacant: {
+ type: Boolean,
+ default: true,
+ },
+ occupantId: {
+ type: String,
+ default: null,
+ },
});
-const Room = mongoose.model('Room', roomSchema);
-
-module.exports = Room;
+const Room = mongoose.model("Room", roomSchema);
+export default Room;
diff --git a/backend/resources/lostAndFound/lostAndFoundListResource.js b/backend/resources/lostAndFound/lostAndFoundListResource.js
new file mode 100644
index 0000000..0a682ce
--- /dev/null
+++ b/backend/resources/lostAndFound/lostAndFoundListResource.js
@@ -0,0 +1,74 @@
+import { Router } from "express";
+import LostAndFoundItem from "../../models/lost_and_found.js";
+import fs from "fs/promises";
+import uploader from "../../middlewares/multerConfig.js";
+
+const router = Router();
+
+// GET method to retrieve all items
+router.get("/", async (req, res) => {
+ try {
+ // Query the database to retrieve all items
+ const items = await LostAndFoundItem.find({});
+
+ // Create an empty array to store items with images
+ const itemsWithImages = [];
+
+ // Iterate through each item
+ for (const item of items) {
+ // Check if imagePath is null
+ let imagePathBase64 = null;
+ if (item.imagePath) {
+ // Read the image file if imagePath is not null
+ const bufferImage = await fs.readFile(item.imagePath);
+ imagePathBase64 = bufferImage.toString("base64");
+ }
+
+ // Create a new object with the required attributes
+ const itemWithImage = {
+ _id: item._id,
+ name: item.name,
+ lastSeenLocation: item.lastSeenLocation,
+ imagePath: imagePathBase64, // Set imagePath to null if null in the database
+ description: item.description,
+ contactNumber: item.contactNumber,
+ isLost: item.isLost,
+ };
+
+ // Push the item with image to the array
+ itemsWithImages.push(itemWithImage);
+ }
+
+ console.log("Retrieved items:", itemsWithImages.length);
+
+ // Send the response with the items
+ res.json(itemsWithImages);
+ } catch (error) {
+ // Handle errors
+ console.error("Error:", error);
+ res.status(500).send("Error retrieving items");
+ }
+});
+
+// POST method
+router.post("/", uploader.single("image"), async (req, res) => {
+ // Access the uploaded file using req.file
+ const file = req.file;
+
+ // Construct the LostAndFoundItem object with data from the request
+ const newItem = new LostAndFoundItem({
+ name: req.body.name,
+ lastSeenLocation: req.body.lastSeenLocation,
+ imagePath: file ? file.path : null,
+ description: req.body.description,
+ contactNumber: req.body.contactNumber,
+ isLost: req.body.isLost,
+ });
+
+ // Save the new item to the database
+ await newItem.save();
+
+ res.send("Added new item");
+});
+
+export default router;
diff --git a/backend/resources/rooms/occupantListResource.js b/backend/resources/rooms/occupantListResource.js
new file mode 100644
index 0000000..1fb4034
--- /dev/null
+++ b/backend/resources/rooms/occupantListResource.js
@@ -0,0 +1,27 @@
+import { Router } from "express";
+import Room from "../../models/room.js";
+import Student from "../../models/student.js";
+const router = Router();
+
+//GET method
+router.get("/", async (req, res) => {
+ try {
+ const documentIds = req.body.documentIds; // Assuming the documentIds are sent in the request body
+
+ const occupants = await Promise.all(
+ documentIds.map(async (documentId) => {
+ const room = await Room.findOne({ documentId });
+ const occupant = await Student.findOne({ _id: room.occupantId });
+ return {
+ occupantName: occupant.name,
+ roomId: room._id,
+ };
+ })
+ );
+
+ res.json(occupants);
+ } catch (error) {
+ console.error(error);
+ res.status(500).json({ message: "Internal Server Error" });
+ }
+});
diff --git a/backend/resources/rooms/roomListResource.js b/backend/resources/rooms/roomListResource.js
new file mode 100644
index 0000000..ceb9cc6
--- /dev/null
+++ b/backend/resources/rooms/roomListResource.js
@@ -0,0 +1,40 @@
+import { Router } from "express";
+import Room from "../../models/room.js";
+const router = Router();
+
+// GET method
+router.get("/", async (req, res) => {
+ // Your code here
+ const rooms = await Room.find({});
+ res.send(rooms);
+});
+
+// POST method
+router.post("/", async (req, res) => {
+ try {
+ // Extract data from request body
+ const { name, vacant, occupantId } = req.body;
+
+ // Create a new room instance
+ const newRoom = new Room({
+ name,
+ vacant: vacant || true, // Set default value if not provided
+ occupantId: occupantId || null, // Set default value if not provided
+ });
+
+ // Save the new room to the database
+ await newRoom.save();
+ console.log("Room created successfully");
+
+ // Respond with success message
+ res
+ .status(201)
+ .json({ message: "Room created successfully", room: newRoom });
+ } catch (error) {
+ // Handle errors
+ console.error("Error creating room:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+});
+
+export default router;
diff --git a/backend/resources/rooms/roomResource.js b/backend/resources/rooms/roomResource.js
new file mode 100644
index 0000000..f78114f
--- /dev/null
+++ b/backend/resources/rooms/roomResource.js
@@ -0,0 +1,28 @@
+import { Router } from "express";
+import Room from "../../models/room.js";
+const router = Router();
+
+// PUT method
+router.put("/:id", async (req, res) => {
+ try {
+ const { id } = req.params;
+ const { occupantId } = req.body;
+ const room = await Room.findById(id);
+ if (!room) {
+ return res.status(404).json({ error: "Room not found" });
+ }
+
+ room.vacant = false;
+ room.occupantId = occupantId || room.occupantId;
+
+ await room.save();
+
+ console.log("Room updated successfully");
+ res.json({ message: "Room updated successfully", room });
+ } catch (error) {
+ console.error("Error updating room:", error);
+ res.status(500).json({ error: "Internal server error" });
+ }
+});
+
+export default router;
diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml
index 443f830..59be995 100644
--- a/frontend/android/app/src/main/AndroidManifest.xml
+++ b/frontend/android/app/src/main/AndroidManifest.xml
@@ -16,20 +16,41 @@
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme" />
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/lib/assets/placeholder.png b/frontend/lib/assets/placeholder.png
index a242799..42e388f 100644
Binary files a/frontend/lib/assets/placeholder.png and b/frontend/lib/assets/placeholder.png differ
diff --git a/frontend/lib/assets/profile_placeholder.png b/frontend/lib/assets/profile_placeholder.png
new file mode 100644
index 0000000..a242799
Binary files /dev/null and b/frontend/lib/assets/profile_placeholder.png differ
diff --git a/frontend/lib/components/image_tile.dart b/frontend/lib/components/image_tile.dart
new file mode 100644
index 0000000..0717239
--- /dev/null
+++ b/frontend/lib/components/image_tile.dart
@@ -0,0 +1,64 @@
+import 'package:flutter/material.dart';
+
+class ImageTile extends StatelessWidget {
+ const ImageTile(
+ {super.key,
+ this.icon,
+ required this.onTap,
+ required this.primaryColor,
+ required this.secondaryColor,
+ this.body,
+ this.contentPadding,
+ this.image});
+
+ final Image? image;
+ final List? body;
+ final IconData? icon;
+ final Function onTap;
+ final EdgeInsets? contentPadding;
+ final Color primaryColor;
+ final Color secondaryColor;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ margin: const EdgeInsets.all(10),
+ child: Material(
+ borderRadius: BorderRadius.circular(15),
+ child: Ink(
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: primaryColor),
+ child: InkWell(
+ overlayColor: MaterialStateProperty.all(secondaryColor),
+ borderRadius: BorderRadius.circular(15),
+ splashColor: secondaryColor,
+ onTap: () => onTap(),
+ child: Center(
+ child: Container(
+ padding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ const SizedBox(height: 20),
+ image != null
+ ? ClipRRect(borderRadius: BorderRadius.circular(10), child: image)
+ : Container(
+ width: double.infinity,
+ height: 115,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(10),
+ color: Colors.white,
+ ),
+ child: const Icon(Icons.image, size: 50, color: Colors.black45),
+ ),
+ const SizedBox(height: 10),
+ ] +
+ (body ?? []),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/frontend/lib/components/menu_tile.dart b/frontend/lib/components/menu_tile.dart
index 11828f9..eec90d8 100644
--- a/frontend/lib/components/menu_tile.dart
+++ b/frontend/lib/components/menu_tile.dart
@@ -2,13 +2,22 @@ import 'package:flutter/material.dart';
class MenuTile extends StatelessWidget {
const MenuTile(
- {super.key, required this.title, this.icon, required this.onTap, this.primaryColor ,this.secondaryColor });
+ {super.key,
+ required this.title,
+ this.icon,
+ required this.onTap,
+ required this.primaryColor,
+ required this.secondaryColor,
+ this.body,
+ this.contentPadding});
final String title;
+ final List? body;
final IconData? icon;
final Function onTap;
- final Color? primaryColor;
- final Color? secondaryColor;
+ final EdgeInsets? contentPadding;
+ final Color primaryColor;
+ final Color secondaryColor;
@override
Widget build(BuildContext context) {
@@ -17,23 +26,29 @@ class MenuTile extends StatelessWidget {
child: Material(
borderRadius: BorderRadius.circular(15),
child: Ink(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(15),
- color:primaryColor ?? Colors.grey[100],
- ),
+ decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: primaryColor),
child: InkWell(
- overlayColor: MaterialStateProperty.all(secondaryColor ?? Colors.grey[300]),
+ overlayColor: MaterialStateProperty.all(secondaryColor),
borderRadius: BorderRadius.circular(15),
- splashColor: secondaryColor ?? Colors.grey[200],
+ splashColor: secondaryColor,
onTap: () => onTap(),
child: Center(
- child: Text(
- title,
- textAlign: TextAlign.center,
- style: const TextStyle(
- color: Colors.black,
- fontSize: 21,
- fontFamily: "GoogleSansFlex",
+ child: Padding(
+ padding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 16.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ title,
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 21,
+ fontFamily: "GoogleSansFlex",
+ ),
+ ),
+ ] +
+ (body ?? []),
),
),
),
diff --git a/frontend/lib/constants/constants.dart b/frontend/lib/constants/constants.dart
index ab1ecfa..b18a610 100644
--- a/frontend/lib/constants/constants.dart
+++ b/frontend/lib/constants/constants.dart
@@ -5,6 +5,8 @@ class AppConstants {
static const Color seedColor = Colors.lightBlueAccent;
}
+enum LoadingState { progress, success, error }
+
class AuthConstants {
static const String facultyAuthLabel = "Faculty";
static const String studentAuthLabel = "Student";
@@ -80,6 +82,11 @@ class StudentRoles {
];
}
+class LostAndFoundConstants {
+ static const String lostState = 'Lost';
+ static const String foundState = 'Found';
+}
+
class MessMenuConstants {
static Map>> emptyMenu = {
'Sunday': >{
@@ -137,7 +144,8 @@ class MessMenuConstants {
];
static final List mealTypes = [
- Text('Breakfast', style: TextStyle(color: Colors.teal.shade900, fontSize: 14)),
+ Text('Breakfast',
+ style: TextStyle(color: Colors.teal.shade900, fontSize: 14)),
Text('Lunch', style: TextStyle(color: Colors.teal.shade900, fontSize: 14)),
Text('Snacks', style: TextStyle(color: Colors.teal.shade900, fontSize: 14)),
Text('Dinner', style: TextStyle(color: Colors.teal.shade900, fontSize: 14)),
@@ -155,6 +163,34 @@ class MessMenuConstants {
}
class Validators {
+ static String? descriptionValidator(String? value) {
+ if (value != null && value.length > 250) {
+ return "Description cannot exceed 250 characters";
+ }
+
+ return null;
+ }
+
+ static String? contactNumberValidator(String? value) {
+ if (value == null || value.isEmpty) {
+ return "Contact number cannot be empty";
+ }
+
+ // Check if the contact number contains only digits
+ if (!RegExp(r'^\+?\d+$').hasMatch(value)) {
+ return "Invalid contact number format";
+ }
+
+ return null;
+ }
+
+ static String? nonEmptyValidator(String? value) {
+ if (value == null || value.isEmpty) {
+ return "Field cannot be empty";
+ }
+ return null;
+ }
+
static String? emailValidator(String? value) {
if (value == null || value.isEmpty) {
return "Email cannot be empty";
diff --git a/frontend/lib/constants/dummy_entries.dart b/frontend/lib/constants/dummy_entries.dart
index 32f06d8..220c056 100644
--- a/frontend/lib/constants/dummy_entries.dart
+++ b/frontend/lib/constants/dummy_entries.dart
@@ -1,6 +1,7 @@
import '../models/achievement.dart';
import '../models/course.dart';
import '../models/faculty.dart';
+import '../models/lost_and_found_item.dart';
import '../models/mess_menu.dart';
import '../models/room.dart';
import '../models/skills.dart';
@@ -433,11 +434,7 @@ class DummyFaculties {
id: '2',
name: 'Prof. Johnson',
email: 'johnson@example.com',
- courses: [
- DummyCourses.courses[1],
- DummyCourses.courses[6],
- DummyCourses.courses[11]
- ],
+ courses: [DummyCourses.courses[1], DummyCourses.courses[6], DummyCourses.courses[11]],
cabinNumber: 'C-102',
department: 'Mechanical Engineering',
),
@@ -445,11 +442,7 @@ class DummyFaculties {
id: '3',
name: 'Dr. Brown',
email: 'brown@example.com',
- courses: [
- DummyCourses.courses[2],
- DummyCourses.courses[7],
- DummyCourses.courses[12]
- ],
+ courses: [DummyCourses.courses[2], DummyCourses.courses[7], DummyCourses.courses[12]],
cabinNumber: 'C-103',
department: 'Electrical Engineering',
),
@@ -457,187 +450,119 @@ class DummyFaculties {
id: '4',
name: 'Prof. Davis',
email: 'davis@example.com',
- courses: [
- DummyCourses.courses[3],
- DummyCourses.courses[8],
- DummyCourses.courses[13]
- ],
+ courses: [DummyCourses.courses[3], DummyCourses.courses[8], DummyCourses.courses[13]],
cabinNumber: 'C-104',
department: 'Civil Engineering'),
Faculty(
id: '5',
name: 'Dr. Wilson',
email: 'wilson@example.com',
- courses: [
- DummyCourses.courses[4],
- DummyCourses.courses[9],
- DummyCourses.courses[14]
- ],
+ courses: [DummyCourses.courses[4], DummyCourses.courses[9], DummyCourses.courses[14]],
cabinNumber: 'C-105',
department: 'Chemical Engineering'),
Faculty(
id: '6',
name: 'Prof. Miller',
email: 'miller@example.com',
- courses: [
- DummyCourses.courses[0],
- DummyCourses.courses[5],
- DummyCourses.courses[10]
- ],
+ courses: [DummyCourses.courses[0], DummyCourses.courses[5], DummyCourses.courses[10]],
cabinNumber: 'C-106',
department: 'Biotechnology'),
Faculty(
id: '7',
name: 'Dr. Turner',
email: 'turner@example.com',
- courses: [
- DummyCourses.courses[1],
- DummyCourses.courses[6],
- DummyCourses.courses[11]
- ],
+ courses: [DummyCourses.courses[1], DummyCourses.courses[6], DummyCourses.courses[11]],
cabinNumber: 'C-107',
department: 'Aerospace Engineering'),
Faculty(
id: '8',
name: 'Prof. Clark',
email: 'clark@example.com',
- courses: [
- DummyCourses.courses[2],
- DummyCourses.courses[7],
- DummyCourses.courses[12]
- ],
+ courses: [DummyCourses.courses[2], DummyCourses.courses[7], DummyCourses.courses[12]],
cabinNumber: 'C-108',
department: 'Information Technology'),
Faculty(
id: '9',
name: 'Dr. Harris',
email: 'harris@example.com',
- courses: [
- DummyCourses.courses[3],
- DummyCourses.courses[8],
- DummyCourses.courses[13]
- ],
+ courses: [DummyCourses.courses[3], DummyCourses.courses[8], DummyCourses.courses[13]],
cabinNumber: 'C-109',
department: 'Mechatronics'),
Faculty(
id: '10',
name: 'Prof. Turner',
email: 'turner@example.com',
- courses: [
- DummyCourses.courses[4],
- DummyCourses.courses[9],
- DummyCourses.courses[14]
- ],
+ courses: [DummyCourses.courses[4], DummyCourses.courses[9], DummyCourses.courses[14]],
cabinNumber: 'C-110',
department: 'Robotics Engineering'),
Faculty(
id: '11',
name: 'Dr. White',
email: 'white@example.com',
- courses: [
- DummyCourses.courses[0],
- DummyCourses.courses[5],
- DummyCourses.courses[10]
- ],
+ courses: [DummyCourses.courses[0], DummyCourses.courses[5], DummyCourses.courses[10]],
cabinNumber: 'D-101',
department: 'Industrial Engineering'),
Faculty(
id: '12',
name: 'Prof. Allen',
email: 'allen@example.com',
- courses: [
- DummyCourses.courses[1],
- DummyCourses.courses[6],
- DummyCourses.courses[11]
- ],
+ courses: [DummyCourses.courses[1], DummyCourses.courses[6], DummyCourses.courses[11]],
cabinNumber: 'D-102',
department: 'Computer Engineering'),
Faculty(
id: '13',
name: 'Dr. Young',
email: 'young@example.com',
- courses: [
- DummyCourses.courses[2],
- DummyCourses.courses[7],
- DummyCourses.courses[12]
- ],
+ courses: [DummyCourses.courses[2], DummyCourses.courses[7], DummyCourses.courses[12]],
cabinNumber: 'D-103',
department: 'Software Engineering'),
Faculty(
id: '14',
name: 'Prof. Walker',
email: 'walker@example.com',
- courses: [
- DummyCourses.courses[3],
- DummyCourses.courses[8],
- DummyCourses.courses[13]
- ],
+ courses: [DummyCourses.courses[3], DummyCourses.courses[8], DummyCourses.courses[13]],
cabinNumber: 'D-104',
department: 'Environmental Engineering'),
Faculty(
id: '15',
name: 'Dr. Lee',
email: 'lee@example.com',
- courses: [
- DummyCourses.courses[4],
- DummyCourses.courses[9],
- DummyCourses.courses[14]
- ],
+ courses: [DummyCourses.courses[4], DummyCourses.courses[9], DummyCourses.courses[14]],
cabinNumber: 'D-105',
department: 'Petrolesum[ Engineer]ing'),
Faculty(
id: '16',
name: 'Prof. Hall',
email: 'hall@example.com',
- courses: [
- DummyCourses.courses[0],
- DummyCourses.courses[5],
- DummyCourses.courses[10]
- ],
+ courses: [DummyCourses.courses[0], DummyCourses.courses[5], DummyCourses.courses[10]],
cabinNumber: 'D-106',
department: 'Nuclear Engineering'),
Faculty(
id: '17',
name: 'Dr. Miller',
email: 'miller@example.com',
- courses: [
- DummyCourses.courses[1],
- DummyCourses.courses[6],
- DummyCourses.courses[11]
- ],
+ courses: [DummyCourses.courses[1], DummyCourses.courses[6], DummyCourses.courses[11]],
cabinNumber: 'D-107',
department: 'Biomedical Engineering'),
Faculty(
id: '18',
name: 'Prof. Baker',
email: 'baker@example.com',
- courses: [
- DummyCourses.courses[2],
- DummyCourses.courses[7],
- DummyCourses.courses[12]
- ],
+ courses: [DummyCourses.courses[2], DummyCourses.courses[7], DummyCourses.courses[12]],
cabinNumber: 'D-108',
department: 'Chemical Engineering'),
Faculty(
id: '19',
name: 'Dr. Turner',
email: 'turner@example.com',
- courses: [
- DummyCourses.courses[3],
- DummyCourses.courses[8],
- DummyCourses.courses[13]
- ],
+ courses: [DummyCourses.courses[3], DummyCourses.courses[8], DummyCourses.courses[13]],
cabinNumber: 'D-109',
department: 'Electronics Engineering'),
Faculty(
id: '20',
name: 'Prof. Smith',
email: 'smith@example.com',
- courses: [
- DummyCourses.courses[4],
- DummyCourses.courses[9],
- DummyCourses.courses[14]
- ],
+ courses: [DummyCourses.courses[4], DummyCourses.courses[9], DummyCourses.courses[14]],
cabinNumber: 'D-110',
department: 'Computer Science'),
];
@@ -889,6 +814,7 @@ class DummyMenus {
}
class DummyRooms {
+ //static List rooms = [];
static List rooms = [
Room(id: '1', name: 'Auditorium', vacant: true),
Room(id: '2', name: 'Classroom 101', vacant: false, occupantId: 'T001'),
@@ -908,15 +834,86 @@ class DummyRooms {
Room(id: '16', name: 'Outdoor Sports Arena', vacant: true),
Room(id: '17', name: 'Medical Clinic', vacant: false, occupantId: 'S004'),
Room(id: '18', name: 'Music Room', vacant: true),
- Room(
- id: '19',
- name: 'Student Council Office',
- vacant: false,
- occupantId: 'T005'),
+ Room(id: '19', name: 'Student Council Office', vacant: false, occupantId: 'T005'),
Room(id: '20', name: 'Virtual Reality Lab', vacant: true),
];
}
+class DummyLostAndFound {
+ static List lostAndFoundItems = [
+ LostAndFoundItem(
+ name: 'Laptop',
+ description: 'Black Dell laptop with a sticker on the back',
+ lastSeenLocation: 'Library',
+ contactNumber: '+91 1234567890',
+ isLost: false,
+ ),
+ LostAndFoundItem(
+ name: 'Mobile Phone',
+ description: 'White iPhone 12 with a black case',
+ lastSeenLocation: 'Cafeteria',
+ contactNumber: '+91 9876543210',
+ isLost: true,
+ ),
+ LostAndFoundItem(
+ name: 'Water Bottle',
+ description: 'Blue steel water bottle with a dent on the bottom',
+ lastSeenLocation: 'Gymnasium',
+ contactNumber: '+91 4567890123',
+ isLost: false,
+ ),
+ LostAndFoundItem(
+ name: 'Backpack',
+ description: 'Red and black backpack with a broken zipper',
+ lastSeenLocation: 'Auditorium',
+ contactNumber: '+91 7890123456',
+ isLost: true,
+ ),
+ LostAndFoundItem(
+ name: 'Watch',
+ description: 'Silver wristwatch with a black leather strap',
+ lastSeenLocation: 'Classroom 101',
+ contactNumber: '+91 2345678901',
+ isLost: false,
+ ),
+ LostAndFoundItem(
+ name: 'Umbrella',
+ description: 'Green and white striped umbrella with a broken handle',
+ lastSeenLocation: 'Student Lounge',
+ contactNumber: '+91 8901234567',
+ isLost: true,
+ ),
+ LostAndFoundItem(
+ name: 'Sunglasses',
+ description: 'Black aviator sunglasses with a scratch on the left lens',
+ lastSeenLocation: 'Cafeteria',
+ contactNumber: '+91 3456789012',
+ isLost: false,
+ ),
+ LostAndFoundItem(
+ name: 'Wallet',
+ description: 'Brown leather wallet with a broken zipper',
+ lastSeenLocation: 'Library',
+ contactNumber: '+91 9012345678',
+ isLost: true,
+ ),
+ LostAndFoundItem(
+ name: 'Headphones',
+ description: 'Black over-ear headphones with a missing ear cushion',
+ lastSeenLocation: 'Auditorium',
+ contactNumber: '+91 6789012345',
+ isLost: false,
+ ),
+ LostAndFoundItem(
+ name: 'Jacket',
+ description: 'Blue denim jacket with a tear on the left sleeve',
+ lastSeenLocation: 'Gymnasium',
+ contactNumber: '+91 5678901234',
+ isLost: true,
+ ),
+ ];
+}
+
class DummySkills {
static List skills = [
Skill(id: '1', name: 'Programming', level: 5),
@@ -1014,15 +1011,13 @@ class DummyAchievements {
id: '12',
name: 'Training and Development',
date: DateTime(2022, 4, 22),
- description:
- 'Contributed significantly to employee training and development.',
+ description: 'Contributed significantly to employee training and development.',
),
Achievement(
id: '13',
name: 'Quality Assurance Recognition',
date: DateTime(2023, 2, 14),
- description:
- 'Acknowledged for ensuring high-quality standards in projects.',
+ description: 'Acknowledged for ensuring high-quality standards in projects.',
),
Achievement(
id: '14',
@@ -1058,16 +1053,14 @@ class DummyAchievements {
id: '19',
name: 'Health and Wellness Initiative',
date: DateTime(2023, 9, 10),
- description:
- 'Led initiatives to promote health and wellness in the workplace.',
+ description: 'Led initiatives to promote health and wellness in the workplace.',
),
Achievement(
id: '20',
name: 'Public Speaking Achievement',
date: DateTime(2022, 1, 30),
- description:
- 'Received acclaim for public speaking skills at a conference.',
+ description: 'Received acclaim for public speaking skills at a conference.',
),
];
- // You can use the dummyEntries list as needed in your application
+// You can use the dummyEntries list as needed in your application
}
diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart
index e13fae9..77efd40 100644
--- a/frontend/lib/main.dart
+++ b/frontend/lib/main.dart
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:smart_insti_app/constants/constants.dart';
import 'package:smart_insti_app/routes/routes.dart';
-void main() {
+Future main() async {
+ await dotenv.load(fileName: ".env");
runApp(const ProviderScope(child: SmartInstiApp()));
}
diff --git a/frontend/lib/models/lost_and_found.dart b/frontend/lib/models/lost_and_found_item.dart
similarity index 81%
rename from frontend/lib/models/lost_and_found.dart
rename to frontend/lib/models/lost_and_found_item.dart
index 64a01ad..38a9ef1 100644
--- a/frontend/lib/models/lost_and_found.dart
+++ b/frontend/lib/models/lost_and_found_item.dart
@@ -1,43 +1,44 @@
+// This class is used to create an object of Lost and Found item
+
class LostAndFoundItem {
- String userId;
+ String? id;
String name;
String lastSeenLocation;
- String imagePath;
+ String? imagePath;
String description;
String contactNumber;
bool isLost;
LostAndFoundItem({
- required this.userId,
+ this.id,
required this.name,
required this.lastSeenLocation,
- required this.imagePath,
+ this.imagePath,
required this.description,
- required this.contactNumber,
required this.isLost,
+ required this.contactNumber,
});
factory LostAndFoundItem.fromJson(Map json) {
return LostAndFoundItem(
- userId: json['user_id'],
+ id: json['_id'],
name: json['name'],
lastSeenLocation: json['lastSeenLocation'],
imagePath: json['imagePath'],
description: json['description'],
- contactNumber: json['contactNumber'],
isLost: json['isLost'],
+ contactNumber: json['contactNumber'],
);
}
Map toJson() {
return {
- 'user_id': userId,
'name': name,
'lastSeenLocation': lastSeenLocation,
'imagePath': imagePath,
'description': description,
- 'contactNumber': contactNumber,
'isLost': isLost,
+ 'contact': contactNumber,
};
}
}
diff --git a/frontend/lib/models/room.dart b/frontend/lib/models/room.dart
index 105ee48..ea40e55 100644
--- a/frontend/lib/models/room.dart
+++ b/frontend/lib/models/room.dart
@@ -1,14 +1,14 @@
class Room {
- Room({this.occupantId, this.id, required this.name, this.vacant});
+ Room({this.occupantId, this.id, required this.name, this.vacant = true});
final String? id;
final String name;
- final bool? vacant;
+ final bool vacant;
final String? occupantId;
factory Room.fromJson(Map json) {
return Room(
- id: json['id'],
+ id: json['_id'],
name: json['name'],
vacant: json['vacant'],
occupantId: json['occupantId'],
@@ -17,7 +17,6 @@ class Room {
Map toJson() {
return {
- 'id': id,
'name': name,
'vacant': vacant,
'occupantId': occupantId,
diff --git a/frontend/lib/models/user.dart b/frontend/lib/models/user.dart
new file mode 100644
index 0000000..c858ff1
--- /dev/null
+++ b/frontend/lib/models/user.dart
@@ -0,0 +1,6 @@
+class User {
+ String email;
+ String otp;
+
+ User({required this.email, required this.otp});
+}
diff --git a/frontend/lib/provider/lost_and_found_provider.dart b/frontend/lib/provider/lost_and_found_provider.dart
new file mode 100644
index 0000000..3b3968b
--- /dev/null
+++ b/frontend/lib/provider/lost_and_found_provider.dart
@@ -0,0 +1,181 @@
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:image_cropper/image_cropper.dart';
+import 'package:image_picker/image_picker.dart';
+import 'package:logger/logger.dart';
+import 'package:smart_insti_app/constants/dummy_entries.dart';
+import 'package:smart_insti_app/models/lost_and_found_item.dart';
+import 'package:smart_insti_app/repositories/lost_and_found_repository.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+import 'dart:io';
+import '../constants/constants.dart';
+
+final lostAndFoundProvider =
+ StateNotifierProvider((ref) => LostAndFoundStateNotifier(ref));
+
+class LostAndFoundState {
+ final List lostAndFoundItemList;
+ final List lostAndFoundImageList;
+ final TextEditingController itemNameController;
+ final TextEditingController itemDescriptionController;
+ final TextEditingController lastSeenLocationController;
+ final TextEditingController searchLostAndFoundController;
+ final TextEditingController contactNumberController;
+ final String listingStatus;
+ final File? selectedImage;
+ final LoadingState loadingState;
+
+ LostAndFoundState({
+ required this.lostAndFoundItemList,
+ required this.lostAndFoundImageList,
+ required this.itemNameController,
+ required this.itemDescriptionController,
+ required this.lastSeenLocationController,
+ required this.searchLostAndFoundController,
+ required this.contactNumberController,
+ required this.listingStatus,
+ this.selectedImage,
+ required this.loadingState,
+ });
+
+ LostAndFoundState copyWith({
+ List? lostAndFoundItemList,
+ List? lostAndFoundImageList,
+ TextEditingController? itemNameController,
+ TextEditingController? itemDescriptionController,
+ TextEditingController? lastSeenLocationController,
+ TextEditingController? searchLostAndFoundController,
+ TextEditingController? contactNumberController,
+ String? listingStatus,
+ File? selectedImage,
+ LoadingState? loadingState,
+ }) {
+ return LostAndFoundState(
+ lostAndFoundItemList: lostAndFoundItemList ?? this.lostAndFoundItemList,
+ lostAndFoundImageList: lostAndFoundImageList ?? this.lostAndFoundImageList,
+ itemNameController: itemNameController ?? this.itemNameController,
+ itemDescriptionController: itemDescriptionController ?? this.itemDescriptionController,
+ lastSeenLocationController: lastSeenLocationController ?? this.lastSeenLocationController,
+ searchLostAndFoundController: searchLostAndFoundController ?? this.searchLostAndFoundController,
+ contactNumberController: contactNumberController ?? this.contactNumberController,
+ listingStatus: listingStatus ?? this.listingStatus,
+ selectedImage: selectedImage,
+ loadingState: loadingState ?? this.loadingState,
+ );
+ }
+}
+
+class LostAndFoundStateNotifier extends StateNotifier {
+ LostAndFoundStateNotifier(Ref ref)
+ : _api = ref.read(lostAndFoundRepositoryProvider),
+ super(
+ LostAndFoundState(
+ lostAndFoundItemList: DummyLostAndFound.lostAndFoundItems,
+ lostAndFoundImageList: [],
+ itemNameController: TextEditingController(),
+ itemDescriptionController: TextEditingController(),
+ lastSeenLocationController: TextEditingController(),
+ searchLostAndFoundController: TextEditingController(),
+ contactNumberController: TextEditingController(),
+ listingStatus: LostAndFoundConstants.lostState,
+ selectedImage: null,
+ loadingState: LoadingState.progress,
+ ),
+ ) {
+ loadItems();
+ }
+
+ final LostAndFoundRepository _api;
+
+ final Logger _logger = Logger();
+
+ void addItem() {
+ final LostAndFoundItem item = LostAndFoundItem(
+ name: state.itemNameController.text,
+ lastSeenLocation: state.lastSeenLocationController.text,
+ imagePath: state.selectedImage?.path,
+ description: state.itemDescriptionController.text,
+ isLost: state.listingStatus == LostAndFoundConstants.lostState,
+ contactNumber: state.contactNumberController.text,
+ );
+ state = state.copyWith(loadingState: LoadingState.progress);
+ _api.addLostAndFoundItem(item);
+ loadItems();
+ }
+
+ launchCaller(String number) async {
+ final url = "tel:$number";
+ if (await canLaunchUrlString(url)) {
+ await launch(url);
+ } else {
+ throw 'Could not launch $url';
+ }
+ }
+
+ void updateListingStatus(String status) {
+ state = state.copyWith(listingStatus: status);
+ }
+
+ void clearControllers() {
+ state.itemNameController.clear();
+ state.itemDescriptionController.clear();
+ state.lastSeenLocationController.clear();
+ state.contactNumberController.clear();
+ state = state.copyWith(selectedImage: null);
+ }
+
+ Future _cropImage(XFile image) {
+ return ImageCropper().cropImage(
+ sourcePath: image.path,
+ aspectRatio: const CropAspectRatio(ratioX: 4, ratioY: 3),
+ compressQuality: 100,
+ compressFormat: ImageCompressFormat.jpg,
+ uiSettings: [
+ AndroidUiSettings(
+ toolbarTitle: 'Lost and Found',
+ toolbarColor: Colors.tealAccent,
+ toolbarWidgetColor: Colors.white,
+ initAspectRatio: CropAspectRatioPreset.original,
+ lockAspectRatio: true,
+ ),
+ ],
+ );
+ }
+
+ void pickImageFromCamera() async {
+ final pickedFile = await ImagePicker().pickImage(source: ImageSource.camera);
+
+ if (pickedFile != null) {
+ CroppedFile? file = await _cropImage(pickedFile);
+ state = state.copyWith(selectedImage: File(file!.path));
+ _logger.d(file.path);
+ }
+ }
+
+ void pickImageFromGallery() async {
+ final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery);
+
+ if (pickedFile != null) {
+ CroppedFile? file = await _cropImage(pickedFile);
+ state = state.copyWith(selectedImage: File(file!.path));
+ _logger.d(file.path);
+ }
+ _logger.d(pickedFile?.path);
+ }
+
+ void resetImageSelection() {
+ state = state.copyWith(selectedImage: null);
+ _logger.d('Image selection reset');
+ }
+
+ void loadItems() async {
+ final items = await _api.lostAndFoundItems();
+ state = state.copyWith(lostAndFoundItemList: items, loadingState: LoadingState.success);
+ }
+
+ Image imageFromBase64String(String base64String) {
+ return Image.memory(base64Decode(base64String));
+ }
+}
diff --git a/frontend/lib/provider/room_provider.dart b/frontend/lib/provider/room_provider.dart
index 322f37a..a189feb 100644
--- a/frontend/lib/provider/room_provider.dart
+++ b/frontend/lib/provider/room_provider.dart
@@ -8,46 +8,58 @@ import 'package:smart_insti_app/constants/dummy_entries.dart';
import 'package:smart_insti_app/components/menu_tile.dart';
import 'package:smart_insti_app/models/room.dart';
import 'dart:io';
+import '../constants/constants.dart';
+import '../repositories/room_repository.dart';
-final roomProvider = StateNotifierProvider((ref) => RoomProvider());
+final roomProvider = StateNotifierProvider((ref) => RoomProvider(ref));
class RoomState {
final List roomList;
final List roomTiles;
final TextEditingController searchRoomController;
final TextEditingController roomNameController;
+ final LoadingState loadingState;
- RoomState({
- required this.roomList,
- required this.roomTiles,
- required this.searchRoomController,
- required this.roomNameController,
- });
+ RoomState(
+ {required this.roomList,
+ required this.roomTiles,
+ required this.searchRoomController,
+ required this.roomNameController,
+ required this.loadingState});
RoomState copyWith({
List? roomList,
List? roomTiles,
TextEditingController? searchRoomController,
TextEditingController? roomNameController,
+ LoadingState? loadingState,
}) {
return RoomState(
roomList: roomList ?? this.roomList,
roomTiles: roomTiles ?? this.roomTiles,
searchRoomController: searchRoomController ?? this.searchRoomController,
roomNameController: roomNameController ?? this.roomNameController,
+ loadingState: loadingState ?? this.loadingState,
);
}
}
class RoomProvider extends StateNotifier {
- RoomProvider()
- : super(RoomState(
- roomList: DummyRooms.rooms,
- roomTiles: [],
- searchRoomController: TextEditingController(),
- roomNameController: TextEditingController(),
- ));
+ RoomProvider(Ref ref)
+ : _api = ref.read(roomRepositoryProvider),
+ super(
+ RoomState(
+ roomList: DummyRooms.rooms,
+ roomTiles: [],
+ searchRoomController: TextEditingController(),
+ roomNameController: TextEditingController(),
+ loadingState: LoadingState.progress,
+ ),
+ ) {
+ loadRooms();
+ }
+ final RoomRepository _api;
final Logger _logger = Logger();
void pickSpreadsheet() async {
@@ -70,6 +82,16 @@ class RoomProvider extends StateNotifier {
}
}
+ int getVacantCount() {
+ int vacantCount = 0;
+ for (Room room in state.roomList) {
+ if (room.vacant) {
+ vacantCount++;
+ }
+ }
+ return vacantCount;
+ }
+
void addRoom() {
final newState = state.copyWith(
roomList: [
@@ -82,7 +104,23 @@ class RoomProvider extends StateNotifier {
_logger.i("Added room: ${state.roomNameController.text}");
}
- void buildRoomTiles(BuildContext context) {
+ Future loadRooms() async {
+ final rooms = await _api.getRooms();
+ final newState = state.copyWith(roomList: rooms, loadingState: LoadingState.success);
+ state = newState;
+ }
+
+ Future reserveRoom(Room room) async {
+ state = state.copyWith(
+ loadingState: LoadingState.progress,
+ );
+
+ await _api.reserveRoom(room.id!, '12345');
+
+ await loadRooms();
+ }
+
+ void buildRoomTiles(BuildContext context) async {
final roomTiles = [];
for (Room room in state.roomList) {
roomTiles.add(
@@ -119,9 +157,9 @@ class RoomProvider extends StateNotifier {
)
],
),
- ),
- ),
),
+ ),
+ ),
icon: Icons.add,
primaryColor: Colors.grey.shade200,
secondaryColor: Colors.grey.shade300,
diff --git a/frontend/lib/repositories/lost_and_found_repository.dart b/frontend/lib/repositories/lost_and_found_repository.dart
new file mode 100644
index 0000000..ac04aab
--- /dev/null
+++ b/frontend/lib/repositories/lost_and_found_repository.dart
@@ -0,0 +1,56 @@
+import 'package:dio/dio.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:logger/logger.dart';
+import 'package:smart_insti_app/constants/dummy_entries.dart';
+import 'package:smart_insti_app/models/lost_and_found_item.dart';
+
+final lostAndFoundRepositoryProvider = Provider((_) => LostAndFoundRepository());
+
+class LostAndFoundRepository {
+ final _client = Dio(
+ BaseOptions(
+ baseUrl: dotenv.env['BACKEND_DOMAIN']!,
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ ),
+ );
+
+ Future> lostAndFoundItems() async {
+ try {
+ final response = await _client.get('/lost-and-found');
+ List items = [];
+ for (var item in response.data) {
+ items.add(LostAndFoundItem.fromJson(item));
+ }
+ return items;
+ } catch (e) {
+ Logger().e(e);
+ return DummyLostAndFound.lostAndFoundItems;
+ }
+ }
+
+ Future addLostAndFoundItem(LostAndFoundItem item) async {
+ try {
+ String? fileName = item.imagePath?.split('/').last;
+ FormData formData = FormData.fromMap({
+ "name": item.name,
+ "lastSeenLocation": item.lastSeenLocation,
+ "description": item.description,
+ "contactNumber": item.contactNumber,
+ "isLost": item.isLost,
+ "image": item.imagePath != null
+ ? await MultipartFile.fromFile(
+ item.imagePath!,
+ filename: fileName,
+ )
+ : null,
+ });
+ final response = await _client.post('/lost-and-found', data: formData);
+ Logger().w(response.data);
+ } catch (e) {
+ Logger().e(e);
+ }
+ }
+}
diff --git a/frontend/lib/repositories/room_repository.dart b/frontend/lib/repositories/room_repository.dart
new file mode 100644
index 0000000..e14a1a7
--- /dev/null
+++ b/frontend/lib/repositories/room_repository.dart
@@ -0,0 +1,47 @@
+import 'package:dio/dio.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:logger/logger.dart';
+import 'package:smart_insti_app/constants/dummy_entries.dart';
+import '../models/room.dart';
+
+final roomRepositoryProvider = Provider((_) => RoomRepository());
+
+class RoomRepository {
+ final _client = Dio(
+ BaseOptions(
+ baseUrl: dotenv.env['BACKEND_DOMAIN']!,
+ ),
+ );
+
+ Future> getRooms() async {
+ try {
+ final response = await _client.get('/rooms');
+ List rooms = [];
+ for (var room in response.data) {
+ rooms.add(Room.fromJson(room));
+ }
+ return rooms;
+ } catch (e) {
+ return DummyRooms.rooms;
+ }
+ }
+
+ Future reserveRoom(String roomId, String occupantId) async {
+ try {
+ final response = await _client.put('/room/$roomId', data: {'occupantId': occupantId});
+ Logger().i(response.data);
+ } catch (e) {
+ Logger().e(e);
+ }
+ }
+
+ void addRoom(Room room) async {
+ try {
+ final response = await _client.post('/rooms', data: room.toJson());
+ Logger().i(response.data);
+ } catch (e) {
+ Logger().e(e);
+ }
+ }
+}
diff --git a/frontend/lib/routes/routes.dart b/frontend/lib/routes/routes.dart
index 4ba1694..e909319 100644
--- a/frontend/lib/routes/routes.dart
+++ b/frontend/lib/routes/routes.dart
@@ -7,6 +7,8 @@ import 'package:smart_insti_app/screens/admin/manage_rooms.dart';
import 'package:smart_insti_app/screens/admin/view_students.dart';
import 'package:smart_insti_app/screens/auth/login_general.dart';
import 'package:smart_insti_app/screens/loading_page.dart';
+import 'package:smart_insti_app/screens/home.dart';
+import 'package:smart_insti_app/screens/lost_and_found.dart';
import '../screens/admin/add_faculty.dart';
import '../screens/admin/add_menu.dart';
import '../screens/admin/admin_profile.dart';
@@ -14,8 +16,10 @@ import '../screens/admin/view_courses.dart';
import '../screens/admin/view_faculty.dart';
import '../screens/admin/view_menu.dart';
import '../screens/auth/admin_login.dart';
+import '../screens/room_vacancy.dart';
final GoRouter routes = GoRouter(
+ initialLocation: '/home',
routes: [
GoRoute(
path: '/',
@@ -23,11 +27,13 @@ final GoRouter routes = GoRouter(
),
GoRoute(
path: '/login',
- pageBuilder: (context, state) => const MaterialPage(child: GeneralLogin()),
+ pageBuilder: (context, state) =>
+ const MaterialPage(child: GeneralLogin()),
routes: [
GoRoute(
path: 'admin_login',
- pageBuilder: (context, state) => const MaterialPage(child: AdminLogin()),
+ pageBuilder: (context, state) =>
+ const MaterialPage(child: AdminLogin()),
),
],
),
@@ -37,7 +43,8 @@ final GoRouter routes = GoRouter(
routes: [
GoRoute(
path: 'profile',
- pageBuilder: (context, state) => const MaterialPage(child: AdminProfile()),
+ pageBuilder: (context, state) =>
+ const MaterialPage(child: AdminProfile()),
),
GoRoute(
path: 'add_students',
@@ -77,5 +84,20 @@ final GoRouter routes = GoRouter(
)
],
),
+ GoRoute(
+ path: '/home',
+ pageBuilder: (context, state) => const MaterialPage(child: Home()),
+ routes: [
+ GoRoute(
+ path: 'classroom_vacancy',
+ pageBuilder: (context, state) =>
+ const MaterialPage(child: RoomVacancy()),
+ ),
+ GoRoute(
+ path: 'lost_and_found',
+ pageBuilder: (context, state) => MaterialPage(child: LostAndFound()),
+ ),
+ ],
+ ),
],
);
diff --git a/frontend/lib/screens/auth/signin_page.dart b/frontend/lib/screens/auth/signin_page.dart
new file mode 100644
index 0000000..e7ed415
--- /dev/null
+++ b/frontend/lib/screens/auth/signin_page.dart
@@ -0,0 +1,160 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import '../../components/snackbar.dart';
+import '../../services/auth/auth_service.dart';
+
+class SignIn extends StatefulWidget {
+ @override
+ _SignInState createState() => _SignInState();
+}
+
+class _SignInState extends State {
+ bool showOTPFields = false;
+ final emailRegex = RegExp(r'^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+');
+ final emailController = TextEditingController();
+ final otpController1 = TextEditingController();
+ final otpController2 = TextEditingController();
+ final otpController3 = TextEditingController();
+ final otpController4 = TextEditingController();
+
+ final focusNode1 = FocusNode();
+ final focusNode2 = FocusNode();
+ final focusNode3 = FocusNode();
+ final focusNode4 = FocusNode();
+
+ final authService = AuthService(); // Create an instance of AuthService
+
+ @override
+ void initState() {
+ super.initState();
+
+ // When the text in the first OTPBox changes, request focus for the second OTPBox
+ otpController1.addListener(() {
+ if (otpController1.text.length >= 1) {
+ FocusScope.of(context).requestFocus(focusNode2);
+ }
+ });
+
+ // Do the same for the other OTPBoxes
+ otpController2.addListener(() {
+ if (otpController2.text.length >= 1) {
+ FocusScope.of(context).requestFocus(focusNode3);
+ }
+ });
+
+ otpController3.addListener(() {
+ if (otpController3.text.length >= 1) {
+ FocusScope.of(context).requestFocus(focusNode4);
+ }
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Login'),
+ ),
+ body: Padding(
+ padding: EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ TextField(
+ controller: emailController,
+ decoration: InputDecoration(
+ labelText: 'Email',
+ ),
+ ),
+ SizedBox(height: 16.0),
+ ElevatedButton(
+ onPressed: () {
+ // Call sendOtp when the button is clicked
+ if (emailRegex.hasMatch(emailController.text)) {
+ // If the email is valid, set the state
+ setState(() {
+ showOTPFields = true;
+ });
+ authService.sendOTP(
+ context: context,
+ email: emailController.text,
+ );
+ } else {
+ // If the email is not valid, show a SnackBar with an error message
+ showSnackBar(
+ context,
+ 'Please enter a valid email',
+ );
+ }
+ },
+ child: Text('Send OTP'),
+ ),
+ if (showOTPFields) ...[
+ SizedBox(height: 16.0),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ OTPBox(controller: otpController1, focusNode: focusNode1),
+ OTPBox(controller: otpController2, focusNode: focusNode2),
+ OTPBox(controller: otpController3, focusNode: focusNode3),
+ OTPBox(controller: otpController4, focusNode: focusNode4),
+ ],
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ final otp = otpController1.text +
+ otpController2.text +
+ otpController3.text +
+ otpController4.text;
+ print('OTP: $otp'); // Print the OTP
+ // Call verifyOTP
+ final isVerified =
+ await authService.verifyOTP(emailController.text, otp);
+
+ if (isVerified) {
+ // Navigate to the admin home page
+ context.go('/admin_home');
+ } else {
+ showSnackBar(
+ context,
+ 'Incorrect OTP',
+ );
+ }
+ },
+ child: Text('Verify OTP'),
+ ),
+ ],
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class OTPBox extends StatelessWidget {
+ final TextEditingController controller;
+ final FocusNode focusNode;
+ OTPBox({required this.controller, required this.focusNode});
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: 50.0,
+ height: 50.0,
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.grey),
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ child: TextField(
+ controller: controller,
+ focusNode: focusNode,
+ keyboardType: TextInputType.number,
+ maxLength: 1,
+ textAlign: TextAlign.center,
+ decoration: InputDecoration(
+ counterText: '',
+ border: InputBorder.none,
+ ),
+ ),
+ );
+ }
+}
diff --git a/frontend/lib/screens/home.dart b/frontend/lib/screens/home.dart
index 2b8b54a..87fb18d 100644
--- a/frontend/lib/screens/home.dart
+++ b/frontend/lib/screens/home.dart
@@ -1,19 +1,49 @@
import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+import 'package:smart_insti_app/components/menu_tile.dart';
import '../constants/constants.dart';
+import '../provider/room_provider.dart';
-class Home extends StatelessWidget {
+class Home extends ConsumerWidget {
const Home({super.key});
@override
- Widget build(BuildContext context) {
+ Widget build(BuildContext context, WidgetRef ref) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text(AppConstants.appName),
backgroundColor: Colors.lightBlueAccent,
),
- body: const Center(
- child: Text(AppConstants.appName),
+ body: GridView.count(
+ padding: const EdgeInsets.all(10),
+ crossAxisCount: 2,
+ children: [
+ MenuTile(
+ title: 'Room\nVacancy',
+ onTap: () => context.push('/home/classroom_vacancy'),
+ body: [
+ const SizedBox(height: 5),
+ Consumer(
+ builder: (_, ref, __) => Text(
+ '${ref.read(roomProvider.notifier).getVacantCount()} Vacant',
+ textAlign: TextAlign.center,
+ style: const TextStyle(fontSize: 14),
+ ),
+ ),
+ ],
+ icon: Icons.class_,
+ primaryColor: Colors.lightBlueAccent.shade100,
+ secondaryColor: Colors.lightBlueAccent.shade200,
+ ),
+ MenuTile(
+ title: "Lost\n&\nFound",
+ onTap: () => context.push('/home/lost_and_found'),
+ primaryColor: Colors.orangeAccent.shade100,
+ secondaryColor: Colors.orangeAccent.shade200,
+ icon: Icons.search),
+ ],
),
),
);
diff --git a/frontend/lib/screens/lost_and_found.dart b/frontend/lib/screens/lost_and_found.dart
new file mode 100644
index 0000000..7a085a8
--- /dev/null
+++ b/frontend/lib/screens/lost_and_found.dart
@@ -0,0 +1,312 @@
+import 'package:animated_toggle_switch/animated_toggle_switch.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+import 'package:responsive_framework/responsive_framework.dart';
+import 'package:smart_insti_app/components/borderless_button.dart';
+import 'package:smart_insti_app/components/material_textformfield.dart';
+import 'package:smart_insti_app/provider/lost_and_found_provider.dart';
+import '../components/image_tile.dart';
+import '../constants/constants.dart';
+
+class LostAndFound extends ConsumerWidget {
+ LostAndFound({super.key});
+
+ final _formKey = GlobalKey();
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ return ResponsiveScaledBox(
+ width: 411,
+ child: Scaffold(
+ appBar: AppBar(
+ title: const Text('Lost & Found'),
+ actions: [
+ BorderlessButton(
+ backgroundColor: Colors.greenAccent.shade100,
+ splashColor: Colors.green.shade700,
+ onPressed: () => showDialog(
+ context: context,
+ builder: (context) => Dialog(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: SingleChildScrollView(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Text(
+ 'Add Listing',
+ style: TextStyle(fontSize: 30),
+ ),
+ const SizedBox(height: 20),
+ Consumer(
+ builder: (_, ref, __) {
+ if (ref.watch(lostAndFoundProvider).selectedImage == null) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ ElevatedButton(
+ onPressed: () => ref.read(lostAndFoundProvider.notifier).pickImageFromCamera(),
+ style: ButtonStyle(
+ shape: MaterialStateProperty.all(
+ RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ ),
+ child: const Icon(Icons.camera_alt),
+ ),
+ const Text("OR"),
+ ElevatedButton(
+ onPressed: () => ref.watch(lostAndFoundProvider.notifier).pickImageFromGallery(),
+ style: ButtonStyle(
+ shape: MaterialStateProperty.all(
+ RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ ),
+ child: const Icon(Icons.photo),
+ ),
+ ],
+ );
+ } else {
+ return GestureDetector(
+ onTap: () {
+ showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Text("Remove image?"),
+ const SizedBox(height: 20),
+ BorderlessButton(
+ onPressed: () {
+ ref.read(lostAndFoundProvider.notifier).resetImageSelection();
+ Navigator.pop(context);
+ },
+ backgroundColor: Colors.redAccent.shade100.withOpacity(0.5),
+ splashColor: Colors.red.shade700,
+ label: const Text('Remove Image'),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ child: SizedBox(
+ width: 200,
+ height: 150,
+ child: Image.file(ref.watch(lostAndFoundProvider).selectedImage!),
+ ),
+ );
+ }
+ },
+ ),
+ const SizedBox(height: 20),
+ Form(
+ key: _formKey,
+ child: Column(
+ children: [
+ MaterialTextFormField(
+ hintText: "Item Name",
+ controller: ref.read(lostAndFoundProvider).itemNameController,
+ validator: (value) => Validators.nameValidator(value),
+ ),
+ const SizedBox(height: 20),
+ MaterialTextFormField(
+ hintText: "Item Description",
+ controller: ref.read(lostAndFoundProvider).itemDescriptionController,
+ validator: (value) => Validators.descriptionValidator(value),
+ ),
+ const SizedBox(height: 20),
+ MaterialTextFormField(
+ hintText: "Contact Number",
+ controller: ref.read(lostAndFoundProvider).contactNumberController,
+ validator: (value) => Validators.contactNumberValidator(value),
+ ),
+ const SizedBox(height: 20),
+ MaterialTextFormField(
+ hintText: "Last seen at location",
+ controller: ref.read(lostAndFoundProvider).lastSeenLocationController,
+ validator: (value) => Validators.nonEmptyValidator(value),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 20),
+ Align(
+ alignment: Alignment.centerRight,
+ child: Consumer(
+ builder: (_, ref, __) {
+ return AnimatedToggleSwitch.dual(
+ height: 45,
+ spacing: 20,
+ current: ref.watch(lostAndFoundProvider).listingStatus,
+ first: LostAndFoundConstants.lostState,
+ second: LostAndFoundConstants.foundState,
+ textBuilder: (value) => Text(value),
+ onChanged: (value) =>
+ ref.read(lostAndFoundProvider.notifier).updateListingStatus(value),
+ styleBuilder: (value) => value == LostAndFoundConstants.lostState
+ ? ToggleStyle(
+ indicatorColor: Colors.redAccent,
+ backgroundColor: Colors.redAccent.shade100,
+ borderColor: Colors.transparent,
+ )
+ : ToggleStyle(
+ indicatorColor: Colors.teal,
+ backgroundColor: Colors.tealAccent.shade100,
+ borderColor: Colors.transparent,
+ ),
+ iconBuilder: (value) => value == LostAndFoundConstants.lostState
+ ? const Icon(
+ Icons.search_outlined,
+ color: Colors.white,
+ )
+ : const Icon(
+ Icons.check,
+ color: Colors.white,
+ ),
+ );
+ },
+ ),
+ ),
+ const SizedBox(height: 20),
+ Row(
+ children: [
+ BorderlessButton(
+ onPressed: () => context.pop(),
+ backgroundColor: Colors.redAccent.shade100.withOpacity(0.5),
+ splashColor: Colors.red.shade700,
+ label: const Text('Cancel'),
+ ),
+ const Spacer(),
+ ElevatedButton(
+ onPressed: () {
+ if (_formKey.currentState!.validate()) {
+ ref.read(lostAndFoundProvider.notifier).addItem();
+ ref.read(lostAndFoundProvider.notifier).clearControllers();
+ context.pop();
+ }
+ },
+ child: const Text('Add'),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ label: const Text('Add Listing'),
+ ),
+ const SizedBox(width: 20),
+ ],
+ ),
+ body: ref.watch(lostAndFoundProvider).loadingState == LoadingState.success
+ ? (ref.read(lostAndFoundProvider).lostAndFoundItemList.isNotEmpty
+ ? GridView.count(
+ crossAxisCount: 2,
+ childAspectRatio: 0.8,
+ children: [
+ for (var item in ref.watch(lostAndFoundProvider).lostAndFoundItemList)
+ ImageTile(
+ image: item.imagePath != null
+ ? ref.read(lostAndFoundProvider.notifier).imageFromBase64String(item.imagePath!)
+ : null,
+ body: [
+ Text(
+ "Item : ${item.name}",
+ textAlign: TextAlign.center,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: const TextStyle(
+ fontSize: 14,
+ ),
+ ),
+ Text(
+ "Last Seen at : ${item.lastSeenLocation}",
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ fontSize: 14,
+ ),
+ ),
+ Text(
+ item.isLost ? " Status : Lost" : "Status : Found",
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ fontSize: 14,
+ ),
+ ),
+ ],
+ primaryColor: item.isLost ? Colors.redAccent.shade100 : Colors.tealAccent.shade100,
+ secondaryColor: item.isLost ? Colors.redAccent.shade200 : Colors.tealAccent.shade200,
+ onTap: () => showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ "Item name : ${item.name}",
+ textAlign: TextAlign.center,
+ style: const TextStyle(fontSize: 20),
+ ),
+ const SizedBox(height: 20),
+ Text(
+ "Item description : \n${item.description}",
+ textAlign: TextAlign.center,
+ style: const TextStyle(fontSize: 16),
+ ),
+ const SizedBox(height: 20),
+ Text(
+ "Last seen at : ${item.lastSeenLocation}",
+ textAlign: TextAlign.center,
+ style: const TextStyle(fontSize: 16),
+ ),
+ const SizedBox(height: 20),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ item.contactNumber,
+ style: const TextStyle(fontSize: 16),
+ ),
+ const SizedBox(width: 10),
+ IconButton(
+ onPressed: () => ref
+ .read(lostAndFoundProvider.notifier)
+ .launchCaller(item.contactNumber),
+ icon: const Icon(Icons.call, color: Colors.green)),
+ ],
+ ),
+ const SizedBox(height: 20),
+ BorderlessButton(
+ onPressed: () => context.pop(),
+ backgroundColor: Colors.redAccent.shade100.withOpacity(0.5),
+ splashColor: Colors.red.shade700,
+ label: const Text('Close'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ )
+ : const Center(
+ child: Text("No Listings"),
+ ))
+ : const Center(child: CircularProgressIndicator()),
+ ),
+ );
+ }
+}
diff --git a/frontend/lib/screens/room_vacancy.dart b/frontend/lib/screens/room_vacancy.dart
new file mode 100644
index 0000000..05c4b06
--- /dev/null
+++ b/frontend/lib/screens/room_vacancy.dart
@@ -0,0 +1,90 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+import 'package:responsive_framework/responsive_framework.dart';
+import 'package:smart_insti_app/components/borderless_button.dart';
+import 'package:smart_insti_app/components/menu_tile.dart';
+import 'package:smart_insti_app/constants/constants.dart';
+import 'package:smart_insti_app/provider/room_provider.dart';
+import '../models/room.dart';
+
+class RoomVacancy extends ConsumerWidget {
+ const RoomVacancy({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ if (ref.read(roomProvider).loadingState == LoadingState.progress) ref.read(roomProvider.notifier).loadRooms();
+ return ResponsiveScaledBox(
+ width: 411,
+ child: Scaffold(
+ appBar: AppBar(
+ title: const Text('Room Vacancy'),
+ ),
+ body: ref.watch(roomProvider).loadingState == LoadingState.success
+ ? ref.read(roomProvider).roomList.isNotEmpty
+ ? GridView.count(
+ crossAxisCount: 2,
+ children: [
+ for (Room room in ref.read(roomProvider).roomList)
+ MenuTile(
+ title: room.name,
+ onTap: () => showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text("${room.name} : ${room.vacant ? 'Vacant' : 'Occupied'}",
+ style: const TextStyle(fontSize: 20)),
+ const SizedBox(height: 20),
+ room.vacant
+ ? Row(
+ children: [
+ BorderlessButton(
+ onPressed: () => context.pop(),
+ label: const Text('Cancel'),
+ backgroundColor: Colors.red.shade100,
+ splashColor: Colors.redAccent,
+ ),
+ const Spacer(),
+ BorderlessButton(
+ onPressed: () {
+ ref.read(roomProvider.notifier).reserveRoom(room);
+ context.pop();
+ },
+ label: const Text('Reserve'),
+ backgroundColor: Colors.blue.shade100,
+ splashColor: Colors.blueAccent,
+ ),
+ ],
+ )
+ : Container(),
+ ],
+ ),
+ ),
+ ),
+ ),
+ body: [
+ const SizedBox(height: 5),
+ Text(
+ room.vacant ? 'Vacant' : 'Occupied',
+ textAlign: TextAlign.center,
+ style: const TextStyle(fontSize: 14),
+ ),
+ const SizedBox(height: 10),
+ room.vacant ? Container() : const Text("by : Aadarsh"),
+ ],
+ icon: Icons.class_,
+ primaryColor: room.vacant ? Colors.greenAccent.shade100 : Colors.redAccent.shade100,
+ secondaryColor: room.vacant ? Colors.greenAccent.shade200 : Colors.redAccent.shade200,
+ ),
+ ],
+ )
+ : const Center(child: Text('No rooms found'))
+ : const Center(child: CircularProgressIndicator()),
+ ),
+ );
+ }
+}
diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock
index 499dd29..a73f18a 100644
--- a/frontend/pubspec.lock
+++ b/frontend/pubspec.lock
@@ -33,6 +33,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.8.1"
+ animated_toggle_switch:
+ dependency: "direct main"
+ description:
+ name: animated_toggle_switch
+ sha256: "19afc84373ad0fca147869e1b984afb3778ea8080f8d0af4fd1ff604d41b2635"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.0"
args:
dependency: transitive
description:
@@ -137,6 +145,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.1"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.3+8"
crypto:
dependency: transitive
description:
@@ -209,6 +225,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.1"
+ file_selector_linux:
+ dependency: transitive
+ description:
+ name: file_selector_linux
+ sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.2+1"
+ file_selector_macos:
+ dependency: transitive
+ description:
+ name: file_selector_macos
+ sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.3+3"
+ file_selector_platform_interface:
+ dependency: transitive
+ description:
+ name: file_selector_platform_interface
+ sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.6.2"
+ file_selector_windows:
+ dependency: transitive
+ description:
+ name: file_selector_windows
+ sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.3+1"
flutter:
dependency: "direct main"
description: flutter
@@ -222,6 +270,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.4.0"
+ flutter_dotenv:
+ dependency: "direct main"
+ description:
+ name: flutter_dotenv
+ sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.0"
flutter_keyboard_visibility:
dependency: transitive
description:
@@ -416,6 +472,94 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
+ image_cropper:
+ dependency: "direct main"
+ description:
+ name: image_cropper
+ sha256: f4bad5ed2dfff5a7ce0dfbad545b46a945c702bb6182a921488ef01ba7693111
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.1"
+ image_cropper_for_web:
+ dependency: transitive
+ description:
+ name: image_cropper_for_web
+ sha256: "865d798b5c9d826f1185b32e5d0018c4183ddb77b7b82a931e1a06aa3b74974e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ image_cropper_platform_interface:
+ dependency: transitive
+ description:
+ name: image_cropper_platform_interface
+ sha256: ee160d686422272aa306125f3b6fb1c1894d9b87a5e20ed33fa008e7285da11e
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.0"
+ image_picker:
+ dependency: "direct main"
+ description:
+ name: image_picker
+ sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.7"
+ image_picker_android:
+ dependency: transitive
+ description:
+ name: image_picker_android
+ sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.9+3"
+ image_picker_for_web:
+ dependency: transitive
+ description:
+ name: image_picker_for_web
+ sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ image_picker_ios:
+ dependency: transitive
+ description:
+ name: image_picker_ios
+ sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.9+1"
+ image_picker_linux:
+ dependency: transitive
+ description:
+ name: image_picker_linux
+ sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1+1"
+ image_picker_macos:
+ dependency: transitive
+ description:
+ name: image_picker_macos
+ sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1+1"
+ image_picker_platform_interface:
+ dependency: transitive
+ description:
+ name: image_picker_platform_interface
+ sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.9.3"
+ image_picker_windows:
+ dependency: transitive
+ description:
+ name: image_picker_windows
+ sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1+1"
intl:
dependency: transitive
description:
@@ -496,6 +640,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
package_config:
dependency: transitive
description:
@@ -765,6 +917,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.4"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.2"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.4"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.1"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.3"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
vector_math:
dependency: transitive
description:
diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml
index 786c29d..7592272 100644
--- a/frontend/pubspec.yaml
+++ b/frontend/pubspec.yaml
@@ -53,6 +53,11 @@ dependencies:
animated_toggle_switch: ^0.8.0
dio: ^5.4.0
animated_text_kit: ^4.2.2
+ image_picker: ^1.0.7
+ image_cropper: ^5.0.1
+ animated_toggle_switch: ^0.8.0
+ url_launcher: ^6.2.4
+ flutter_dotenv: ^5.1.0
dev_dependencies:
flutter_test:
@@ -79,6 +84,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
+ - .env
- lib/assets/
# An image asset can refer to one or more resolution-specific "variants", see